diff --git a/.golangci.yml b/.golangci.yml
index bb78ecc6ed..4c09369e1f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -15,10 +15,10 @@ linters:
- misspell
- nilerr
- nolintlint
+ - paralleltest
- revive
- staticcheck
- typecheck
- - paralleltest
- unused
severity:
@@ -124,6 +124,8 @@ issues:
linters: unused
- path: tools/
linters: unused
+ - path: daemon/kmd/lib/kmdapi/
+ linters: unused
- path: _test\.go
linters:
- errcheck
@@ -142,6 +144,9 @@ issues:
linters:
- staticcheck
text: "SA4006: this value" # of X is never used
+ - linters:
+ - staticcheck
+ text: "(SA3001|SA1019):"
- path: _test\.go
linters:
- revive
diff --git a/Makefile b/Makefile
index 1958f7088d..9889fa078f 100644
--- a/Makefile
+++ b/Makefile
@@ -51,11 +51,6 @@ export GOTESTCOMMAND=gotestsum --format pkgname --jsonfile testresults.json --
endif
ifeq ($(OS_TYPE), darwin)
-# For Xcode >= 15, set -no_warn_duplicate_libraries linker option
-CLANG_MAJOR_VERSION := $(shell clang --version | grep '^Apple clang version ' | awk '{print $$4}' | cut -d. -f1)
-ifeq ($(shell [ $(CLANG_MAJOR_VERSION) -ge 15 ] && echo true), true)
-EXTLDFLAGS := -Wl,-no_warn_duplicate_libraries
-endif
# M1 Mac--homebrew install location in /opt/homebrew
ifeq ($(ARCH), arm64)
export CPATH=/opt/homebrew/include
@@ -148,6 +143,13 @@ generate: deps
msgp: $(patsubst %,%/msgp_gen.go,$(MSGP_GENERATE))
+api:
+ make -C daemon/algod/api
+
+logic:
+ make -C data/transactions/logic
+
+
%/msgp_gen.go: deps ALWAYS
@set +e; \
printf "msgp: $(@D)..."; \
@@ -335,7 +337,11 @@ node_exporter: $(GOPATH1)/bin/node_exporter
$(GOPATH1)/bin/node_exporter:
mkdir -p $(GOPATH1)/bin && \
cd $(GOPATH1)/bin && \
- tar -xzvf $(SRCPATH)/installer/external/node_exporter-stable-$(shell ./scripts/ostype.sh)-$(shell uname -m | tr '[:upper:]' '[:lower:]').tar.gz && \
+ if [ -z "$(CROSS_COMPILE_ARCH)" ]; then \
+ tar -xzvf $(SRCPATH)/installer/external/node_exporter-stable-$(shell ./scripts/ostype.sh)-$(shell uname -m | tr '[:upper:]' '[:lower:]').tar.gz; \
+ else \
+ tar -xzvf $(SRCPATH)/installer/external/node_exporter-stable-$(shell ./scripts/ostype.sh)-universal.tar.gz; \
+ fi && \
cd -
# deploy
diff --git a/agreement/actions.go b/agreement/actions.go
index 89d2bad4a6..29e37b5c4c 100644
--- a/agreement/actions.go
+++ b/agreement/actions.go
@@ -566,7 +566,6 @@ func (c checkpointAction) do(ctx context.Context, s *Service) {
// we don't expect this to happen in recovery
s.log.with(logEvent).Errorf("checkpoint action for (%v, %v, %v) reached with nil completion channel", c.Round, c.Period, c.Step)
}
- return
}
func (c checkpointAction) String() string {
diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go
index 5c436a761c..0bb4d8d109 100644
--- a/agreement/agreementtest/simulate_test.go
+++ b/agreement/agreementtest/simulate_test.go
@@ -207,7 +207,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
err := fmt.Errorf("Lookup called on future round: %v > %v! (this is probably a bug)", r, l.nextRound)
panic(err)
}
- return l.state[a].OnlineAccountData(), nil
+ return basics_testing.OnlineAccountData(l.state[a]), nil
}
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
@@ -222,7 +222,7 @@ func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.M
var sum basics.MicroAlgos
var overflowed bool
for _, rec := range l.state {
- sum, overflowed = basics.OAddA(sum, rec.OnlineAccountData().VotingStake())
+ sum, overflowed = basics.OAddA(sum, basics_testing.OnlineAccountData(rec).VotingStake())
if overflowed {
panic("circulation computation overflowed")
}
diff --git a/agreement/autopsy.go b/agreement/autopsy.go
index 088687fb1c..ccefd6f207 100644
--- a/agreement/autopsy.go
+++ b/agreement/autopsy.go
@@ -384,7 +384,7 @@ func (a *Autopsy) extractNextCdv(ch chan<- autopsyTrace) (bounds AutopsyBounds,
close(pch)
}
- pch = make(chan autopsyPair, 0)
+ pch = make(chan autopsyPair)
acc = autopsyTrace{m: acc.m, p: pch}
err = protocol.DecodeStream(a, &acc.x)
if err != nil {
diff --git a/agreement/certificate_test.go b/agreement/certificate_test.go
index c4272fff15..99f40cb35c 100644
--- a/agreement/certificate_test.go
+++ b/agreement/certificate_test.go
@@ -276,7 +276,7 @@ func TestCertificateCertWrongRound(t *testing.T) {
ledger, addresses, vrfSecrets, otSecrets := readOnlyFixture100()
round := ledger.NextRound()
period := period(0)
- block := makeRandomBlock(1 - 1)
+ block := makeRandomBlock(0)
votes := make([]vote, 0)
equiVotes := make([]equivocationVote, 0)
diff --git a/agreement/common_test.go b/agreement/common_test.go
index 1cbba828ab..9fa2a2829e 100644
--- a/agreement/common_test.go
+++ b/agreement/common_test.go
@@ -29,6 +29,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/committee"
"github.com/algorand/go-algorand/logging"
@@ -332,7 +333,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
return basics.OnlineAccountData{}, &LedgerDroppedRoundError{}
}
- return l.state[a].OnlineAccountData(), nil
+ return basics_testing.OnlineAccountData(l.state[a]), nil
}
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
@@ -347,7 +348,7 @@ func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.M
var sum basics.MicroAlgos
var overflowed bool
for _, rec := range l.state {
- sum, overflowed = basics.OAddA(sum, rec.OnlineAccountData().VotingStake())
+ sum, overflowed = basics.OAddA(sum, basics_testing.OnlineAccountData(rec).VotingStake())
if overflowed {
panic("circulation computation overflowed")
}
diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go
index 53a05f761b..efe21958ad 100644
--- a/agreement/fuzzer/ledger_test.go
+++ b/agreement/fuzzer/ledger_test.go
@@ -25,6 +25,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/committee"
"github.com/algorand/go-algorand/protocol"
@@ -241,7 +242,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
err := fmt.Errorf("Lookup called on future round: %d >= %d! (this is probably a bug)", r, l.nextRound)
panic(err)
}
- return l.state[a].OnlineAccountData(), nil
+ return basics_testing.OnlineAccountData(l.state[a]), nil
}
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
@@ -256,7 +257,7 @@ func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.M
var sum basics.MicroAlgos
var overflowed bool
for _, rec := range l.state {
- sum, overflowed = basics.OAddA(sum, rec.OnlineAccountData().VotingStake())
+ sum, overflowed = basics.OAddA(sum, basics_testing.OnlineAccountData(rec).VotingStake())
if overflowed {
panic("circulation computation overflowed")
}
diff --git a/agreement/gossip/network.go b/agreement/gossip/network.go
index f456ee1eeb..41294d28cf 100644
--- a/agreement/gossip/network.go
+++ b/agreement/gossip/network.go
@@ -20,7 +20,6 @@ package gossip
import (
"context"
- "time"
"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/config"
@@ -179,12 +178,3 @@ func (i *networkImpl) Disconnect(h agreement.MessageHandle) {
i.net.Disconnect(metadata.raw.Sender)
}
-
-// broadcastTimeout is currently only used by test code.
-// In test code we want to queue up a bunch of outbound packets and then see that they got through, so we need to wait at least a little bit for them to all go out.
-// Normal agreement state machine code uses GossipNode.Broadcast non-blocking and may drop outbound packets.
-func (i *networkImpl) broadcastTimeout(t protocol.Tag, data []byte, timeout time.Duration) error {
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
- defer cancel()
- return i.net.Broadcast(ctx, t, data, true, nil)
-}
diff --git a/agreement/persistence.go b/agreement/persistence.go
index 0dccd61713..ba2b05a006 100644
--- a/agreement/persistence.go
+++ b/agreement/persistence.go
@@ -355,7 +355,7 @@ func (p *asyncPersistenceLoop) loop(ctx context.Context) {
select {
case <-ctx.Done():
return
- case s, _ = <-p.pending:
+ case s = <-p.pending:
}
// make sure that the ledger finished writing the previous round to disk.
diff --git a/agreement/player.go b/agreement/player.go
index 8d23aa71b7..1f582137d3 100644
--- a/agreement/player.go
+++ b/agreement/player.go
@@ -275,7 +275,7 @@ func (p *player) issueFastVote(r routerHandle) (actions []action) {
func (p *player) handleCheckpointEvent(r routerHandle, e checkpointEvent) []action {
return []action{
- checkpointAction{
+ checkpointAction{ //nolint:gosimple // explicit assignment for clarity
Round: e.Round,
Period: e.Period,
Step: e.Step,
diff --git a/agreement/vote.go b/agreement/vote.go
index b9261a4853..599023ada0 100644
--- a/agreement/vote.go
+++ b/agreement/vote.go
@@ -146,6 +146,12 @@ func (uv unauthenticatedVote) verify(l LedgerReader) (vote, error) {
return vote{R: rv, Cred: cred, Sig: uv.Sig}, nil
}
+var (
+ // testMakeVoteCheck is a function that can be set to check every
+ // unauthenticatedVote before it is returned by makeVote. It is only set by tests.
+ testMakeVoteCheck func(*unauthenticatedVote) error
+)
+
// makeVote creates a new unauthenticated vote from its constituent components.
//
// makeVote returns an error if it fails.
@@ -178,7 +184,15 @@ func makeVote(rv rawVote, voting crypto.OneTimeSigner, selection *crypto.VRFSecr
}
cred := committee.MakeCredential(&selection.SK, m.Selector)
- return unauthenticatedVote{R: rv, Cred: cred, Sig: sig}, nil
+ ret := unauthenticatedVote{R: rv, Cred: cred, Sig: sig}
+
+ // for use when running in tests
+ if testMakeVoteCheck != nil {
+ if testErr := testMakeVoteCheck(&ret); testErr != nil {
+ return unauthenticatedVote{}, fmt.Errorf("makeVote: testMakeVoteCheck failed: %w", testErr)
+ }
+ }
+ return ret, nil
}
// ToBeHashed implements the Hashable interface.
diff --git a/agreement/vote_test.go b/agreement/vote_test.go
index 9f5b9c9aed..9c5f74c8c7 100644
--- a/agreement/vote_test.go
+++ b/agreement/vote_test.go
@@ -17,6 +17,10 @@
package agreement
import (
+ "bytes"
+ "encoding/base64"
+ "encoding/hex"
+ "fmt"
"os"
"testing"
@@ -27,10 +31,38 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/committee"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/network/vpack"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
+func init() {
+ testMakeVoteCheck = testVPackMakeVote
+}
+
+func testVPackMakeVote(v *unauthenticatedVote) error {
+ vbuf := protocol.Encode(v)
+ enc := vpack.NewStatelessEncoder()
+ dec := vpack.NewStatelessDecoder()
+ encBuf, err := enc.CompressVote(nil, vbuf)
+ if err != nil {
+ return fmt.Errorf("makeVote: failed to parse vote msgpack: %v", err)
+ }
+ decBuf, err := dec.DecompressVote(nil, encBuf)
+ if err != nil {
+ return fmt.Errorf("makeVote: failed to decompress vote msgpack: %v", err)
+ }
+ if !bytes.Equal(vbuf, decBuf) {
+ fmt.Printf("vote: %+v\n", v)
+ fmt.Printf("oldbuf: %s\n", hex.EncodeToString(vbuf))
+ fmt.Printf("decbuf: %s\n", hex.EncodeToString(decBuf))
+ fmt.Printf("base64 oldbuf: %s\n", base64.StdEncoding.EncodeToString(vbuf))
+ fmt.Printf("base64 decbuf: %s\n", base64.StdEncoding.EncodeToString(decBuf))
+ return fmt.Errorf("makeVote: decompressed vote msgpack does not match original")
+ }
+ return nil
+}
+
// error is set if this address is not selected
func makeVoteTesting(addr basics.Address, vrfSecs *crypto.VRFSecrets, otSecs crypto.OneTimeSigner, ledger Ledger, round basics.Round, period period, step step, digest crypto.Digest) (vote, error) {
var proposal proposalValue
diff --git a/buildnumber.dat b/buildnumber.dat
index 00750edc07..573541ac97 100644
--- a/buildnumber.dat
+++ b/buildnumber.dat
@@ -1 +1 @@
-3
+0
diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go
index 28823f393c..422c30b28d 100644
--- a/catchup/catchpointService.go
+++ b/catchup/catchpointService.go
@@ -763,9 +763,8 @@ func (cs *CatchpointCatchupService) updateNodeCatchupMode(catchupModeEnabled boo
case newCtx, open := <-newCtxCh:
if open {
cs.ctx, cs.cancelCtxFunc = context.WithCancel(newCtx)
- } else {
- // channel is closed, this means that the node is stopping
}
+ // if channel is closed, this means that the node is stopping
case <-cs.ctx.Done():
// the node context was canceled before the SetCatchpointCatchupMode goroutine had
// the chance of completing. We At this point, the service is shutting down. However,
@@ -823,12 +822,14 @@ func (cs *CatchpointCatchupService) checkLedgerDownload() error {
for i := 0; i < cs.config.CatchupLedgerDownloadRetryAttempts; i++ {
psp, peerError := cs.blocksDownloadPeerSelector.getNextPeer()
if peerError != nil {
- return err
+ cs.log.Debugf("checkLedgerDownload: error on getNextPeer: %s", peerError.Error())
+ return peerError
}
err = ledgerFetcher.headLedger(context.Background(), psp.Peer, round)
if err == nil {
return nil
}
+ cs.log.Debugf("checkLedgerDownload: failed to headLedger from peer %s: %v", peerAddress(psp.Peer), err)
// a non-nil error means that the catchpoint is not available, so we should rank it accordingly
cs.blocksDownloadPeerSelector.rankPeer(psp, peerRankNoCatchpointForRound)
}
diff --git a/catchup/classBasedPeerSelector.go b/catchup/classBasedPeerSelector.go
index 2eca3bef2c..360228269b 100644
--- a/catchup/classBasedPeerSelector.go
+++ b/catchup/classBasedPeerSelector.go
@@ -18,9 +18,10 @@ package catchup
import (
"errors"
+ "time"
+
"github.com/algorand/go-algorand/network"
"github.com/algorand/go-deadlock"
- "time"
)
// classBasedPeerSelector is a rankPooledPeerSelector that tracks and ranks classes of peers based on their response behavior.
@@ -56,8 +57,13 @@ func (c *classBasedPeerSelector) rankPeer(psp *peerSelectorPeer, rank int) (int,
}
// Peer was in this class, if there was any kind of download issue, we increment the failure count
- if rank >= peerRankNoBlockForRound {
+ failure := rank >= peerRankNoBlockForRound
+ if failure {
wp.downloadFailures++
+ } else {
+ // class usually multiple peers and we do not want to punish the entire class for one peer's failure
+ // by decrementing the downloadFailures
+ wp.downloadFailures = max(wp.downloadFailures-1, 0)
}
break
diff --git a/catchup/classBasedPeerSelector_test.go b/catchup/classBasedPeerSelector_test.go
index 40c708fe13..bfaa2100cf 100644
--- a/catchup/classBasedPeerSelector_test.go
+++ b/catchup/classBasedPeerSelector_test.go
@@ -17,11 +17,12 @@
package catchup
import (
+ "testing"
+ "time"
+
"github.com/algorand/go-algorand/network"
"github.com/algorand/go-algorand/test/partitiontest"
"github.com/stretchr/testify/require"
- "testing"
- "time"
)
// Use to mock the wrapped peer selectors where warranted
@@ -481,6 +482,7 @@ func TestClassBasedPeerSelector_integration(t *testing.T) {
require.Nil(t, err)
require.Equal(t, mockP1WrappedPeer, peerResult)
cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound)
+ cps.rankPeer(mockP1WrappedPeer, peerRankNoBlockForRound)
peerResult, err = cps.getNextPeer()
require.Nil(t, err)
@@ -495,4 +497,8 @@ func TestClassBasedPeerSelector_integration(t *testing.T) {
require.Equal(t, 4, cps.peerSelectors[0].downloadFailures)
require.Equal(t, 0, cps.peerSelectors[1].downloadFailures)
+
+ // make sure successful download decreases download failures
+ cps.rankPeer(mockP1WrappedPeer, durationRank)
+ require.Equal(t, 3, cps.peerSelectors[0].downloadFailures)
}
diff --git a/catchup/fetcher_test.go b/catchup/fetcher_test.go
index 3d1ce1c837..8c4fd2bb4a 100644
--- a/catchup/fetcher_test.go
+++ b/catchup/fetcher_test.go
@@ -287,9 +287,8 @@ func (p *testUnicastPeer) Request(ctx context.Context, tag protocol.Tag, topics
func (p *testUnicastPeer) Respond(ctx context.Context, reqMsg network.IncomingMessage, outMsg network.OutgoingMessage) (e error) {
hashKey := uint64(0)
- channel, found := p.responseChannels[hashKey]
- if !found {
- }
+ require.Contains(p.t, p.responseChannels, hashKey)
+ channel := p.responseChannels[hashKey]
select {
case channel <- &network.Response{Topics: outMsg.Topics}:
diff --git a/catchup/peerSelector.go b/catchup/peerSelector.go
index 851e60771c..f534fec5c5 100644
--- a/catchup/peerSelector.go
+++ b/catchup/peerSelector.go
@@ -17,6 +17,7 @@
package catchup
import (
+ "container/list"
"errors"
"math"
"sort"
@@ -142,7 +143,7 @@ type rankPooledPeerSelector struct {
// selection gaps, and the count of peerRankDownloadFailed incidents.
type historicStats struct {
windowSize int
- rankSamples []int
+ rankSamples *list.List
rankSum uint64
requestGaps []uint64
gapSum float64
@@ -157,12 +158,12 @@ func makeHistoricStatus(windowSize int, class peerClass) *historicStats {
// that will determine the rank of the peer.
hs := historicStats{
windowSize: windowSize,
- rankSamples: make([]int, windowSize),
+ rankSamples: list.New(),
requestGaps: make([]uint64, 0, windowSize),
rankSum: uint64(class.initialRank) * uint64(windowSize),
gapSum: 0.0}
for i := 0; i < windowSize; i++ {
- hs.rankSamples[i] = class.initialRank
+ hs.rankSamples.PushBack(class.initialRank)
}
return &hs
}
@@ -209,7 +210,7 @@ func (hs *historicStats) resetRequestPenalty(steps int, initialRank int, class p
if steps == 0 {
hs.requestGaps = make([]uint64, 0, hs.windowSize)
hs.gapSum = 0.0
- return int(float64(hs.rankSum) / float64(len(hs.rankSamples)))
+ return int(float64(hs.rankSum) / float64(hs.rankSamples.Len()))
}
if steps > len(hs.requestGaps) {
@@ -219,7 +220,7 @@ func (hs *historicStats) resetRequestPenalty(steps int, initialRank int, class p
hs.gapSum -= 1.0 / float64(hs.requestGaps[s])
}
hs.requestGaps = hs.requestGaps[steps:]
- return boundRankByClass(int(hs.computerPenalty()*(float64(hs.rankSum)/float64(len(hs.rankSamples)))), class)
+ return boundRankByClass(int(hs.computerPenalty()*(float64(hs.rankSum)/float64(hs.rankSamples.Len()))), class)
}
// push pushes a new rank to the historicStats, and returns the new
@@ -233,12 +234,6 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera
return value
}
- // This is a moving window. Remore the least recent value once the window is full
- if len(hs.rankSamples) == hs.windowSize {
- hs.rankSum -= uint64(hs.rankSamples[0])
- hs.rankSamples = hs.rankSamples[1:]
- }
-
initialRank := value
// Download may fail for various reasons. Give it additional tries
@@ -266,11 +261,17 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera
}
}
- hs.rankSamples = append(hs.rankSamples, value)
- hs.rankSum += uint64(value)
+ // This is a moving window of windowSize size so an old value is removed and a new value is added.
+ oldFrontElem := hs.rankSamples.Front()
+ oldRank := oldFrontElem.Value.(int)
+ // Update rankSum (remove old value, add new value)
+ hs.rankSum = hs.rankSum - uint64(oldRank) + uint64(value)
+ // Update node value and move it to the back of the list by reusing the node
+ oldFrontElem.Value = value
+ hs.rankSamples.MoveToBack(oldFrontElem)
// The average performance of the peer
- average := float64(hs.rankSum) / float64(len(hs.rankSamples))
+ average := float64(hs.rankSum) / float64(hs.rankSamples.Len())
if int(average) > upperBound(class) && (initialRank == peerRankDownloadFailed || initialRank == peerRankNoBlockForRound) {
// peerRankDownloadFailed will be delayed, to give the peer
@@ -280,10 +281,10 @@ func (hs *historicStats) push(value int, counter uint64, class peerClass) (avera
return initialRank
}
- // A penalty is added relative to how freequently the peer is used
+ // A penalty is added relative to how frequently the peer is used
penalty := hs.updateRequestPenalty(counter)
- // The rank based on the performance and the freequency
+ // The rank based on the performance and the frequency
avgWithPenalty := int(penalty * average)
// Keep the peer in the same class. The value passed will be
@@ -303,7 +304,7 @@ func makeRankPooledPeerSelector(net peersRetriever, initialPeersClasses []peerCl
return selector
}
-// getNextPeer returns the next peer. It randomally selects a peer from a pool that has
+// getNextPeer returns the next peer. It randomly selects a peer from a pool that has
// the lowest rank value. Given that the peers are grouped by their ranks, allow us to
// prioritize peers based on their class and/or performance.
func (ps *rankPooledPeerSelector) getNextPeer() (psp *peerSelectorPeer, err error) {
diff --git a/catchup/service.go b/catchup/service.go
index dfd0361960..60b2c5ffb2 100644
--- a/catchup/service.go
+++ b/catchup/service.go
@@ -281,7 +281,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
i++
select {
case <-ctx.Done():
- s.log.Debugf("fetchAndWrite(%v): Aborted", r)
+ s.log.Debugf("fetchAndWrite(%d): Aborted", r)
return false
default:
}
@@ -309,10 +309,11 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
psp, getPeerErr := peerSelector.getNextPeer()
if getPeerErr != nil {
- s.log.Debugf("fetchAndWrite: was unable to obtain a peer to retrieve the block from")
+ s.log.Debugf("fetchAndWrite(%d): was unable to obtain a peer to retrieve the block from: %v", r, getPeerErr)
return false
}
peer := psp.Peer
+ s.log.Debugf("fetchAndWrite(%d): got %s peer: %s", r, psp.peerClass, peerAddress(peer))
// Try to fetch, timing out after retryInterval
block, cert, blockDownloadDuration, err := s.innerFetch(ctx, r, peer)
@@ -343,15 +344,16 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
peerErrors[peer]++
}
}
- s.log.Debugf("fetchAndWrite(%v): Could not fetch: %v (attempt %d)", r, err, i)
- peerSelector.rankPeer(psp, failureRank)
+ s.log.Debugf("fetchAndWrite(%d): Could not fetch: %v (attempt %d), peer %s", r, err, i, peerAddress(psp.Peer))
+ o, n := peerSelector.rankPeer(psp, failureRank)
+ s.log.Debugf("fetchAndWrite(%d): Could not fetch: ranked peer %s with %d from %d to %d", r, peerAddress(psp.Peer), failureRank, o, n)
// 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 exist, and we're making many requests down the chain
// for no reason.
select {
case <-ctx.Done():
- s.log.Infof("fetchAndWrite(%v): Aborted while waiting for lookback block to ledger", r)
+ s.log.Infof("fetchAndWrite(%d): Aborted while waiting for lookback block to ledger", r)
return false
case <-lookbackComplete:
}
@@ -360,7 +362,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
// someone already wrote the block to the ledger, we should stop syncing
return false
}
- s.log.Debugf("fetchAndWrite(%v): Got block and cert contents: %v %v", r, block, cert)
+ s.log.Debugf("fetchAndWrite(%d): Got block and cert contents: %v %v", r, block, cert)
// Check that the block's contents match the block header (necessary with an untrusted block because b.Hash() only hashes the header)
if s.cfg.CatchupVerifyPaysetHash() {
@@ -368,11 +370,11 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
peerSelector.rankPeer(psp, peerRankInvalidDownload)
// Check if this mismatch is due to an unsupported protocol version
if _, ok := config.Consensus[block.BlockHeader.CurrentProtocol]; !ok {
- s.log.Errorf("fetchAndWrite(%v): unsupported protocol version detected: '%v'", r, block.BlockHeader.CurrentProtocol)
+ s.log.Errorf("fetchAndWrite(%d): unsupported protocol version detected: '%v'", r, block.BlockHeader.CurrentProtocol)
return false
}
- s.log.Warnf("fetchAndWrite(%v): block contents do not match header (attempt %d)", r, i)
+ s.log.Warnf("fetchAndWrite(%d): block contents do not match header (attempt %d)", r, i)
continue // retry the fetch
}
}
@@ -380,7 +382,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
// make sure that we have the lookBack block that's required for authenticating this block
select {
case <-ctx.Done():
- s.log.Debugf("fetchAndWrite(%v): Aborted while waiting for lookback block to ledger", r)
+ s.log.Debugf("fetchAndWrite(%d): Aborted while waiting for lookback block to ledger", r)
return false
case <-lookbackComplete:
}
@@ -388,7 +390,7 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
if s.cfg.CatchupVerifyCertificate() {
err = s.auth.Authenticate(block, cert)
if err != nil {
- s.log.Warnf("fetchAndWrite(%v): cert did not authenticate block (attempt %d): %v", r, i, err)
+ s.log.Warnf("fetchAndWrite(%d): cert did not authenticate block (attempt %d): %v", r, i, err)
peerSelector.rankPeer(psp, peerRankInvalidDownload)
continue // retry the fetch
}
@@ -396,12 +398,12 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
peerRank := peerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration)
r1, r2 := peerSelector.rankPeer(psp, peerRank)
- s.log.Debugf("fetchAndWrite(%d): ranked peer with %d from %d to %d", r, peerRank, r1, r2)
+ s.log.Debugf("fetchAndWrite(%d): ranked peer %s with %d from %d to %d", r, peerAddress(psp.Peer), peerRank, r1, r2)
// Write to ledger, noting that ledger writes must be in order
select {
case <-ctx.Done():
- s.log.Debugf("fetchAndWrite(%v): Aborted while waiting to write to ledger", r)
+ s.log.Debugf("fetchAndWrite(%d): Aborted while waiting to write to ledger", r)
return false
case <-prevFetchCompleteChan:
// make sure the ledger wrote enough of the account data to disk, since we don't want the ledger to hold a large amount of data in memory.
@@ -457,16 +459,16 @@ func (s *Service) fetchAndWrite(ctx context.Context, r basics.Round, prevFetchCo
return false
case errors.As(err, &protocolErr):
if !s.protocolErrorLogged {
- logging.Base().Errorf("fetchAndWrite(%v): unrecoverable protocol error detected: %v", r, err)
+ logging.Base().Errorf("fetchAndWrite(%d): unrecoverable protocol error detected: %v", r, err)
s.protocolErrorLogged = true
}
default:
- s.log.Errorf("fetchAndWrite(%v): ledger write failed: %v", r, err)
+ s.log.Errorf("fetchAndWrite(%d): ledger write failed: %v", r, err)
}
return false
}
- s.log.Debugf("fetchAndWrite(%v): Wrote block to ledger", r)
+ s.log.Debugf("fetchAndWrite(%d): Wrote block to ledger", r)
return true
}
}
@@ -497,7 +499,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) {
ps := createPeerSelector(s.net)
if _, err := ps.getNextPeer(); err != nil {
- s.log.Debugf("pipelinedFetch: was unable to obtain a peer to retrieve the block from")
+ s.log.Debugf("pipelinedFetch: was unable to obtain a peer to retrieve the block from: %v", err)
return
}
@@ -546,6 +548,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) {
if !completedOK {
// there was an error; defer will cancel the pipeline
+ s.log.Debugf("pipelinedFetch: quitting on fetchAndWrite error (firstRound=%d, nextRound=%d)", firstRound-1, nextRound)
return
}
@@ -582,6 +585,7 @@ func (s *Service) pipelinedFetch(seedLookback uint64) {
}
case <-s.ctx.Done():
+ s.log.Debugf("pipelinedFetch: Aborted (firstRound=%d, nextRound=%d)", firstRound, nextRound)
return
}
}
@@ -759,7 +763,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy
for s.ledger.LastRound() < cert.Round {
psp, getPeerErr := ps.getNextPeer()
if getPeerErr != nil {
- s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from")
+ s.log.Debugf("fetchRound: was unable to obtain a peer to retrieve the block from: %s", getPeerErr)
select {
case <-s.ctx.Done():
logging.Base().Debugf("fetchRound was asked to quit while collecting peers")
@@ -788,7 +792,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy
failureRank = peerRankNoBlockForRound
// If a peer does not have the block after few attempts it probably has not persisted the block yet.
// Give it some time to persist the block and try again.
- // None, there is no exit condition on too many retries as per the function contract.
+ // Note, there is no exit condition on too many retries as per the function contract.
if count, ok := peerErrors[peer]; ok {
if count > errNoBlockForRoundThreshold {
time.Sleep(50 * time.Millisecond)
@@ -797,7 +801,7 @@ func (s *Service) fetchRound(cert agreement.Certificate, verifier *agreement.Asy
// for the low number of connected peers (like 2) the following scenario is possible:
// - both peers do not have the block
// - peer selector punishes one of the peers more than the other
- // - the punished peer gets the block, and the less punished peer stucks.
+ // - the punished peer gets the block, and the less punished peer stuck.
// It this case reset the peer selector to let it re-learn priorities.
ps = createPeerSelector(s.net)
}
@@ -877,7 +881,7 @@ func (s *Service) roundIsNotSupported(nextRound basics.Round) bool {
return true
}
-func createPeerSelector(net network.GossipNode) peerSelector {
+func createPeerSelector(net peersRetriever) peerSelector {
wrappedPeerSelectors := []*wrappedPeerSelector{
{
peerClass: network.PeersConnectedOut,
diff --git a/cmd/algod/main.go b/cmd/algod/main.go
index c806fcb5f3..f50dc00f77 100644
--- a/cmd/algod/main.go
+++ b/cmd/algod/main.go
@@ -17,6 +17,7 @@
package main
import (
+ "context"
"encoding/json"
"flag"
"fmt"
@@ -59,6 +60,11 @@ var sessionGUID = flag.String("s", "", "Telemetry Session GUID to use")
var telemetryOverride = flag.String("t", "", `Override telemetry setting if supported (Use "true", "false", "0" or "1")`)
var seed = flag.String("seed", "", "input to math/rand.Seed()")
+const (
+ defaultStaticTelemetryStartupTimeout = 5 * time.Second
+ defaultStaticTelemetryBGDialRetry = 1 * time.Minute
+)
+
func main() {
flag.Parse()
exitCode := run()
@@ -232,9 +238,28 @@ func run() int {
telemetryConfig.SessionGUID = *sessionGUID
}
}
- err = log.EnableTelemetry(telemetryConfig)
+ // Try to enable remote telemetry now when URI is defined. Skip for DNS based telemetry.
+ ctx, telemetryCancelFn := context.WithTimeout(context.Background(), defaultStaticTelemetryStartupTimeout)
+ err = log.EnableTelemetryContext(ctx, telemetryConfig)
+ telemetryCancelFn()
if err != nil {
fmt.Fprintln(os.Stdout, "error creating telemetry hook", err)
+
+ // Remote telemetry init loop
+ go func() {
+ for {
+ time.Sleep(defaultStaticTelemetryBGDialRetry)
+ // Try to enable remote telemetry now when URI is defined. Skip for DNS based telemetry.
+ err := log.EnableTelemetryContext(context.Background(), telemetryConfig)
+ // Error occurs only if URI is defined and we need to retry later
+ if err == nil {
+ // Remote telemetry enabled or empty static URI, stop retrying
+ return
+ }
+ fmt.Fprintln(os.Stdout, "error creating telemetry hook", err)
+ // Try to reenable every minute
+ }
+ }()
}
}
}
diff --git a/cmd/algoh/main.go b/cmd/algoh/main.go
index 35e9887bfd..9cab7214b3 100644
--- a/cmd/algoh/main.go
+++ b/cmd/algoh/main.go
@@ -17,8 +17,10 @@
package main
import (
+ "context"
"flag"
"fmt"
+ "io"
"os"
"os/exec"
"os/signal"
@@ -46,10 +48,14 @@ var telemetryOverride = flag.String("t", "", `Override telemetry setting if supp
// the following flags aren't being used by the algoh, but are needed so that the flag package won't complain that
// these flags were provided but were not defined. We grab all the input flags and pass these downstream to the algod executable
// as an input arguments.
-var peerOverride = flag.String("p", "", "Override phonebook with peer ip:port (or semicolon separated list: ip:port;ip:port;ip:port...)")
-var listenIP = flag.String("l", "", "Override config.EndpointAddress (REST listening address) with ip:port")
-var seed = flag.String("seed", "", "input to math/rand.Seed()")
-var genesisFile = flag.String("g", "", "Genesis configuration file")
+//
+//nolint:unused // unused for reasons above
+var (
+ peerOverride = flag.String("p", "", "Override phonebook with peer ip:port (or semicolon separated list: ip:port;ip:port;ip:port...)")
+ listenIP = flag.String("l", "", "Override config.EndpointAddress (REST listening address) with ip:port")
+ seed = flag.String("seed", "", "input to math/rand.Seed()")
+ genesisFile = flag.String("g", "", "Genesis configuration file")
+)
const algodFileName = "algod"
const goalFileName = "goal"
@@ -122,7 +128,7 @@ func main() {
done := make(chan struct{})
log := logging.Base()
- configureLogging(genesis, log, absolutePath, done, algodConfig)
+ configureLogging(genesis, log, absolutePath, done, algodConfig, algohConfig)
defer log.CloseTelemetry()
exeDir, err = util.ExeDir()
@@ -269,26 +275,58 @@ func getNodeController() nodecontrol.NodeController {
return nc
}
-func configureLogging(genesis bookkeeping.Genesis, log logging.Logger, rootPath string, abort chan struct{}, algodConfig config.Local) {
- log = logging.Base()
-
+func configureLogging(genesis bookkeeping.Genesis, log logging.Logger, rootPath string, abort chan struct{}, algodConfig config.Local, algohConfig algoh.HostConfig) {
liveLog := fmt.Sprintf("%s/host.log", rootPath)
- fmt.Println("Logging to: ", liveLog)
- writer, err := os.OpenFile(liveLog, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
- if err != nil {
- panic(fmt.Sprintf("configureLogging: cannot open log file %v", err))
+ if algohConfig.LogFileDir != "" {
+ liveLog = fmt.Sprintf("%s/%s", algohConfig.LogFileDir, "host.log")
+ }
+
+ // Default to the root path if no archive log directory is specified
+ archiveLog := rootPath
+ // If archive dir or log file dir is specified, use that instead
+ if algohConfig.LogArchiveDir != "" {
+ archiveLog = algohConfig.LogArchiveDir
+ } else if algohConfig.LogFileDir != "" {
+ archiveLog = algohConfig.LogFileDir
+ }
+
+ if algohConfig.LogArchiveName != "" {
+ archiveLog = fmt.Sprintf("%s/%s", archiveLog, algohConfig.LogArchiveName)
}
- log.SetOutput(writer)
+
+ var maxLogAge time.Duration
+ var err error
+ if algohConfig.LogArchiveMaxAge != "" {
+ maxLogAge, err = time.ParseDuration(algohConfig.LogArchiveMaxAge)
+ if err != nil {
+ log.Fatalf("invalid algoh config LogArchiveMaxAge: %s", err)
+ }
+ }
+
+ var logWriter io.Writer
+ if algohConfig.LogSizeLimit > 0 {
+ fmt.Println("algoh logging to: ", liveLog)
+ logWriter = logging.MakeCyclicFileWriter(liveLog, archiveLog, algohConfig.LogSizeLimit, maxLogAge)
+ } else {
+ fmt.Println("Logging to: stdout")
+ logWriter = os.Stdout
+ }
+ log.SetOutput(logWriter)
log.SetJSONFormatter()
- log.SetLevel(logging.Debug)
+ minLogLevel := logging.Level(algohConfig.MinLogLevel)
+ // Debug is the highest level, so default to Warn if the user sets out of bounds
+ if minLogLevel > logging.Debug {
+ minLogLevel = logging.Warn
+ }
+ log.SetLevel(minLogLevel)
initTelemetry(genesis, log, rootPath, abort, algodConfig)
// if we have the telemetry enabled, we want to use it's sessionid as part of the
// collected metrics decorations.
- fmt.Fprintln(writer, "++++++++++++++++++++++++++++++++++++++++")
- fmt.Fprintln(writer, "Logging Starting")
- fmt.Fprintln(writer, "++++++++++++++++++++++++++++++++++++++++")
+ fmt.Fprintln(logWriter, "++++++++++++++++++++++++++++++++++++++++")
+ fmt.Fprintln(logWriter, "Logging Starting")
+ fmt.Fprintln(logWriter, "++++++++++++++++++++++++++++++++++++++++")
}
func initTelemetry(genesis bookkeeping.Genesis, log logging.Logger, dataDirectory string, abort chan struct{}, algodConfig config.Local) {
@@ -307,7 +345,7 @@ func initTelemetry(genesis bookkeeping.Genesis, log logging.Logger, dataDirector
telemetryConfig.Enable = logging.TelemetryOverride(*telemetryOverride, &telemetryConfig)
if telemetryConfig.Enable {
- err = log.EnableTelemetry(telemetryConfig)
+ err = log.EnableTelemetryContext(context.Background(), telemetryConfig)
if err != nil {
fmt.Fprintln(os.Stdout, "error creating telemetry hook", err)
return
diff --git a/cmd/algokey/common.go b/cmd/algokey/common.go
index 6c98b91725..456d09e4d0 100644
--- a/cmd/algokey/common.go
+++ b/cmd/algokey/common.go
@@ -45,7 +45,7 @@ func loadKeyfileOrMnemonic(keyfile string, mnemonic string) crypto.Seed {
}
fmt.Fprintf(os.Stderr, "Must specify one of keyfile or mnemonic\n")
- os.Exit(1)
+ os.Exit(1) //nolint:revive // intentional
panic("unreachable")
}
diff --git a/cmd/algons/dnsCmd.go b/cmd/algons/dnsCmd.go
index d8d54dfc63..9f432e76b1 100644
--- a/cmd/algons/dnsCmd.go
+++ b/cmd/algons/dnsCmd.go
@@ -330,7 +330,7 @@ func doDeleteDNS(network string, noPrompt bool, excludePattern string, includePa
var idsToDelete []cloudflare.DNSRecordResponseEntry
services := []string{"_algobootstrap", "_metrics"}
- servicesRegexp, err := regexp.Compile("^(_algobootstrap|_metrics)\\._tcp\\..*algodev.network$")
+ servicesRegexp, err := regexp.Compile(`^(_algobootstrap|_metrics)\._tcp\..*algodev.network$`)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create services expression: %v", err)
diff --git a/cmd/algorelay/commands.go b/cmd/algorelay/commands.go
index c73028c07f..0f8b0f84fb 100644
--- a/cmd/algorelay/commands.go
+++ b/cmd/algorelay/commands.go
@@ -38,8 +38,6 @@ var rootCmd = &cobra.Command{
}
type exitError struct {
- error
-
exitCode int
errorMessage string
}
diff --git a/cmd/algorelay/relayCmd.go b/cmd/algorelay/relayCmd.go
index 803073f07b..b6ee24ed04 100644
--- a/cmd/algorelay/relayCmd.go
+++ b/cmd/algorelay/relayCmd.go
@@ -44,7 +44,6 @@ var (
)
var nameRecordTypes = []string{"A", "CNAME", "SRV"}
-var srvRecordTypes = []string{"SRV"}
const metricsPort = uint16(9100)
@@ -356,6 +355,7 @@ func ensureRelayStatus(checkOnly bool, relay eb.Relay, nameDomain string, srvDom
// Error if target has another name entry - target should be relay provider's domain so shouldn't be possible
if mapsTo, has := ctx.nameEntries[target]; has {
err = fmt.Errorf("relay target has a DNS Name entry and should not (%s -> %s)", target, mapsTo)
+ return
}
names, err := getTargetDNSChain(ctx.nameEntries, target)
@@ -375,6 +375,7 @@ func ensureRelayStatus(checkOnly bool, relay eb.Relay, nameDomain string, srvDom
if relay.DNSAlias == "" {
err = fmt.Errorf("missing DNSAlias name")
+ return
}
targetDomainAlias := relay.DNSAlias + "." + nameDomain
diff --git a/cmd/catchpointdump/bench.go b/cmd/catchpointdump/bench.go
new file mode 100644
index 0000000000..e2094ef9f4
--- /dev/null
+++ b/cmd/catchpointdump/bench.go
@@ -0,0 +1,181 @@
+// Copyright (C) 2019-2025 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 (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/data/bookkeeping"
+ "github.com/algorand/go-algorand/ledger"
+ "github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/protocol"
+ tools "github.com/algorand/go-algorand/tools/network"
+)
+
+var reportJSONPath string
+
+func init() {
+ benchCmd.Flags().StringVarP(&networkName, "net", "n", "", "Specify the network name ( i.e. mainnet.algorand.network )")
+ benchCmd.Flags().IntVarP(&round, "round", "r", 0, "Specify the round number ( i.e. 7700000 )")
+ benchCmd.Flags().StringVarP(&relayAddress, "relay", "p", "", "Relay address to use ( i.e. r-ru.algorand-mainnet.network:4160 )")
+ benchCmd.Flags().StringVarP(&catchpointFile, "tar", "t", "", "Specify the catchpoint file (either .tar or .tar.gz) to process")
+ benchCmd.Flags().StringVarP(&reportJSONPath, "report", "j", "", "Specify the file to save the JSON formatted report to")
+}
+
+var benchCmd = &cobra.Command{
+ Use: "bench",
+ Short: "Benchmark a catchpoint restore",
+ Long: "Benchmark a catchpoint restore",
+ Args: validateNoPosArgsFn,
+ RunE: func(cmd *cobra.Command, args []string) (err error) {
+
+ // Either source the file locally or require a network name to download
+ if catchpointFile == "" && networkName == "" {
+ return fmt.Errorf("provide either catchpoint file or network name")
+ }
+ loadOnly = true
+ benchmark := makeBenchmarkReport()
+
+ if catchpointFile == "" {
+ if round == 0 {
+ return fmt.Errorf("round not set")
+ }
+ stage := benchmark.startStage("network")
+ catchpointFile, err = downloadCatchpointFromAnyRelay(networkName, round, relayAddress)
+ if err != nil {
+ return fmt.Errorf("failed to download catchpoint : %v", err)
+ }
+ stage.completeStage()
+ }
+ stats, err := os.Stat(catchpointFile)
+ if err != nil {
+ return fmt.Errorf("unable to stat '%s' : %v", catchpointFile, err)
+ }
+
+ catchpointSize := stats.Size()
+ if catchpointSize == 0 {
+ return fmt.Errorf("empty file '%s' : %v", catchpointFile, err)
+ }
+
+ genesisInitState := ledgercore.InitState{
+ Block: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{
+ UpgradeState: bookkeeping.UpgradeState{
+ CurrentProtocol: protocol.ConsensusCurrentVersion,
+ },
+ }},
+ }
+ cfg := config.GetDefaultLocal()
+ l, err := ledger.OpenLedger(logging.Base(), "./ledger", false, genesisInitState, cfg)
+ if err != nil {
+ return fmt.Errorf("unable to open ledger : %v", err)
+ }
+
+ defer os.Remove("./ledger.block.sqlite")
+ defer os.Remove("./ledger.block.sqlite-shm")
+ defer os.Remove("./ledger.block.sqlite-wal")
+ defer os.Remove("./ledger.tracker.sqlite")
+ defer os.Remove("./ledger.tracker.sqlite-shm")
+ defer os.Remove("./ledger.tracker.sqlite-wal")
+ defer l.Close()
+
+ catchupAccessor := ledger.MakeCatchpointCatchupAccessor(l, logging.Base())
+ err = catchupAccessor.ResetStagingBalances(context.Background(), true)
+ if err != nil {
+ return fmt.Errorf("unable to initialize catchup database : %v", err)
+ }
+
+ reader, err := os.Open(catchpointFile)
+ if err != nil {
+ return fmt.Errorf("unable to read '%s' : %v", catchpointFile, err)
+ }
+ defer reader.Close()
+
+ printDigests = false
+ stage := benchmark.startStage("database")
+
+ _, err = loadCatchpointIntoDatabase(context.Background(), catchupAccessor, reader, catchpointSize)
+ if err != nil {
+ return fmt.Errorf("unable to load catchpoint file into in-memory database : %v", err)
+ }
+ stage.completeStage()
+
+ stage = benchmark.startStage("digest")
+
+ err = buildMerkleTrie(context.Background(), catchupAccessor)
+ if err != nil {
+ return fmt.Errorf("unable to build Merkle tree : %v", err)
+ }
+ stage.completeStage()
+
+ benchmark.printReport()
+ if reportJSONPath != "" {
+ if err = benchmark.saveReport(reportJSONPath); err != nil {
+ fmt.Printf("error writing report to %s: %v\n", reportJSONPath, err)
+ }
+ }
+
+ return err
+ },
+}
+
+func downloadCatchpointFromAnyRelay(network string, round int, relayAddress string) (string, error) {
+ var addrs []string
+ if relayAddress != "" {
+ addrs = []string{relayAddress}
+ } else {
+ // append archivers
+ dnsaddrs, err := tools.ReadFromSRV(context.Background(), "archive", "tcp", networkName, "", false)
+ if err == nil && len(dnsaddrs) > 0 {
+ addrs = append(addrs, dnsaddrs...)
+ }
+ }
+
+ for _, addr := range addrs {
+ tarName, err := downloadCatchpoint(addr, round)
+ if err != nil {
+ reportInfof("failed to download catchpoint from '%s' : %v", addr, err)
+ continue
+ }
+ return tarName, nil
+ }
+ return "", fmt.Errorf("catchpoint for round %d on network %s could not be downloaded from any relay", round, network)
+}
+
+func buildMerkleTrie(ctx context.Context, catchupAccessor ledger.CatchpointCatchupAccessor) (err error) {
+ fmt.Printf("\n Building Merkle Trie, this might take a few minutes...\n")
+ err = catchupAccessor.BuildMerkleTrie(ctx, func(uint64, uint64) {})
+ if err != nil {
+ return err
+ }
+
+ var balanceHash, spverHash, onlineAccountsHash, onlineRoundParamsHash crypto.Digest
+ balanceHash, spverHash, onlineAccountsHash, onlineRoundParamsHash, _, err = catchupAccessor.GetVerifyData(ctx)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("done. \naccounts digest=%s, spver digest=%s, onlineaccounts digest=%s onlineroundparams digest=%s\n",
+ balanceHash, spverHash, onlineAccountsHash, onlineRoundParamsHash)
+
+ return nil
+}
diff --git a/cmd/catchpointdump/bench_report.go b/cmd/catchpointdump/bench_report.go
new file mode 100644
index 0000000000..7daabdb137
--- /dev/null
+++ b/cmd/catchpointdump/bench_report.go
@@ -0,0 +1,143 @@
+// Copyright (C) 2019-2025 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 (
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "os"
+ "runtime"
+ "time"
+
+ "github.com/algorand/go-algorand/util"
+ "github.com/google/uuid"
+ "github.com/klauspost/cpuid/v2"
+)
+
+type benchStage struct {
+ stage string
+ start time.Time
+ duration time.Duration
+ cpuTimeNS int64
+ completed bool
+}
+
+type hostInfo struct {
+ CPUCoreCnt int `json:"cores"`
+ CPULogicalCnt int `json:"log_cores"`
+ CPUBaseMHz int64 `json:"base_mhz"`
+ CPUMaxMHz int64 `json:"max_mhz"`
+ CPUName string `json:"cpu_name"`
+ CPUVendor string `json:"cpu_vendor"`
+ MemMB int `json:"mem_mb"`
+ OS string `json:"os"`
+ ID uuid.UUID `json:"uuid"`
+}
+
+type benchReport struct {
+ ReportID uuid.UUID `json:"report"`
+ Stages []*benchStage `json:"stages"`
+ HostInfo *hostInfo `json:"host"`
+ // TODO: query cpu cores, bogomips and stuff (windows/mac compatible)
+}
+
+func (bs *benchStage) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&struct {
+ Stage string `json:"stage"`
+ Duration int64 `json:"duration_sec"`
+ CPUTime int64 `json:"cpu_time_sec"`
+ }{
+ Stage: bs.stage,
+ Duration: int64(bs.duration.Seconds()),
+ CPUTime: bs.cpuTimeNS / 1000000000,
+ })
+}
+
+func (bs *benchStage) String() string {
+ return fmt.Sprintf(">> stage:%s duration_sec:%.1f duration_min:%.1f cpu_sec:%d", bs.stage, bs.duration.Seconds(), bs.duration.Minutes(), bs.cpuTimeNS/1000000000)
+}
+
+func gatherHostInfo() *hostInfo {
+ nid := sha256.Sum256(uuid.NodeID())
+ uuid, _ := uuid.FromBytes(nid[0:16])
+
+ ni := &hostInfo{
+ CPUCoreCnt: cpuid.CPU.PhysicalCores,
+ CPULogicalCnt: cpuid.CPU.LogicalCores,
+ CPUName: cpuid.CPU.BrandName,
+ CPUVendor: cpuid.CPU.VendorID.String(),
+ CPUMaxMHz: cpuid.CPU.BoostFreq / 1_000_000,
+ CPUBaseMHz: cpuid.CPU.Hz / 1_000_000,
+ MemMB: int(util.GetTotalMemory()) / 1024 / 1024,
+ ID: uuid,
+ OS: runtime.GOOS,
+ }
+
+ return ni
+}
+
+func makeBenchmarkReport() *benchReport {
+ uuid, _ := uuid.NewV7()
+ return &benchReport{
+ Stages: make([]*benchStage, 0),
+ HostInfo: gatherHostInfo(),
+ ReportID: uuid,
+ }
+}
+
+func (br *benchReport) startStage(stage string) *benchStage {
+ utime, stime, _ := util.GetCurrentProcessTimes()
+ bs := &benchStage{
+ stage: stage,
+ start: time.Now(),
+ duration: 0,
+ cpuTimeNS: utime + stime,
+ completed: false,
+ }
+ br.Stages = append(br.Stages, bs)
+ return bs
+}
+
+func (bs *benchStage) completeStage() {
+ utime, stime, _ := util.GetCurrentProcessTimes()
+ bs.duration = time.Since(bs.start)
+ bs.completed = true
+ bs.cpuTimeNS = utime + stime - bs.cpuTimeNS
+}
+
+func (br *benchReport) printReport() {
+ fmt.Print("\nBenchmark report:\n")
+ for i := range br.Stages {
+ fmt.Println(br.Stages[i].String())
+ }
+}
+
+func (br *benchReport) saveReport(filename string) error {
+ jsonData, err := json.MarshalIndent(br, "", " ")
+ if err != nil {
+ return err
+ }
+
+ // Write to file with permissions set to 0644
+ err = os.WriteFile(filename, jsonData, 0644)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/cmd/catchpointdump/commands.go b/cmd/catchpointdump/commands.go
index ba68869b61..6334e42e07 100644
--- a/cmd/catchpointdump/commands.go
+++ b/cmd/catchpointdump/commands.go
@@ -43,6 +43,7 @@ var versionCheck bool
func init() {
rootCmd.AddCommand(fileCmd)
rootCmd.AddCommand(netCmd)
+ rootCmd.AddCommand(benchCmd)
rootCmd.AddCommand(databaseCmd)
rootCmd.AddCommand(infoCmd)
}
diff --git a/cmd/catchpointdump/file.go b/cmd/catchpointdump/file.go
index 235ca55f85..31e27c1662 100644
--- a/cmd/catchpointdump/file.go
+++ b/cmd/catchpointdump/file.go
@@ -40,6 +40,7 @@ import (
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/ledger"
"github.com/algorand/go-algorand/ledger/ledgercore"
+ "github.com/algorand/go-algorand/ledger/store/trackerdb"
"github.com/algorand/go-algorand/ledger/store/trackerdb/sqlitedriver"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
@@ -50,6 +51,8 @@ var catchpointFile string
var outFileName string
var excludedFields = cmdutil.MakeCobraStringSliceValue(nil, []string{"version", "catchpoint"})
var printDigests bool
+var onlineOnly bool
+var rawDump bool
func init() {
fileCmd.Flags().StringVarP(&catchpointFile, "tar", "t", "", "Specify the catchpoint file (either .tar or .tar.gz) to process")
@@ -57,6 +60,8 @@ func init() {
fileCmd.Flags().BoolVarP(&loadOnly, "load", "l", false, "Load only, do not dump")
fileCmd.Flags().BoolVarP(&printDigests, "digest", "d", false, "Print balances and spver digests")
fileCmd.Flags().VarP(excludedFields, "exclude-fields", "e", "List of fields to exclude from the dump: ["+excludedFields.AllowedString()+"]")
+ fileCmd.Flags().BoolVarP(&rawDump, "raw", "R", false, "Dump raw catchpoint data, ignoring ledger database operations")
+ fileCmd.Flags().BoolVarP(&onlineOnly, "online-only", "O", false, "Only print online accounts and online round params data")
}
var fileCmd = &cobra.Command{
@@ -69,6 +74,13 @@ var fileCmd = &cobra.Command{
cmd.HelpFunc()(cmd, args)
return
}
+ if rawDump {
+ err := rawDumpCatchpointFile(catchpointFile, outFileName)
+ if err != nil {
+ reportErrorf("Error dumping raw catchpoint file: %v", err)
+ }
+ return
+ }
stats, err := os.Stat(catchpointFile)
if err != nil {
reportErrorf("Unable to stat '%s' : %v", catchpointFile, err)
@@ -130,18 +142,22 @@ 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)
- }
- err = printKeyValueStore("./ledger.tracker.sqlite", true, outFile)
- 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)
+ if !onlineOnly {
+ err = printAccountsDatabase("./ledger.tracker.sqlite", true, fileHeader, outFile, excludedFields.GetSlice())
+ if err != nil {
+ reportErrorf("Unable to print account database : %v", err)
+ }
+ err = printKeyValueStore("./ledger.tracker.sqlite", true, outFile)
+ 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)
+ }
}
+
+ // Always print online accounts and online round params
err = printOnlineAccounts("./ledger.tracker.sqlite", true, outFile)
if err != nil {
reportErrorf("Unable to print online accounts : %v", err)
@@ -181,6 +197,167 @@ func isGzipCompressed(catchpointReader *bufio.Reader, catchpointFileSize int64)
return prefixBytes[0] == gzipPrefix[0] && prefixBytes[1] == gzipPrefix[1]
}
+func rawDumpCatchpointFile(catchpointFile string, outFileName string) error {
+ stat, err := os.Stat(catchpointFile)
+ if err != nil {
+ return fmt.Errorf("unable to stat '%s': %v", catchpointFile, err)
+ }
+ if stat.Size() == 0 {
+ return fmt.Errorf("file '%s' is empty", catchpointFile)
+ }
+
+ f, err := os.Open(catchpointFile)
+ if err != nil {
+ return fmt.Errorf("unable to open file '%s': %v", catchpointFile, err)
+ }
+ defer f.Close()
+
+ outFile := os.Stdout
+ if outFileName != "" {
+ outFile, err = os.OpenFile(outFileName, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644)
+ if err != nil {
+ return fmt.Errorf("unable to create file '%s': %v", outFileName, err)
+ }
+ defer outFile.Close()
+ }
+
+ return rawDumpCatchpointStream(f, stat.Size(), outFile)
+}
+
+func rawDumpCatchpointStream(r io.Reader, fileSize int64, outFile *os.File) error {
+ bufRd := bufio.NewReader(r)
+ tarReader, _, err := getCatchpointTarReader(bufRd, fileSize)
+ if err != nil {
+ return err
+ }
+ var fileHeader ledger.CatchpointFileHeader
+ var fileHeaderFound bool
+ var version uint64
+
+ for {
+ hdr, err := tarReader.Next()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+ fname := hdr.Name
+
+ chunkData := make([]byte, hdr.Size)
+ _, readErr := io.ReadFull(tarReader, chunkData)
+ if readErr != nil && readErr != io.EOF {
+ return readErr
+ }
+
+ switch fname {
+ case ledger.CatchpointContentFileName:
+ err = protocol.Decode(chunkData, &fileHeader)
+ if err != nil {
+ return err
+ }
+ fileHeaderFound = true
+ version = fileHeader.Version
+ printHeaderFields(fileHeader)
+
+ case "stateProofVerificationContext.msgpack":
+ // skip decoding state proof verification context
+
+ default:
+ // it might be balances.*.msgpack or something else
+ if strings.HasPrefix(fname, "balances.") && strings.HasSuffix(fname, ".msgpack") {
+ if !fileHeaderFound {
+ fmt.Fprintf(outFile, "Warning: found a balances chunk %s before content.json\n", fname)
+ }
+ if version == ledger.CatchpointFileVersionV5 {
+ var chunk ledger.CatchpointSnapshotChunkV5
+ err = protocol.Decode(chunkData, &chunk)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding chunk %s: %v\n", fname, err)
+ return err
+ }
+ for _, brec := range chunk.Balances {
+ // decode accountData
+ var ad basics.AccountData
+ err = protocol.Decode(brec.AccountData, &ad)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding account data %s: %v\n", brec.Address.String(), err)
+ return err
+ }
+ adJSON, _ := json.Marshal(ad)
+ fmt.Fprintf(outFile, "%s : %s\n", brec.Address.String(), string(adJSON))
+ // v5 has no resources map
+ }
+ } else {
+ var chunk ledger.CatchpointSnapshotChunkV6
+ err = protocol.Decode(chunkData, &chunk)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding chunk %s: %v\n", fname, err)
+ return err
+ }
+ // Balances - only print if not onlineOnly
+ if !onlineOnly {
+ for _, brec := range chunk.Balances {
+ var ad trackerdb.BaseAccountData
+ err = protocol.Decode(brec.AccountData, &ad)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding account data %s: %v\n", brec.Address.String(), err)
+ return err
+ }
+ adJSON, _ := json.Marshal(ad)
+ fmt.Fprintf(outFile, "%s : %s\n", brec.Address.String(), string(adJSON))
+
+ // Now print each resource
+ for k, rawRes := range brec.Resources {
+ // decode as a generic object
+ var resDecoded trackerdb.ResourcesData
+ err = protocol.Decode(rawRes, &resDecoded)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding resource %s: %v\n", brec.Address.String(), err)
+ return err
+ }
+ resJSON, _ := json.Marshal(resDecoded)
+ fmt.Fprintf(outFile, "%s resource %d : %s\n", brec.Address.String(), k, string(resJSON))
+ }
+ }
+ // KVs
+ for _, kv := range chunk.KVs {
+ printKeyValue(bufio.NewWriterSize(outFile, 1024), kv.Key, kv.Value)
+ }
+ }
+ // OnlineAccounts
+ for _, oa := range chunk.OnlineAccounts {
+ var dataDecoded trackerdb.BaseOnlineAccountData
+ err = protocol.Decode(oa.Data, &dataDecoded)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding online account %s: %v\n", oa.Address.String(), err)
+ return err
+ }
+ dataJSON, _ := json.Marshal(dataDecoded)
+ fmt.Fprintf(outFile, "onlineaccount: %s %d %d %d %s\n",
+ oa.Address.String(), oa.UpdateRound, oa.NormalizedOnlineBalance, oa.VoteLastValid, string(dataJSON))
+ }
+ // OnlineRoundParams
+ for _, rp := range chunk.OnlineRoundParams {
+ var dataDecoded ledgercore.OnlineRoundParamsData
+ err = protocol.Decode(rp.Data, &dataDecoded)
+ if err != nil {
+ fmt.Fprintf(outFile, "Error decoding online round params %d: %v\n", rp.Round, err)
+ return err
+ }
+ dataJSON, _ := json.Marshal(dataDecoded)
+ fmt.Fprintf(outFile, "onlineroundparams: %d %s\n", rp.Round, string(dataJSON))
+ }
+ }
+ } else {
+ // unknown chunk name => ignore or just mention
+ fmt.Fprintf(outFile, "Unknown tar filename %s\n", fname)
+ }
+ }
+ }
+ return nil
+}
+
func getCatchpointTarReader(catchpointReader *bufio.Reader, catchpointFileSize int64) (*tar.Reader, bool, error) {
if isGzipCompressed(catchpointReader, catchpointFileSize) {
gzipReader, err := gzip.NewReader(catchpointReader)
diff --git a/cmd/catchpointdump/net.go b/cmd/catchpointdump/net.go
index 24a6ccfe65..908b8d6c84 100644
--- a/cmd/catchpointdump/net.go
+++ b/cmd/catchpointdump/net.go
@@ -35,6 +35,8 @@ 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/network/p2p"
+ "github.com/algorand/go-algorand/network/p2p/peerstore"
"github.com/algorand/go-algorand/protocol"
tools "github.com/algorand/go-algorand/tools/network"
"github.com/algorand/go-algorand/util"
@@ -62,6 +64,8 @@ func init() {
netCmd.Flags().VarP(excludedFields, "exclude-fields", "e", "List of fields to exclude from the dump: ["+excludedFields.AllowedString()+"]")
netCmd.Flags().StringVarP(&outFileName, "output", "o", "", "Specify an outfile for the dump ( i.e. tracker.dump.txt )")
netCmd.Flags().BoolVarP(&printDigests, "digest", "d", false, "Print balances and spver digests")
+ netCmd.Flags().BoolVarP(&rawDump, "raw", "R", false, "Dump raw catchpoint data, ignoring ledger database operations")
+ netCmd.Flags().BoolVarP(&onlineOnly, "online-only", "O", false, "Only print online accounts and online round params data")
}
var netCmd = &cobra.Command{
@@ -93,16 +97,22 @@ var netCmd = &cobra.Command{
reportInfof("failed to download catchpoint from '%s' : %v", addr, err)
continue
}
- genesisInitState := ledgercore.InitState{
- Block: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{
- UpgradeState: bookkeeping.UpgradeState{
- CurrentProtocol: protocol.ConsensusCurrentVersion,
- },
- }},
+
+ if rawDump {
+ err = rawDumpCatchpointFile(tarName, outFileName)
+ } else {
+ genesisInitState := ledgercore.InitState{
+ Block: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{
+ UpgradeState: bookkeeping.UpgradeState{
+ CurrentProtocol: protocol.ConsensusCurrentVersion,
+ },
+ }},
+ }
+ err = loadAndDump(addr, tarName, genesisInitState)
}
- err = loadAndDump(addr, tarName, genesisInitState)
+
if err != nil {
- reportInfof("failed to load/dump from tar file for '%s' : %v", addr, err)
+ reportInfof("failed to process catchpoint for '%s' : %v", addr, err)
continue
}
// clear possible errors from previous run: at this point we've succeeded
@@ -164,8 +174,8 @@ func printDownloadProgressLine(progress int, barLength int, url string, dld int6
fmt.Printf(escapeCursorUp+escapeDeleteLine+outString+" %s\n", formatSize(dld))
}
-func getRemoteDataStream(url string, hint string) (result io.ReadCloser, ctxCancel context.CancelFunc, err error) {
- fmt.Printf("downloading %s from %s\n", hint, url)
+func getRemoteDataStream(addr string, url string, client *http.Client, hint string) (result io.ReadCloser, ctxCancel context.CancelFunc, err error) {
+ fmt.Printf("downloading %s from %s %s\n", hint, addr, url)
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return
@@ -174,7 +184,7 @@ func getRemoteDataStream(url string, hint string) (result io.ReadCloser, ctxCanc
timeoutContext, ctxCancel := context.WithTimeout(context.Background(), config.GetDefaultLocal().MaxCatchpointDownloadDuration)
request = request.WithContext(timeoutContext)
network.SetUserAgentHeader(request.Header)
- response, err := http.DefaultClient.Do(request)
+ response, err := client.Do(request)
if err != nil {
return
}
@@ -229,13 +239,28 @@ func doDownloadCatchpoint(url string, wdReader util.WatchdogStreamReader, out io
}
}
+func buildURL(genesisID string, round int, resource string) string {
+ return fmt.Sprintf("/v1/%s/%s/%s", genesisID, resource, strconv.FormatUint(uint64(round), 36))
+}
+
// Downloads a catchpoint tar file and returns the path to the tar file.
func downloadCatchpoint(addr string, round int) (string, error) {
genesisID := strings.Split(networkName, ".")[0] + "-v1.0"
- urlTemplate := "http://" + addr + "/v1/" + genesisID + "/%s/" + strconv.FormatUint(uint64(round), 36)
- catchpointURL := fmt.Sprintf(urlTemplate, "ledger")
- catchpointStream, catchpointCtxCancel, err := getRemoteDataStream(catchpointURL, "catchpoint")
+ // attempt to parse as p2p address first
+ var httpClient *http.Client
+ catchpointURL := buildURL(genesisID, round, "ledger")
+ if addrInfo, err := peerstore.PeerInfoFromAddr(addr); err == nil {
+ httpClient, err = p2p.MakeTestHTTPClient(addrInfo)
+ if err != nil {
+ return "", err
+ }
+ } else {
+ httpClient = http.DefaultClient
+ catchpointURL = "http://" + addr + catchpointURL
+ }
+
+ catchpointStream, catchpointCtxCancel, err := getRemoteDataStream(addr, catchpointURL, httpClient, "catchpoint")
defer catchpointCtxCancel()
if err != nil {
return "", err
@@ -360,18 +385,22 @@ func loadAndDump(addr string, tarFile string, genesisInitState ledgercore.InitSt
return err
}
defer outFile.Close()
- err = printAccountsDatabase("./ledger.tracker.sqlite", true, fileHeader, outFile, excludedFields.GetSlice())
- if err != nil {
- return err
- }
- err = printKeyValueStore("./ledger.tracker.sqlite", true, outFile)
- if err != nil {
- return err
- }
- err = printStateProofVerificationContext("./ledger.tracker.sqlite", true, outFile)
- if err != nil {
- return err
+ if !onlineOnly {
+ err = printAccountsDatabase("./ledger.tracker.sqlite", true, fileHeader, outFile, excludedFields.GetSlice())
+ if err != nil {
+ return err
+ }
+ err = printKeyValueStore("./ledger.tracker.sqlite", true, outFile)
+ if err != nil {
+ return err
+ }
+ err = printStateProofVerificationContext("./ledger.tracker.sqlite", true, outFile)
+ if err != nil {
+ return err
+ }
}
+
+ // Always print online accounts and online round params
err = printOnlineAccounts("./ledger.tracker.sqlite", true, outFile)
if err != nil {
return err
diff --git a/cmd/catchupsrv/download.go b/cmd/catchupsrv/download.go
index 3c860dc3d1..74a18ac60e 100644
--- a/cmd/catchupsrv/download.go
+++ b/cmd/catchupsrv/download.go
@@ -90,15 +90,6 @@ func blockToPath(blk uint64) string {
)
}
-// stringBlockToPath is the same as blockToPath except it takes a (non-padded) base-36 block
-func stringBlockToPath(s string) (string, error) {
- blk, err := stringToBlock(s)
- if err != nil {
- return "", err
- }
- return blockToPath(blk), nil
-}
-
// blockDir returns the root folder where all the blocks are stored
func blockDir() string {
return filepath.Join(*dirFlag, fmt.Sprintf("v1/%s/block", *genesisFlag))
diff --git a/cmd/catchupsrv/tarblocks.go b/cmd/catchupsrv/tarblocks.go
index 90936e428e..93a4293ad6 100644
--- a/cmd/catchupsrv/tarblocks.go
+++ b/cmd/catchupsrv/tarblocks.go
@@ -197,7 +197,7 @@ func (tbf *tarBlockFile) getBlock(round uint64) (data []byte, err error) {
}
}
err = nil
- for true {
+ for {
tbf.current, err = tbf.tarfile.Next()
if err == io.EOF {
tbf._close()
diff --git a/cmd/diagcfg/metric.go b/cmd/diagcfg/metric.go
index 54f7b66970..df3c3649dc 100644
--- a/cmd/diagcfg/metric.go
+++ b/cmd/diagcfg/metric.go
@@ -104,7 +104,6 @@ func metricEnableDisable(enable bool) {
if err != nil {
fmt.Printf(metricSaveConfigFailed, fmt.Sprintf("%v", err))
}
- return
}
func getConfigFilePath() (string, error) {
diff --git a/cmd/diagcfg/telemetry.go b/cmd/diagcfg/telemetry.go
index b70ec32290..900f4227d5 100644
--- a/cmd/diagcfg/telemetry.go
+++ b/cmd/diagcfg/telemetry.go
@@ -118,7 +118,7 @@ var telemetryStatusCmd = &cobra.Command{
if err != nil {
fmt.Println(err)
fmt.Println(loggingNotConfigured)
- } else if cfg.Enable == false {
+ } else if !cfg.Enable {
fmt.Println(loggingNotEnabled)
} else {
fmt.Printf(loggingEnabled, cfg.Name, cfg.GUID)
diff --git a/cmd/goal/account.go b/cmd/goal/account.go
index 524542ec53..794d646ef3 100644
--- a/cmd/goal/account.go
+++ b/cmd/goal/account.go
@@ -573,7 +573,6 @@ var assetDetailsCmd = &cobra.Command{
}
printAccountAssetsInformation(accountAddress, response)
-
},
}
var infoCmd = &cobra.Command{
@@ -734,7 +733,12 @@ func printAccountInfo(client libgoal.Client, address string, onlyShowAssetIDs bo
extraPages = fmt.Sprintf(", %d extra page%s", *app.Params.ExtraProgramPages, plural)
}
- fmt.Fprintf(report, "\tID %d%s, global state used %d/%d uints, %d/%d byte slices\n", app.Id, extraPages, usedInts, allocatedInts, usedBytes, allocatedBytes)
+ version := uint64(0)
+ if app.Params.Version != nil {
+ version = *app.Params.Version
+ }
+
+ fmt.Fprintf(report, "\tID %d%s, global state used %d/%d uints, %d/%d byte slices, version %d\n", app.Id, extraPages, usedInts, allocatedInts, usedBytes, allocatedBytes, version)
}
fmt.Fprintln(report, "Opted In Apps:")
@@ -772,7 +776,7 @@ func printAccountAssetsInformation(address string, response model.AccountAssetsI
fmt.Printf("Account: %s\n", address)
fmt.Printf("Round: %d\n", response.Round)
if response.NextToken != nil {
- fmt.Printf("NextToken (to retrieve more account assets): %s\n", *response.NextToken)
+ fmt.Printf("NextToken (use with --next to retrieve more account assets): %s\n", *response.NextToken)
}
fmt.Printf("Assets:\n")
for _, asset := range *response.AssetHoldings {
@@ -1241,9 +1245,9 @@ var listParticipationKeysCmd = &cobra.Command{
rowFormat := "%-10s %-11s %-15s %10s %11s %10s\n"
fmt.Printf(rowFormat, "Registered", "Account", "ParticipationID", "Last Used", "First round", "Last round")
for _, part := range parts {
- onlineInfoStr := "unknown"
onlineAccountInfo, err := client.AccountInformation(part.Address, false)
if err == nil {
+ onlineInfoStr := "no"
votingBytes := part.Key.VoteParticipationKey
vrfBytes := part.Key.SelectionParticipationKey
if onlineAccountInfo.Participation != nil &&
@@ -1253,8 +1257,6 @@ var listParticipationKeysCmd = &cobra.Command{
(onlineAccountInfo.Participation.VoteLastValid == part.Key.VoteLastValid) &&
(onlineAccountInfo.Participation.VoteKeyDilution == part.Key.VoteKeyDilution) {
onlineInfoStr = "yes"
- } else {
- onlineInfoStr = "no"
}
/*
diff --git a/cmd/goal/application.go b/cmd/goal/application.go
index 587810b22f..0735043c8f 100644
--- a/cmd/goal/application.go
+++ b/cmd/goal/application.go
@@ -58,7 +58,8 @@ var (
extraPages uint32
- onCompletion string
+ onCompletion string
+ rejectVersion uint64
localSchemaUints uint64
localSchemaByteSlices uint64
@@ -95,6 +96,7 @@ func init() {
appCmd.AddCommand(methodAppCmd)
appCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation")
+ appCmd.PersistentFlags().Uint64Var(&rejectVersion, "reject-version", 0, "If set non-zero, reject for this app version or higher")
appCmd.PersistentFlags().StringArrayVar(&appArgs, "app-arg", nil, "Args to encode for application transactions (all will be encoded to a byte slice). 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...'.")
appCmd.PersistentFlags().StringSliceVar(&foreignApps, "foreign-app", nil, "Indexes of other apps whose global state is read in this transaction")
appCmd.PersistentFlags().StringSliceVar(&foreignAssets, "foreign-asset", nil, "Indexes of assets whose parameters are read in this transaction")
@@ -128,6 +130,7 @@ func init() {
methodAppCmd.Flags().StringVar(&method, "method", "", "Method to be called")
methodAppCmd.Flags().StringArrayVar(&methodArgs, "arg", nil, "Args to pass in for calling a method")
methodAppCmd.Flags().StringVar(&onCompletion, "on-completion", "NoOp", "OnCompletion action for application transaction")
+ methodAppCmd.Flags().Uint64Var(&rejectVersion, "reject-version", 0, "RejectVersion for application transaction")
methodAppCmd.Flags().BoolVar(&methodCreatesApp, "create", false, "Create an application in this method call")
methodAppCmd.Flags().Uint64Var(&globalSchemaUints, "global-ints", 0, "Maximum number of integer values that may be stored in the global key/value store. Immutable, only valid when passed with --create.")
methodAppCmd.Flags().Uint64Var(&globalSchemaByteSlices, "global-byteslices", 0, "Maximum number of byte slices that may be stored in the global key/value store. Immutable, only valid when passed with --create.")
@@ -526,7 +529,7 @@ var updateAppCmd = &cobra.Command{
approvalProg, clearProg := mustParseProgArgs()
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, approvalProg, clearProg)
+ tx, err := client.MakeUnsignedAppUpdateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, approvalProg, clearProg, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -596,7 +599,7 @@ var optInAppCmd = &cobra.Command{
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
+ tx, err := client.MakeUnsignedAppOptInTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -666,7 +669,7 @@ var closeOutAppCmd = &cobra.Command{
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
+ tx, err := client.MakeUnsignedAppCloseOutTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -736,7 +739,7 @@ var clearAppCmd = &cobra.Command{
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
+ tx, err := client.MakeUnsignedAppClearStateTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -806,7 +809,7 @@ var callAppCmd = &cobra.Command{
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
+ tx, err := client.MakeUnsignedAppNoOpTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -876,7 +879,7 @@ var deleteAppCmd = &cobra.Command{
// Parse transaction parameters
appArgs, appAccounts, foreignApps, foreignAssets, boxes := getAppInputs()
- tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes)
+ tx, err := client.MakeUnsignedAppDeleteTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, boxes, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
@@ -1012,7 +1015,6 @@ var readStateAppCmd = &cobra.Command{
}
// Should be unreachable
- return
},
}
@@ -1030,25 +1032,29 @@ var infoAppCmd = &cobra.Command{
}
params := meta.Params
- gsch := params.GlobalStateSchema
- lsch := params.LocalStateSchema
- epp := params.ExtraProgramPages
-
fmt.Printf("Application ID: %d\n", appIdx)
fmt.Printf("Application account: %v\n", basics.AppIndex(appIdx).Address())
fmt.Printf("Creator: %v\n", params.Creator)
fmt.Printf("Approval hash: %v\n", basics.Address(logic.HashProgram(params.ApprovalProgram)))
fmt.Printf("Clear hash: %v\n", basics.Address(logic.HashProgram(params.ClearStateProgram)))
+ ver := params.Version
+ if ver != nil {
+ fmt.Printf("Program version: %d\n", *ver)
+ }
+
+ epp := params.ExtraProgramPages
if epp != nil {
fmt.Printf("Extra program pages: %d\n", *epp)
}
+ gsch := params.GlobalStateSchema
if gsch != nil {
fmt.Printf("Max global byteslices: %d\n", gsch.NumByteSlice)
fmt.Printf("Max global integers: %d\n", gsch.NumUint)
}
+ lsch := params.LocalStateSchema
if lsch != nil {
fmt.Printf("Max local byteslices: %d\n", lsch.NumByteSlice)
fmt.Printf("Max local integers: %d\n", lsch.NumUint)
@@ -1275,6 +1281,10 @@ var methodAppCmd = &cobra.Command{
case transactions.CloseOutOC, transactions.ClearStateOC:
reportWarnf("'--on-completion %s' may be ill-formed for use with --create", onCompletion)
}
+
+ if rejectVersion != 0 {
+ reportErrorf("--reject-version should not be provided with --create")
+ }
} else {
if appIdx == 0 {
reportErrorf("one of --app-id or --create must be provided")
@@ -1366,7 +1376,7 @@ var methodAppCmd = &cobra.Command{
appCallTxn, err := client.MakeUnsignedApplicationCallTx(
appIdx, applicationArgs, appAccounts, foreignApps, foreignAssets, boxes,
- onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages)
+ onCompletionEnum, approvalProg, clearProg, globalSchema, localSchema, extraPages, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
diff --git a/cmd/goal/box.go b/cmd/goal/box.go
index bc98b02061..92ea12812c 100644
--- a/cmd/goal/box.go
+++ b/cmd/goal/box.go
@@ -20,11 +20,17 @@ import (
"bytes"
"strings"
+ "github.com/algorand/go-algorand/cmd/util/datadir"
"github.com/spf13/cobra"
)
-var boxName string
-var maxBoxes uint64
+var (
+ boxName string
+ // next uint64 // declared in account.go
+ // limit uint64 // declared in account.go
+ boxPrefix string
+ boxValues bool
+)
func init() {
appCmd.AddCommand(appBoxCmd)
@@ -37,7 +43,10 @@ func init() {
appBoxInfoCmd.Flags().StringVarP(&boxName, "name", "n", "", "Application box name. Use the same form as app-arg to name the box.")
appBoxInfoCmd.MarkFlagRequired("name")
- appBoxListCmd.Flags().Uint64VarP(&maxBoxes, "max", "m", 0, "Maximum number of boxes to list. 0 means no limit.")
+ appBoxListCmd.Flags().StringVarP(&boxPrefix, "prefix", "p", "", "Return only boxes that begin with the supplied prefix.")
+ appBoxListCmd.Flags().StringVarP(&next, "next", "n", "", "The next-token returned from a previous call, used for pagination.")
+ appBoxListCmd.Flags().Uint64VarP(&limit, "limit", "l", 0, "The maximum number of boxes to list. 0 means no limit.")
+ appBoxListCmd.Flags().BoolVarP(&boxValues, "values", "v", false, "Request and display box values.")
}
var appBoxCmd = &cobra.Command{
@@ -89,30 +98,36 @@ var appBoxInfoCmd = &cobra.Command{
var appBoxListCmd = &cobra.Command{
Use: "list",
- Short: "List all application boxes belonging to an application",
- Long: "List all application boxes belonging to an application.\n" +
- "For printable strings, the box name is formatted as 'str:hello'\n" +
- "For everything else, the box name is formatted as 'b64:A=='. ",
+ Short: "List application boxes belonging to an application",
+ Long: "List application boxes belonging to an application.\n" +
+ "Printable names and values are formatted as 'str:hello' otherwise 'b64:A=='.",
Args: validateNoPosArgsFn,
Run: func(cmd *cobra.Command, args []string) {
- _, client := getDataDirAndClient()
+ dataDir := datadir.EnsureSingleDataDir()
+ client := ensureAlgodClient(dataDir)
- // Get app boxes
- boxesRes, err := client.ApplicationBoxes(appIdx, maxBoxes)
+ response, err := client.ApplicationBoxes(appIdx, boxPrefix, &next, limit, boxValues)
if err != nil {
reportErrorf(errorRequestFail, err)
}
- boxes := boxesRes.Boxes
- // Error if no boxes found
- if len(boxes) == 0 {
- reportErrorf("No boxes found for appid %d", appIdx)
+ // Endpoint did not originally report the Round, so don't show it if it's 0
+ if response.Round != 0 {
+ reportInfof("Round: %d", response.Round)
}
-
- // Print app boxes
- for _, descriptor := range boxes {
- encodedName := encodeBytesAsAppCallBytes(descriptor.Name)
- reportInfof("%s", encodedName)
+ // There will only be a next-token if there are more boxes to list
+ if response.NextToken != nil {
+ encoded := encodeBytesAsAppCallBytes([]byte(*response.NextToken))
+ reportInfof("NextToken (use with --next to retrieve more boxes): %s", encoded)
+ }
+ reportInfoln("Boxes:")
+ for _, descriptor := range response.Boxes {
+ name := encodeBytesAsAppCallBytes(descriptor.Name)
+ if boxValues {
+ reportInfof("%s : %s", name, encodeBytesAsAppCallBytes(descriptor.Value))
+ } else {
+ reportInfoln(name)
+ }
}
},
}
diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go
index 36cd291e83..165c37ed9c 100644
--- a/cmd/goal/clerk.go
+++ b/cmd/goal/clerk.go
@@ -358,7 +358,6 @@ var sendCmd = &cobra.Command{
dataDir := datadir.EnsureSingleDataDir()
accountList := makeAccountsList(dataDir)
- var fromAddressResolved string
var program []byte = nil
var programArgs [][]byte = nil
var lsig transactions.LogicSig
@@ -380,19 +379,19 @@ var sendCmd = &cobra.Command{
lsigFromArgs(&lsig)
}
if program != nil {
- ph := logic.HashProgram(program)
- pha := basics.Address(ph)
- fromAddressResolved = pha.String()
+ if account == "" {
+ ph := logic.HashProgram(program)
+ pha := basics.Address(ph)
+ account = pha.String()
+ }
programArgs = getProgramArgs()
} else {
// Check if from was specified, else use default
if account == "" {
account = accountList.getDefaultAccount()
}
-
- // Resolving friendly names
- fromAddressResolved = accountList.getAddressByName(account)
}
+ fromAddressResolved := accountList.getAddressByName(account)
toAddressResolved := accountList.getAddressByName(toAddress)
// Parse notes and lease fields
@@ -439,6 +438,14 @@ var sendCmd = &cobra.Command{
payment.Fee = basics.MicroAlgos{Raw: fee}
}
+ var authAddr basics.Address
+ if signerAddress != "" {
+ authAddr, err = basics.UnmarshalChecksumAddress(signerAddress)
+ if err != nil {
+ reportErrorf("Signer invalid (%s): %v", signerAddress, err)
+ }
+ }
+
var stx transactions.SignedTxn
if lsig.Logic != nil {
@@ -471,18 +478,14 @@ var sendCmd = &cobra.Command{
Logic: program,
Args: programArgs,
},
+ AuthAddr: authAddr,
}
} else {
signTx := sign || (outFilename == "")
- var authAddr basics.Address
if signerAddress != "" {
if !signTx {
reportErrorf("Signer specified when txn won't be signed")
}
- authAddr, err = basics.UnmarshalChecksumAddress(signerAddress)
- if err != nil {
- reportErrorf("Signer invalid (%s): %v", signerAddress, err)
- }
}
stx, err = createSignedTransaction(client, signTx, dataDir, walletName, payment, authAddr)
if err != nil {
@@ -1223,7 +1226,7 @@ var dryrunRemoteCmd = &cobra.Command{
reportErrorf("dryrun-remote: %s", err.Error())
}
if rawOutput {
- fmt.Fprintf(os.Stdout, string(protocol.EncodeJSON(&resp)))
+ fmt.Fprint(os.Stdout, string(protocol.EncodeJSON(&resp)))
return
}
diff --git a/cmd/goal/commands.go b/cmd/goal/commands.go
index 8054884af9..707d3ccc14 100644
--- a/cmd/goal/commands.go
+++ b/cmd/goal/commands.go
@@ -17,6 +17,7 @@
package main
import (
+ "errors"
"flag"
"fmt"
"io"
@@ -369,9 +370,9 @@ func getWalletHandleMaybePassword(dataDir string, walletName string, getPassword
walletID = []byte(wallets[0].ID)
accountList.setDefaultWalletID(walletID)
} else if len(wallets) == 0 {
- return nil, nil, fmt.Errorf(errNoWallets)
+ return nil, nil, errors.New(errNoWallets)
} else {
- return nil, nil, fmt.Errorf(errNoDefaultWallet)
+ return nil, nil, errors.New(errNoDefaultWallet)
}
}
// Fetch the wallet name (useful for error messages, and to check
diff --git a/cmd/goal/formatting.go b/cmd/goal/formatting.go
index 892e2d999a..69e5bb5e3f 100644
--- a/cmd/goal/formatting.go
+++ b/cmd/goal/formatting.go
@@ -154,7 +154,7 @@ func jsonPrintable(str string) bool {
if r >= utf8.RuneSelf {
return false
}
- if htmlSafeSet[r] == false {
+ if !htmlSafeSet[r] {
return false
}
}
diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go
index a0796506cd..32198de3de 100644
--- a/cmd/goal/interact.go
+++ b/cmd/goal/interact.go
@@ -78,7 +78,6 @@ type appInteractDatum interface {
}
func helpList(help map[string]appInteractDatum) string {
- var names []string
largestName := 0
largestKind := 0
for k, v := range help {
@@ -91,7 +90,6 @@ func helpList(help map[string]appInteractDatum) string {
if len(v.kind()) > largestKind {
largestKind = len(v.kind())
}
- names = append(names, k)
}
namesize := "%-" + fmt.Sprintf("%d", largestName+3) + "s"
@@ -588,7 +586,7 @@ var appExecuteCmd = &cobra.Command{
localSchema = header.Query.Local.ToStateSchema()
globalSchema = header.Query.Global.ToStateSchema()
}
- tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, nil, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0)
+ tx, err := client.MakeUnsignedApplicationCallTx(appIdx, appArgs, appAccounts, foreignApps, foreignAssets, nil, onCompletion, approvalProg, clearProg, globalSchema, localSchema, 0, rejectVersion)
if err != nil {
reportErrorf("Cannot create application txn: %v", err)
}
diff --git a/cmd/goal/logging.go b/cmd/goal/logging.go
index 6ca2ba444e..6120f547f4 100644
--- a/cmd/goal/logging.go
+++ b/cmd/goal/logging.go
@@ -59,7 +59,7 @@ var loggingCmd = &cobra.Command{
if err != nil {
fmt.Println(err)
fmt.Println(loggingNotConfigured)
- } else if cfg.Enable == false {
+ } else if !cfg.Enable {
fmt.Println(loggingNotEnabled)
} else {
fmt.Printf(loggingEnabled, cfg.Name, cfg.GUID)
diff --git a/cmd/goal/messages.go b/cmd/goal/messages.go
index 0b6440b75c..13be8dc7e7 100644
--- a/cmd/goal/messages.go
+++ b/cmd/goal/messages.go
@@ -165,29 +165,30 @@ const (
tealsignTooManyArg = "--set-lsig-arg-idx too large, maximum of %d arguments"
tealsignInfoWroteSig = "Wrote signature for %s to LSig.Args[%d]"
- tealLogicSigSize = "%s: logicsig program size too large: %d > %d"
- tealAppSize = "%s: app program size too large: %d > %d"
-
// Wallet
infoRecoveryPrompt = "Please type your recovery mnemonic below, and hit return when you are done: "
infoChoosePasswordPrompt = "Please choose a password for wallet '%s': "
infoPasswordConfirmation = "Please confirm the password: "
infoCreatingWallet = "Creating wallet..."
infoCreatedWallet = "Created wallet '%s'"
+ infoUnencrypted = "Creating unencrypted wallet"
infoBackupExplanation = "Your new wallet has a backup phrase that can be used for recovery.\nKeeping this backup phrase safe is extremely important.\nWould you like to see it now? (Y/n): "
infoPrintedBackupPhrase = "Your backup phrase is printed below.\nKeep this information safe -- never share it with anyone!"
infoBackupPhrase = "\n%s"
infoNoWallets = "No wallets found. You can create a wallet with `goal wallet new`"
+ infoRenamedWallet = "Renamed wallet '%s' to '%s'"
errorCouldntCreateWallet = "Couldn't create wallet: %s"
errorCouldntInitializeWallet = "Couldn't initialize wallet: %s"
errorCouldntExportMDK = "Couldn't export master derivation key: %s"
errorCouldntMakeMnemonic = "Couldn't make mnemonic: %s"
errorCouldntListWallets = "Couldn't list wallets: %s"
+ errorCouldntFindWallet = "Couldn't find wallet: %s"
errorPasswordConfirmation = "Password confirmation did not match"
errorBadMnemonic = "Problem with mnemonic: %s"
errorBadRecoveredKey = "Recovered invalid key"
errorFailedToReadResponse = "Couldn't read response: %s"
errorFailedToReadPassword = "Couldn't read password: %s"
+ errorCouldntRenameWallet = "Couldn't rename wallet: %s"
// Commands
infoPasswordPrompt = "Please enter the password for wallet '%s': "
diff --git a/cmd/goal/node.go b/cmd/goal/node.go
index 900b05204c..220837e270 100644
--- a/cmd/goal/node.go
+++ b/cmd/goal/node.go
@@ -282,28 +282,6 @@ var startCmd = &cobra.Command{
},
}
-var shutdownCmd = &cobra.Command{
- Use: "shutdown",
- Short: "Shut down the node",
- Args: validateNoPosArgsFn,
- Run: func(cmd *cobra.Command, _ []string) {
- binDir, err := util.ExeDir()
- if err != nil {
- panic(err)
- }
- datadir.OnDataDirs(func(dataDir string) {
- nc := nodecontrol.MakeNodeController(binDir, dataDir)
- err := nc.Shutdown()
-
- if err == nil {
- reportInfoln(infoNodeShuttingDown)
- } else {
- reportErrorf(errorNodeFailedToShutdown, err)
- }
- })
- },
-}
-
func getRunHostedConfigFlag(dataDir string) bool {
// See if this instance wants to run Hosted, even if '-H' wasn't specified on our cmdline
cfg, err := config.LoadConfigFromDisk(dataDir)
diff --git a/cmd/goal/wallet.go b/cmd/goal/wallet.go
index 104e5998c3..8bc1b427c8 100644
--- a/cmd/goal/wallet.go
+++ b/cmd/goal/wallet.go
@@ -32,19 +32,24 @@ import (
)
var (
- recoverWallet bool
- defaultWalletName string
+ recoverWallet bool
+ createUnencryptedWallet bool
+ noDisplaySeed bool
+ defaultWalletName string
)
func init() {
walletCmd.AddCommand(newWalletCmd)
walletCmd.AddCommand(listWalletsCmd)
+ walletCmd.AddCommand(renameWalletCmd)
// Default wallet to use when -w not specified
walletCmd.Flags().StringVarP(&defaultWalletName, "default", "f", "", "Set the wallet with this name to be the default wallet")
// Should we recover the wallet?
newWalletCmd.Flags().BoolVarP(&recoverWallet, "recover", "r", false, "Recover the wallet from the backup mnemonic provided at wallet creation (NOT the mnemonic provided by goal account export or by algokey). Regenerate accounts in the wallet with `goal account new`")
+ newWalletCmd.Flags().BoolVar(&createUnencryptedWallet, "unencrypted", false, "Create a new wallet without a password.")
+ newWalletCmd.Flags().BoolVar(&noDisplaySeed, "no-display-seed", false, "Create a new wallet without displaying the seed phrase.")
}
var walletCmd = &cobra.Command{
@@ -113,17 +118,23 @@ var newWalletCmd = &cobra.Command{
}
}
- // Fetch a password for the wallet
- fmt.Printf(infoChoosePasswordPrompt, walletName)
- walletPassword := ensurePassword()
+ walletPassword := []byte{}
- // Confirm the password
- fmt.Printf(infoPasswordConfirmation)
- passwordConfirmation := ensurePassword()
+ if createUnencryptedWallet {
+ reportInfoln(infoUnencrypted)
+ } else {
+ // Fetch a password for the wallet
+ fmt.Printf(infoChoosePasswordPrompt, walletName)
+ walletPassword = ensurePassword()
- // Check the password confirmation
- if !bytes.Equal(walletPassword, passwordConfirmation) {
- reportErrorln(errorPasswordConfirmation)
+ // Confirm the password
+ fmt.Print(infoPasswordConfirmation)
+ passwordConfirmation := ensurePassword()
+
+ // Check the password confirmation
+ if !bytes.Equal(walletPassword, passwordConfirmation) {
+ reportErrorln(errorPasswordConfirmation)
+ }
}
// Create the wallet
@@ -134,9 +145,9 @@ var newWalletCmd = &cobra.Command{
}
reportInfof(infoCreatedWallet, walletName)
- if !recoverWallet {
+ if !recoverWallet && !noDisplaySeed {
// Offer to print backup seed
- fmt.Printf(infoBackupExplanation)
+ fmt.Println(infoBackupExplanation)
resp, err := reader.ReadString('\n')
resp = strings.TrimSpace(resp)
if err != nil {
@@ -200,6 +211,53 @@ var listWalletsCmd = &cobra.Command{
},
}
+var renameWalletCmd = &cobra.Command{
+ Use: "rename [wallet name] [new wallet name]",
+ Short: "Rename wallet",
+ Args: cobra.ExactArgs(2),
+ Run: func(cmd *cobra.Command, args []string) {
+ dataDir := datadir.EnsureSingleDataDir()
+
+ client := ensureKmdClient(dataDir)
+
+ walletName := []byte(args[0])
+ newWalletName := []byte(args[1])
+
+ if bytes.Equal(walletName, newWalletName) {
+ reportErrorf(errorCouldntRenameWallet, "new name is identical to current name")
+ }
+
+ wid, duplicate, err := client.FindWalletIDByName(walletName)
+
+ if err != nil {
+ reportErrorf(errorCouldntRenameWallet, err)
+ }
+
+ if wid == nil {
+ reportErrorf(errorCouldntFindWallet, string(walletName))
+ }
+
+ if duplicate {
+ reportErrorf(errorCouldntRenameWallet, "Multiple wallets by the same name are not supported")
+ }
+
+ walletPassword := []byte{}
+
+ // if wallet is encrypted, fetch the password
+ if !client.WalletIsUnencrypted(wid) {
+ fmt.Printf(infoPasswordPrompt, walletName)
+ walletPassword = ensurePassword()
+ }
+
+ err = client.RenameWallet(wid, newWalletName, walletPassword)
+ if err != nil {
+ reportErrorf(errorCouldntRenameWallet, err)
+ }
+
+ reportInfof(infoRenamedWallet, walletName, newWalletName)
+ },
+}
+
func printWallets(dataDir string, wallets []kmdapi.APIV1Wallet) {
accountList := makeAccountsList(dataDir)
defaultWalletID := string(accountList.getDefaultWalletID())
diff --git a/cmd/loadgenerator/main.go b/cmd/loadgenerator/main.go
index 8f7dd213eb..9afd126d3f 100644
--- a/cmd/loadgenerator/main.go
+++ b/cmd/loadgenerator/main.go
@@ -268,7 +268,7 @@ func generateTransactions(restClient client.RestClient, cfg config, privateKeys
// each thread makes new HTTP connections per API call
var sendWaitGroup sync.WaitGroup
sendWaitGroup.Add(nroutines)
- sent := make([]int, nroutines, nroutines)
+ sent := make([]int, nroutines)
for i := 0; i < nroutines; i++ {
go func(base int) {
defer sendWaitGroup.Done()
diff --git a/cmd/netdummy/main.go b/cmd/netdummy/main.go
index 2dd434fe9b..5d9fdf370f 100644
--- a/cmd/netdummy/main.go
+++ b/cmd/netdummy/main.go
@@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"os"
+ "runtime"
"time"
"github.com/algorand/go-deadlock"
@@ -47,7 +48,7 @@ func main() {
log.SetLevel(logging.Debug)
log.SetOutput(os.Stderr)
- var nodes []network.GossipNode
+ var p runtime.Pinner
for i := 0; i < *numClients; i++ {
n, _ := network.NewWebsocketGossipNode(log,
conf,
@@ -55,7 +56,7 @@ func main() {
*genesisID,
protocol.NetworkID(*networkID))
n.Start()
- nodes = append(nodes, n)
+ p.Pin(n)
}
fmt.Printf("Created %d clients\n", *numClients)
diff --git a/cmd/nodecfg/apply.go b/cmd/nodecfg/apply.go
index cfb9bfcd60..1f349f4681 100644
--- a/cmd/nodecfg/apply.go
+++ b/cmd/nodecfg/apply.go
@@ -91,7 +91,7 @@ func doApply(rootDir string, rootNodeDir, channel string, hostName string, dnsNa
missing = true
} else {
fmt.Fprintf(os.Stdout, "Loading config from %s...\n", rootDir)
- cfg, err = remote.LoadDeployedNetworkConfigFromDir(rootDir)
+ _, err = remote.LoadDeployedNetworkConfigFromDir(rootDir)
if err != nil {
missing = os.IsNotExist(err)
if !missing {
diff --git a/cmd/pingpong/commands.go b/cmd/pingpong/commands.go
index 81f793ecb2..ab938063b7 100644
--- a/cmd/pingpong/commands.go
+++ b/cmd/pingpong/commands.go
@@ -20,12 +20,9 @@ import (
"fmt"
"os"
- "github.com/algorand/go-algorand/logging"
"github.com/spf13/cobra"
)
-var log = logging.Base()
-
var rootCmd = &cobra.Command{
Use: "pingpong",
Short: "pingpong",
diff --git a/cmd/tealdbg/cdtSession.go b/cmd/tealdbg/cdtSession.go
index f7324923ae..0b15ce11a9 100644
--- a/cmd/tealdbg/cdtSession.go
+++ b/cmd/tealdbg/cdtSession.go
@@ -74,14 +74,12 @@ func (s *cdtSession) sourceMapHandler(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(http.StatusOK)
w.Write(sm)
- return
}
func (s *cdtSession) sourceHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_, source := s.debugger.GetSource()
w.Write(source)
- return
}
func (s *cdtSession) websocketHandler(w http.ResponseWriter, r *http.Request) {
@@ -267,11 +265,8 @@ func (s *cdtSession) websocketHandler(w http.ResponseWriter, r *http.Request) {
s.debugger.SetBreakpointsActive(false)
s.debugger.Resume()
defer func() {
- for {
- select {
- case <-notifications:
- return
- }
+ for range notifications {
+ return
}
}()
}
diff --git a/cmd/tealdbg/cdtSession_test.go b/cmd/tealdbg/cdtSession_test.go
index 8c45e7c518..83b9ca815e 100644
--- a/cmd/tealdbg/cdtSession_test.go
+++ b/cmd/tealdbg/cdtSession_test.go
@@ -602,7 +602,7 @@ func TestCdtSessionGetObjects(t *testing.T) {
require.True(t, ok)
}
- objIds := []string{
+ objIDs := []string{
encodeTxnArrayField(0, 1), encodeGroupTxnID(0), encodeGroupTxnID(1),
encodeArrayLength(stackObjID), encodeArraySlice(scratchObjID, 0, 1),
encodeAppLocalsAddr(basics.Address{}.String()),
@@ -612,7 +612,7 @@ func TestCdtSessionGetObjects(t *testing.T) {
encodeInnerTxnID([]int{0}), encodeInnerTxnID([]int{0, 0}),
encodeInnerTxnID([]int{0, 1}),
}
- for _, k := range objIds {
+ for _, k := range objIDs {
req.Params = map[string]interface{}{"objectId": k, "generatePreview": true}
resp, events, err = s.handleCdtRequest(&req, &state)
require.NoError(t, err)
diff --git a/cmd/tealdbg/cdtdbg.go b/cmd/tealdbg/cdtdbg.go
index 8ed3d8f1c8..0394d6dc63 100644
--- a/cmd/tealdbg/cdtdbg.go
+++ b/cmd/tealdbg/cdtdbg.go
@@ -178,7 +178,6 @@ func (a *CdtFrontend) versionHandler(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(http.StatusOK)
w.Write(enc)
- return
}
func (a *CdtFrontend) jsonHandler(w http.ResponseWriter, r *http.Request) {
@@ -198,5 +197,4 @@ func (a *CdtFrontend) jsonHandler(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(http.StatusOK)
w.Write(enc)
- return
}
diff --git a/cmd/tealdbg/localLedger.go b/cmd/tealdbg/localLedger.go
index 2592556f94..33b80b6fb9 100644
--- a/cmd/tealdbg/localLedger.go
+++ b/cmd/tealdbg/localLedger.go
@@ -59,12 +59,11 @@ type ApplicationIndexerResponse struct {
}
type localLedger struct {
- balances map[basics.Address]basics.AccountData
- txnGroup []transactions.SignedTxn
- groupIndex int
- round uint64
- aidx basics.AppIndex
- latestTimestamp int64
+ balances map[basics.Address]basics.AccountData
+ txnGroup []transactions.SignedTxn
+ groupIndex int
+ round uint64
+ aidx basics.AppIndex
}
func makeBalancesAdapter(
diff --git a/cmd/tealdbg/main.go b/cmd/tealdbg/main.go
index c0f3fe5842..f2c2f8d055 100644
--- a/cmd/tealdbg/main.go
+++ b/cmd/tealdbg/main.go
@@ -160,11 +160,6 @@ func debugRemote() {
}
func debugLocal(args []string) {
- // simple pre-invalidation
- if roundNumber < 0 {
- log.Fatalln("Invalid round")
- }
-
// local debugging works in two modes:
// - listening for upcoming Dryrun Requests
// - or taking program, transaction or Dryrun Request from command line
diff --git a/cmd/tealdbg/remote.go b/cmd/tealdbg/remote.go
index 35e10d94e0..ff4f66a2d9 100644
--- a/cmd/tealdbg/remote.go
+++ b/cmd/tealdbg/remote.go
@@ -63,7 +63,6 @@ func (rha *RemoteHookAdapter) registerHandler(w http.ResponseWriter, r *http.Req
// Proceed!
w.WriteHeader(http.StatusOK)
- return
}
func (rha *RemoteHookAdapter) updateHandler(w http.ResponseWriter, r *http.Request) {
@@ -81,7 +80,6 @@ func (rha *RemoteHookAdapter) updateHandler(w http.ResponseWriter, r *http.Reque
}
w.WriteHeader(http.StatusOK)
- return
}
func (rha *RemoteHookAdapter) completeHandler(w http.ResponseWriter, r *http.Request) {
@@ -100,5 +98,4 @@ func (rha *RemoteHookAdapter) completeHandler(w http.ResponseWriter, r *http.Req
// Proceed!
w.WriteHeader(http.StatusOK)
- return
}
diff --git a/cmd/tealdbg/server.go b/cmd/tealdbg/server.go
index 85a3b889c0..5bce050a03 100644
--- a/cmd/tealdbg/server.go
+++ b/cmd/tealdbg/server.go
@@ -241,5 +241,4 @@ func (ds *DebugServer) dryrunReqHander(w http.ResponseWriter, r *http.Request) {
// let the main thread to exit
close(ds.spinoffCh)
- return
}
diff --git a/cmd/tealdbg/server_test.go b/cmd/tealdbg/server_test.go
index aa89f73f08..df15bc3736 100644
--- a/cmd/tealdbg/server_test.go
+++ b/cmd/tealdbg/server_test.go
@@ -57,8 +57,7 @@ func (t testServerDebugFrontend) eventLoop() {
if n.Event == "completed" {
return
}
- if n.Event == "registered" {
- }
+ // No special action needed for 'registered' events
// simulate user delay to workaround race cond
time.Sleep(10 * time.Millisecond)
t.debugger.Resume()
diff --git a/cmd/tealdbg/webdbg.go b/cmd/tealdbg/webdbg.go
index d05bff822b..8039259cd5 100644
--- a/cmd/tealdbg/webdbg.go
+++ b/cmd/tealdbg/webdbg.go
@@ -124,7 +124,6 @@ func (a *WebPageFrontend) homeHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
home.Execute(w, nil)
- return
}
func (a *WebPageFrontend) stepHandler(w http.ResponseWriter, r *http.Request) {
@@ -148,7 +147,6 @@ func (a *WebPageFrontend) stepHandler(w http.ResponseWriter, r *http.Request) {
s.debugger.Step()
w.WriteHeader(http.StatusOK)
- return
}
func (a *WebPageFrontend) configHandler(w http.ResponseWriter, r *http.Request) {
@@ -188,7 +186,6 @@ func (a *WebPageFrontend) configHandler(w http.ResponseWriter, r *http.Request)
}
w.WriteHeader(http.StatusOK)
- return
}
func (a *WebPageFrontend) continueHandler(w http.ResponseWriter, r *http.Request) {
@@ -212,7 +209,6 @@ func (a *WebPageFrontend) continueHandler(w http.ResponseWriter, r *http.Request
s.debugger.Resume()
w.WriteHeader(http.StatusOK)
- return
}
func (a *WebPageFrontend) subscribeHandler(w http.ResponseWriter, r *http.Request) {
@@ -248,14 +244,11 @@ func (a *WebPageFrontend) subscribeHandler(w http.ResponseWriter, r *http.Reques
a.mu.Unlock()
// Wait on notifications and forward to the user
- for {
- select {
- case notification := <-notifications:
- enc := protocol.EncodeJSONStrict(¬ification)
- err = ws.WriteMessage(websocket.TextMessage, enc)
- if err != nil {
- return
- }
+ for notification := range notifications {
+ enc := protocol.EncodeJSONStrict(¬ification)
+ err = ws.WriteMessage(websocket.TextMessage, enc)
+ if err != nil {
+ return
}
}
}
diff --git a/cmd/updater/commands.go b/cmd/updater/commands.go
index d418ef0049..d32a7a8434 100644
--- a/cmd/updater/commands.go
+++ b/cmd/updater/commands.go
@@ -21,12 +21,8 @@ import (
"os"
"github.com/spf13/cobra"
-
- "github.com/algorand/go-algorand/logging"
)
-var log = logging.Base()
-
var channel string
func init() {
diff --git a/cmd/updater/toolsCmd.go b/cmd/updater/toolsCmd.go
index bc6866dffe..b03825244a 100644
--- a/cmd/updater/toolsCmd.go
+++ b/cmd/updater/toolsCmd.go
@@ -57,10 +57,10 @@ var getToolsCmd = &cobra.Command{
}
file, err := os.Create(os.ExpandEnv(toolsDestFile))
- defer file.Close()
if err != nil {
exitErrorf("Error creating output file: %s\n", err.Error())
}
+ defer file.Close()
err = s3Session.DownloadFile(name, file)
if err != nil {
diff --git a/cmd/updater/versionCmd.go b/cmd/updater/versionCmd.go
index 19e57b7ae1..5468e06cdb 100644
--- a/cmd/updater/versionCmd.go
+++ b/cmd/updater/versionCmd.go
@@ -117,10 +117,10 @@ var getCmd = &cobra.Command{
}
file, err := os.Create(os.ExpandEnv(destFile))
- defer file.Close()
if err != nil {
exitErrorf("Error creating output file: %s\n", err.Error())
}
+ defer file.Close()
err = s3Session.DownloadFile(name, file)
if err != nil {
diff --git a/config/config.go b/config/config.go
index 5bda44c3b0..495eba5890 100644
--- a/config/config.go
+++ b/config/config.go
@@ -306,3 +306,90 @@ const (
catchupValidationModeVerifyTransactionSignatures = 4
catchupValidationModeVerifyApplyData = 8
)
+
+// SaveConfigurableConsensus saves the configurable protocols file to the provided data directory.
+// if the params contains zero protocols, the existing consensus.json file will be removed if exists.
+func SaveConfigurableConsensus(dataDirectory string, params ConsensusProtocols) error {
+ consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename)
+
+ if len(params) == 0 {
+ // we have no consensus params to write. In this case, just delete the existing file
+ // ( if any )
+ err := os.Remove(consensusProtocolPath)
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ encodedConsensusParams, err := json.Marshal(params)
+ if err != nil {
+ return err
+ }
+ err = os.WriteFile(consensusProtocolPath, encodedConsensusParams, 0644)
+ return err
+}
+
+// PreloadConfigurableConsensusProtocols loads the configurable protocols from the data directory
+// and merge it with a copy of the Consensus map. Then, it returns it to the caller.
+func PreloadConfigurableConsensusProtocols(dataDirectory string) (ConsensusProtocols, error) {
+ consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename)
+ file, err := os.Open(consensusProtocolPath)
+
+ if err != nil {
+ if os.IsNotExist(err) {
+ // this file is not required, only optional. if it's missing, no harm is done.
+ return Consensus, nil
+ }
+ return nil, err
+ }
+ defer file.Close()
+
+ configurableConsensus := make(ConsensusProtocols)
+
+ decoder := json.NewDecoder(file)
+ err = decoder.Decode(&configurableConsensus)
+ if err != nil {
+ return nil, err
+ }
+ return Consensus.Merge(configurableConsensus), nil
+}
+
+// SetConfigurableConsensusProtocols sets the configurable protocols.
+func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) ConsensusProtocols {
+ oldConsensus := Consensus
+ Consensus = newConsensus
+ // Set allocation limits
+ for _, p := range Consensus {
+ checkSetAllocBounds(p)
+ }
+ return oldConsensus
+}
+
+// LoadConfigurableConsensusProtocols loads the configurable protocols from the data directory
+func LoadConfigurableConsensusProtocols(dataDirectory string) error {
+ newConsensus, err := PreloadConfigurableConsensusProtocols(dataDirectory)
+ if err != nil {
+ return err
+ }
+ if newConsensus != nil {
+ SetConfigurableConsensusProtocols(newConsensus)
+ }
+ return nil
+}
+
+// ApplyShorterUpgradeRoundsForDevNetworks applies a shorter upgrade round time for the Devnet and Betanet networks.
+// This function should not take precedence over settings loaded via `PreloadConfigurableConsensusProtocols`.
+func ApplyShorterUpgradeRoundsForDevNetworks(id protocol.NetworkID) {
+ if id == Betanet || id == Devnet {
+ // Go through all approved upgrades and set to the MinUpgradeWaitRounds valid where MinUpgradeWaitRounds is set
+ for _, p := range Consensus {
+ if p.ApprovedUpgrades != nil {
+ for v := range p.ApprovedUpgrades {
+ if p.MinUpgradeWaitRounds > 0 {
+ p.ApprovedUpgrades[v] = p.MinUpgradeWaitRounds
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/config/consensus.go b/config/consensus.go
index 4642e9d76a..61f71e8de9 100644
--- a/config/consensus.go
+++ b/config/consensus.go
@@ -17,9 +17,6 @@
package config
import (
- "encoding/json"
- "os"
- "path/filepath"
"time"
"github.com/algorand/go-algorand/protocol"
@@ -449,8 +446,14 @@ type ConsensusParams struct {
// 6. checking that in the case of going online the VoteFirst is less or equal to the next network round.
EnableKeyregCoherencyCheck bool
+ // Allow app updates to specify the extra pages they use. This allows the
+ // update to pass WellFormed(), but they cannot _change_ the extra pages.
EnableExtraPagesOnAppUpdate bool
+ // Autoincrements an app's version when the app is updated, careful callers
+ // may avoid making inner calls to apps that have changed.
+ EnableAppVersioning bool
+
// MaxProposedExpiredOnlineAccounts is the maximum number of online accounts
// that a proposer can take offline for having expired voting keys.
MaxProposedExpiredOnlineAccounts int
@@ -704,8 +707,8 @@ var MaxBytesKeyValueLen int
// of the consensus protocols. used for decoding purposes.
var MaxExtraAppProgramLen int
-// MaxAvailableAppProgramLen is the largest supported app program size include the extra pages
-// supported supported by any of the consensus protocols. used for decoding purposes.
+// MaxAvailableAppProgramLen is the largest supported app program size including the extra
+// pages supported by any of the consensus protocols. used for decoding purposes.
var MaxAvailableAppProgramLen int
// MaxProposedExpiredOnlineAccounts is the maximum number of online accounts
@@ -805,28 +808,6 @@ func checkSetAllocBounds(p ConsensusParams) {
checkSetMax(p.MaxAppTxnForeignApps, &MaxAppTxnForeignApps)
}
-// SaveConfigurableConsensus saves the configurable protocols file to the provided data directory.
-// if the params contains zero protocols, the existing consensus.json file will be removed if exists.
-func SaveConfigurableConsensus(dataDirectory string, params ConsensusProtocols) error {
- consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename)
-
- if len(params) == 0 {
- // we have no consensus params to write. In this case, just delete the existing file
- // ( if any )
- err := os.Remove(consensusProtocolPath)
- if os.IsNotExist(err) {
- return nil
- }
- return err
- }
- encodedConsensusParams, err := json.Marshal(params)
- if err != nil {
- return err
- }
- err = os.WriteFile(consensusProtocolPath, encodedConsensusParams, 0644)
- return err
-}
-
// DeepCopy creates a deep copy of a consensus protocols map.
func (cp ConsensusProtocols) DeepCopy() ConsensusProtocols {
staticConsensus := make(ConsensusProtocols)
@@ -869,54 +850,6 @@ func (cp ConsensusProtocols) Merge(configurableConsensus ConsensusProtocols) Con
return staticConsensus
}
-// LoadConfigurableConsensusProtocols loads the configurable protocols from the data directory
-func LoadConfigurableConsensusProtocols(dataDirectory string) error {
- newConsensus, err := PreloadConfigurableConsensusProtocols(dataDirectory)
- if err != nil {
- return err
- }
- if newConsensus != nil {
- SetConfigurableConsensusProtocols(newConsensus)
- }
- return nil
-}
-
-// SetConfigurableConsensusProtocols sets the configurable protocols.
-func SetConfigurableConsensusProtocols(newConsensus ConsensusProtocols) ConsensusProtocols {
- oldConsensus := Consensus
- Consensus = newConsensus
- // Set allocation limits
- for _, p := range Consensus {
- checkSetAllocBounds(p)
- }
- return oldConsensus
-}
-
-// PreloadConfigurableConsensusProtocols loads the configurable protocols from the data directory
-// and merge it with a copy of the Consensus map. Then, it returns it to the caller.
-func PreloadConfigurableConsensusProtocols(dataDirectory string) (ConsensusProtocols, error) {
- consensusProtocolPath := filepath.Join(dataDirectory, ConfigurableConsensusProtocolsFilename)
- file, err := os.Open(consensusProtocolPath)
-
- if err != nil {
- if os.IsNotExist(err) {
- // this file is not required, only optional. if it's missing, no harm is done.
- return Consensus, nil
- }
- return nil, err
- }
- defer file.Close()
-
- configurableConsensus := make(ConsensusProtocols)
-
- decoder := json.NewDecoder(file)
- err = decoder.Decode(&configurableConsensus)
- if err != nil {
- return nil, err
- }
- return Consensus.Merge(configurableConsensus), nil
-}
-
// initConsensusProtocols defines the consensus protocol values and how values change across different versions of the protocol.
//
// These are the only valid and tested consensus values and transitions. Other settings are not tested and may lead to unexpected behavior.
@@ -1551,7 +1484,8 @@ func initConsensusProtocols() {
vFuture := v40
vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
- vFuture.LogicSigVersion = 12 // When moving this to a release, put a new higher LogicSigVersion here
+ vFuture.LogicSigVersion = 12 // When moving this to a release, put a new higher LogicSigVersion here
+ vFuture.EnableAppVersioning = true // if not promoted when v12 goes into effect, update logic/field.go
Consensus[protocol.ConsensusFuture] = vFuture
@@ -1587,23 +1521,6 @@ func initConsensusProtocols() {
vAlpha4.ApprovedUpgrades[protocol.ConsensusVAlpha5] = 10000
}
-// ApplyShorterUpgradeRoundsForDevNetworks applies a shorter upgrade round time for the Devnet and Betanet networks.
-// This function should not take precedence over settings loaded via `PreloadConfigurableConsensusProtocols`.
-func ApplyShorterUpgradeRoundsForDevNetworks(id protocol.NetworkID) {
- if id == Betanet || id == Devnet {
- // Go through all approved upgrades and set to the MinUpgradeWaitRounds valid where MinUpgradeWaitRounds is set
- for _, p := range Consensus {
- if p.ApprovedUpgrades != nil {
- for v := range p.ApprovedUpgrades {
- if p.MinUpgradeWaitRounds > 0 {
- p.ApprovedUpgrades[v] = p.MinUpgradeWaitRounds
- }
- }
- }
- }
- }
-}
-
// Global defines global Algorand protocol parameters which should not be overridden.
type Global struct {
SmallLambda time.Duration // min amount of time to wait for leader's credential (i.e., time to propagate one credential)
diff --git a/config/localTemplate.go b/config/localTemplate.go
index f8b673d195..db32ce742c 100644
--- a/config/localTemplate.go
+++ b/config/localTemplate.go
@@ -44,7 +44,7 @@ type Local struct {
// Version tracks the current version of the defaults so we can migrate old -> new
// This is specifically important whenever we decide to change the default value
// for an existing parameter. This field tag must be updated any time we add a new version.
- Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33" version[34]:"34" version[35]:"35"`
+ Version uint32 `version[0]:"0" version[1]:"1" version[2]:"2" version[3]:"3" version[4]:"4" version[5]:"5" version[6]:"6" version[7]:"7" version[8]:"8" version[9]:"9" version[10]:"10" version[11]:"11" version[12]:"12" version[13]:"13" version[14]:"14" version[15]:"15" version[16]:"16" version[17]:"17" version[18]:"18" version[19]:"19" version[20]:"20" version[21]:"21" version[22]:"22" version[23]:"23" version[24]:"24" version[25]:"25" version[26]:"26" version[27]:"27" version[28]:"28" version[29]:"29" version[30]:"30" version[31]:"31" version[32]:"32" version[33]:"33" version[34]:"34" version[35]:"35" version[36]:"36"`
// Archival nodes retain a full copy of the block history. Non-Archival nodes will delete old blocks and only retain what's need to properly validate blockchain messages (the precise number of recent blocks depends on the consensus parameters. Currently the last 1321 blocks are required). This means that non-Archival nodes require significantly less storage than Archival nodes. If setting this to true for the first time, the existing ledger may need to be deleted to get the historical values stored as the setting only affects current blocks forward. To do this, shutdown the node and delete all .sqlite files within the data/testnet-version directory, except the crash.sqlite file. Restart the node and wait for the node to sync.
Archival bool `version[0]:"false"`
@@ -643,6 +643,9 @@ type Local struct {
// GoMemLimit provides the Go runtime with a soft memory limit. The default behavior is no limit,
// unless the GOMEMLIMIT environment variable is set.
GoMemLimit uint64 `version[34]:"0"`
+
+ // EnableVoteCompression controls whether vote compression is enabled for websocket networks
+ EnableVoteCompression bool `version[36]:"true"`
}
// DNSBootstrapArray returns an array of one or more DNS Bootstrap identifiers
diff --git a/config/local_defaults.go b/config/local_defaults.go
index fcc6e3d3d8..70df924d87 100644
--- a/config/local_defaults.go
+++ b/config/local_defaults.go
@@ -20,7 +20,7 @@
package config
var defaultLocal = Local{
- Version: 35,
+ Version: 36,
AccountUpdatesStatsInterval: 5000000000,
AccountsRebuildSynchronousMode: 1,
AgreementIncomingBundlesQueueLength: 15,
@@ -89,6 +89,7 @@ var defaultLocal = Local{
EnableTxnEvalTracer: false,
EnableUsageLog: false,
EnableVerbosedTransactionSyncLogging: false,
+ EnableVoteCompression: true,
EndpointAddress: "127.0.0.1:0",
FallbackDNSResolverAddress: "",
ForceFetchTransactions: false,
diff --git a/config/version.go b/config/version.go
index cd10be13b5..7494c6bd05 100644
--- a/config/version.go
+++ b/config/version.go
@@ -33,7 +33,7 @@ const VersionMajor = 4
// 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 = 0
+const VersionMinor = 1
// Version is the type holding our full version information.
type Version struct {
diff --git a/crypto/batchverifier.go b/crypto/batchverifier.go
index d265e6e4f4..65b2febeaa 100644
--- a/crypto/batchverifier.go
+++ b/crypto/batchverifier.go
@@ -18,17 +18,12 @@ package crypto
// #cgo CFLAGS: -Wall -std=c99
// #cgo darwin,amd64 CFLAGS: -I${SRCDIR}/libs/darwin/amd64/include
-// #cgo darwin,amd64 LDFLAGS: ${SRCDIR}/libs/darwin/amd64/lib/libsodium.a
// #cgo darwin,arm64 CFLAGS: -I${SRCDIR}/libs/darwin/arm64/include
-// #cgo darwin,arm64 LDFLAGS: ${SRCDIR}/libs/darwin/arm64/lib/libsodium.a
// #cgo linux,amd64 CFLAGS: -I${SRCDIR}/libs/linux/amd64/include
-// #cgo linux,amd64 LDFLAGS: ${SRCDIR}/libs/linux/amd64/lib/libsodium.a
// #cgo linux,arm64 CFLAGS: -I${SRCDIR}/libs/linux/arm64/include
-// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/libs/linux/arm64/lib/libsodium.a
// #cgo linux,arm CFLAGS: -I${SRCDIR}/libs/linux/arm/include
-// #cgo linux,arm LDFLAGS: ${SRCDIR}/libs/linux/arm/lib/libsodium.a
+// #cgo linux,riscv64 CFLAGS: -I${SRCDIR}/libs/linux/riscv64/include
// #cgo windows,amd64 CFLAGS: -I${SRCDIR}/libs/windows/amd64/include
-// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/libs/windows/amd64/lib/libsodium.a
// #include
// enum {
// sizeofPtr = sizeof(void*),
diff --git a/crypto/curve25519.go b/crypto/curve25519.go
index 9a7e92ad2a..1f6dfcbd16 100644
--- a/crypto/curve25519.go
+++ b/crypto/curve25519.go
@@ -27,6 +27,8 @@ package crypto
// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/libs/linux/arm64/lib/libsodium.a
// #cgo linux,arm CFLAGS: -I${SRCDIR}/libs/linux/arm/include
// #cgo linux,arm LDFLAGS: ${SRCDIR}/libs/linux/arm/lib/libsodium.a
+// #cgo linux,riscv64 CFLAGS: -I${SRCDIR}/libs/linux/riscv64/include
+// #cgo linux,riscv64 LDFLAGS: ${SRCDIR}/libs/linux/riscv64/lib/libsodium.a
// #cgo windows,amd64 CFLAGS: -I${SRCDIR}/libs/windows/amd64/include
// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/libs/windows/amd64/lib/libsodium.a
// #include
diff --git a/crypto/merklearray/worker.go b/crypto/merklearray/worker.go
index 9d5179cbef..e3c2607353 100644
--- a/crypto/merklearray/worker.go
+++ b/crypto/merklearray/worker.go
@@ -80,7 +80,7 @@ func (ws *workerState) nextWorker() bool {
return false
}
- _ = <-ws.starting
+ <-ws.starting
curidx := ws.nextidx.Load()
if curidx >= ws.maxidx {
diff --git a/crypto/merkletrie/cache.go b/crypto/merkletrie/cache.go
index 3f8a8d861d..6132007559 100644
--- a/crypto/merkletrie/cache.go
+++ b/crypto/merkletrie/cache.go
@@ -117,7 +117,6 @@ func (mtc *merkleTrieCache) initialize(mt *Trie, committer Committer, memoryConf
}
}
mtc.modified = false
- return
}
// allocateNewNode allocates a new node
@@ -312,7 +311,7 @@ func (mtc *merkleTrieCache) commitTransaction() {
delete(mtc.pageToNIDsPtr[page], nodeID)
// if the page is empty, and it's not on the pendingDeletionPages, it means that we have no further references to it,
// so we can delete it right away.
- if len(mtc.pageToNIDsPtr[page]) == 0 && mtc.pendingDeletionPages[page] == false {
+ if len(mtc.pageToNIDsPtr[page]) == 0 && !mtc.pendingDeletionPages[page] {
delete(mtc.pageToNIDsPtr, page)
}
} else {
@@ -540,7 +539,7 @@ func (mtc *merkleTrieCache) reallocatePendingPages(stats *CommitStats) (pagesToC
func (mtc *merkleTrieCache) calculatePageHashes(page int64, newPage bool) (fanoutRelocatedNodes int64, err error) {
nodes := mtc.pageToNIDsPtr[uint64(page)]
for i := storedNodeIdentifier(page * mtc.nodesPerPage); i < storedNodeIdentifier((page+1)*mtc.nodesPerPage); i++ {
- if !newPage && mtc.pendingCreatedNID[i] == false {
+ if !newPage && !mtc.pendingCreatedNID[i] {
continue
}
node := nodes[i]
diff --git a/crypto/merkletrie/node.go b/crypto/merkletrie/node.go
index c7b616d6e6..6611c649f2 100644
--- a/crypto/merkletrie/node.go
+++ b/crypto/merkletrie/node.go
@@ -84,7 +84,7 @@ func (n *node) find(cache *merkleTrieCache, d []byte) (bool, error) {
if n.leaf() {
return 0 == bytes.Compare(d, n.hash), nil
}
- if n.childrenMask.Bit(d[0]) == false {
+ if !n.childrenMask.Bit(d[0]) {
return false, nil
}
childNodeID := n.children[n.indexOf(d[0])].id
@@ -159,7 +159,7 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored
return nodeID, nil
}
- if n.childrenMask.Bit(d[0]) == false {
+ if !n.childrenMask.Bit(d[0]) {
// no such child.
childNode, childNodeID := cache.allocateNewNode()
childNode.hash = d[1:]
@@ -171,9 +171,7 @@ func (n *node) add(cache *merkleTrieCache, d []byte, path []byte) (nodeID stored
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 {
- pnode.children[i] = child
- }
+ copy(pnode.children, n.children)
pnode.children[len(pnode.children)-1] = childEntry{
id: childNodeID,
hashIndex: d[0],
diff --git a/crypto/multisig.go b/crypto/multisig.go
index f696b1d361..ee02401ae1 100644
--- a/crypto/multisig.go
+++ b/crypto/multisig.go
@@ -316,9 +316,8 @@ func MultisigAdd(unisig []MultisigSig, msig *MultisigSig) (err error) {
// invalid duplicates
err = errInvalidDuplicates
return
- } else {
- // valid duplicates
}
+ // valid duplicates
}
}
}
diff --git a/crypto/vrf.go b/crypto/vrf.go
index b78580671f..5b89500d70 100644
--- a/crypto/vrf.go
+++ b/crypto/vrf.go
@@ -18,15 +18,11 @@ package crypto
// #cgo CFLAGS: -Wall -std=c99
// #cgo darwin,amd64 CFLAGS: -I${SRCDIR}/libs/darwin/amd64/include
-// #cgo darwin,amd64 LDFLAGS: ${SRCDIR}/libs/darwin/amd64/lib/libsodium.a
// #cgo linux,amd64 CFLAGS: -I${SRCDIR}/libs/linux/amd64/include
-// #cgo linux,amd64 LDFLAGS: ${SRCDIR}/libs/linux/amd64/lib/libsodium.a
// #cgo linux,arm64 CFLAGS: -I${SRCDIR}/libs/linux/arm64/include
-// #cgo linux,arm64 LDFLAGS: ${SRCDIR}/libs/linux/arm64/lib/libsodium.a
// #cgo linux,arm CFLAGS: -I${SRCDIR}/libs/linux/arm/include
-// #cgo linux,arm LDFLAGS: ${SRCDIR}/libs/linux/arm/lib/libsodium.a
+// #cgo linux,riscv64 CFLAGS: -I${SRCDIR}/libs/linux/riscv64/include
// #cgo windows,amd64 CFLAGS: -I${SRCDIR}/libs/windows/amd64/include
-// #cgo windows,amd64 LDFLAGS: ${SRCDIR}/libs/windows/amd64/lib/libsodium.a
// #include
// #include "sodium.h"
import "C"
diff --git a/daemon/algod/api/Makefile b/daemon/algod/api/Makefile
index 59ab489183..5dd3055944 100644
--- a/daemon/algod/api/Makefile
+++ b/daemon/algod/api/Makefile
@@ -33,7 +33,8 @@ server/v2/generated/model/types.go: algod.oas3.yml
$(GOPATH1)/bin/oapi-codegen -config ./server/v2/generated/model/model_types.yml algod.oas3.yml
algod.oas3.yml: algod.oas2.json
- jq < algod.oas2.json > /dev/null # fail with a nice explantion if json is malformed
+ jq < algod.oas2.json > /dev/null # fail with a nice explantion if json is malformed
+ ! grep '"type": "number"' $< # Don't use the number type. Use integer (and format uint64 usually)
curl -s -X POST "$(SWAGGER_CONVERTER_API)/api/convert" -H "accept: application/json" -H "Content-Type: application/json" -d @./algod.oas2.json -o .3tmp.json
python3 jsoncanon.py < .3tmp.json > algod.oas3.yml
rm -f .3tmp.json
diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json
index 0ca4115f8e..9c37f8d931 100644
--- a/daemon/algod/api/algod.oas2.json
+++ b/daemon/algod/api/algod.oas2.json
@@ -120,7 +120,7 @@
"200": {
"description": "The genesis file in json.",
"schema": {
- "type": "string"
+ "$ref": "#/definitions/Genesis"
}
},
"default": {
@@ -2280,7 +2280,7 @@
},
"/v2/applications/{application-id}/boxes": {
"get": {
- "description": "Given an application ID, return all Box names. No particular ordering is guaranteed. Request fails when client or server-side configured limits prevent returning all Box names.",
+ "description": "Given an application ID, return boxes in lexographical order by name. If the results must be truncated, a next-token is supplied to continue the request.",
"tags": [
"public",
"nonparticipating"
@@ -2291,7 +2291,7 @@
"schemes": [
"http"
],
- "summary": "Get all box names for a given application.",
+ "summary": "Get boxes for a given application.",
"operationId": "GetApplicationBoxes",
"parameters": [
{
@@ -2303,9 +2303,27 @@
},
{
"type": "integer",
- "description": "Max number of box names to return. If max is not set, or max == 0, returns all box-names.",
+ "description": "Maximum number of boxes to return. Server may impose a lower limit.",
"name": "max",
"in": "query"
+ },
+ {
+ "type": "string",
+ "description": "A box name prefix, in the goal app call arg 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...'.",
+ "name": "prefix",
+ "in": "query"
+ },
+ {
+ "type": "string",
+ "description": "A box name, in the goal app call arg form 'encoding:value'. When provided, the returned boxes begin (lexographically) with the supplied name. Callers may implement pagination by reinvoking the endpoint with the token from a previous call's next-token.",
+ "name": "next",
+ "in": "query"
+ },
+ {
+ "type": "boolean",
+ "description": "If true, box values will be returned.",
+ "name": "values",
+ "in": "query"
}
],
"responses": {
@@ -3060,6 +3078,105 @@
}
},
"definitions": {
+ "GenesisAllocation":{
+ "title": "Allocations for Genesis File",
+ "type": "object",
+ "properties": {
+ "addr": {
+ "type": "string"
+ },
+ "comment": {
+ "type": "string"
+ },
+ "state": {
+ "type": "object",
+ "properties": {
+ "algo": {
+ "type": "integer",
+ "format" : "uint64"
+ },
+ "onl": {
+ "type": "integer"
+ },
+ "sel": {
+ "type": "string"
+ },
+ "stprf": {
+ "type": "string"
+ },
+ "vote": {
+ "type": "string"
+ },
+ "voteKD": {
+ "type": "integer",
+ "format" : "uint64"
+ },
+ "voteFst": {
+ "type": "integer",
+ "format" : "uint64"
+ },
+ "voteLst": {
+ "type": "integer",
+ "format" : "uint64"
+ }
+ },
+ "required": [
+ "algo"
+ ]
+ }
+ },
+ "required": [
+ "addr",
+ "comment",
+ "state"
+ ]
+ },
+ "Genesis":{
+ "title": "Genesis File in JSON",
+ "type": "object",
+ "properties": {
+ "alloc": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GenesisAllocation"
+ }
+ },
+ "comment": {
+ "type": "string"
+ },
+ "devmode": {
+ "type": "boolean"
+ },
+ "fees": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "network": {
+ "type": "string"
+ },
+ "proto": {
+ "type": "string"
+ },
+ "rwd": {
+ "type": "string"
+ },
+ "timestamp": {
+ "type": "integer",
+ "format" : "int64"
+ }
+ },
+ "required": [
+ "alloc",
+ "fees",
+ "id",
+ "network",
+ "proto",
+ "rwd",
+ "timestamp"
+ ]
+ },
"LedgerStateDelta": {
"description": "Ledger StateDelta object",
"type": "object",
@@ -3739,6 +3856,10 @@
"global-state": {
"description": "\\[gs\\] global state",
"$ref": "#/definitions/TealKeyValueStore"
+ },
+ "version": {
+ "description": "\\[v\\] the number of updates to the application programs",
+ "type": "integer"
}
}
},
@@ -4033,7 +4154,6 @@
"description": "Box name and its content.",
"type": "object",
"required": [
- "round",
"name",
"value"
],
@@ -4043,26 +4163,12 @@
"type": "integer"
},
"name": {
- "description": "\\[name\\] box name, base64 encoded",
+ "description": "The box name, base64 encoded",
"type": "string",
"format": "byte"
},
"value": {
- "description": "\\[value\\] box value, base64 encoded.",
- "type": "string",
- "format": "byte"
- }
- }
- },
- "BoxDescriptor": {
- "description": "Box descriptor describes a Box.",
- "type": "object",
- "required": [
- "name"
- ],
- "properties": {
- "name": {
- "description": "Base64 encoded box name",
+ "description": "The box value, base64 encoded.",
"type": "string",
"format": "byte"
}
@@ -4087,22 +4193,6 @@
}
}
},
- "KvDelta": {
- "description": "A single Delta containing the key, the previous value and the current value for a single round.",
- "type": "object",
- "properties": {
- "key": {
- "description": "The key, base64 encoded.",
- "type": "string",
- "format": "byte"
- },
- "value": {
- "description": "The new value of the KV store entry, base64 encoded.",
- "type": "string",
- "format": "byte"
- }
- }
- },
"Version": {
"description": "algod version information.",
"type": "object",
@@ -5510,17 +5600,26 @@
}
},
"BoxesResponse": {
- "description": "Box names of an application",
+ "description": "Boxes of an application",
"schema": {
"type": "object",
"required": [
+ "round",
"boxes"
],
"properties": {
+ "round": {
+ "description": "The round for which this information is relevant.",
+ "type": "integer"
+ },
+ "next-token": {
+ "description": "Used for pagination, when making another request provide this token with the next parameter.",
+ "type": "string"
+ },
"boxes": {
"type": "array",
"items": {
- "$ref": "#/definitions/BoxDescriptor"
+ "$ref": "#/definitions/Box"
}
}
}
diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml
index ebffbe2e9e..fa5a2b8268 100644
--- a/daemon/algod/api/algod.oas3.yml
+++ b/daemon/algod/api/algod.oas3.yml
@@ -439,19 +439,28 @@
"properties": {
"boxes": {
"items": {
- "$ref": "#/components/schemas/BoxDescriptor"
+ "$ref": "#/components/schemas/Box"
},
"type": "array"
+ },
+ "next-token": {
+ "description": "Used for pagination, when making another request provide this token with the next parameter.",
+ "type": "string"
+ },
+ "round": {
+ "description": "The round for which this information is relevant.",
+ "type": "integer"
}
},
"required": [
- "boxes"
+ "boxes",
+ "round"
],
"type": "object"
}
}
},
- "description": "Box names of an application"
+ "description": "Boxes of an application"
},
"CatchpointAbortResponse": {
"content": {
@@ -1457,6 +1466,10 @@
},
"local-state-schema": {
"$ref": "#/components/schemas/ApplicationStateSchema"
+ },
+ "version": {
+ "description": "\\[v\\] the number of updates to the application programs",
+ "type": "integer"
}
},
"required": [
@@ -1707,7 +1720,7 @@
"description": "Box name and its content.",
"properties": {
"name": {
- "description": "\\[name\\] box name, base64 encoded",
+ "description": "The box name, base64 encoded",
"format": "byte",
"pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
"type": "string"
@@ -1717,7 +1730,7 @@
"type": "integer"
},
"value": {
- "description": "\\[value\\] box value, base64 encoded.",
+ "description": "The box value, base64 encoded.",
"format": "byte",
"pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
"type": "string"
@@ -1725,26 +1738,10 @@
},
"required": [
"name",
- "round",
"value"
],
"type": "object"
},
- "BoxDescriptor": {
- "description": "Box descriptor describes a Box.",
- "properties": {
- "name": {
- "description": "Base64 encoded box name",
- "format": "byte",
- "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
- "type": "string"
- }
- },
- "required": [
- "name"
- ],
- "type": "object"
- },
"BoxReference": {
"description": "References a box of an application.",
"properties": {
@@ -2060,22 +2057,103 @@
],
"type": "object"
},
- "KvDelta": {
- "description": "A single Delta containing the key, the previous value and the current value for a single round.",
+ "Genesis": {
"properties": {
- "key": {
- "description": "The key, base64 encoded.",
- "format": "byte",
- "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
+ "alloc": {
+ "items": {
+ "$ref": "#/components/schemas/GenesisAllocation"
+ },
+ "type": "array"
+ },
+ "comment": {
"type": "string"
},
- "value": {
- "description": "The new value of the KV store entry, base64 encoded.",
- "format": "byte",
- "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
+ "devmode": {
+ "type": "boolean"
+ },
+ "fees": {
+ "type": "string"
+ },
+ "id": {
+ "type": "string"
+ },
+ "network": {
+ "type": "string"
+ },
+ "proto": {
+ "type": "string"
+ },
+ "rwd": {
+ "type": "string"
+ },
+ "timestamp": {
+ "format": "int64",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "alloc",
+ "fees",
+ "id",
+ "network",
+ "proto",
+ "rwd",
+ "timestamp"
+ ],
+ "title": "Genesis File in JSON",
+ "type": "object"
+ },
+ "GenesisAllocation": {
+ "properties": {
+ "addr": {
+ "type": "string"
+ },
+ "comment": {
"type": "string"
+ },
+ "state": {
+ "properties": {
+ "algo": {
+ "format": "uint64",
+ "type": "integer"
+ },
+ "onl": {
+ "type": "integer"
+ },
+ "sel": {
+ "type": "string"
+ },
+ "stprf": {
+ "type": "string"
+ },
+ "vote": {
+ "type": "string"
+ },
+ "voteFst": {
+ "format": "uint64",
+ "type": "integer"
+ },
+ "voteKD": {
+ "format": "uint64",
+ "type": "integer"
+ },
+ "voteLst": {
+ "format": "uint64",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "algo"
+ ],
+ "type": "object"
}
},
+ "required": [
+ "addr",
+ "comment",
+ "state"
+ ],
+ "title": "Allocations for Genesis File",
"type": "object"
},
"LedgerStateDelta": {
@@ -2863,7 +2941,7 @@
"content": {
"application/json": {
"schema": {
- "type": "string"
+ "$ref": "#/components/schemas/Genesis"
}
}
},
@@ -3804,7 +3882,7 @@
},
"/v2/applications/{application-id}/boxes": {
"get": {
- "description": "Given an application ID, return all Box names. No particular ordering is guaranteed. Request fails when client or server-side configured limits prevent returning all Box names.",
+ "description": "Given an application ID, return boxes in lexographical order by name. If the results must be truncated, a next-token is supplied to continue the request.",
"operationId": "GetApplicationBoxes",
"parameters": [
{
@@ -3817,12 +3895,36 @@
}
},
{
- "description": "Max number of box names to return. If max is not set, or max == 0, returns all box-names.",
+ "description": "Maximum number of boxes to return. Server may impose a lower limit.",
"in": "query",
"name": "max",
"schema": {
"type": "integer"
}
+ },
+ {
+ "description": "A box name prefix, in the goal app call arg 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...'.",
+ "in": "query",
+ "name": "prefix",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "A box name, in the goal app call arg form 'encoding:value'. When provided, the returned boxes begin (lexographically) with the supplied name. Callers may implement pagination by reinvoking the endpoint with the token from a previous call's next-token.",
+ "in": "query",
+ "name": "next",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "description": "If true, box values will be returned.",
+ "in": "query",
+ "name": "values",
+ "schema": {
+ "type": "boolean"
+ }
}
],
"responses": {
@@ -3833,19 +3935,28 @@
"properties": {
"boxes": {
"items": {
- "$ref": "#/components/schemas/BoxDescriptor"
+ "$ref": "#/components/schemas/Box"
},
"type": "array"
+ },
+ "next-token": {
+ "description": "Used for pagination, when making another request provide this token with the next parameter.",
+ "type": "string"
+ },
+ "round": {
+ "description": "The round for which this information is relevant.",
+ "type": "integer"
}
},
"required": [
- "boxes"
+ "boxes",
+ "round"
],
"type": "object"
}
}
},
- "description": "Box names of an application"
+ "description": "Boxes of an application"
},
"400": {
"content": {
@@ -3882,7 +3993,7 @@
"description": "Unknown Error"
}
},
- "summary": "Get all box names for a given application.",
+ "summary": "Get boxes for a given application.",
"tags": [
"public",
"nonparticipating"
diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go
index 1749327ee3..8ae7a018fc 100644
--- a/daemon/algod/api/client/restClient.go
+++ b/daemon/algod/api/client/restClient.go
@@ -475,12 +475,16 @@ func (client RestClient) ApplicationInformation(index uint64) (response model.Ap
}
type applicationBoxesParams struct {
- Max uint64 `url:"max,omitempty"`
+ Prefix string `url:"prefix,omitempty"`
+ Next *string `url:"next,omitempty"`
+ Max uint64 `url:"max,omitempty"`
+ Values bool `url:"values,omitempty"`
}
// ApplicationBoxes gets the BoxesResponse associated with the passed application ID
-func (client RestClient) ApplicationBoxes(appID uint64, maxBoxNum uint64) (response model.BoxesResponse, err error) {
- err = client.get(&response, fmt.Sprintf("/v2/applications/%d/boxes", appID), applicationBoxesParams{maxBoxNum})
+func (client RestClient) ApplicationBoxes(appID uint64, prefix string, next *string, limit uint64, values bool) (response model.BoxesResponse, err error) {
+ err = client.get(&response, fmt.Sprintf("/v2/applications/%d/boxes", appID),
+ applicationBoxesParams{prefix, next, limit, values})
return
}
diff --git a/daemon/algod/api/server/router.go b/daemon/algod/api/server/router.go
index 32b1b40ac7..20dd7bd2fd 100644
--- a/daemon/algod/api/server/router.go
+++ b/daemon/algod/api/server/router.go
@@ -107,6 +107,7 @@ func NewRouter(logger logging.Logger, node APINodeInterface, shutdown <-chan str
middleware.RemoveTrailingSlash())
e.Use(
middlewares.MakeLogger(logger),
+ middleware.Gzip(),
)
// Optional middleware for Private Network Access Header (PNA). Must come before CORS middleware.
if node.Config().EnablePrivateNetworkAccessHeader {
diff --git a/daemon/algod/api/server/v2/account.go b/daemon/algod/api/server/v2/account.go
index 5c0305a123..00d4720420 100644
--- a/daemon/algod/api/server/v2/account.go
+++ b/daemon/algod/api/server/v2/account.go
@@ -421,6 +421,8 @@ func ApplicationParamsToAppParams(gap *model.ApplicationParams) (basics.AppParam
}
ap.ExtraProgramPages = uint32(*gap.ExtraProgramPages)
}
+ ap.Version = nilToZero(gap.Version)
+
if gap.LocalStateSchema != nil {
ap.LocalStateSchema = basics.StateSchema{
NumUint: gap.LocalStateSchema.NumUint,
@@ -462,6 +464,7 @@ func AppParamsToApplication(creator string, appIdx basics.AppIndex, appParams *b
NumByteSlice: appParams.GlobalStateSchema.NumByteSlice,
NumUint: appParams.GlobalStateSchema.NumUint,
},
+ Version: omitEmpty(appParams.Version),
},
}
return app
diff --git a/daemon/algod/api/server/v2/account_test.go b/daemon/algod/api/server/v2/account_test.go
index 97b6e83d30..5db8e8c3cb 100644
--- a/daemon/algod/api/server/v2/account_test.go
+++ b/daemon/algod/api/server/v2/account_test.go
@@ -54,6 +54,7 @@ func TestAccount(t *testing.T) {
GlobalStateSchema: basics.StateSchema{NumUint: 2},
},
ExtraProgramPages: 1,
+ Version: 2,
}
totalAppSchema := basics.StateSchema{
@@ -119,6 +120,12 @@ func TestAccount(t *testing.T) {
verifyCreatedApp := func(index int, appIdx basics.AppIndex, params basics.AppParams) {
require.Equal(t, uint64(appIdx), (*conv.CreatedApps)[index].Id)
require.Equal(t, params.ApprovalProgram, (*conv.CreatedApps)[index].Params.ApprovalProgram)
+ if params.Version != 0 {
+ require.NotNil(t, (*conv.CreatedApps)[index].Params.Version)
+ require.Equal(t, params.Version, *(*conv.CreatedApps)[index].Params.Version)
+ } else {
+ require.Nil(t, (*conv.CreatedApps)[index].Params.Version)
+ }
if params.ExtraProgramPages != 0 {
require.NotNil(t, (*conv.CreatedApps)[index].Params.ExtraProgramPages)
require.Equal(t, uint64(params.ExtraProgramPages), *(*conv.CreatedApps)[index].Params.ExtraProgramPages)
diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go
index 1ac2ae0fa2..32fb6704d5 100644
--- a/daemon/algod/api/server/v2/dryrun.go
+++ b/daemon/algod/api/server/v2/dryrun.go
@@ -17,7 +17,6 @@
package v2
import (
- "encoding/base64"
"fmt"
"strings"
@@ -208,8 +207,7 @@ func (ddr *dryrunDebugReceiver) Complete(state *logic.DebugState) {
type dryrunLedger struct {
// inputs:
- dr *DryrunRequest
- hdr *bookkeeping.BlockHeader
+ dr *DryrunRequest
// intermediate state:
@@ -559,21 +557,8 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
}
result.Disassembly = debug.lines
result.AppCallTrace = &debug.history
- result.GlobalDelta = StateDeltaToStateDelta(delta.GlobalDelta)
- if len(delta.LocalDeltas) > 0 {
- localDeltas := make([]model.AccountStateDelta, 0, len(delta.LocalDeltas))
- for k, v := range delta.LocalDeltas {
- ldaddr, err2 := stxn.Txn.AddressByIndex(k, stxn.Txn.Sender)
- if err2 != nil {
- messages = append(messages, err2.Error())
- }
- localDeltas = append(localDeltas, model.AccountStateDelta{
- Address: ldaddr.String(),
- Delta: *StateDeltaToStateDelta(v),
- })
- }
- result.LocalDeltas = &localDeltas
- }
+ result.GlobalDelta = sliceOrNil(globalDeltaToStateDelta(delta.GlobalDelta))
+ result.LocalDeltas = sliceOrNil(localDeltasToLocalDeltas(delta, &stxn.Txn))
// ensure the program has not exceeded execution budget
cost := maxCurrentBudget - pooledAppBudget
@@ -620,33 +605,6 @@ func doDryrunRequest(dr *DryrunRequest, response *model.DryrunResponse) {
}
}
-// StateDeltaToStateDelta converts basics.StateDelta to model.StateDelta
-func StateDeltaToStateDelta(sd basics.StateDelta) *model.StateDelta {
- if len(sd) == 0 {
- return nil
- }
-
- gsd := make(model.StateDelta, 0, len(sd))
- for k, v := range sd {
- value := model.EvalDelta{Action: uint64(v.Action)}
- if v.Action == basics.SetBytesAction {
- bytesVal := base64.StdEncoding.EncodeToString([]byte(v.Bytes))
- value.Bytes = &bytesVal
- } else if v.Action == basics.SetUintAction {
- uintVal := v.Uint
- value.Uint = &uintVal
- }
- // basics.DeleteAction does not require Uint/Bytes
- kv := model.EvalDeltaKeyValue{
- Key: base64.StdEncoding.EncodeToString([]byte(k)),
- Value: value,
- }
- gsd = append(gsd, kv)
- }
-
- return &gsd
-}
-
// DeltaLogToLog base64 encode the logs
func DeltaLogToLog(logs []string) (*[][]byte, error) {
if len(logs) == 0 {
diff --git a/daemon/algod/api/server/v2/dryrun_test.go b/daemon/algod/api/server/v2/dryrun_test.go
index 8c71bc7118..ec8aefad86 100644
--- a/daemon/algod/api/server/v2/dryrun_test.go
+++ b/daemon/algod/api/server/v2/dryrun_test.go
@@ -1084,12 +1084,12 @@ func TestStateDeltaToStateDelta(t *testing.T) {
Action: basics.DeleteAction,
},
}
- gsd := StateDeltaToStateDelta(sd)
- require.Equal(t, 3, len(*gsd))
+ gsd := globalDeltaToStateDelta(sd)
+ require.Equal(t, 3, len(gsd))
var keys []string
// test with a loop because sd is a map and iteration order is random
- for _, item := range *gsd {
+ for _, item := range gsd {
if item.Key == b64("byteskey") {
require.Equal(t, uint64(1), item.Value.Action)
require.Nil(t, item.Value.Uint)
diff --git a/daemon/algod/api/server/v2/generated/data/routes.go b/daemon/algod/api/server/v2/generated/data/routes.go
index 4c32b80945..c3fbbd17b4 100644
--- a/daemon/algod/api/server/v2/generated/data/routes.go
+++ b/daemon/algod/api/server/v2/generated/data/routes.go
@@ -114,227 +114,229 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "NwAA//+q4vIh1ggBAA==",
+ "H4sIAAAAAAAC/+x9f5fbNpLgV8HT7nuOfWK37TjZie/N2+vYSaY3TuLndrK3a/sSiCxJmKYADgB2S/H5",
+ "u99DFUCCJChR3R175t78ZbeIH4VCoVC/UPV+lqtNpSRIa2ZP388qrvkGLGj8i+e5qqXNROH+KsDkWlRW",
+ "KDl7Gr4xY7WQq9l8JtyvFbfr2Xwm+QbaNq7/fKbhb7XQUMyeWl3DfGbyNWy4G9juKte6GWmbrVTmhzij",
+ "Ic6fzz7s+cCLQoMxQyh/kuWOCZmXdQHMai4Nz90nw66FXTO7Fob5zkxIpiQwtWR23WnMlgLKwpyERf6t",
+ "Br2LVuknH1/ShxbETKsShnA+U5uFkBCgggaoZkOYVayAJTZac8vcDA7W0NAqZoDrfM2WSh8AlYCI4QVZ",
+ "b2ZP38wMyAI07lYO4gr/u9QAv0NmuV6Bnb2bpxa3tKAzKzaJpZ177GswdWkNw7a4xpW4AslcrxP2Q20s",
+ "WwDjkr369hn7/PPPv3IL2XBrofBENrqqdvZ4TdR99nRWcAvh85DWeLlSmssia9q/+vYZzn/hFzi1FTcG",
+ "0oflzH1h58/HFhA6JkhISAsr3IcO9bseiUPR/ryApdIwcU+o8Z1uSjz/J92VnNt8XSkhbWJfGH5l9DnJ",
+ "w6Lu+3hYA0CnfeUwpd2gbx5mX717/2j+6OGHf3lzlv23//OLzz9MXP6zZtwDGEg2zGutQea7bKWB42lZ",
+ "cznExytPD2at6rJga36Fm883yOp9X+b6Euu84mXt6ETkWp2VK2UY92RUwJLXpWVhYlbL0rEpN5qndiYM",
+ "q7S6EgUUc8d9r9ciX7OcGxoC27FrUZaOBmsDxRitpVe35zB9iFHi4LoRPnBBf7/IaNd1ABOwRW6Q5aUy",
+ "kFl14HoKNw6XBYsvlPauMsddVuz1GhhO7j7QZYu4k46my3LHLO5rwbhhnIWrac7Eku1Uza5xc0pxif39",
+ "ahzWNswhDTenc4+6wzuGvgEyEshbKFUCl4i8cO6GKJNLsao1GHa9Brv2d54GUylpgKnFXyG3btv/4+Kn",
+ "H5nS7Acwhq/gJc8vGchcFVCcsPMlk8pGpOFpCXHoeo6tw8OVuuT/apSjiY1ZVTy/TN/opdiIxKp+4Fux",
+ "qTdM1psFaLel4QqximmwtZZjANGIB0hxw7fDSV/rWua4/+20HVnOUZswVcl3iLAN3/754dyDYxgvS1aB",
+ "LIRcMbuVo3Kcm/sweJlWtSwmiDnW7Wl0sZoKcrEUULBmlD2Q+GkOwSPkcfC0wlcEThhkFJxmlgPgSNgm",
+ "aMadbveFVXwFEcmcsJ89c8OvVl2CbAidLXb4qdJwJVRtmk4jMOLU+yVwqSxklYalSNDYhUeHYzDUxnPg",
+ "jZeBciUtFxIKx5wRaGWBmNUoTNGE+/Wd4S2+4Aa+fDJ2x7dfJ+7+UvV3fe+OT9ptbJTRkUxcne6rP7Bp",
+ "yarTf4J+GM9txCqjnwcbKVav3W2zFCXeRH91+xfQUBtkAh1EhLvJiJXkttbw9K184P5iGbuwXBZcF+6X",
+ "Df30Q11acSFW7qeSfnqhViK/EKsRZDawJhUu7Lahf9x4aXZst0m94oVSl3UVLyjvKK6LHTt/PrbJNOax",
+ "hHnWaLux4vF6G5SRY3vYbbORI0CO4q7iruEl7DQ4aHm+xH+2S6QnvtS/u3+qqnS9bbVModbRsb+S0Xzg",
+ "zQpnVVWKnDskvvKf3VfHBIAUCd62OMUL9en7CMRKqwq0FTQor6qsVDkvM2O5xZH+VcNy9nT2L6et/eWU",
+ "upvTaPIXrtcFdnIiK4lBGa+qI8Z46UQfs4dZOAaNn5BNENtDoUlI2kRHSsKx4BKuuLQnrcrS4QfNAX7j",
+ "Z2rxTdIO4bungo0inFHDBRiSgKnhPcMi1DNEK0O0okC6KtWi+eGzs6pqMYjfz6qK8IHSIwgUzGArjDX3",
+ "cfm8PUnxPOfPT9h38dgoiitZ7tzlQKKGuxuW/tbyt1hjW/JraEe8Zxhup9InbmsCGpyYfxcUh2rFWpVO",
+ "6jlIK67xX3zbmMzc75M6/2OQWIzbceJCRctjjnQc/CVSbj7rUc6QcLy554Sd9fvejGzcKHsIxpy3WLxr",
+ "4sFfhIWNOUgJEUQRNfnt4Vrz3cwLiRkKe0My+dkAUUjFV0IitHOnPkm24Ze0Hwrx7ggBTKMXES2RBNmY",
+ "UL3M6VF/MrCz/ANQa2pjgyTqJNVSGIt6NTZmayhRcOYyEHRMKjeijAkbvmcRDczXmldEy/4LiV1Coj5P",
+ "jQjWW168E+/EJMwRu482GqG6MVs+yDqTkCDX6MHwdanyy79ws76DE74IYw1pH6dha+AFaLbmZp04OD3a",
+ "bkebQt+uIdIsW0RTnTRLfKFW5g6WWKpjWFdVPeNl6aYesqzeanHgSQe5LJlrzGAj0GDuFUeysJP+xb7h",
+ "+dqJBSznZTlvTUWqykq4gtIp7UJK0HNm19y2hx9HDnoNniMDjtlZYNFqvJkJTWy6sUVoYBuON9DGaTNV",
+ "2e3TcFDDN9CTgvBGVDVaESJF4/x5WB1cgUSe1AyN4DdrRGtNPPiJm9t/wpmlosWRBdAG912Dv4ZfdIB2",
+ "rdv7VLZTKF2Qzdq634RmudI0BN3wfnL3H+C67UzU+VmlIfNDaH4F2vDSra63qPsN+d7V6TxwMgtueXQy",
+ "PRWmFTDiHNgPxTvQCSvNT/gfXjL32UkxjpJa6hEojKjInVrQxexQRTO5BmhvVWxDpkxW8fzyKCiftZOn",
+ "2cykk/cNWU/9FvpFNDv0eisKc1fbhION7VX3hJDtKrCjgSyyl+lEc01BwGtVMWIfPRCIU+BohBC1vfNr",
+ "7Wu1TcH0tdoOrjS1hTvZCTfOZGaP8P1TLvWEhaibHyGf4qbhBS7ju8GB3boezxZK30xg6t2hkrUOVcbd",
+ "qJG8OO/RATatq8yzn4RThhr0BmpjWPbLOf3hU9jqYOHC8j8AC8aNehdY6A5011hQm0qUcAene52UUxfc",
+ "wOeP2cVfzr549PjXx1986Uiy0mql+YYtdhYM+8xbHpmxuxLuJw8aClDp0b98Etxw3XFT4xhV6xw2vBoO",
+ "Re49UvCpGXPthljrohlX3QA4iemDu70J7Yw81w6057CoVxdgrVPmX2q1vHOGP5ghBR02ellpJzuZrivU",
+ "C4SnhWtyClur+WmFLUEWFErh1iGMU3M3izshqrGNL9pZCuYxWsDBQ3HsNrXT7OKt0jtd34UFB7RWOill",
+ "VFpZlasyc6KsUIm77qVvwXyLsF1V/3eCll1zw9zc6KCtZTFypdmtnH5F09Cvt7LFzV7xiNabWJ2fd8q+",
+ "dJHfKloV6MxuJUPq7Ny0S602jLMCO6I49R1YEjHFBi4s31Q/LZd3Y9BVOFBCJBAbMG4mRi2cgGcgV5Li",
+ "FQ/c/n7UKejpIyY40uw4AB4jFzuZozfwLo7tuGC0ERJDE8xO5pGU5GAsoVh1yPL2VroxdNBU90wCHIeO",
+ "F/gZ3RHPobT8W6VftxL6d1rV1Z2z5/6cU5fD/WK8w6NwfYOlW8hV2Y2RXTnYT1Jr/CQLetbYSWgNCD1S",
+ "5AuxWttIJX6p1R9wJyZnSQGKH8geVro+Q6vYj6pwzMTW5g5EyXawlsM5uo35Gl+o2jLOpCoAN782aSFz",
+ "JKoSw7kwCs3GciuaYIRhC3DUlfParbauGMZYDe6LtmPGczqhGaLGjESYNKFB1Iqmo4i9UgMvdmwBIJla",
+ "+DAOH2CCi+QYIGaDmOZF3AS/6MBVaZWDMVBk3tp+ELTQjq4OuwdPCDgC3MzCjGJLrm8N7OXVQTgvYZdh",
+ "OKNhn33/i7n/CeC1yvLyAGKxTQq9fZPhEOpp0+8juP7kMdmRMZKo1om3jkGUYGEMhUfhZHT/+hANdvH2",
+ "aLkCjVEzfyjFh0luR0ANqH8wvd8W2roaCdL3arqT8NyGSS5VEKxSg5Xc2OwQW3aNOrYEt4KIE6Y4MQ48",
+ "Ini94MZSpJeQBZpt6TrBeUgIc1OMAzyqhriRfwkayHDs3N2D0tSmUUdMXVVKWyhSa0Dj3uhcP8K2mUst",
+ "o7EbnccqVhs4NPIYlqLxPbK8Box/cNuY8rxxcLg4DBtw9/wuicoOEC0i9gFyEVpF2I0DlUcAEaZFNBGO",
+ "MD3KaaKj5zNjVVU5bmGzWjb9xtB0Qa3P7M9t2yFxkR+H7u1CgUEfkW/vIb8mzFKI+pob5uEI1lo051BI",
+ "2hBmdxgzI2QO2T7KRxXPtYqPwMFDWlcrzQvICij5LmFnps+MPu8bAHe8VXeVhYxijdOb3lJyCO3cM7TC",
+ "8UxKeGT4heXuCDpVoCUQ3/vAyAXg2Cnm5OnoXjMUzpXcojAeLpu2OjEi3oZXyrod9/SAIHuOPgXgETw0",
+ "Q98cFdg5a3XP/hT/BcZP0MgRx0+yAzO2hHb8oxYwYgv2z7ii89Jj7z0OnGSbo2zsAB8ZO7IjhumXXFuR",
+ "iwp1ne9hd+eqX3+CZGwAK8ByUULBog+kBlZxf0ZRsv0xb6YKTrK9DcEfGN8SywmRSF3gL2GHOvdLen4R",
+ "mTruQpdNjOruJy4ZAhqCup0IHjeBLc9tuXOCml3Djl2DBmbqBUVpDP0pVlVZPEDSP7NnRu+ATrp/93rE",
+ "L3CoaHkptyXpBPvhe91TDDro8LpApVQ5wUI2QEYSgknhMaxSbteFf+EV3vgESuoA6Zk2Rh801/8900Ez",
+ "roD9l6pZziWqXLWFRqZRGgUFFCDdDE4Ea+b08ZcthqCEDZAmiV8ePOgv/MEDv+fCsCVch2eRrmEfHQ8e",
+ "oB3npTK2c7juwB7qjtt54vpAx5W7+LwW0ucph4O6/MhTdvJlb/DG2+XOlDGecN3yb80AeidzO2XtMY1M",
+ "C2jDcSf5crohUIN1475fiE1dcnsXXiu44mWmrkBrUcBBTu4nFkp+c8XLn5pu+OQTckejOWQ5PlScOBa8",
+ "dn3obaMbR0jhDjC9a5gKEJxTrwvqdEDFbIMexGYDheAWyh2rNORAT/qc5GiapZ4wCvbP11yuUGHQql75",
+ "OAkaBxl+bcg0o2s5GCIpVNmtzNDInboAfCReeNXpxCngTqXrW8hJgbnmzXz+Ie+Umznag77HIOkkm89G",
+ "NV6H1KtW4yXkdJ+mTrgMOvJehJ924omuFESdk32G+Iq3xR0mt7l/jMm+HToF5XDiKKi5/TgW1+zU7XJ3",
+ "B0IPDcQ0VBoMXlGxmcrQV7WMn6GHaMidsbAZWvKp668jx+/VqL6oZCkkZBslYZfMvCIk/IAfk8cJr8mR",
+ "ziiwjPXt6yAd+HtgdeeZQo23xS/udv+E9j1W5lul78olSgNOFu8neCAPutv9lDf1k/KyTLgW/SPVPgMw",
+ "8yZyTmjGjVG5QJntvDBzH/hM3kj/orWL/pfN05s7OHv9cXs+tDj/AdqIoawYZ3kp0IKspLG6zu1bydFG",
+ "FS01EcQVlPFxq+Wz0CRtJk1YMf1QbyXHAL7GcpUM2FhCwkzzLUAwXpp6tQJje7rOEuCt9K2EZLUUFufa",
+ "uOOS0XmpQGMk1Qm13PAdWzqasIr9DlqxRW270j++wTZWlKV36LlpmFq+ldyyErix7AchX29xuOD0D0dW",
+ "gr1W+rLBQvp2X4EEI0yWDjb7jr7i0wW//LV/xoAR/fQ5xNW2SSFmbpmdPDD/57N/f/rmLPtvnv3+MPvq",
+ "f5y+e//kw/0Hgx8ff/jzn/9v96fPP/z5/r//a2qnAuypF8Ie8vPnXjM+f47qT/QaoQ/7R7P/b4TMkkQW",
+ "R3P0aIt9htkwPAHd7xrH7BreSruVjpCueCkKx1tuQg79G2ZwFul09KimsxE9Y1hY65FKxS24DEswmR5r",
+ "vLEUNYzPTL/FR6ekf16P52VZS9rKIH3TU9MQX6aW8ybfAqVie8rwMf6ahyBP/+fjL76czdtH9M332Xzm",
+ "v75LULIotqlUCQVsU7pi/A7knmEV3xmwae6BsCdD6Si2Ix52A5sFaLMW1cfnFMaKRZrDhVdZ3ua0leeS",
+ "3jC484Muzp33nKjlx4fbaoACKrtOpWjqCGrYqt1NgF7YSaXVFcg5Eydw0rf5FE5f9EF9JfBlCEzVSk3R",
+ "hppzQIQWqCLCeryQSYaVFP30XnD4y9/cuTrkB07B1Z8zFdF777tvXrNTzzDNPcraQUNHeRYSqrR/H9oJ",
+ "SHLcLH4291a+lc9hidYHJZ++lQW3/HTBjcjNaW1Af81LLnM4WSn2NDw5fc4tfysHktZo7sjoXTir6kUp",
+ "cnYZKyQteVI+sOEIb9++4eVKvX37bhCbMVQf/FRJ/kITZE4QVrXNfDajTMM11ynfl2my2eDIlK5s36wk",
+ "ZKuaDKQhW5IfP83zeFWZflaL4fKrqnTLj8jQ+JwNbsuYsap5cucEFP9q2e3vj8pfDJpfB7tKbcCw3za8",
+ "eiOkfceyt/XDh5/j48U2zcNv/sp3NLmrYLJ1ZTTrRt+oggsntRJj1bOKr1Iutrdv31jgFe4+yssbtHGU",
+ "JcNunYeV4YEBDtUuoHnFPboBBMfR759xcRfUK2SuTC8BP+EWdt+Y32q/ohQBN96uA2kGeG3XmTvbyVUZ",
+ "R+JhZ5qEdisnZIVoDCNWqK363H8LYPka8kuflA02ld3NO91DwI8XNAPrEIbS9dEjSkwYhQ6KBbC6KrgX",
+ "xbnc9TP3GHpRgYO+gkvYvVZtvqljUvV0M8eYsYOKlBpJl45Y42Prx+hvvo8qC29pfQIWfJ8ayOJpQxeh",
+ "z/hBJpH3Dg5xiig6mU3GEMF1AhFE/CMouMFC3Xi3Iv3U8oTMQVpxBRmUYiUWqUzD/zn0hwVYHVX65Io+",
+ "CrkZ0DCxZE6VX9DF6tV7zeUK3PXsrlRleEmJY5NBG6gPrYFruwBu99r5Zfy2MUCHKuU1Pi5HC9/cLQG2",
+ "br+FRYudhGunVaChiNr46OWT8fgzAhyKG8ITureawsmorutRl0iqGG7lBruNWutD82I6Q7jo+wYwK6u6",
+ "dvvioFA+oSjlrYnul9rwFYzoLrH3bmLKj47HDwc5JJEkZRC17IsaA0kgCTI1ztyak2cY3Bd3iFHN7AVk",
+ "hpnIQex9Rpgn3CNsUaIA20Su0t5z3fGiUuLjMdDSrAW0bEXBAEYXI/FxXHMTjiOmhA1cdpJ09ge+IN6X",
+ "fe88iiWM8r42ufXCbdjnoAO93+fgC4n3Qra9WOmfkDnP6V74fCG1HUqiaFpACStaODUOhNLmhGo3yMHx",
+ "03KJvCVLhSVGBupIAPBzgNNcHjBGvhE2eYQUGUdgY+ADDsx+VPHZlKtjgJQ+pxUPY+MVEf0N6Yd9FKjv",
+ "hFFVuctVjPgb88ABfLaNVrLoRVTjMEzIOXNs7oqXjs15XbwdZJAEDhWKXso3H3pzf0zR2OOaoiv/qDWR",
+ "kHCT1cTSbAA6LWrvgXihthm9UE7qIovtwtF78u0CvpdOHUxKt3fPsIXaYjgXXi0UK38AlnE4AhiR7WUr",
+ "DNIr9huTswiYfdPul3NTVGiQZLyhtSGXMUFvytQjsuUYuXwWZdC7EQA9M1RbjsKbJQ6aD7riyfAyb2+1",
+ "eZsZNjwLSx3/sSOU3KUR/A3tY92cd39pcxuO508LJ+qjJPsbWpZuk4SROleUWPGYHIx9cugAsQerL/ty",
+ "YBKt3VivLl4jrKVYiWO+Q6fkEG0GSkAlOOuIptllKlLA6fKA9/hF6BYZ63D3uNzdjwIINayEsdA6jUJc",
+ "0Kcwx3PMEK3Ucnx1ttJLt75XSjWXP7nNsWNnmR99BRiBvxTa2Aw9bskluEbfGjQifeuapiXQbogi1VMQ",
+ "RZrj4rSXsMsKUdZpevXzfv/cTftjc9GYeoG3mJAUoLXA+h/JwOU9U1Ns+94Fv6AFv+B3tt5pp8E1dRNr",
+ "Ry7dOf5BzkWPge1jBwkCTBHHcNdGUbqHQUYPzofcMZJGo5iWk33ehsFhKsLYB6PUwrP3sZufRkquJcp0",
+ "mH4hqFYrKEIGt+APk1GevFLJVVSoqqr2pQU8YZSdD5Pr7cnL58PwYSwIPxL3MyEL2Kahj7UChLx9WYc5",
+ "BXGSFUhKV5I2CyVRE4f4Y4vIVveRfaH9BwDJIOjXPWd2G51Mu9RsJ25ACbzwOomBsL79x3K4IR5187Hw",
+ "6U5y1/1HCAdEmhI2qt0yTEMwwoB5VYli23M80aijRjB+lHV5RNpC1uIHO4CBbhB0kuA62cJ9qLU3sJ+i",
+ "znvqtDKKvfaBxY6+ee4f4Be1Rg9GJ7J5mJq+0dUmrv37Xy6s0nwF3guVEUi3GgKXcwwaosTvhllB4SSF",
+ "WC4h9r6Ym3gOOsANbOzFBNJNEFnaRVMLab98kiKjA9TTwngYZWmKSdDCmE/+9dDLFWT6yJTUXAnR1tzA",
+ "VZV8rv897LJfeFk7JUNo04bnerdT9/I9YtevNt/DDkc+GPXqADuwK2h5egVIgylLf/PJRDm675lOFQNU",
+ "LztbeMROnaV36Y62xtedGCf+9pbp1GXoLuU2B6MNknCwTNmNi3Rsgjs90EV8n5QPbYIoDssgkbwfTyVM",
+ "qNI5vIqaXBSHaPc18DIQLy5n9mE+u10kQOo28yMewPXL5gJN4hkjTckz3AnsORLlvKq0uuJl5uMlxi5/",
+ "ra785Y/NQ3jFR9Zk0pT9+puzFy89+B/ms7wErrPGEjC6KmxX/cOsiipV7L9KKKG5N3SSpSja/CbpdBxj",
+ "cY3Jy3vGpkHdlzZ+JjqKPuZimQ54P8j7fKgPLXFPyA9UTcRP6/OkgJ9ukA+/4qIMzsYA7UhwOi5uWvGg",
+ "JFeIB7h1sFAU83XrsUYfN7x9++Yq4LF1E1DATJNCPhFBZSYYyPtMJH0IWyI+wPpwST9hBsy0YiN9fkzk",
+ "eD7GiN+5kPat0p07xj+ATMYo/XHSm5PlCY8jIeGhEmhfZjthJN/9tvrNHfoHD+IT/eDBnP1W+g8RgPj7",
+ "wv+OasyDB0knZdJa5ngRGsMk38D95jHH6EZ8XD1fwvU0OeDsatMIsGqcDBsKpWCjgO5rj71rLTw+C/9L",
+ "ASW4n06m2ALiTSd0x8BMOUEXYw8em1jWDRUfNUzJfug2vrV1pIV3ii9uQT7f4RGS9Qb9pJkpRZ6OIJEL",
+ "47iPpJhN15hh4xGjsBuxFiMhwLIW0Viu2ZTUrD0gozmSyDTJ7LAt7hbKH+9air/VwEThlKelAI3XZ+9G",
+ "DToIjjqQe9PmNz8wucPa4W9jbtnj1gomp322lr1uwueN6yosNFU+6chA83jGAePeEyTu6SPccvhobt2N",
+ "9JymLk0pQh8YnfcJjsyRLCovTLbU6ndIX9jopkrk2wj+VYHW5N9BpgIE+yyl8V23tfHb2Q9t93QVfGzj",
+ "b61yh0U39dtucpmmT/VxG3kT3dqks0J7JI/penEgQ/cFwghrweMVxdxiRY0Q5MQlnSdKNtF5yJY+lfGT",
+ "0VMavz2VHubBM9uSXy94qtqOU7kcTNH2dsKxrGKhc9gA06RSoNlZFCjetBWUsK4C3bo6hslvb6g+0bST",
+ "FadWT0KKijWkOUVDlEYlhqnlNZdUj931I37lexsgT7/rda00pps06cixAnKxSVp93759U+TDKKFCrASV",
+ "Gq8NRLWs/UCMcloiFfl64E2CEI+a8yV7OI8K6vvdKMSVMGJRArZ4RC0W3OB12Xjdmy5ueSDt2mDzxxOa",
+ "r2tZaCjs2hBijWKNiotCXhP/uAB7DSDZQ2z36Cv2GUZ+GnEF9x0WvRA0e/roK4zboT8epm5ZXyp+H8su",
+ "kGeHmPA0HWPoK43hmKQfNR3kvdQAv8P47bDnNFHXKWcJW/oL5fBZ2nDJV5B+BrI5ABP1xd3EqIEeXiQ5",
+ "HcBYrXZM2PT8YLnjTyNPyx37IzBYrjYbYTc+PtCojaOntlA1TRqGw5JuofJWgCt8xDDbKqEmfwI1hm9G",
+ "noZhMPSP6AqO0TpnnHKMlqINgA+VT9l5SGGMpciaCmSEGzeXWzrKkhgPv2SVFtKimaW2y+xPTi3WPHfs",
+ "72QM3Gzx5ZNESa9uSRh5HOAfHe8aDOirNOr1CNkHmcX3ZZ9JJbON4yjF/TaVQ3QqR+OB05GfY+Gn+4ee",
+ "Kvm6UbJRcqs75MYjTn0rwpN7BrwlKTbrOYoej17ZR6fMWqfJg9duh35+9cJLGRulU3UJ2uPuJQ4NVgu4",
+ "wod56U1yY95yL3Q5aRduA/2nDbMKImckloWznFQEIsfpvjf5Tor/5Yc2wTr6b+nBY88GqHTC2untdh85",
+ "qPE4q1vfTUxxafhtBHOT0YajDLEyEuRPUfxNn08RltQHifa8Y3B89BvTTgdHOf7BAwT6wYO5F4N/e9z9",
+ "TOz9wYN0nuOkyc392mLhNhox9k3t4dcqYQD7Wm2JC4e4JZ+GIWGATF5S7mZc+DHmrFuB7uOLD3fzfiwd",
+ "zZom/7B+/NxHwCfmjrhj+041FlKdZHTCNQ7KZyZ93QeDLaINcKMuoFROdYor6sRW6iTZ9W6wQIGfFt9u",
+ "8R7gJLZrURa/tL7DHnvUXObrZIjtwnX8lSTPzsVCDCBZpGPNpYQyORxpbL8GzS6he/5VTZ1nI+TEtv0S",
+ "rrTc3uJawLtgBqDChA69wpZughir3ZxTTU6DcqUKhvO0FSHakz8s9ZyqP5l4HIzDbmrrgz7xIbXP1rMU",
+ "JcYwpr2h2DLT3I7wE6yHHorzuHGwPLkh5ZlGB8242OB1Y/imKgFP5hVop/mrJT5I7XbH/GM4clTugZnK",
+ "fcKWmO1BMVtrydRyGS0DpBUayt2cVdwYGuShWxZsce7Z00cPHyaNOYidCSslLIZl/tQu5dEpNqEvvkIR",
+ "5dE/CtjDsH5oKeqYjR0Sji/IiBWVUzyVSi2jvQN9f+5KomKMTeHQE/Ydpg1yRNzJE49GuJCBt5uNsq5K",
+ "xYs5ZgZ+/c3ZC0azUh8qMU/FIFdog+qSf9JpMD07Z0iLNJJ2Zvo4+/NguFUbmzW1G1OJ/VyLtrqk6AWs",
+ "oHUqxs4Je06GwSY6gyZhmF9ab6CISkWSaorE4f5jLc/XaHHrXPPjvHJ6FdPAzlp/RPR0rykdhAzbwe0L",
+ "mVId0znDot7XwgA+Z4cr6OYSbBJreotvyC3YXZ6upSRKOabWd1Mo6Fi0B+BITAuu8iRkPcQfaW+hYsbH",
+ "FnW9wF7phwy9CrE9X3bITBfyU7MfvMk851JJkWMdgZS4iHnPpjnfJpRcSHvNzMyf0MThStalbR7SeiyO",
+ "VqoNjNAjbujIjr66TSXqoD8tbH29shVY4zkbFPNQJtq7eYQ04EtBOSKK+aTSiVCd5CuCJizgSDLClEYj",
+ "drtv3bcfvVUXM0pcCon2G482r3yQI6Y0Av2tkgnLVgqMX0/3KYx54/qcYIrDArbvTl6olcgvxArHoOAw",
+ "t2wKuBwOdRbCL324o2v7zLX1ieebnztBTjTpWVX5SceLiCcFSbuVowhOReOE8IgIuc348Wh7yG1v3DTe",
+ "p47Q4ApjsaDCe3hAGE0h6u4o3zhFiigKWzB6jpjMPitkAowXQgbHYPqCyJNXAm4MnteRfibX3JLuMImn",
+ "vQZejrwewOe95Fm+7VD9tPsOJbjGMMf4NrY1tEcYR9Oglfi53LFwKBx1R8LEM142cceJitgoVXkhiiI1",
+ "ezWyU4zDMe4svDfsoOvg27emO5ayOPYmGkvwt6iLFdiMF0UqL9TX+JXh1/DCCraQ100Fp+ZpXTfB95Da",
+ "/ES5kqbe7JkrNLjldFHR+QQ1xIXvww5jmprFDv9NlS8a3xkfcXz0k9YQXlwcl9V++EQ3JfU6ms6MWGXT",
+ "MYF3yu3R0U59M0Jv+98ppYe3rn8XT1l7XC7eoxR/+8ZdHHHW20HUNV0tTVJajHBW+D1kC2rSKXa5El5l",
+ "gyJd6MvHzUtsWQ/40DAJ+BUvR56Rxx4Aul/JKj72mDwfzX3Arc9tZTnby4JG8wVRBGzPpzB0jI1FvVLQ",
+ "693Z4v1a9yJ03CP1fcf/RJFPLbMY9TvdzDXUbvCxviGfrX9o0uRlqfLJp94Pc+Y6jefCVJuNTxKdiMy6",
+ "2qgipvM4xgcgzbQo6DQRyI66Z/IbKkbJL/o6PVrHZnGsqZTQ6Jcwp/dtAbwADE0dTxSZSD1m2beixCo/",
+ "/3Hx04+z8Y2MdmC4pT43bdKoPLYxzROgPnmsVAcf9bjtRMkypUTMZ2bEyI0ZaNKnwZdyTX74lox2U0Ci",
+ "RC3HtH4xdfABAaxUKvX6MJHGrN2IgPaIDtqNJV4S00WKHvpVdxIaDZkg2yasqQ05qVZkR/KZUuQnVU/G",
+ "y//BHkrXh893RUV2BvV5Bqzz+RSRb4CPD/PZeXGUUJSqSTSjUVKs9YVYrS2WNPgL8AL0ywMlG9oyDajV",
+ "VMqItkRr6QbzOXLXONzJ1GcSr9fg01uEl9KDsUL47BXkFuvytmGBGuCYAhRusuDY+2fphnG20Lwm8RUb",
+ "9pVpGBbjPSDHDTJLRdnRqJDpyfSiBGdN8De9Xbvmps1n03tUPvlp63IJOaaN3pvJ6z/XIKMsUfNge0NY",
+ "llFiL9G8wMLE58dblluA9iXa2gtPVIDo1uCMPfS/hN09wzrUkKys2jw/vElmZcQAuTlDku0xZ4GPdxOm",
+ "oQzEQghm9rmq2+oho0mxo7x0N5wrkKS7ONpcdXumTFeFnzSX63pUXkwU+8aSfQ2LSo/rmM+xhrfxoX28",
+ "ycwcW2LY+bCy0LXP7Ix51xr/WMjxDCb8FpIs0iyluPQFFhAr5I285roILe4kaxbdTSIN9LKZWbRPT4aB",
+ "LIlaFfiKKy+VEyOysadw3dceTajkPUMxrW2GI4RrCVpD0bi9SmUgsyo8VdkHxz5UUODujZBgRutDEXCj",
+ "ucFftcnPsU4ex1zg3MfrxgtkGjbcQaejFOXjc+5D9jP6HrIUhDppB62IDb0eLtgbHh0JM0BiTPVL5m/L",
+ "w9kPbmJQFFKCzoJ3sZ+vXHZT1mFi0qLO6YKOD0ZjdJ2cXGgPK0na4vLhKns6QvS8/xJ2p2TRCJWOww7G",
+ "QJPkRKBHGVl7m3ynJlaTgnt1J+B92kR7lVJlNuLQOh8mWe9T/KXILwGTJDbB+SNF7Nln6EdpIhau17uQ",
+ "VLyqQEJx/4SxM0nPoULwQrf+Ym9yec/um3+LsxY11T3whtOTtzL9rgQrEuhbcrMwzH4eZsCxultORYMc",
+ "SOG9lWNhVddYvaBb5vRkqlY+DCfol9lviYqgSMkkF+SVfIYHPVV9HJM3RFlG0FnNmfdmMlOqVBTyTRJM",
+ "uKHSmIonQ4AsyCl5Dhoo/OBJBCQLxydOIeUG9FkB1ZJpaAMFbpoecVjjPqXR92duZunyu6XS0KlW73pT",
+ "KtTmyQ7mGcX/LITVXO9uksRwUGN/YD0ZxfLBkLsm2q5dSBtxN8RhWarrDJlV1hQCSam2rp3pXsahKl3b",
+ "z53qBUSxe9x4QW3H1rxgudIa8rhH+qUqQbVRGrJSYShfKspgaZ3cvcHnaZKVasVUlasCqKBOmoLG5qql",
+ "5Cg2QRQ5lUQB0Q6+c6Y+ER1PnNLdqeQrzFDUOph/Pmz+a9eH3ty3aa9o0Rn5q0ei0sH4NFceQ9R4CC8S",
+ "DiVs6dsS07x5KbZIN6BTR37JrK5hznyLfhFxf/C5BrYRxhAoDS1di7LEJ+9iG3nXm+CUNGpHxN5zDJ29",
+ "Ehhf1U1/QNJw5e68JidEzAMu4oRNzK61qlfrKAN3A2dQeXXtFeJ4lJ9NjSFw+PbNTfGEbZSxXtOkkdol",
+ "t2GFn+VKWq3KsmuUIhF95T2QP/DtWZ7bF0pdLnh+eR/1Wqlss9JiHl6G9wNA25l0L/da9wLOqN774VzG",
+ "1A7DIT3RTmaQPRZ3dOX7CMx3hznoYZv72XBh/XV1mWlajTmTjFu1EXn6TP1jRVSOxkGmWFQy2xoVn6T8",
+ "GNgMD3t8WTUBNMgih2gGyZPV886YZwQ+kADZjfsvSuD9cdkSPKMZuSiHzMVLUVk+Kuv1AEBI6dG2rTVV",
+ "rIwlsYarqBUlecAwiD6gE28VjDa7HWxuhDsHysKtgBpEuDYAfkbGhzllxaNo2YXahu/327R5NwL+w34q",
+ "7zCPsTC+i5a0NAXyhRQ7IxwhnQN8b8zba3ywv5ga+dZUF554w0cAjMfCdWCYFBF3LBhLLkooslRxyvPG",
+ "RjWPNG3//K5fM14Yz8lzXofakG7sWoNP+UIivu76vyruSEk1zYeWZFnAFujtzu+gFRV9nEf+FyipJmTP",
+ "GKCqrIQr6IQI+jw0NYqa4gpCX9N0ZgVAhd7Ivo0sFfsW3+U9w4lfexZFT03BbtKSQoilnWIHzCRJo85W",
+ "ZnRMzNSj5CC6EkXNO/gzx4ocXTOgO8oJVA10hCzokVOn+ZlGeBUGOAv9U6JMwMS7aXzoaBaURt0+BnQw",
+ "FrY2Y6depkNh4yRLjYMFZysaRyyReMs3TMWv5bhBckjyrbo1cZ+EkhFiv9lCjlKN13eg8BrPiJPC52tB",
+ "apcABWkFrkvC2r4GyaSKanBec9OoKm32x/ADTYyNhPTa9A2cym3E6u13luFgzPTSwI0qErqh05ub5z/J",
+ "Sdx7EEfHS9GIAf/Ec4/9K1C3VzuwAdY6l24/neyPVSz9Lea5+Jwt6jBQWaprKqoZ66HPIfhBifqCC8iL",
+ "5aK5lkNk7twnJu2bOkT0JmHDd0xp/MdpnX+reSmWO+QzBH7oxsyaOxLyjleKCPCRvm7i/eLVPAAWrC0q",
+ "TEXrFlPHjIbbuVEioN1FHqofKbbhlxBvAwY7EP/MrWOcpl6g5cJd2b3tHGLBLz4kl9nwItb0McVlt858",
+ "SHrsev/P9r1jPFXITFeVPA8lVH0Npy6fwTLJgbjsGjb7H8QO+Voggab0cku0OmRQKG5gMj2SdaVemYzV",
+ "p+mAPShJOyjNc6tlTLT89oqQ7HlKPGkpd70LU6NuBkDHhSwPgR/X9fw4+E9mnx1bxhTw/17wPlLJN4aX",
+ "ivZ+BCx3sqwkYCVr9UJtMw1LcyjAhMzVTp3XbX6WYGIVMtfADUXcnP/kFc82uaqQThGmmNDGp9mMUsBS",
+ "yJZZClnVNqHHYI5VuYsQFhv9Ea0jLrQxKcEJk1e8/OkKtBbF2Ma500E1L+MaGsHR4fsmTBjNnTocQJhW",
+ "h8M3uK0ZPW7mLnCq0kXhmsZyWXBdxM2FZDlod++za74zN/coNc6BQz4lHkkz3cwQkXcJSZsAKXfeKXxL",
+ "f08DIL9Dx88Ehw3GBSecNWTasWrEPzOE4R/CYbPh26xUK3wpOnIgfFZd9PCRCqgkmsFJPpu27jCPEb/D",
+ "/mmwoIBnRFbhrFOm2H/uf8KtRDXyZyns3pNPNsr+012Ku6WDGZAqV23wPxHL8DymXlv7BDvxi+sgbIYM",
+ "FYH2INpEGPEPde3iI7uIYRD+qX5sBJ9eD64baZF6002WgQwtBmZPeD+YNpSd5z48a2hKG5gaCClz/yL+",
+ "SEsb2efDvTQCHhXv92e9O20TMuPGOaaI3v438FmlqiyfEvNJNUcK7ybwkHZhHKGPyAkwsu4mPMY0VXg6",
+ "ua065XiOrSM4Wg7okLeryvcp/WNmohGO3nVBqCXyMiptj9YtfMnTGFPmQb0OPumuGaxhEowzDXmt0Ux8",
+ "zXeH67KN5Lq++MvZF48e//r4iy+Za8AKsQLT5kvv1TVr4wKF7Nt9Pm4k4GB5Nr0JIcMEIS74H8OjqmZT",
+ "/FkjbmvaZKiDqm7H2JcTF0Dq0eew0NWN9grHaUP7/762K7XIO9+xFAr++D3TqizT9SoauSrhQEntVuRC",
+ "cRpIBdoIYx0j7HpAhW0jos0azYOYtfiKMgYpmUOwH3sqEHYk5Cq1kLGAWuRn+H7fe40YbKvS8yry9Oxb",
+ "l9fTyEKHQiNGxSyAVaryor1YshRE+IJI19BYxr3hEy3iUYxsw2wpWjZFiD7yPE16cUXx/dy+W+3Wpjm9",
+ "28SEeBEO5Q1Ic8w/MZ6b4iacpDXt/93wj0SyjTvjGs1y/whekdQP9rw5PhvEPTSJJiaBNky8kCAPBGDk",
+ "tW3nnWT0UCxKoazJS4D+hOBA7osfP7SO5YPPQhCS0OEAePHz2bZd85LBg/OJUxP/0CAlWsq7MUroLP/Q",
+ "i9zAepuLJNoibzSxFgyxJTUUC6Pn1uZZ84p5RCsZPHbWSlnmNNOyTDySJjsOnqmYcJxKoK94+fG5xrdC",
+ "G3uG+IDi1fjTqPilbIxkQqW5WS7GF3zS3NGr2LubWr7Eh9n/CW6PkvecH8o74Qe3GRp3sKT/KtwK9Nab",
+ "XeOYFGT16Eu28GVCKg25MH3n/nUQTpqHoaDF0ge0wtYeeIl6aJ2/KHsLMl6GSBz2Y+Teanz2HsL2iH5i",
+ "pjJycpNUnqK+AVkk8JfiUXH14gPXxS1LStwstU+UpO/I1D7DusxTl4frwEunNjBc5+TbuoPbxEXdrm1q",
+ "XqrJlSnevn1jF1PSSaWrSLjumM/qTspJHFVM4g/IZEU48mP4eVMU88tYbmPK3zuSf723H7UoDwasdLLp",
+ "f5jPVpTMBvPF/+qr3nzcuzRAMJJRyi/9NuliCDGJtXYmj6aKkv9MSJHvuyXymuOrxrzWwu6w4nEwoIlf",
+ "kzWqv2tye/jcMI0vzd99Vl1CU9y+zQRSm3C7fqd4ifcRufiku4VUecK+oSzu/qD8+d7i3+DzPz0pHn7+",
+ "6N8Wf3r4xcMcnnzx1cOH/Ksn/NFXnz+Cx3/64slDeLT88qvF4+Lxk8eLJ4+ffPnFV/nnTx4tnnz51b/d",
+ "c3zIgUyAhvINT2f/OzsrVyo7e3mevXbAtjjhlfge3N6grrzEFFaI1BxPImy4KGdPw0//K5ywk1xt2uHD",
+ "rzNfWWq2trYyT09Pr6+vT+Iupyt8+p9ZVefr0zAPZjvryCsvz5sYfYrDwR1trce4qU0eKPft1TcXr9nZ",
+ "y/OTWVTRfvbw5OHJI1+UW/JKzJ7OPsef8PSscd9PMYfqqfHlEU6bt1of5oNvVUXFE9ynVZMozv21Bl5i",
+ "gh33xwasFnn4pIEXO/9/c81XK9An+HqDfrp6fBqkkdP3PnPCh33fTuPIkNP3nQQTxYGeIfLhUJPT96Ho",
+ "7/4BOwVffcxZ1GEioPuanS6w0M/UphCvbnwpqMaY0/coiI/+fuqtKemPqBDRSTsNiVpGWtKT/PTHDgrf",
+ "261byP7hXJtovJzbfF1Xp+/xP3hoohVRFtdTu5Wn6EA+fd9BhP88QET397Z73AITFAbg1HJJlZL3fT59",
+ "T/9GE8G2Ai2cNIpZdfyvlP3sFAvm7YY/76R3d5aQylnzszRA2nKoKrGTefv0reEj50VofLGTeRCbQ0wk",
+ "cofHDx/S9E/wPzNfUKqX2eXUn+eZaSro7zXadPKmIu/t2esaeOmBH9iTGcLw6OPBcC4pDtIxY7o0Psxn",
+ "X3xMLJxLJ9/wkmFLmv7zj7gJoK9EDuw1bCqluRbljv0sm1DOqLxvigIvpbqWAXIncdSbDdc7lOQ36goM",
+ "85WDI+JkGpzsROEeGALQ0jBeedzxkTezql6UIp/NKUvuO5TWbEpwCUak4UzBgNYO3j0V3x08E9N3oSsP",
+ "70lZMwnOA8kMaPhEfsnB/oa977tgaap7qQ2a/ZMR/JMR3CEjsLWWo0c0ur8w7xpU/olrzvM17OMHw9sy",
+ "uuBnlUollrjYwyx8BZsxXnHR5RVtqOHs6Ztp1Qu914MM2gUYd5hPgjLjJPVW19ANRwpnHn2u0V7vq8j+",
+ "4d3fxf3+jMtwnjs7Tm5NrksBuqECLodFhf7JBf6/4QJUHY3Tvs6ZhbI08dm3Cs8+eYB8Ok1JnrmJfKCT",
+ "/bQVpjs/nwa7RUoH7bZ83/mzq1eZdW0LdR3NghZ/clcNtQz3sTb9v0+vubDZUmmfdJMvLehhZwu8PPVV",
+ "lHq/toULBl+wGkP0Y/ycNPnrKffqRuob8rqxjgN9OPXVq3wjjUIUdPjcWt1iKxby2cZ+9ead43JYPt6z",
+ "4NYo8/T0FJ/FrJWxp7MP8/c9g0388V1DWKFc7qzS4grrWLybz7aZ0mIlJC8zb9VoS8HNHp88nH34fwEA",
+ "AP//sn63M9oKAQA=",
}
// 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 3d5c749701..d8672d7974 100644
--- a/daemon/algod/api/server/v2/generated/experimental/routes.go
+++ b/daemon/algod/api/server/v2/generated/experimental/routes.go
@@ -130,232 +130,234 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "AAD//6UnopQICgEA",
+ "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",
+ "AQAA//+DQtmvDAwBAA==",
}
// 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 4cef568e7b..52f37b79fc 100644
--- a/daemon/algod/api/server/v2/generated/model/types.go
+++ b/daemon/algod/api/server/v2/generated/model/types.go
@@ -385,6 +385,9 @@ type ApplicationParams struct {
// LocalStateSchema Specifies maximums on the number of each type that may be stored.
LocalStateSchema *ApplicationStateSchema `json:"local-state-schema,omitempty"`
+
+ // Version \[v\] the number of updates to the application programs
+ Version *uint64 `json:"version,omitempty"`
}
// ApplicationStateOperation An operation against an application's global/local/box state.
@@ -527,22 +530,16 @@ type AvmValue struct {
// Box Box name and its content.
type Box struct {
- // Name \[name\] box name, base64 encoded
+ // Name The box name, base64 encoded
Name []byte `json:"name"`
// Round The round for which this information is relevant
- Round uint64 `json:"round"`
+ Round *uint64 `json:"round,omitempty"`
- // Value \[value\] box value, base64 encoded.
+ // Value The box value, base64 encoded.
Value []byte `json:"value"`
}
-// BoxDescriptor Box descriptor describes a Box.
-type BoxDescriptor struct {
- // Name Base64 encoded box name
- Name []byte `json:"name"`
-}
-
// BoxReference References a box of an application.
type BoxReference struct {
// App Application ID which this box belongs to
@@ -663,13 +660,33 @@ type EvalDeltaKeyValue struct {
Value EvalDelta `json:"value"`
}
-// KvDelta A single Delta containing the key, the previous value and the current value for a single round.
-type KvDelta struct {
- // Key The key, base64 encoded.
- Key *[]byte `json:"key,omitempty"`
-
- // Value The new value of the KV store entry, base64 encoded.
- Value *[]byte `json:"value,omitempty"`
+// Genesis defines model for Genesis.
+type Genesis struct {
+ Alloc []GenesisAllocation `json:"alloc"`
+ Comment *string `json:"comment,omitempty"`
+ Devmode *bool `json:"devmode,omitempty"`
+ Fees string `json:"fees"`
+ Id string `json:"id"`
+ Network string `json:"network"`
+ Proto string `json:"proto"`
+ Rwd string `json:"rwd"`
+ Timestamp uint64 `json:"timestamp"`
+}
+
+// GenesisAllocation defines model for GenesisAllocation.
+type GenesisAllocation struct {
+ Addr string `json:"addr"`
+ Comment string `json:"comment"`
+ State struct {
+ Algo uint64 `json:"algo"`
+ Onl *uint64 `json:"onl,omitempty"`
+ Sel *string `json:"sel,omitempty"`
+ Stprf *string `json:"stprf,omitempty"`
+ Vote *string `json:"vote,omitempty"`
+ VoteFst *uint64 `json:"voteFst,omitempty"`
+ VoteKD *uint64 `json:"voteKD,omitempty"`
+ VoteLst *uint64 `json:"voteLst,omitempty"`
+ } `json:"state"`
}
// LedgerStateDelta Ledger StateDelta object
@@ -1180,7 +1197,13 @@ type BoxResponse = Box
// BoxesResponse defines model for BoxesResponse.
type BoxesResponse struct {
- Boxes []BoxDescriptor `json:"boxes"`
+ Boxes []Box `json:"boxes"`
+
+ // NextToken Used for pagination, when making another request provide this token with the next parameter.
+ NextToken *string `json:"next-token,omitempty"`
+
+ // Round The round for which this information is relevant.
+ Round uint64 `json:"round"`
}
// CatchpointAbortResponse An catchpoint abort response.
@@ -1512,8 +1535,17 @@ type GetApplicationBoxByNameParams struct {
// GetApplicationBoxesParams defines parameters for GetApplicationBoxes.
type GetApplicationBoxesParams struct {
- // Max Max number of box names to return. If max is not set, or max == 0, returns all box-names.
+ // Max Maximum number of boxes to return. Server may impose a lower limit.
Max *uint64 `form:"max,omitempty" json:"max,omitempty"`
+
+ // Prefix A box name prefix, in the goal app call arg 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...'.
+ Prefix *string `form:"prefix,omitempty" json:"prefix,omitempty"`
+
+ // Next A box name, in the goal app call arg form 'encoding:value'. When provided, the returned boxes begin (lexographically) with the supplied name. Callers may implement pagination by reinvoking the endpoint with the token from a previous call's next-token.
+ Next *string `form:"next,omitempty" json:"next,omitempty"`
+
+ // Values If true, box values will be returned.
+ Values *bool `form:"values,omitempty" json:"values,omitempty"`
}
// GetBlockParams defines parameters for GetBlock.
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 aee8f09bc9..03474cebda 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/private/routes.go
@@ -184,232 +184,234 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
}
// 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 cdbdbd8750..646cd7f8f5 100644
--- a/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/nonparticipating/public/routes.go
@@ -36,7 +36,7 @@ type ServerInterface interface {
// Get box information for a given application.
// (GET /v2/applications/{application-id}/box)
GetApplicationBoxByName(ctx echo.Context, applicationId uint64, params GetApplicationBoxByNameParams) error
- // Get all box names for a given application.
+ // Get boxes for a given application.
// (GET /v2/applications/{application-id}/boxes)
GetApplicationBoxes(ctx echo.Context, applicationId uint64, params GetApplicationBoxesParams) error
// Get asset information.
@@ -280,6 +280,27 @@ func (w *ServerInterfaceWrapper) GetApplicationBoxes(ctx echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter max: %s", err))
}
+ // ------------- Optional query parameter "prefix" -------------
+
+ err = runtime.BindQueryParameter("form", true, false, "prefix", ctx.QueryParams(), ¶ms.Prefix)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter prefix: %s", err))
+ }
+
+ // ------------- Optional query parameter "next" -------------
+
+ err = runtime.BindQueryParameter("form", true, false, "next", ctx.QueryParams(), ¶ms.Next)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter next: %s", err))
+ }
+
+ // ------------- Optional query parameter "values" -------------
+
+ err = runtime.BindQueryParameter("form", true, false, "values", ctx.QueryParams(), ¶ms.Values)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter values: %s", err))
+ }
+
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.GetApplicationBoxes(ctx, applicationId, params)
return err
@@ -753,308 +774,313 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
+ "Pv3fAAAA//9NN7DNVcUBAA==",
}
// 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 3a53efec1e..a2757febb7 100644
--- a/daemon/algod/api/server/v2/generated/participating/private/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/private/routes.go
@@ -248,236 +248,238 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
- "GQ1x/mL2cc8HXhQajBlC+aMsd0zIvKwLYFZzaXjuPhl2Leya2bUwzHdmQjIlgakls+tOY7YUUBbmJCzy",
- "HzXoXbRKP/n4kj62IGZalTCE87naLISEABU0QDUbwqxiBSyx0Zpb5mZwsIaGVjEDXOdrtlT6AKgERAwv",
- "yHoze/Z2ZkAWoHG3chBX+N+lBvgFMsv1Cuzs/Ty1uKUFnVmxSSzt3GNfg6lLaxi2xTWuxBVI5nqdsO9r",
- "Y9kCGJfs9TfP2WefffalW8iGWwuFJ7LRVbWzx2ui7rNns4JbCJ+HtMbLldJcFlnT/vU3z3H+C7/Aqa24",
- "MZA+LGfuCzt/MbaA0DFBQkJaWOE+dKjf9UgcivbnBSyVhol7Qo3vdFPi+X/TXcm5zdeVEtIm9oXhV0af",
- "kzws6r6PhzUAdNpXDlPaDfr2Ufbl+w+P548fffyXt2fZ//g/P//s48TlP2/GPYCBZMO81hpkvstWGjie",
- "ljWXQ3y89vRg1qouC7bmV7j5fIOs3vdlri+xzite1o5ORK7VWblShnFPRgUseV1aFiZmtSwdm3KjeWpn",
- "wrBKqytRQDF33Pd6LfI1y7mhIbAduxZl6WiwNlCM0Vp6dXsO08cYJQ6uG+EDF/TPi4x2XQcwAVvkBlle",
- "KgOZVQeup3DjcFmw+EJp7ypz3GXF3qyB4eTuA122iDvpaLosd8zivhaMG8ZZuJrmTCzZTtXsGjenFJfY",
- "36/GYW3DHNJwczr3qDu8Y+gbICOBvIVSJXCJyAvnbogyuRSrWoNh12uwa3/naTCVkgaYWvwdcuu2/T8v",
- "fvyBKc2+B2P4Cl7x/JKBzFUBxQk7XzKpbEQanpYQh67n2Do8XKlL/u9GOZrYmFXF88v0jV6KjUis6nu+",
- "FZt6w2S9WYB2WxquEKuYBltrOQYQjXiAFDd8O5z0ja5ljvvfTtuR5Ry1CVOVfIcI2/Dtnx/NPTiG8bJk",
- "FchCyBWzWzkqx7m5D4OXaVXLYoKYY92eRherqSAXSwEFa0bZA4mf5hA8Qh4HTyt8ReCEQUbBaWY5AI6E",
- "bYJm3Ol2X1jFVxCRzAn7yTM3/GrVJciG0Nlih58qDVdC1abpNAIjTr1fApfKQlZpWIoEjV14dDgGQ208",
- "B954GShX0nIhoXDMGYFWFohZjcIUTbhf3xne4gtu4IunY3d8+3Xi7i9Vf9f37vik3cZGGR3JxNXpvvoD",
- "m5asOv0n6Ifx3EasMvp5sJFi9cbdNktR4k30d7d/AQ21QSbQQUS4m4xYSW5rDc/eyYfuL5axC8tlwXXh",
- "ftnQT9/XpRUXYuV+Kumnl2ol8guxGkFmA2tS4cJuG/rHjZdmx3ab1CteKnVZV/GC8o7iutix8xdjm0xj",
- "HkuYZ422Gyseb7ZBGTm2h902GzkC5CjuKu4aXsJOg4OW50v8Z7tEeuJL/Yv7p6pK19tWyxRqHR37KxnN",
- "B96scFZVpci5Q+Jr/9l9dUwASJHgbYtTvFCffYhArLSqQFtBg/KqykqV8zIzllsc6V81LGfPZv9y2tpf",
- "Tqm7OY0mf+l6XWAnJ7KSGJTxqjpijFdO9DF7mIVj0PgJ2QSxPRSahKRNdKQkHAsu4YpLe9KqLB1+0Bzg",
- "t36mFt8k7RC+eyrYKMIZNVyAIQmYGt4zLEI9Q7QyRCsKpKtSLZof7p9VVYtB/H5WVYQPlB5BoGAGW2Gs",
- "eYDL5+1Jiuc5f3HCvo3HRlFcyXLnLgcSNdzdsPS3lr/FGtuSX0M74j3DcDuVPnFbE9DgxPy7oDhUK9aq",
- "dFLPQVpxjf/i28Zk5n6f1Pn3QWIxbseJCxUtjznScfCXSLm536OcIeF4c88JO+v3vRnZuFH2EIw5b7F4",
- "18SDvwgLG3OQEiKIImry28O15ruZFxIzFPaGZPKTAaKQiq+ERGjnTn2SbMMvaT8U4t0RAphGLyJaIgmy",
- "MaF6mdOj/mRgZ/kdUGtqY4Mk6iTVUhiLejU2ZmsoUXDmMhB0TCo3oowJG75nEQ3M15pXRMv+C4ldQqI+",
- "T40I1ltevBPvxCTMEbuPNhqhujFbPsg6k5Ag1+jB8FWp8su/cLO+gxO+CGMNaR+nYWvgBWi25madODg9",
- "2m5Hm0LfriHSLFtEU500S3ypVuYOlliqY1hXVT3nZemmHrKs3mpx4EkHuSyZa8xgI9Bg7hVHsrCT/sW+",
- "5vnaiQUs52U5b01FqspKuILSKe1CStBzZtfctocfRw56DZ4jA47ZWWDRaryZCU1surFFaGAbjjfQxmkz",
- "Vdnt03BQwzfQk4LwRlQ1WhEiReP8RVgdXIFEntQMjeA3a0RrTTz4iZvbf8KZpaLFkQXQBvddg7+GX3SA",
- "dq3b+1S2UyhdkM3aut+EZrnSNATd8H5y9x/guu1M1Hm/0pD5ITS/Am146VbXW9SDhnzv6nQeOJkFtzw6",
- "mZ4K0woYcQ7sh+Id6ISV5kf8Dy+Z++ykGEdJLfUIFEZU5E4t6GJ2qKKZXAO0tyq2IVMmq3h+eRSUz9vJ",
- "02xm0sn7mqynfgv9IpoderMVhbmrbcLBxvaqe0LIdhXY0UAW2ct0ormmIOCNqhixjx4IxClwNEKI2t75",
- "tfaV2qZg+kptB1ea2sKd7IQbZzKz/0ptX3jIlD6MeRx7CtLdAiXfgMHbTcaM083S+uXOFkrfTJroXTCS",
- "td5Gxt2okTA17yEJm9ZV5s9mwmNBDXoDtQEe+4WA/vApjHWwcGH5r4AF40a9Cyx0B7prLKhNJUq4A9Jf",
- "J4W4BTfw2RN28Zezzx8/+euTz79wJFlptdJ8wxY7C4bd92Y5ZuyuhAdJ7Qili/ToXzwNPqruuKlxjKp1",
- "DhteDYci3xdpv9SMuXZDrHXRjKtuAJzEEcFdbYR2Rm5dB9oLWNSrC7DWabqvtFreOTcczJCCDhu9qrQT",
- "LEzXT+ilpdPCNTmFrdX8tMKWIAuKM3DrEMbpgJvFnRDV2MYX7SwF8xgt4OChOHab2ml28Vbpna7vwrwB",
- "WiudvIIrrazKVZk5OU+ohIHilW/BfIuwXVX/d4KWXXPD3NzovaxlMWKHsFs5/f6iod9sZYubvTcYrTex",
- "Oj/vlH3pIr/VQirQmd1KhtTZMY8stdowzgrsiLLGt2BJ/hIbuLB8U/24XN6NtVPhQAk7jtiAcTMxauGk",
- "HwO5khTMd8Bk40edgp4+YoKXyY4D4DFysZM5usru4tiOW7M2QqLf3uxkHpm2HIwlFKsOWd7ehDWGDprq",
- "nkmA49DxEj+jrf4FlJZ/o/SbVnz9Vqu6unP23J9z6nK4X4z3BhSubzADC7kquwGkKwf7SWqNv8mCnjdG",
- "BFoDQo8U+VKs1jbSF19p9SvciclZUoDiBzIWla7P0GT0gyocM7G1uQNRsh2s5XCObmO+xheqtowzqQrA",
- "za9NWsgcCTnEWCcM0bKx3Ir2CWHYAhx15bx2q60rhgFIg/ui7ZjxnE5ohqgxI+EXTdwMtaLpKJyt1MCL",
- "HVsASKYWPsbBR1/gIjlGT9kgpnkRN8EvOnBVWuVgDBSZN0UfBC20o6vD7sETAo4AN7Mwo9iS61sDe3l1",
- "EM5L2GUY62fY/e9+Ng9+A3itsrw8gFhsk0Jv3542hHra9PsIrj95THZkqSOqdeKtYxAlWBhD4VE4Gd2/",
- "PkSDXbw9Wq5AY0jJr0rxYZLbEVAD6q9M77eFtq5GIti9mu4kPLdhkksVBKvUYCU3NjvEll2jji3BrSDi",
- "hClOjAOPCF4vubEUBiVkgTZNuk5wHhLC3BTjAI+qIW7kn4MGMhw7d/egNLVp1BFTV5XSForUGtAjOzrX",
- "D7Bt5lLLaOxG57GK1QYOjTyGpWh8jyyvAeMf3Db+V+/RHS4Oferunt8lUdkBokXEPkAuQqsIu3EU7wgg",
- "wrSIJsIRpkc5TejwfGasqirHLWxWy6bfGJouqPWZ/altOyQucnLQvV0oMOhA8e095NeEWYrfXnPDPBzB",
- "xY7mHIrXGsLsDmNmhMwh20f5qOK5VvEROHhI62qleQFZASXfJYID6DOjz/sGwB1v1V1lIaNA3PSmt5Qc",
- "4h73DK1wPJMSHhl+Ybk7gk4VaAnE9z4wcgE4doo5eTq61wyFcyW3KIyHy6atToyIt+GVsm7HPT0gyJ6j",
- "TwF4BA/N0DdHBXbOWt2zP8V/g/ETNHLE8ZPswIwtoR3/qAWM2IL9G6fovPTYe48DJ9nmKBs7wEfGjuyI",
- "YfoV11bkokJd5zvY3bnq158g6ThnBVguSihY9IHUwCruzyiEtD/mzVTBSba3IfgD41tiOSFMpwv8JexQ",
- "535FbxMiU8dd6LKJUd39xCVDQEPEsxPB4yaw5bktd05Qs2vYsWvQwEy9oBCGoT/FqiqLB0j6Z/bM6L2z",
- "Sd/oXnfxBQ4VLS8Va0Y6wX743vQUgw46vC5QKVVOsJANkJGEYFLsCKuU23Xhnz+FBzCBkjpAeqaNrvnm",
- "+r9nOmjGFbD/VjXLuUSVq7bQyDRKo6CAAqSbwYlgzZw+OLHFEJSwAdIk8cvDh/2FP3zo91wYtoTr8GbQ",
- "Neyj4+FDtOO8UsZ2Dtcd2EPdcTtPXB/ouHIXn9dC+jzlcMSTH3nKTr7qDd54u9yZMsYTrlv+rRlA72Ru",
- "p6w9ppFp0V447iRfTjc+aLBu3PcLsalLbu/CawVXvMzUFWgtCjjIyf3EQsmvr3j5Y9MN30NC7mg0hyzH",
- "V3wTx4I3rg89/HPjCCncAaag/6kAwTn1uqBOB1TMNlJVbDZQCG6h3LFKQw703s1JjqZZ6gmjSPh8zeUK",
- "FQat6pUPbqVxkOHXhkwzupaDIZJCld3KDI3cqQvAh6mFJ49OnALuVLq+hZwUmGvezOdfuU65maM96HsM",
- "kk6y+WxU43VIvWo1XkJO993mhMugI+9F+GknnuhKQdQ52WeIr3hb3GFym/vrmOzboVNQDieOIn7bj2NB",
- "v07dLnd3IPTQQExDpcHgFRWbqQx9Vcv4jXYIFdwZC5uhJZ+6/nXk+L0e1ReVLIWEbKMk7JJpSYSE7/Fj",
- "8jjhNTnSGQWWsb59HaQDfw+s7jxTqPG2+MXd7p/QvsfKfKP0XblEacDJ4v0ED+RBd7uf8qZ+Ul6WCdei",
- "f8HZZwBm3gTrCs24MSoXKLOdF2buo4LJG+mfe3bR/6p5l3IHZ68/bs+HFicHQBsxlBXjLC8FWpCVNFbX",
- "uX0nOdqooqUmgriCMj5utXwemqTNpAkrph/qneQYwNdYrpIBG0tImGm+AQjGS1OvVmBsT9dZAryTvpWQ",
- "rJbC4lwbd1wyOi8VaIykOqGWG75jS0cTVrFfQCu2qG1X+scHysaKsvQOPTcNU8t3kltWAjeWfS/kmy0O",
- "F5z+4chKsNdKXzZYSN/uK5BghMnSwWbf0leM6/fLX/sYfwx3p88h6LTNmDBzy+wkSfnf9//j2duz7H94",
- "9suj7Mt/O33/4enHBw8HPz75+Oc//5/uT599/POD//jX1E4F2FPPZz3k5y+8Znz+AtWfKFS/D/sns/9v",
- "hMySRBZHc/Roi93HVBGegB50jWN2De+k3UpHSFe8FIXjLTchh/4NMziLdDp6VNPZiJ4xLKz1SKXiFlyG",
- "JZhMjzXeWIoaxmemH6qjU9K/PcfzsqwlbWWQvukdZogvU8t5k4yA8pQ9Y/hSfc1DkKf/88nnX8zm7Qvz",
- "5vtsPvNf3ycoWRTbVB6BArYpXTF+JHHPsIrvDNg090DYk6F0FNsRD7uBzQK0WYvq03MKY8UizeHCkyVv",
- "c9rKc0kB/u78oItz5z0navnp4bYaoIDKrlP5izqCGrZqdxOgF3ZSaXUFcs7ECZz0bT6F0xd9UF8JfBkC",
- "U7VSU7Sh5hwQoQWqiLAeL2SSYSVFP73nDf7yN3euDvmBU3D150xF9N779us37NQzTHOPUlrQ0FESgoQq",
- "7R9PdgKSHDeL35S9k+/kC1ii9UHJZ+9kwS0/XXAjcnNaG9Bf8ZLLHE5Wij0L7zFfcMvfyYGkNZpYMXo0",
- "zap6UYqcXcYKSUuelCxrOMK7d295uVLv3r0fxGYM1Qc/VZK/0ASZE4RVbTOf6ifTcM11yvdlmlQvODLl",
- "8to3KwnZqiYDaUgl5MdP8zxeVaaf8mG4/Koq3fIjMjQ+oYHbMmasat6jOQHFP+l1+/uD8heD5tfBrlIb",
- "MOxvG169FdK+Z9m7+tGjz/BlX5sD4W/+ync0uatgsnVlNCVF36iCCye1EmPVs4qvUi62d+/eWuAV7j7K",
- "yxu0cZQlw26dV4fhgQEO1S6geeI8ugEEx9GPg3FxF9QrpHVMLwE/4RZ2H2Dfar+i9/M33q4Db/B5bdeZ",
- "O9vJVRlH4mFnmmxvKydkhWgMI1aorfrEeAtg+RryS5+xDDaV3c073UPAjxc0A+sQhnLZ0QtDzKaEDooF",
- "sLoquBfFudz109oYelGBg76GS9i9UW0ypmPy2HTTqpixg4qUGkmXjljjY+vH6G++jyoLD019dhJ8vBnI",
- "4llDF6HP+EEmkfcODnGKKDppP8YQwXUCEUT8Iyi4wULdeLci/dTyhMxBWnEFGZRiJRapNLz/NfSHBVgd",
- "VfrMgz4KuRnQMLFkTpVf0MXq1XvN5Qrc9eyuVGV4SVlVk0EbqA+tgWu7AG732vllnJAiQIcq5TW+vEYL",
- "39wtAbZuv4VFi52Ea6dVoKGI2vjo5ZPx+DMCHIobwhO6t5rCyaiu61GXyDgYbuUGu41a60PzYjpDuOj7",
- "BjBlqbp2++KgUD7bJiV1ie6X2vAVjOgusfduYj6MjscPBzkkkSRlELXsixoDSSAJMjXO3JqTZxjcF3eI",
- "Uc3sBWSGmchB7H1GmETbI2xRogDbRK7S3nPd8aJSVuAx0NKsBbRsRcEARhcj8XFccxOOI+ZLDVx2knT2",
- "K6Z92Zea7jyKJYySojaJ58Jt2OegA73fJ6gLWelCKrpY6Z+QVs7pXvh8IbUdSqJoWkAJK1o4NQ6E0iZM",
- "ajfIwfHjcom8JUuFJUYG6kgA8HOA01weMka+ETZ5hBQZR2Bj4AMOzH5Q8dmUq2OAlD7hEw9j4xUR/Q3p",
- "h30UqO+EUVW5y1WM+BvzwAF8KopWsuhFVOMwTMg5c2zuipeOzXldvB1kkCENFYpePjQfevNgTNHY45qi",
- "K/+oNZGQcJPVxNJsADotau+BeKG2Gb1QTuoii+3C0Xvy7QK+l04dTMpFd8+whdpiOBdeLRQrfwCWcTgC",
- "GJHtZSsM0iv2G5OzCJh90+6Xc1NUaJBkvKG1IZcxQW/K1COy5Ri53I/Sy90IgJ4Zqq3V4M0SB80HXfFk",
- "eJm3t9q8TZsanoWljv/YEUru0gj+hvaxbkK4v7SJ/8aTi4UT9Uky4Q0tS7fJUEidK8o6eEyCwj45dIDY",
- "g9VXfTkwidZurFcXrxHWUqzEMd+hU3KINgMloBKcdUTT7DIVKeB0ecB7/CJ0i4x1uHtc7h5EAYQaVsJY",
- "aJ1GIS7otzDHc0yfrNRyfHW20ku3vtdKNZc/uc2xY2eZn3wFGIG/FNrYDD1uySW4Rt8YNCJ945qmJdBu",
- "iCIVGxBFmuPitJewywpR1ml69fN+98JN+0Nz0Zh6gbeYkBSgtcDiGMnA5T1TU2z73gW/pAW/5He23mmn",
- "wTV1E2tHLt05fifnosfA9rGDBAGmiGO4a6Mo3cMgowfnQ+4YSaNRTMvJPm/D4DAVYeyDUWrh2fvYzU8j",
- "JdcSpQFMvxBUqxUUIb1Z8IfJKIlcqeQqquJUVfty5p0wSl2Hmef2JK3zYfgwFoQfifuZkAVs09DHWgFC",
- "3r6sw4R7OMkKJKUrSZuFkqiJQ/yxRWSr+8S+0P4DgGQQ9JueM7uNTqZdarYTN6AEXnidxEBY3/5jOdwQ",
- "j7r5WPh0J/Pp/iOEAyJNCRsVNhmmIRhhwLyqRLHtOZ5o1FEjGD/KujwibSFr8YMdwEA3CDpJcJ1U2j7U",
- "2hvYT1HnPXVaGcVe+8BiR9889w/wi1qjB6MT2TzM297oahPX/t3PF1ZpvgLvhcoIpFsNgcs5Bg1RVnTD",
- "rKBwkkIslxB7X8xNPAcd4AY29mIC6SaILO2iqYW0XzxNkdEB6mlhPIyyNMUkaGHMJ/9m6OUKMn1kSmqu",
- "hGhrbuCqSj7X/w522c+8rJ2SIbRpw3O926l7+R6x61eb72CHIx+MenWAHdgVtDy9BqTBlKW/+WSiBNb3",
- "TCfFP6qXnS08YqfO0rt0R1vjizKME397y3SKFnSXcpuD0QZJOFim7MZFOjbBnR7oIr5Pyoc2QRSHZZBI",
- "3o+nEiaUsBxeRU0uikO0+wZ4GYgXlzP7OJ/dLhIgdZv5EQ/g+lVzgSbxjJGm5BnuBPYciXJeVVpd8TLz",
- "8RJjl79WV/7yx+YhvOITazJpyn7z9dnLVx78j/NZXgLXWWMJGF0Vtqt+N6uiMg77rxLK9u0NnWQpija/",
- "ycgcx1hcY2bvnrFpUBSljZ+JjqKPuVimA94P8j4f6kNL3BPyA1UT8dP6PCngpxvkw6+4KIOzMUA7EpyO",
- "i5tWWSfJFeIBbh0sFMV8ZXfKbganO306Wuo6wJNwrh8xNWVa45A+cSWyIh/8w+9cevpG6Q7z9y8Tk8FD",
- "v55Y5YRswuNIrHaoX9kXpk4YCV5/W/3NncaHD+Oj9vDhnP2t9B8iAPH3hf8d9YuHD5Pew6QZyzEJtFJJ",
- "voEHzSuL0Y34tAq4hOtpF/TZ1aaRLNU4GTYUSlFAAd3XHnvXWnh8Fv6XAkpwP51MUdLjTSd0x8BMOUEX",
- "Yy8RmyDTDZXMNEzJfkw1PoJ1pIXM3pdkIGfs8AjJeoMOzMyUIk+HdsiFcexVUjCla8yw8Yi11o1Yi5HY",
- "XFmLaCzXbErO1B6Q0RxJZJpk2tYWdwvlj3ctxT9qYKJwWs1SgMZ7rXfVBeUARx0IpGm7mB+Y/FTt8Lex",
- "g+zxNwVb0D4jyF7/3YvGpxQWmir6c2QEeDzjgHHvid729OGpmV6zrbshmNP0mCml0wOj8866kTmSpdCF",
- "yZZa/QJpRwj6jxKJMILjU6CZ9xeQqci9PktpnMptRfd29kPbPV03Htv4W+vCYdFN1bGbXKbpU33cRt5E",
- "6TXpdM0eyWNKWBxh0H0aMMJa8HhFwbBYBiVEH3FJ54myQHRemKVPZfyW85TGb0+lh3nw/rXk1wueqhHj",
- "dCEHU7S9nTgpq1joHDbANDkOaHYWRXA3bQVlkqtAtz6IYVbaG+o1NO1kjaZVYJCiYtVlTmEKpVGJYWp5",
- "zSVVEXf9iF/53gbIBe96XSuNeSBNOqSrgFxskubYd+/eFvkwfKcQK0EFsmsDUQVmPxCjZJNIRb6KdZO5",
- "w6PmfMkezaMy8H43CnEljFiUgC0eU4sFN3hdNu7wpotbHki7Ntj8yYTm61oWGgq7NoRYo1ije6KQ1wQm",
- "LsBeA0j2CNs9/pLdx5BMI67ggcOiF4Jmzx5/iQE19Mej1C3rC5zvY9kF8uwQrJ2mY4xJpTEck/SjpqOv",
- "lxrgFxi/HfacJuo65SxhS3+hHD5LGy75CtLvMzYHYKK+uJvozu/hRZI3AIzVaseETc8Pljv+NPLm27E/",
- "AoPlarMRduMD94zaOHpqyyvTpGE4qvXv60UFuMJHjH+tQvhfz9b1idUYvhl5s4VRyj+gjzZG65xxSv5Z",
- "ijYyPdTrZOchtzAW0GrqZhFu3Fxu6ShLYqD6klVaSIv2j9ousz85tVjz3LG/kzFws8UXTxOFqLq1WuRx",
- "gH9yvGswoK/SqNcjZB9kFt+X3ZdKZhvHUYoHbY6F6FSOBuqmQzLH4kL3Dz1V8nWjZKPkVnfIjUec+laE",
- "J/cMeEtSbNZzFD0evbJPTpm1TpMHr90O/fT6pZcyNkqnCga0x91LHBqsFnCFL+bSm+TGvOVe6HLSLtwG",
- "+t82/imInJFYFs5yUhGIPJr7Hss7Kf7n79vM5+hYpZeIPRug0glrp7fbfeJow+Osbn3/LQWM4bcRzE1G",
- "G44yxMpI9D2F1zd9fot4oT5ItOcdg+PjvzHtdHCU4x8+RKAfPpx7MfhvT7qfib0/fJhOQJw0ublfWyzc",
- "RiPGvqk9/EolDGChamETUOTzIyQMkGOXlPvgmODCDzVn3Qpxn16KuJv3Xelo0/QpePfuLX4JeMA/+oj4",
- "jZklbmD7SmH8sHcrZCZJpmi+R3HunH2ltlMJp3cHBeL5J0DRCEommudwJYMKoEl3/cF4kYhG3agLKJVT",
- "MuOiQLE9//eDZ7f4+R5s16Isfm5zu/UuEs1lvk5GCS9cx7+SjN65golVJuuMrLmUUCaHI932r0EHTmjp",
- "f1dT59kIObFtvwItLbe3uBbwLpgBqDChQ6+wpZsgxmo3bVaTlqFcqYLhPG1Ri5Y5Dks5p0poJt4347Cb",
- "2vq4VXwL7hMOLUWJYZhpvzG2zDS3Iwm0sN55qC/kxsHy44bMDDQ6aMbFBi9mwzdVCXgyr0DzFXZVEnrd",
- "MYUajhxVrGCmcp+wJSasUMzWWjK1XEbLAGmFhnI3ZxU3hgZ55JYFW5x79uzxo0dJsxdiZ8JKCYthmT+2",
- "S3l8ik3oiy+yRKUAjgL2MKwfW4o6ZmOHhONrSv6jBmNTPBU/0MtV9JK6W5vqSTa1T0/Yt5j5yBFxJ9U9",
- "mitDEuFuQs26KhUv5pjc+M3XZy8ZzUp9qIQ81bNcobWuS/5J98r0BKMhs9NI5pzp4+xP5eFWbWzWlJ9M",
- "5SZ0LdoCmaIXc4N2vBg7J+wFmVCbAv40CcMU2XoDRVTtkpR4JA73H2t5vkbbZEcCGueV0wuxBnbWem6i",
- "14dN9SNk2A5uX4uVSrHOmbJr0NfCAL7IhyvopkNscoN623hIj9hdnq6lJEo5OUIYbWodHYv2ABxJsiGo",
- "IAlZD/FHWqaoHvOxdWkvsFf6LUavyG3P6x+S64UU2+x771zIuVRS5FgKISVJY+q2aW7KCVUj0v5FM/Mn",
- "NHG4kqV1m7fAHoujxXYDI/SIG7r8o69uU4k66E8LW19ybQXWeM4GxTxUuvYOMSEN+GpWjohiPql0Iqgp",
- "+RCiCaA4kowwK9OIhfMb9+0Hb//GpBiXQqKly6PN62fksiqNQM+0ZMKylQLj19N9zWPeuj4nmKWxgO37",
- "k5dqJfILscIxKIzOLZtiRodDnYUIUh+x6do+d2197vzm5044GE16VlV+0vE66ElB0m7lKIJTcUshkCRC",
- "bjN+PNoectsb+o33qSM0uMKoNajwHh4QRlNLuzvK1063JIrCFoxeVCYT6AqZAOOlkMGFmr4g8uSVgBuD",
- "53Wkn8k1t6Q7TOJpb4CXIw8g8IUy+eBvO1S/coBDCa4xzDG+jW0Z8BHG0TRoJX4udywcCkfdkTDxnJdN",
- "6HSiqDdKVV6IKvBxUa/Md4pxOMadhSeTHXQdfL7XdMdqHMfeRGM5Chd1sQKb8aJIpbb6Cr8y/BoeicEW",
- "8ropQtW8DuzmKB9Sm58oV9LUmz1zhQa3nC6qm5+ghrh2f9hhzLSz2OG/qQpM4zvjg6aPfpUbIqSL4xLz",
- "D18Zp6ReR9OZEatsOibwTrk9Otqpb0bobf87pfTwXPef4jVuj8vFe5Tib1+7iyNO3DuIT6erpcmri7Hg",
- "Cr+HhEdNRsguV8KrbFBnDKMecPMSW9YDPjRMAn7Fy5GX8LGvhO5X8h+MvYfPR9M3cOvTc1nO9rKg0ZRH",
- "FCvc874MXYhj8cEUHnx3Xgu/1r0IHffdfdfx1FGMWMssRj10N3OitRt8rBftu6uxFAmhTgd+j+uB+Cie",
- "uU8DD1dC1SH6KsRAB5WQfvUpeDp1P0bWn3xZ8Ft7LUZ9LG98/VpaptfJv/uZvLAMpNW7fwKPy2DT+0Vl",
- "EtIumafaJqwpfTipFGLnVpxSwyZVLsXLhsFWRqylQ0uD8jMDsnoxRRwY4OPjfHZeHHVhpkruzGiU1LF7",
- "KVZrixn7/wK8AP3qQEWCtgoBHrFKGdFWIC3dYD4F7BqHO5n62MARsIgrKgzHCkGoV5BbLDvbBtdpgGPq",
- "K7jJgtPnj8oE4+p08ybDFyTYV4VgWGv2wB0/SJwUJf+iOp0n03PunzUh1PQC7JqbNl1L78305JebyyXk",
- "mBV5b6Kq/1qDjJIgzYNdBmFZRnmrRPOOCfN6H291bAHal0dqLzxRfZ1bgzP2jv0SdvcM61BDsnBo84jv",
- "JomDEQPkAgs5pMcMyT5qTJiGMhALISTYp2Jui2OM5nyO0q7dcK5Aku7iaFOx7ZkyXfR80lyu61FpH/FJ",
- "zlguq2HN5HH94wWWqDY+QI43iYdjLZ2dDwvnXPvExZhWrPGdhBTGYMJvIYcgzVKKS18/ALFCnqprrovQ",
- "4k6SQtHdJNJAL5uZRfuAYxjkkCjFgG+h8lI5MSIbe1DWfTPRBBzeMxQZ2ibwQbiWoDUUjUukVAYyq8KD",
- "j31w7EMFhb/eCAlmtPwRATea+vp1m9sby8BxTHXNfdRrvECmYcMddDrKwD0+5z5kP6fv4RF+KAN20MLU",
- "0OvherTh6Y4wAyTGVL9k/rY8/Lj/JsYmISXoLHie+um4ZTcjG+bdLOqcLuj4YDQGucm5c/awkqSdJh+u",
- "sqcjRI/kL2F3SkpQKOQbdjAGmiQnAj1KONrb5Ds1v5kU3Ks7Ae+3zSNXKVVmI86O82EO8T7FX4r8EjAH",
- "YBPiPlKjnd1HG3vjzb5e70LO7KoCCcWDE8bOJD0qCo7tbnnB3uTynt03/xZnLWpK6++NaifvZPp1Bibc",
- "17fkZmGY/TzMgGN1t5yKBjmQoXorx0JurjE5f7eK58lUrXzoau5XkW+JiqBIySQX5LF6jgc9ZTjCFAhR",
- "rg50ZHLmPV3MlCoVy3uTNA1uqDSm4skQIAtySraABgo/eBIBybroiVNIqe980ju1ZBpaJ/JNs/8NS7in",
- "NPr+zM0sXX63VBo6xdhdb8r02Tx8wTSa+J+FsJrr3U1y9A1KyA+sJ6NYPhiO1URitQtpo7GGOCxLdZ0h",
- "s8qaOhcp1da1M93LOBRda/u5U72AKK6LGy+o7diaFyxXWkMe90i/9ySoNkpDVioM80p5oJfWyd0bfOQl",
- "WalWTFW5KoDqxaQpaGyuWkqOYhNEUTVJFBDt4Gth6hPR8cQp3Z1KfqQMRa3VEbXzc6CX621WJ1p0Rr7M",
- "kYhlMD6Lk8cQNR7Cu6f2f5o3L8UW6QZ06sgvmdU1zJlv0a+R7Q8+18A2whgCpaGla1GW+HBcbCPPaxO4",
- "kEbtiNh7jmGVVwJjb7pJBEgartyd12RWiHnARZz2iNm1VvVqHSWYbuAMKq+uvUIcj/KTqTE8Cl+QuSme",
- "so0y1muaNFK75Dbk7H6upNWqLLtGKRLRV97S/j3fnuW5fanU5YLnlw9Qr5XKNist5uF9dT84sJ1J91KL",
- "dS/gjMqZH07VS+0wVM4T7WQG2WNxRxd2j8B8f5iDHra5nw0X1l9Xl5mm1ZgzybhVG5Gnz9TvK9puNEYu",
- "xaKSOcuotiJlmcBmeNjjy6oJrkAWOUQzSJ4sDnfGPCPwTmZkN+6/KIH3x2VL8Ixm5KIcMhcvRWX5qKzX",
- "AwAhpafPttZUkDGWxBquolaUKgFd5H1AJ94qGIl0O9jcCHcOlIVbATWIfmwAvE/GhznllqNIyoXahu8P",
- "2uRzNwL+434q7zCPsRCvi5a0NAV5hUQ1IxwhneJ6bzzUG3z2vpgaFdUUz514w0cAjMdJdWCYFC11LBhL",
- "LkooslTtxfPGRjWPNG3/NKtfEl0Yz8lzXofSh27sWoNPnEIivu76vyruSEk1zYeWZFnAFuhdxy+gFdU0",
- "nEf+Fyip5GHPGKCqrIQr6ISP+WwuNYqa4gpCX9N0ZgVAhd7Ivo0sFRcV3+U9w4lfexZF1kzBbtKSQoil",
- "nWIHzCRJo85WZnRMzNSj5CC6EkXNO/gzx4ocXTOgO8oJVA10hCzokVOn+YlGeB0GOAv9U6JMwMT7aXzo",
- "aBaURt0+BnQwTrI2Y6depsMk41RFjYMFZysaRyyReMs3TMWv5bhBckjyrbo1cZ+EkhFiv95CjlKN13eg",
- "8BrPiJPCZz1BapcABWkFrkvC2r4GyaSKSkxec9OoKm0OxfADTYyNhPTa9A2cym004+13luFgzPSSqY0q",
- "Erqh05ub53+Tk7j3II6Ol6IRA/753x77V6Bur3ZgAyzlLd1+OtkfizT6W8xz8Tlb1GGgslTXVDMy1kNf",
- "QPCDEvUFF5AXy0VzLYeozblP79k3dYgoXn3Dd0xp/Mdpnf+oeSmWO+QzBH7oxsyaOxLyjleKCPBRoG7i",
- "/eLVPAAWrC0qTEXrFlPHjIbbuVEioN1FHor7KLbhlxBvAwY7EP/MrWOcpl6g5cJd2b3tHGLBLz6kaNnw",
- "Itb0MVFkt4x6SB3sev9/7Vu4eKqQ360qeR4qhPoSRV0+g1WAA3HZNWz2P5Yc8rVAAk1l4ZZodXhdX9zA",
- "ZHok60q9QBgrv9IBe1BxdVB55lbLmGj57dXY2PPMdNJS7noXpkbdDICO6zQeAj8uW/lp8J/M4Tq2jCng",
- "/7PgfaRQbQwv1aT9BFjuZOBIwErW6oXaZhqW5lCACZmrnTqv29wdwcQqZK6BG4q4Of/RK55tilIhnSJM",
- "MaGNT7MZpYClkC2zFLKqbUKPwUylchchLDb6I1pHXGhjUoITJq94+eMVaC2KsY1zp4NKOsYlIoKjw/dN",
- "mDCaO3U4gDCtDofvM1szetzMXeBUhIrCNY3lsuC6iJsLyXLQ7t5n13xnbu5RapwDh3xKPJJmulkDIu8S",
- "kjYBUu68U/iW/p4GQH6Hjp8JDhuMC044a8i0Y9WIf2YIw+/CYbPh26xUK3xFOHIgfG5a9PCRCqgkmsFJ",
- "Ppu27jCPEb/A/mkwLb9nRFbhrFOm2H/uf8StRDXyJyns3pNPNsr+s06Ku6WDGZAqV23wPxHL8DymXuL6",
- "5Cvxa9wgbIanKoH2INpEGPEPde3iI7uIYRD+GXdsBJ9e7qwbaZF670uWgQwtBmZPeD+YNpSd5z48a2hK",
- "G5gaCClz/1r6SEsb2efDvTQCHtWm92e9O20TMuPGOaZG3P730VmlqiyfEvNJlTsK7ybwkHZhHKGPyAkw",
- "su4mPMY0tWw6eY86RW2OLZM3WlTnkLeryvcp/WNmohGO3nVBqCXyMqrcjtYtfMnTGFPm/TdmXTNYwyQY",
- "ZxryWqOZ+JrvDpcdG8kYffGXs88fP/nrk8+/YK4BK8QKTJt1vFe2q40LFLJv9/m0kYCD5dn0JoTsA4S4",
- "4H8Mj6qaTfFnjbitaVOKDoqWHWNfTlwAieOYKBd1o73CcdrQ/n+u7Uot8s53LIWCX3/PtCrLdNWHRq5K",
- "OFBSuxW5UJwGUoE2wljHCLseUGHbiGizRvMg5v69omwySuYQ7MeeCoQdCblKLWQsoBb5Gb7t9l4jBtuq",
- "9LyKPD371uX1NLLQodCIUTELYJWqvGgvliwFEb4g0tHLWm/4RIt4FCPbMFuKlk0Roo88T5NeXDB7P7fv",
- "FnO1aU7vNjEhXoRDeQPSHPNPjOctuAknaU37/zT8I5GI4c64RrPcX4NXJPWDmxXlnwTa8FF+gjwQgJHX",
- "tp13ktFDsSgRsSYvAfoTggO5L3583zqWDz4LQUhChwPgxc9n23bNSwYPzm+c0ff7BinRUt6PUUJn+Yde",
- "5AbW21wk0RZ5o4m1YIgtqaFYGD23Ns+bV8wjWsngsbNWyjKnmZZl4pE02XHwTMWE41QCfcXLT881vhHa",
- "2DPEBxSvx59GxS9lYyQTKs3N8vS95JPmjl7F3t3U8hU+zP4vcHuUvOf8UN4JP7jN0LiDFetX4Vagt97s",
- "GsekIKvHX7CFL7ZRaciF6Tv3r4Nw0jwMBS2WPqAVtvbAS9RD6/xZ2VuQ8TJE4rAfIvdW47P3ELZH9Ddm",
- "KiMnN0nlKeobkEUCfykeFRfnPXBd3LIww83SvkQJ3I5M+zIsOzx1eZTaxF06tYHhOiff1h3cJi7qdm1T",
- "cxZNru/w7t1bu5iSaihdi8F1x1xHd1KU4aiSDL9CliPCkR/Dz5uimJ/H8t5SbteR3Ny9/ahFeTBgpZNp",
- "/eN8tgIJRhjMJf5XXzvm096lAQLKvDA8qgTrbdLFEGISa+1MHk0V5VCfkD7dd0vkvMZXjXmthd1h3eBg",
- "QBN/TeZj+rbJ7eFzwzS+NH/3WXUJTe32NhNIbcLt+q3iJd5H5OKT7hZS5Qn7mjJ8+4Py53uLf4fP/vS0",
- "ePTZ439f/OnR549yePr5l48e8S+f8sdffvYYnvzp86eP4PHyiy8XT4onT58snj55+sXnX+afPX28ePrF",
- "l/9+z/EhBzIBGlL7P5v9r+ysXKns7NV59sYB2+KEV+I7cHuDuvJSYV1Lh9QcTyJsuChnz8JP/384YSe5",
- "2rTDh19nvj7TbG1tZZ6dnl5fX5/EXU5X+PQ/s6rO16dhHqw22JFXXp03MfoUh4M72lqPcVM9KZzht9df",
- "X7xhZ6/OT1qCmT2bPTp5dPLYl7aWvBKzZ7PP8Cc8PWvc91PMr3lqfOr80/atVtJv9xpD1oNwrldQsPvN",
- "q5t/azy35kF4vLMUJV4ZfzdEjM0qzgskLl+jdIZV1zAYC8F68uhR2Asv6UQXzim+/nj2YdbWtu8LEwOk",
- "vmkBTkLW1nwcLvoneSnVtWSYDJAOUL3ZcL2jFXSwEQ2O28RXBo3sWlxxC7P3rncf51XlCxaMoRyrXHVP",
- "eeiMBNJkvHcnjBLh+7IDJoXyYbGEW2J/b3LIwWSJ3cFGrxzMIX1Ok1DRO4Q8ztBnTAhrzgiZHQaIns+q",
- "OoHOr/FhjdmHs3mUhJ+gUWXRYHyA0Vf1/yMYdaTr76bZsw/urzXwEhNruT82jlDz8EkDL3b+/+aar1ag",
- "T/w63U9XT06DFnL6wWdM+bjv22kcEXb6oZNYpjjQM0Q8HWpy+iGUzN4/YKdcso81jTpMBHRfs9MFlsma",
- "2hTi1Y0vBWnenH5ABXz091NvRU1/REMI3bCnIUHTSEtKxZH+2EHhB7t1C9k/nGsTjZdzm6/r6vQD/gfJ",
- "NloRZfY9tVt5ioEjpx86iPCfB4jo/t52j1tcbVQBATi1XFKd8X2fTz/Qv9FEsK1AC6eFYjYt/ytlPTzF",
- "cpO74c87mSd/HK6jk/HtwGWO2QRNiKbqJopLXh/97HPmtsxuWi6bfs67oYA9lKT2rezjfPb0DrlyN1Nw",
- "ApiveMFCYgOc+/Gnm/tcUlS3Ey1JBEYInn46CDrbx76DHftBWfYN2mo+zmeff8qdOJdOc+RlEOhuKPpN",
- "Oz79a9TJ3k0zuSJBRVGyi+5ROyuKAdGTDgnGfqXwdh3D2MasKu+lbZHWqtBCuiXMp4nNw/SRlOQsCBJS",
- "FTCLlVura/h4S57Qi+/i2p4nTMroG8GHHstQoD8CNZkLsR/9QiMPzR+HSPj8RZi0fR/xB0/5g6c0POXz",
- "R599uukvQF+JHNgb2FRKcy3KHftJNg9vbszjzooimUC2e/QP8rj5bJvlqoAVyMwzsGyhip2vyDPrTHAJ",
- "ZC0bCDKnwbrU0RhGuGewW6WklTYcfPbsbSoswj9vrOpFKXJGlnU0LVXcriPLT5PRs8v85nssE/NE1nhW",
- "iLJusjnYa+VfSw8vlMjaYhUz/9B48eBBFHbHroUs1PWDkwDuP2pAPu/hDdPMEgBGMb7DAkmtw9ABOABr",
- "bD70NE7Bzp7JX/KbzV3yY6d+/2vboJqMef958eMP0WtEsjRQQBC+hSPSxYcLWmFA/jXHiFAqpPicbEDl",
- "Dl/VWm5r06nhdvLHPfQH77897/+2SaFM1dsslmUasqToLjiZJPAmefuHzp/ebjGjcOxU4mP3O+NshZU3",
- "hxfUYsfOXwy0V+rWvxK+2mHT3q2Q4Pd9EI9i/CPsZZ9I4xayUrYJSqdF/SFk/iFk3kpxnXx4puiuScsS",
- "1cPlA31sHkrbdh7+YPJyDPEagDLF/vSbHt872fihbStly6Ik61Cw6ANlp+ij+Q8W8QeLuB2L+BYShxFP",
- "rWcaCaI7ztY1lWFgKqWiE2IZpI7QvC65jh4EHzJhn+GIaVXwV+Ean9pgl8QV2evw3YCggNnEBt6tDe8P",
- "lvcHy/v9sLyzw4ymK5jc2up1CbsNrxpbl1nXtlDXkYccYaFg96GPjxT//t+n11zYbKm0L9nDlxb0sLMF",
- "Xp76+ty9X9uSmIMvWOcz+jFORpf89ZR3nZZdx7ljvWMdB1711FfvOB5pFHIohM9tzF4cA4dsv4l+e/ve",
- "sWwD+ircCG1I17PTU0yqs1bGns4+zj/0wr3ij+8b8vjQ3COeTD4iXSgtVkLyMvOxEVkbtvXk5NHs4/8N",
- "AAD//1nr4yEUHQEA",
+ "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",
+ "GB8BAA==",
}
// 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 37794f0d92..878f514137 100644
--- a/daemon/algod/api/server/v2/generated/participating/public/routes.go
+++ b/daemon/algod/api/server/v2/generated/participating/public/routes.go
@@ -177,238 +177,240 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
+ "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",
- "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",
+ "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",
- "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",
+ "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",
- "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",
+ "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",
- "+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",
+ "+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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 910394587a..b6d702d008 100644
--- a/daemon/algod/api/server/v2/handlers.go
+++ b/daemon/algod/api/server/v2/handlers.go
@@ -27,6 +27,7 @@ import (
"net/http"
"os"
"runtime"
+ "slices"
"strconv"
"strings"
"sync/atomic"
@@ -103,7 +104,7 @@ type LedgerForAPI interface {
LookupAccount(round basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, basics.MicroAlgos, error)
LookupLatest(addr basics.Address) (basics.AccountData, basics.Round, basics.MicroAlgos, error)
LookupKv(round basics.Round, key string) ([]byte, error)
- LookupKeysByPrefix(round basics.Round, keyPrefix string, maxKeyNum uint64) ([]string, error)
+ LookupKeysByPrefix(prefix, next string, boxLimit, byteLimit int, values bool) (basics.Round, map[string]string, string, error)
ConsensusParams(r basics.Round) (config.ConsensusParams, error)
Latest() basics.Round
LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error)
@@ -117,7 +118,7 @@ type LedgerForAPI interface {
GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error)
EncodedBlockCert(rnd basics.Round) (blk []byte, cert []byte, err error)
Block(rnd basics.Round) (blk bookkeeping.Block, err error)
- AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error)
+ TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error)
GetStateDeltaForRound(rnd basics.Round) (ledgercore.StateDelta, error)
GetTracer() logic.EvalTracer
}
@@ -228,18 +229,18 @@ func GetStateProofTransactionForRound(ctx context.Context, txnFetcher LedgerForA
default:
}
- txns, err := txnFetcher.AddressTxns(transactions.StateProofSender, i)
+ txns, err := txnFetcher.TxnsFrom(transactions.StateProofSender, i)
if err != nil {
return transactions.Transaction{}, err
}
for _, txn := range txns {
- if txn.Txn.Type != protocol.StateProofTx {
+ if txn.Type != protocol.StateProofTx {
continue
}
- if txn.Txn.StateProofTxnFields.Message.FirstAttestedRound <= uint64(round) &&
- uint64(round) <= txn.Txn.StateProofTxnFields.Message.LastAttestedRound {
- return txn.Txn, nil
+ if txn.StateProofTxnFields.Message.FirstAttestedRound <= uint64(round) &&
+ uint64(round) <= txn.StateProofTxnFields.Message.LastAttestedRound {
+ return txn, nil
}
}
}
@@ -448,7 +449,7 @@ func (v2 *Handlers) AccountInformation(ctx echo.Context, address string, params
totalResults := record.TotalAssets + record.TotalAssetParams + record.TotalAppLocalStates + record.TotalAppParams
if totalResults > maxResults {
v2.Log.Infof("MaxAccountAPIResults limit %d exceeded, total results %d", maxResults, totalResults)
- extraData := map[string]interface{}{
+ extraData := map[string]any{
"max-results": maxResults,
"total-assets-opted-in": record.TotalAssets,
"total-created-assets": record.TotalAssetParams,
@@ -1573,7 +1574,8 @@ func (v2 *Handlers) PendingTransactionInformation(ctx echo.Context, txid string,
response.CloseRewards = &txn.ApplyData.CloseRewards.Raw
response.AssetIndex = computeAssetIndexFromTxn(txn, v2.Node.LedgerForAPI())
response.ApplicationIndex = computeAppIndexFromTxn(txn, v2.Node.LedgerForAPI())
- response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(txn)
+ response.LocalStateDelta = sliceOrNil(localDeltasToLocalDeltas(txn.ApplyData.EvalDelta, &txn.Txn.Txn))
+ response.GlobalStateDelta = sliceOrNil(globalDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta))
response.Logs = convertLogs(txn)
response.Inners = convertInners(&txn)
}
@@ -1622,9 +1624,6 @@ func (v2 *Handlers) getPendingTransactions(ctx echo.Context, max *uint64, format
return internalError(ctx, err, errFailedLookingUpTransactionPool, v2.Log)
}
- // MatchAddress uses this to check FeeSink, we don't care about that here.
- spec := transactions.SpecialAddresses{}
-
txnLimit := uint64(math.MaxUint64)
if max != nil && *max != 0 {
txnLimit = *max
@@ -1639,7 +1638,7 @@ func (v2 *Handlers) getPendingTransactions(ctx echo.Context, max *uint64, format
}
// continue if we have an address filter and the address doesn't match the transaction.
- if addrPtr != nil && !txn.Txn.MatchAddress(*addrPtr, spec) {
+ if addrPtr != nil && !txn.Txn.MatchAddress(*addrPtr) {
continue
}
@@ -1753,62 +1752,93 @@ func (v2 *Handlers) GetApplicationByID(ctx echo.Context, applicationID uint64) e
return ctx.JSON(http.StatusOK, response)
}
-func applicationBoxesMaxKeys(requestedMax uint64, algodMax uint64) uint64 {
- if requestedMax == 0 {
- if algodMax == 0 {
- return math.MaxUint64 // unlimited results when both requested and algod max are 0
- }
- return algodMax + 1 // API limit dominates. Increments by 1 to test if more than max supported results exist.
+func applicationBoxesMaxKeys(requestedMax uint64, algodMax uint64) int {
+ if requestedMax == 0 || requestedMax > math.MaxInt {
+ requestedMax = math.MaxInt
}
-
- if requestedMax <= algodMax || algodMax == 0 {
- return requestedMax // requested limit dominates
+ if algodMax == 0 || algodMax > math.MaxInt {
+ algodMax = math.MaxInt
}
-
- return algodMax + 1 // API limit dominates. Increments by 1 to test if more than max supported results exist.
+ return int(min(requestedMax, algodMax))
}
-// GetApplicationBoxes returns the box names of an application
+// GetApplicationBoxes returns the boxes of an application
// (GET /v2/applications/{application-id}/boxes)
func (v2 *Handlers) GetApplicationBoxes(ctx echo.Context, applicationID uint64, params model.GetApplicationBoxesParams) error {
appIdx := basics.AppIndex(applicationID)
ledger := v2.Node.LedgerForAPI()
- lastRound := ledger.Latest()
- keyPrefix := apps.MakeBoxKey(uint64(appIdx), "")
requestedMax, algodMax := nilToZero(params.Max), v2.Node.Config().MaxAPIBoxPerApplication
max := applicationBoxesMaxKeys(requestedMax, algodMax)
- if max != math.MaxUint64 {
- record, _, _, err := ledger.LookupAccount(ledger.Latest(), appIdx.Address())
+ values := nilToZero(params.Values)
+
+ // We'll need to convert between "KV" names and "Box" names, so keep track
+ // of how much gets tacked on to make the kv name.
+ kvPrefix := apps.MakeBoxKey(uint64(appIdx), "")
+ kvPrefixLen := len(kvPrefix)
+
+ prefix := nilToZero(params.Prefix)
+ if len(prefix) > 0 {
+ cb, err := apps.NewAppCallBytes(prefix)
if err != nil {
- return internalError(ctx, err, errFailedLookingUpLedger, v2.Log)
+ return badRequest(ctx, err, err.Error(), v2.Log)
+ }
+ rawPrefix, err := cb.Raw()
+ if err != nil {
+ return badRequest(ctx, err, err.Error(), v2.Log)
+ }
+ prefix = string(rawPrefix)
+ }
+
+ next := nilToZero(params.Next)
+ if len(next) > 0 {
+ cb, err := apps.NewAppCallBytes(next)
+ if err != nil {
+ return badRequest(ctx, err, err.Error(), v2.Log)
+ }
+ rawNext, err := cb.Raw()
+ if err != nil {
+ return badRequest(ctx, err, err.Error(), v2.Log)
}
- if record.TotalBoxes > max {
+ next = kvPrefix + string(rawNext)
+ }
+
+ round, boxes, nextToken, err := ledger.LookupKeysByPrefix(kvPrefix+prefix, next, max, 1_000_000, values)
+ if err != nil {
+ return internalError(ctx, err, errFailedLookingUpLedger, v2.Log)
+ }
+
+ if nextToken != "" {
+ // Preserve existing failure behavior if caller is not using `next`.
+ if params.Next == nil {
return ctx.JSON(http.StatusBadRequest, model.ErrorResponse{
Message: "Result limit exceeded",
- Data: &map[string]interface{}{
+ Data: &map[string]any{
"max-api-box-per-application": algodMax,
"max": requestedMax,
- "total-boxes": record.TotalBoxes,
},
})
}
+ nextToken = nextToken[kvPrefixLen:]
}
- boxKeys, err := ledger.LookupKeysByPrefix(lastRound, keyPrefix, math.MaxUint64)
- if err != nil {
- return internalError(ctx, err, errFailedLookingUpLedger, v2.Log)
- }
-
- prefixLen := len(keyPrefix)
- responseBoxes := make([]model.BoxDescriptor, len(boxKeys))
- for i, boxKey := range boxKeys {
- responseBoxes[i] = model.BoxDescriptor{
- Name: []byte(boxKey[prefixLen:]),
+ responseBoxes := make([]model.Box, 0, len(boxes))
+ for key, value := range boxes {
+ box := model.Box{Name: []byte(key[kvPrefixLen:])}
+ if values {
+ box.Value = []byte(value)
}
+ responseBoxes = append(responseBoxes, box)
+ }
+ slices.SortFunc(responseBoxes, func(a, b model.Box) int {
+ return bytes.Compare(a.Name, b.Name)
+ })
+ response := model.BoxesResponse{
+ Round: uint64(round),
+ Boxes: responseBoxes,
+ NextToken: omitEmpty(nextToken),
}
- response := model.BoxesResponse{Boxes: responseBoxes}
return ctx.JSON(http.StatusOK, response)
}
@@ -1838,7 +1868,7 @@ func (v2 *Handlers) GetApplicationBoxByName(ctx echo.Context, applicationID uint
}
response := model.BoxResponse{
- Round: uint64(lastRound),
+ Round: omitEmpty(uint64(lastRound)),
Name: boxName,
Value: value,
}
diff --git a/daemon/algod/api/server/v2/handlers_test.go b/daemon/algod/api/server/v2/handlers_test.go
index debeffbb7a..96ef2e290c 100644
--- a/daemon/algod/api/server/v2/handlers_test.go
+++ b/daemon/algod/api/server/v2/handlers_test.go
@@ -34,15 +34,15 @@ func TestApplicationBoxesMaxKeys(t *testing.T) {
t.Parallel()
// Response size limited by request supplied value.
- require.Equal(t, uint64(5), applicationBoxesMaxKeys(5, 7))
- require.Equal(t, uint64(5), applicationBoxesMaxKeys(5, 0))
+ require.Equal(t, 5, applicationBoxesMaxKeys(5, 7))
+ require.Equal(t, 5, applicationBoxesMaxKeys(5, 0))
// Response size limited by algod max.
- require.Equal(t, uint64(2), applicationBoxesMaxKeys(5, 1))
- require.Equal(t, uint64(2), applicationBoxesMaxKeys(0, 1))
+ require.Equal(t, 1, applicationBoxesMaxKeys(5, 1))
+ require.Equal(t, 1, applicationBoxesMaxKeys(0, 1))
// Response size _not_ limited
- require.Equal(t, uint64(math.MaxUint64), applicationBoxesMaxKeys(0, 0))
+ require.Equal(t, math.MaxInt, applicationBoxesMaxKeys(0, 0))
}
type tagNode struct {
diff --git a/daemon/algod/api/server/v2/test/genesis_types_test.go b/daemon/algod/api/server/v2/test/genesis_types_test.go
new file mode 100644
index 0000000000..bab000e3fb
--- /dev/null
+++ b/daemon/algod/api/server/v2/test/genesis_types_test.go
@@ -0,0 +1,228 @@
+// Copyright (C) 2019-2025 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 (
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
+ "github.com/algorand/go-algorand/data/bookkeeping"
+ "github.com/stretchr/testify/require"
+)
+
+// getCodecTag extracts the base name from a codec tag, ignoring any additional parameters
+func getCodecTag(field reflect.StructField) string {
+ tag, ok := field.Tag.Lookup("codec")
+ if !ok {
+ return ""
+ }
+ // Split by comma and take the first part (the name)
+ return strings.Split(tag, ",")[0]
+}
+
+// getJSONTag extracts the json tag name
+func getJSONTag(field reflect.StructField) string {
+ tag := field.Tag.Get("json")
+ return strings.Split(tag, ",")[0]
+}
+
+// TestGenesisTypeCompatibility verifies that model.Genesis matches the field structure
+// of bookkeeping.Genesis, using the codec tags from bookkeeping as the source of truth.
+func TestGenesisTypeCompatibility(t *testing.T) {
+ // Test Genesis struct compatibility
+ verifyStructCompatibility(t, reflect.TypeOf(bookkeeping.Genesis{}), reflect.TypeOf(model.Genesis{}))
+
+ // Test GenesisAllocation struct compatibility
+ verifyStructCompatibility(t, reflect.TypeOf(bookkeeping.GenesisAllocation{}), reflect.TypeOf(model.GenesisAllocation{}))
+}
+
+// isStructOrPtrToStruct returns true if the type is a struct or pointer to struct
+func isStructOrPtrToStruct(typ reflect.Type) bool {
+ if typ.Kind() == reflect.Struct {
+ return true
+ }
+ if typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct {
+ return true
+ }
+ return false
+}
+
+// verifyStructCompatibility checks that modelType has json tags matching the codec tags of bkType
+func verifyStructCompatibility(t *testing.T, bkType, modelType reflect.Type) {
+ t.Logf("Verifying compatibility between %s and %s", bkType.Name(), modelType.Name())
+
+ if !isStructOrPtrToStruct(bkType) {
+ t.Logf("Skipping non-struct type %v", bkType)
+ return
+ }
+
+ if bkType.Kind() == reflect.Ptr {
+ bkType = bkType.Elem()
+ }
+ if modelType.Kind() == reflect.Ptr {
+ modelType = modelType.Elem()
+ }
+
+ // Build map of expected tags from bookkeeping type
+ expectedFields := make(map[string]reflect.Type) // map[codec_tag]field_type
+ for i := 0; i < bkType.NumField(); i++ {
+ field := bkType.Field(i)
+ if tag := getCodecTag(field); tag != "" {
+ expectedFields[tag] = field.Type
+
+ // If this is a struct field and the corresponding model field is also a struct,
+ // recursively verify its fields
+ if isStructOrPtrToStruct(field.Type) {
+ modelField := getMatchingField(t, modelType, tag)
+ if isStructOrPtrToStruct(modelField.Type) {
+ t.Logf("Recursively checking field %s", field.Name)
+ verifyStructCompatibility(t, field.Type, modelField.Type)
+ }
+ }
+ }
+ }
+
+ // Build map of actual tags from model type
+ actualFields := make(map[string]reflect.Type) // map[json_tag]field_type
+ for i := 0; i < modelType.NumField(); i++ {
+ field := modelType.Field(i)
+ if tag := getJSONTag(field); tag != "" {
+ actualFields[tag] = field.Type
+ }
+ }
+
+ // Verify each expected tag exists in the model
+ for tag, expectedType := range expectedFields {
+ actualType, exists := actualFields[tag]
+ require.True(t, exists, "%s: model type missing field for codec tag %q",
+ modelType.Name(), tag)
+
+ // For non-struct fields, verify type compatibility
+ if !isStructOrPtrToStruct(expectedType) {
+ t.Logf("Verifying type compatibility for field %s: expected %v, got %v", tag, expectedType, actualType)
+ verifyTypeCompatibility(t, expectedType, actualType, tag)
+ }
+
+ t.Logf("Field verified - tag: %s", tag)
+ }
+
+ // Verify no extra tags in model that aren't in bookkeeping
+ for jsonTag := range actualFields {
+ _, exists := expectedFields[jsonTag]
+ require.True(t, exists, "%s: model type has extra field with json tag %q that doesn't exist in bookkeeping",
+ modelType.Name(), jsonTag)
+ }
+}
+
+// getMatchingField finds a field in the given type that has a json tag matching the given tag
+func getMatchingField(t *testing.T, typ reflect.Type, tag string) reflect.StructField {
+ for i := 0; i < typ.NumField(); i++ {
+ field := typ.Field(i)
+ if jsonTag := getJSONTag(field); jsonTag == tag {
+ return field
+ }
+ }
+ t.Fatalf("Could not find field with json tag %q in type %s", tag, typ.Name())
+ return reflect.StructField{} // never reached
+}
+
+// verifyTypeCompatibility checks if two types are compatible for serialization
+func verifyTypeCompatibility(t *testing.T, bkType, modelType reflect.Type, tag string) {
+ switch modelType.Kind() {
+ case reflect.String:
+ // Special case: if the model uses string for byte slice or protocol types, that's okay
+ switch {
+ case bkType.Kind() == reflect.Slice && bkType.Elem().Kind() == reflect.Uint8:
+ return
+ case strings.HasPrefix(bkType.String(), "protocol."):
+ return
+ }
+
+ case reflect.Uint64:
+ // Special case: We represent all numeric types as uint64
+ switch {
+ case bkType.Kind() == reflect.Uint64,
+ bkType.Kind() == reflect.Int64:
+ return
+ case bkType.String() == "basics.MicroAlgos",
+ bkType.String() == "basics.Status",
+ bkType.String() == "basics.Round":
+ return
+ }
+
+ case reflect.Ptr:
+ switch modelType.Elem().Kind() {
+ case reflect.String:
+ // Special case: if the model uses *string for string or crypto types, that's okay
+ switch {
+ case bkType.Kind() == reflect.String:
+ return
+ case bkType.String() == "crypto.OneTimeSignatureVerifier",
+ bkType.String() == "merklesignature.Commitment",
+ bkType.String() == "crypto.VRFVerifier",
+ bkType.String() == "crypto.VrfPubkey",
+ bkType.String() == "protocol.ConsensusVersion",
+ bkType.String() == "protocol.NetworkID":
+ return
+ }
+
+ case reflect.Bool:
+ // Special case: if the model uses *bool for bool, that's okay
+ if bkType.Kind() == reflect.Bool {
+ return
+ }
+
+ case reflect.Uint64:
+ // Special case: We represent all numeric types as uint64
+ switch {
+ case bkType.Kind() == reflect.Uint64,
+ bkType.Kind() == reflect.Int64:
+ return
+ case bkType.String() == "basics.MicroAlgos",
+ bkType.String() == "basics.Status",
+ bkType.String() == "basics.Round":
+ return
+ }
+
+ default:
+ // For other pointer types, check the underlying type
+ if bkType.Kind() == reflect.Ptr {
+ verifyTypeCompatibility(t, bkType.Elem(), modelType.Elem(), tag)
+ return
+ }
+ }
+
+ case reflect.Slice:
+ // For slice types, check the element type
+ if bkType.Kind() == reflect.Slice {
+ // Special case: allow []model.GenesisAllocation for []bookkeeping.GenesisAllocation
+ if strings.HasSuffix(bkType.Elem().String(), "GenesisAllocation") &&
+ strings.HasSuffix(modelType.Elem().String(), "GenesisAllocation") {
+ return
+ }
+ verifyTypeCompatibility(t, bkType.Elem(), modelType.Elem(), tag)
+ return
+ }
+ }
+
+ // For all other cases, types should match exactly
+ if bkType != modelType {
+ t.Errorf("Type mismatch for field %q: expected %v, got %v", tag, bkType, modelType)
+ }
+}
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 39d9c660cf..c501d06c4f 100644
--- a/daemon/algod/api/server/v2/test/handlers_resources_test.go
+++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go
@@ -85,7 +85,7 @@ func (l *mockLedger) LookupKv(round basics.Round, key string) ([]byte, error) {
return nil, fmt.Errorf("Key %v does not exist", key)
}
-func (l *mockLedger) LookupKeysByPrefix(round basics.Round, keyPrefix string, maxKeyNum uint64) ([]string, error) {
+func (l *mockLedger) LookupKeysByPrefix(prefix, next string, maxBoxes, maxBytes int, values bool) (basics.Round, map[string]string, string, error) {
panic("not implemented")
}
@@ -181,20 +181,11 @@ func (l *mockLedger) Block(rnd basics.Round) (blk bookkeeping.Block, err error)
return l.blocks[0], nil
}
-func (l *mockLedger) AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error) {
- blk := l.blocks[r]
-
- spec := transactions.SpecialAddresses{
- FeeSink: blk.FeeSink,
- RewardsPool: blk.RewardsPool,
- }
-
- var res []transactions.SignedTxnWithAD
-
- for _, tx := range blk.Payset {
- if tx.Txn.MatchAddress(id, spec) {
- signedTxn := transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: tx.Txn}}
- res = append(res, signedTxn)
+func (l *mockLedger) TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error) {
+ var res []transactions.Transaction
+ for _, tx := range l.blocks[r].Payset {
+ if id == tx.Txn.Sender {
+ res = append(res, tx.Txn)
}
}
return res, nil
diff --git a/daemon/algod/api/server/v2/utils.go b/daemon/algod/api/server/v2/utils.go
index f3451dd081..a560872a2e 100644
--- a/daemon/algod/api/server/v2/utils.go
+++ b/daemon/algod/api/server/v2/utils.go
@@ -288,14 +288,16 @@ func decode(handle codec.Handle, data []byte, v interface{}) error {
return nil
}
-// Helper to convert basics.StateDelta -> *model.StateDelta
-func stateDeltaToStateDelta(d basics.StateDelta) *model.StateDelta {
- if len(d) == 0 {
+// globalDeltaToStateDelta converts basics.StateDelta -> model.StateDelta. It
+// should only be used on globals, because locals require extra context to
+// translate account indexes.
+func globalDeltaToStateDelta(bsd basics.StateDelta) model.StateDelta {
+ if len(bsd) == 0 {
return nil
}
- var delta model.StateDelta
- for k, v := range d {
- delta = append(delta, model.EvalDeltaKeyValue{
+ msd := make(model.StateDelta, 0, len(bsd))
+ for k, v := range bsd {
+ msd = append(msd, model.EvalDeltaKeyValue{
Key: base64.StdEncoding.EncodeToString([]byte(k)),
Value: model.EvalDelta{
Action: uint64(v.Action),
@@ -304,7 +306,7 @@ func stateDeltaToStateDelta(d basics.StateDelta) *model.StateDelta {
},
})
}
- return &delta
+ return msd
}
func edIndexToAddress(index uint64, txn *transactions.Transaction, shared []basics.Address) string {
@@ -321,23 +323,21 @@ func edIndexToAddress(index uint64, txn *transactions.Transaction, shared []basi
}
}
-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)
- shared := txn.ApplyData.EvalDelta.SharedAccts
-
- for k, v := range txn.ApplyData.EvalDelta.LocalDeltas {
- d = append(d, model.AccountStateDelta{
- Address: edIndexToAddress(k, &txn.Txn.Txn, shared),
- Delta: *(stateDeltaToStateDelta(v)),
- })
- }
+func localDeltasToLocalDeltas(ed transactions.EvalDelta, txn *transactions.Transaction) []model.AccountStateDelta {
+ if len(ed.LocalDeltas) == 0 {
+ return nil
+ }
+ lsd := make([]model.AccountStateDelta, 0, len(ed.LocalDeltas))
+ shared := ed.SharedAccts
- localStateDelta = &d
+ for k, v := range ed.LocalDeltas {
+ lsd = append(lsd, model.AccountStateDelta{
+ Address: edIndexToAddress(k, txn, shared),
+ Delta: globalDeltaToStateDelta(v),
+ })
}
- return localStateDelta, stateDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta)
+ return lsd
}
func convertLogs(txn node.TxnWithStatus) *[][]byte {
@@ -381,11 +381,12 @@ func ConvertInnerTxn(txn *transactions.SignedTxnWithAD) PreEncodedTxInfo {
response.AssetIndex = omitEmpty(uint64(txn.ApplyData.ConfigAsset))
response.ApplicationIndex = omitEmpty(uint64(txn.ApplyData.ApplicationID))
+ response.LocalStateDelta = sliceOrNil(localDeltasToLocalDeltas(txn.ApplyData.EvalDelta, &txn.Txn))
+ response.GlobalStateDelta = sliceOrNil(globalDeltaToStateDelta(txn.ApplyData.EvalDelta.GlobalDelta))
withStatus := node.TxnWithStatus{
Txn: txn.SignedTxn,
ApplyData: txn.ApplyData,
}
- response.LocalStateDelta, response.GlobalStateDelta = convertToDeltas(withStatus)
response.Logs = convertLogs(withStatus)
response.Inners = convertInners(&withStatus)
return response
diff --git a/daemon/algod/server.go b/daemon/algod/server.go
index 8fdacdfdd7..8a8bbde437 100644
--- a/daemon/algod/server.go
+++ b/daemon/algod/server.go
@@ -129,7 +129,11 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes
return errors.New(
"Initialize() overflowed when adding up ReservedFDs and RestConnectionsHardLimit; decrease them")
}
- err = util.SetFdSoftLimit(fdRequired)
+ if cfg.EnableP2P {
+ // TODO: Decide if this is too much, or not enough.
+ fdRequired = ot.Add(fdRequired, 512)
+ }
+ err = util.RaiseFdSoftLimit(fdRequired)
if err != nil {
return fmt.Errorf("Initialize() err: %w", err)
}
@@ -141,7 +145,7 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes
return errors.New(
"Initialize() overflowed when adding up fdRequired and 1000 needed for pebbledb")
}
- err = util.SetFdSoftLimit(fdRequired)
+ err = util.RaiseFdSoftLimit(fdRequired)
if err != nil {
return fmt.Errorf("Initialize() failed to set FD limit for pebbledb backend, err: %w", err)
}
@@ -190,7 +194,7 @@ func (s *Server) Initialize(cfg config.Local, phonebookAddresses []string, genes
}
}
}
- fdErr = util.SetFdSoftLimit(maxFDs)
+ fdErr = util.RaiseFdSoftLimit(maxFDs)
if fdErr != nil {
// do not fail but log the error
s.log.Errorf("Failed to set a new RLIMIT_NOFILE value to %d (max %d): %s", fdRequired, hard, fdErr.Error())
diff --git a/daemon/kmd/api/v1/errors.go b/daemon/kmd/api/v1/errors.go
index c1e8aa2235..0ad0c5557d 100644
--- a/daemon/kmd/api/v1/errors.go
+++ b/daemon/kmd/api/v1/errors.go
@@ -20,8 +20,6 @@ import (
"fmt"
)
-var errWalletNameLong = fmt.Errorf("wallet name too long")
-var errPasswordLong = fmt.Errorf("password too long")
var errCouldNotDecode = fmt.Errorf("could not decode request body")
var errCouldNotDecodeAddress = fmt.Errorf("could not decode address")
var errCouldNotDecodeTx = fmt.Errorf("could not decode transaction")
diff --git a/daemon/kmd/client/wrappers.go b/daemon/kmd/client/wrappers.go
index 7333530cab..9e9b2fdf48 100644
--- a/daemon/kmd/client/wrappers.go
+++ b/daemon/kmd/client/wrappers.go
@@ -59,6 +59,17 @@ func (kcl KMDClient) CreateWallet(walletName []byte, walletDriverName string, wa
return
}
+// RenameWallet wraps kmdapi.APIV1POSTWalletRenameRequest
+func (kcl KMDClient) RenameWallet(walletID []byte, newWalletName []byte, walletPassword []byte) (resp kmdapi.APIV1POSTWalletRenameResponse, err error) {
+ req := kmdapi.APIV1POSTWalletRenameRequest{
+ WalletID: string(walletID),
+ NewWalletName: string(newWalletName),
+ WalletPassword: string(walletPassword),
+ }
+ err = kcl.DoV1Request(req, &resp)
+ return
+}
+
// InitWallet wraps kmdapi.APIV1POSTWalletInitRequest
func (kcl KMDClient) InitWallet(walletID []byte, walletPassword []byte) (resp kmdapi.APIV1POSTWalletInitResponse, err error) {
req := kmdapi.APIV1POSTWalletInitRequest{
diff --git a/daemon/kmd/config/config.go b/daemon/kmd/config/config.go
index 57a12ba5eb..235935950a 100644
--- a/daemon/kmd/config/config.go
+++ b/daemon/kmd/config/config.go
@@ -69,6 +69,11 @@ type ScryptParams struct {
ScryptP int `json:"scrypt_p"`
}
+// DefaultConfig returns the default KMDConfig
+func DefaultConfig(dataDir string) KMDConfig {
+ return defaultConfig(dataDir)
+}
+
// defaultConfig returns the default KMDConfig
func defaultConfig(dataDir string) KMDConfig {
return KMDConfig{
@@ -121,3 +126,14 @@ func LoadKMDConfig(dataDir string) (cfg KMDConfig, err error) {
err = cfg.Validate()
return
}
+
+// SaveKMDConfig writes the kmd configuration to disk
+func SaveKMDConfig(dataDir string, cfg KMDConfig) error {
+ err := cfg.Validate()
+ if err != nil {
+ return err
+ }
+ configFilename := filepath.Join(dataDir, kmdConfigFilename)
+
+ return codecs.SaveObjectToFile(configFilename, cfg, true)
+}
diff --git a/daemon/kmd/server/server.go b/daemon/kmd/server/server.go
index 0e73bf22b9..ec5e695b5b 100644
--- a/daemon/kmd/server/server.go
+++ b/daemon/kmd/server/server.go
@@ -63,12 +63,10 @@ type WalletServerConfig struct {
// WalletServer deals with serving API requests
type WalletServer struct {
WalletServerConfig
- netPath string
- pidPath string
- lockPath string
- fileLock *flock.Flock
- sockPath string
- tmpSocketDir string
+ netPath string
+ pidPath string
+ lockPath string
+ fileLock *flock.Flock
// This mutex protects shutdown, which lets us know if we died unexpectedly
// or as a result of being killed
diff --git a/daemon/kmd/session/auth.go b/daemon/kmd/session/auth.go
index 304ebbca2b..522cce7190 100644
--- a/daemon/kmd/session/auth.go
+++ b/daemon/kmd/session/auth.go
@@ -224,7 +224,7 @@ func (sm *Manager) authMaybeRenewWalletHandleToken(walletHandleToken []byte, ren
}
// Compute how many seconds are left until the handle expires
- expiresSeconds := int64(handle.expires.Sub(time.Now()).Seconds())
+ expiresSeconds := int64(time.Until(handle.expires).Seconds())
// Return the wallet and seconds remaining to expiration
return handle.wallet, expiresSeconds, nil
diff --git a/daemon/kmd/wallet/driver/ledger.go b/daemon/kmd/wallet/driver/ledger.go
index 1153247bfb..754b7bdd9b 100644
--- a/daemon/kmd/wallet/driver/ledger.go
+++ b/daemon/kmd/wallet/driver/ledger.go
@@ -122,8 +122,7 @@ func (lwd *LedgerWalletDriver) scanWalletsLocked() error {
// Make map of existing device paths. We will pop each one that we
// are able to scan for, meaning anything left over is dead, and we
// should remove it
- var curPaths map[string]bool
- curPaths = make(map[string]bool)
+ curPaths := make(map[string]bool)
for k := range lwd.wallets {
curPaths[k] = true
}
diff --git a/daemon/kmd/wallet/driver/ledger_hid.go b/daemon/kmd/wallet/driver/ledger_hid.go
index 97c3ace1fc..0ccde7602d 100644
--- a/daemon/kmd/wallet/driver/ledger_hid.go
+++ b/daemon/kmd/wallet/driver/ledger_hid.go
@@ -21,7 +21,7 @@ import (
"fmt"
"os"
- "github.com/karalabe/usb"
+ "github.com/karalabe/hid"
)
const ledgerVendorID = 0x2c97
@@ -31,8 +31,8 @@ const ledgerUsagePage = 0xffa0
// the protocol used for sending messages to the application running on the
// Ledger hardware wallet.
type LedgerUSB struct {
- hiddev usb.Device
- info usb.DeviceInfo
+ hiddev hid.Device
+ info hid.DeviceInfo
}
// LedgerUSBError is a wrapper around the two-byte error code that the Ledger
@@ -197,23 +197,23 @@ func (l *LedgerUSB) Exchange(msg []byte) ([]byte, error) {
}
// USBInfo returns information about the underlying USB device.
-func (l *LedgerUSB) USBInfo() usb.DeviceInfo {
+func (l *LedgerUSB) USBInfo() hid.DeviceInfo {
return l.info
}
// LedgerEnumerate returns all of the Ledger devices connected to this machine.
-func LedgerEnumerate() ([]usb.DeviceInfo, error) {
- if !usb.Supported() || os.Getenv("KMD_NOUSB") != "" {
+func LedgerEnumerate() ([]hid.DeviceInfo, error) {
+ if !hid.Supported() || os.Getenv("KMD_NOUSB") != "" {
return nil, fmt.Errorf("HID not supported")
}
- var infos []usb.DeviceInfo
+ var infos []hid.DeviceInfo
// The enumeration process is based on:
// https://github.com/LedgerHQ/blue-loader-python/blob/master/ledgerblue/comm.py#L212
// we search for the Ledger Vendor id and ignore devices that don't have specific usagepage or interface
- hids, err := usb.EnumerateHid(ledgerVendorID, 0)
+ hids, err := hid.Enumerate(ledgerVendorID, 0)
if err != nil {
- return []usb.DeviceInfo{}, err
+ return []hid.DeviceInfo{}, err
}
for _, info := range hids {
if info.UsagePage != ledgerUsagePage && info.Interface != 0 {
diff --git a/daemon/kmd/wallet/driver/sqlite.go b/daemon/kmd/wallet/driver/sqlite.go
index 10b1174ae1..8dd29d4abd 100644
--- a/daemon/kmd/wallet/driver/sqlite.go
+++ b/daemon/kmd/wallet/driver/sqlite.go
@@ -53,7 +53,7 @@ const (
var sqliteWalletSupportedTxs = []protocol.TxType{protocol.PaymentTx, protocol.KeyRegistrationTx}
var disallowedFilenameRegex = regexp.MustCompile("[^a-zA-Z0-9_-]*")
-var databaseFilenameRegex = regexp.MustCompile("^.*\\.db$")
+var databaseFilenameRegex = regexp.MustCompile(`^.*\.db$`)
var walletSchema = `
CREATE TABLE IF NOT EXISTS metadata (
diff --git a/daemon/kmd/wallet/driver/sqlite_errors.go b/daemon/kmd/wallet/driver/sqlite_errors.go
index 8ae1b1f987..bc16e6a839 100644
--- a/daemon/kmd/wallet/driver/sqlite_errors.go
+++ b/daemon/kmd/wallet/driver/sqlite_errors.go
@@ -25,7 +25,6 @@ var errDatabaseConnect = fmt.Errorf("error connecting to database")
var errKeyNotFound = fmt.Errorf("key does not exist in this wallet")
var errMsigDataNotFound = fmt.Errorf("multisig information (pks, threshold) for address does not exist in this wallet")
var errSKToPK = fmt.Errorf("could not convert secret key to public key")
-var errSKToSeed = fmt.Errorf("could not convert secret key to seed")
var errTampering = fmt.Errorf("derived public key mismatch, something fishy is going on with this wallet")
var errNoMnemonicUX = fmt.Errorf("sqlite wallet driver cannot display mnemonics")
var errKeyExists = fmt.Errorf("key already exists in wallet")
diff --git a/data/basics/msgp_gen.go b/data/basics/msgp_gen.go
index de460960bc..585a100765 100644
--- a/data/basics/msgp_gen.go
+++ b/data/basics/msgp_gen.go
@@ -2008,8 +2008,8 @@ func AppLocalStateMaxSize() (s int) {
func (z *AppParams) MarshalMsg(b []byte) (o []byte) {
o = msgp.Require(b, z.Msgsize())
// omitempty: check for empty values
- zb0003Len := uint32(6)
- var zb0003Mask uint8 /* 8 bits */
+ zb0003Len := uint32(7)
+ var zb0003Mask uint16 /* 9 bits */
if len((*z).ApprovalProgram) == 0 {
zb0003Len--
zb0003Mask |= 0x4
@@ -2034,6 +2034,10 @@ func (z *AppParams) MarshalMsg(b []byte) (o []byte) {
zb0003Len--
zb0003Mask |= 0x80
}
+ if (*z).Version == 0 {
+ zb0003Len--
+ zb0003Mask |= 0x100
+ }
// variable map header, size zb0003Len
o = append(o, 0x80|uint8(zb0003Len))
if zb0003Len != 0 {
@@ -2126,6 +2130,11 @@ func (z *AppParams) MarshalMsg(b []byte) (o []byte) {
o = msgp.AppendUint64(o, (*z).StateSchemas.LocalStateSchema.NumUint)
}
}
+ if (zb0003Mask & 0x100) == 0 { // if not empty
+ // string "v"
+ o = append(o, 0xa1, 0x76)
+ o = msgp.AppendUint64(o, (*z).Version)
+ }
}
return
}
@@ -2377,6 +2386,14 @@ func (z *AppParams) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o
return
}
}
+ if zb0003 > 0 {
+ zb0003--
+ (*z).Version, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "Version")
+ return
+ }
+ }
if zb0003 > 0 {
err = msgp.ErrTooManyArrayFields(zb0003)
if err != nil {
@@ -2612,6 +2629,12 @@ func (z *AppParams) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o
err = msgp.WrapError(err, "ExtraProgramPages")
return
}
+ case "v":
+ (*z).Version, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Version")
+ return
+ }
default:
err = msgp.ErrNoField(string(field))
if err != nil {
@@ -2643,13 +2666,13 @@ func (z *AppParams) Msgsize() (s int) {
s += 0 + msgp.StringPrefixSize + len(zb0001) + zb0002.Msgsize()
}
}
- s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint32Size
+ s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint32Size + 2 + msgp.Uint64Size
return
}
// MsgIsZero returns whether this is a zero value
func (z *AppParams) MsgIsZero() bool {
- return (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && (len((*z).GlobalState) == 0) && (((*z).StateSchemas.LocalStateSchema.NumUint == 0) && ((*z).StateSchemas.LocalStateSchema.NumByteSlice == 0)) && (((*z).StateSchemas.GlobalStateSchema.NumUint == 0) && ((*z).StateSchemas.GlobalStateSchema.NumByteSlice == 0)) && ((*z).ExtraProgramPages == 0)
+ return (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && (len((*z).GlobalState) == 0) && (((*z).StateSchemas.LocalStateSchema.NumUint == 0) && ((*z).StateSchemas.LocalStateSchema.NumByteSlice == 0)) && (((*z).StateSchemas.GlobalStateSchema.NumUint == 0) && ((*z).StateSchemas.GlobalStateSchema.NumByteSlice == 0)) && ((*z).ExtraProgramPages == 0) && ((*z).Version == 0)
}
// MaxSize returns a maximum valid message size for this message type
@@ -2660,7 +2683,7 @@ func AppParamsMaxSize() (s int) {
s += EncodedMaxKeyValueEntries * (msgp.StringPrefixSize + config.MaxAppBytesKeyLen)
// Adding size of map values for z.GlobalState
s += EncodedMaxKeyValueEntries * (TealValueMaxSize())
- s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint32Size
+ s += 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 5 + 1 + 4 + msgp.Uint64Size + 4 + msgp.Uint64Size + 4 + msgp.Uint32Size + 2 + msgp.Uint64Size
return
}
diff --git a/data/basics/teal.go b/data/basics/teal.go
index 72753f6fea..7fafe536b4 100644
--- a/data/basics/teal.go
+++ b/data/basics/teal.go
@@ -81,33 +81,6 @@ func (sd StateDelta) Equal(o StateDelta) bool {
return maps.Equal(sd, o)
}
-// Valid checks whether the keys and values in a StateDelta conform to the
-// consensus parameters' maximum lengths
-func (sd StateDelta) Valid(proto *config.ConsensusParams) error {
- if len(sd) > 0 && proto.MaxAppKeyLen == 0 {
- return fmt.Errorf("delta not empty, but proto.MaxAppKeyLen is 0 (why did we make a delta?)")
- }
- for key, delta := range sd {
- if len(key) > proto.MaxAppKeyLen {
- return fmt.Errorf("key too long: length was %d, maximum is %d", len(key), proto.MaxAppKeyLen)
- }
- switch delta.Action {
- case SetBytesAction:
- if len(delta.Bytes) > proto.MaxAppBytesValueLen {
- return fmt.Errorf("value too long for key 0x%x: length was %d", key, len(delta.Bytes))
- }
- if sum := len(key) + len(delta.Bytes); sum > proto.MaxAppSumKeyValueLens {
- return fmt.Errorf("key/value total too long for key 0x%x: sum was %d", key, sum)
- }
- case SetUintAction:
- case DeleteAction:
- default:
- return fmt.Errorf("unknown delta action: %v", delta.Action)
- }
- }
- return nil
-}
-
// StateSchema sets maximums on the number of each type that may be stored
type StateSchema struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`
diff --git a/data/basics/teal_test.go b/data/basics/teal_test.go
index cb16f665f3..50b0501e49 100644
--- a/data/basics/teal_test.go
+++ b/data/basics/teal_test.go
@@ -17,96 +17,13 @@
package basics
import (
- "strings"
"testing"
"github.com/stretchr/testify/require"
- "github.com/algorand/go-algorand/config"
- "github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
-func TestStateDeltaValid(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- a := require.New(t)
-
- // test pre-applications proto
- protoPreF := config.Consensus[protocol.ConsensusV23]
- a.False(protoPreF.Application)
- sd := StateDelta{"key": ValueDelta{Action: SetBytesAction, Bytes: "val"}}
- err := sd.Valid(&protoPreF)
- a.Error(err)
- a.Contains(err.Error(), "proto.MaxAppKeyLen is 0")
-
- sd = StateDelta{"": ValueDelta{Action: SetUintAction, Uint: 1}}
- err = sd.Valid(&protoPreF)
- a.Error(err)
- a.Contains(err.Error(), "proto.MaxAppKeyLen is 0")
-
- sd = StateDelta{"": ValueDelta{Action: SetBytesAction, Bytes: ""}}
- err = sd.Valid(&protoPreF)
- a.Error(err)
- a.Contains(err.Error(), "proto.MaxAppKeyLen is 0")
-
- // test vFuture proto with applications
- sd = StateDelta{"key": ValueDelta{Action: SetBytesAction, Bytes: "val"}}
- protoF := config.Consensus[protocol.ConsensusFuture]
- err = sd.Valid(&protoF)
- a.NoError(err)
-
- // vFuture: key too long, short value
- tooLongKey := strings.Repeat("a", protoF.MaxAppKeyLen+1)
- sd = StateDelta{tooLongKey: ValueDelta{Action: SetBytesAction, Bytes: "val"}}
- err = sd.Valid(&protoF)
- a.Error(err)
- a.Contains(err.Error(), "key too long")
- delete(sd, tooLongKey)
-
- // vFuture: max size key, value too long: total size bigger than MaxAppSumKeyValueLens
- longKey := tooLongKey[1:]
- tooLongValue := strings.Repeat("b", protoF.MaxAppSumKeyValueLens-len(longKey)+1)
- sd = StateDelta{longKey: ValueDelta{Action: SetBytesAction, Bytes: tooLongValue}}
- err = sd.Valid(&protoF)
- a.Error(err)
- a.Contains(err.Error(), "key/value total too long for key")
-
- sd[longKey] = ValueDelta{Action: SetBytesAction, Bytes: tooLongValue[1:]}
- sd["intval"] = ValueDelta{Action: DeltaAction(10), Uint: 0}
- err = sd.Valid(&protoF)
- a.Error(err)
- a.Contains(err.Error(), "unknown delta action")
-
- sd["intval"] = ValueDelta{Action: SetUintAction, Uint: 0}
- sd["delval"] = ValueDelta{Action: DeleteAction, Uint: 0, Bytes: tooLongValue}
- err = sd.Valid(&protoF)
- a.NoError(err)
-}
-
-func TestStateDeltaValidV24(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- a := require.New(t)
-
- // v24: short key, value too long: hits MaxAppBytesValueLen
- protoV24 := config.Consensus[protocol.ConsensusV24]
- shortKey := "k"
- reallyLongValue := strings.Repeat("b", protoV24.MaxAppBytesValueLen+1)
- sd := StateDelta{shortKey: ValueDelta{Action: SetBytesAction, Bytes: reallyLongValue}}
- err := sd.Valid(&protoV24)
- a.Error(err)
- a.Contains(err.Error(), "value too long for key")
-
- // v24: key too long, short value
- tooLongKey := strings.Repeat("a", protoV24.MaxAppKeyLen+1)
- sd = StateDelta{tooLongKey: ValueDelta{Action: SetBytesAction, Bytes: "val"}}
- err = sd.Valid(&protoV24)
- a.Error(err)
- a.Contains(err.Error(), "key too long")
- delete(sd, tooLongKey)
-}
-
func TestStateDeltaEqual(t *testing.T) {
partitiontest.PartitionTest(t)
diff --git a/data/basics/testing/userBalance.go b/data/basics/testing/userBalance.go
index 1d9d673d7b..f12737c767 100644
--- a/data/basics/testing/userBalance.go
+++ b/data/basics/testing/userBalance.go
@@ -29,3 +29,28 @@ func MakeAccountData(status basics.Status, algos basics.MicroAlgos) basics.Accou
}
return ad
}
+
+// OnlineAccountData converts basics.AccountData to basics.OnlineAccountData.
+// Account is expected to be Online otherwise it is cleared out.
+// This function is intended for testing purposes only.
+func OnlineAccountData(u basics.AccountData) basics.OnlineAccountData {
+ if u.Status != basics.Online {
+ // if the account is not Online and agreement requests it for some reason, clear it out
+ return basics.OnlineAccountData{}
+ }
+
+ return basics.OnlineAccountData{
+ MicroAlgosWithRewards: u.MicroAlgos,
+ VotingData: basics.VotingData{
+ VoteID: u.VoteID,
+ SelectionID: u.SelectionID,
+ StateProofID: u.StateProofID,
+ VoteFirstValid: u.VoteFirstValid,
+ VoteLastValid: u.VoteLastValid,
+ VoteKeyDilution: u.VoteKeyDilution,
+ },
+ IncentiveEligible: u.IncentiveEligible,
+ LastProposed: u.LastProposed,
+ LastHeartbeat: u.LastHeartbeat,
+ }
+}
diff --git a/data/basics/testing/userBalance_test.go b/data/basics/testing/userBalance_test.go
new file mode 100644
index 0000000000..38bef08bea
--- /dev/null
+++ b/data/basics/testing/userBalance_test.go
@@ -0,0 +1,73 @@
+// Copyright (C) 2019-2025 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 testing
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/crypto/merklesignature"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/test/partitiontest"
+)
+
+// Helper function to create a sample account data for testing
+func getSampleAccountData() basics.AccountData {
+ oneTimeSecrets := crypto.GenerateOneTimeSignatureSecrets(0, 1)
+ vrfSecrets := crypto.GenerateVRFSecrets()
+ var stateProofID merklesignature.Commitment
+ crypto.RandBytes(stateProofID[:])
+
+ return basics.AccountData{
+ Status: basics.NotParticipating,
+ MicroAlgos: basics.MicroAlgos{},
+ RewardsBase: 0x1234123412341234,
+ RewardedMicroAlgos: basics.MicroAlgos{},
+ VoteID: oneTimeSecrets.OneTimeSignatureVerifier,
+ SelectionID: vrfSecrets.PK,
+ StateProofID: stateProofID,
+ VoteFirstValid: basics.Round(0x1234123412341234),
+ VoteLastValid: basics.Round(0x1234123412341234),
+ VoteKeyDilution: 0x1234123412341234,
+ AssetParams: make(map[basics.AssetIndex]basics.AssetParams),
+ Assets: make(map[basics.AssetIndex]basics.AssetHolding),
+ AppLocalStates: make(map[basics.AppIndex]basics.AppLocalState),
+ AppParams: make(map[basics.AppIndex]basics.AppParams),
+ AuthAddr: basics.Address(crypto.Hash([]byte{1, 2, 3, 4})),
+ IncentiveEligible: true,
+ }
+}
+
+func TestOnlineAccountData(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ ad := getSampleAccountData()
+ ad.MicroAlgos.Raw = 1000000
+ ad.Status = basics.Offline
+
+ oad := OnlineAccountData(ad)
+ require.Empty(t, oad)
+
+ ad.Status = basics.Online
+ oad = OnlineAccountData(ad)
+ require.Equal(t, ad.MicroAlgos, oad.MicroAlgosWithRewards)
+ require.Equal(t, ad.VoteID, oad.VoteID)
+ require.Equal(t, ad.SelectionID, oad.SelectionID)
+ require.Equal(t, ad.IncentiveEligible, oad.IncentiveEligible)
+}
diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go
index ad7995cd0d..468507a102 100644
--- a/data/basics/userBalance.go
+++ b/data/basics/userBalance.go
@@ -274,6 +274,7 @@ type AppParams struct {
GlobalState TealKeyValue `codec:"gs"`
StateSchemas
ExtraProgramPages uint32 `codec:"epp"`
+ Version uint64 `codec:"v"`
}
// StateSchemas is a thin wrapper around the LocalStateSchema and the
@@ -544,30 +545,6 @@ func MinBalance(
return MicroAlgos{min}
}
-// OnlineAccountData returns subset of AccountData as OnlineAccountData data structure.
-// Account is expected to be Online otherwise its is cleared out
-func (u AccountData) OnlineAccountData() OnlineAccountData {
- if u.Status != Online {
- // if the account is not Online and agreement requests it for some reason, clear it out
- return OnlineAccountData{}
- }
-
- return OnlineAccountData{
- MicroAlgosWithRewards: u.MicroAlgos,
- VotingData: VotingData{
- VoteID: u.VoteID,
- SelectionID: u.SelectionID,
- StateProofID: u.StateProofID,
- VoteFirstValid: u.VoteFirstValid,
- VoteLastValid: u.VoteLastValid,
- VoteKeyDilution: u.VoteKeyDilution,
- },
- IncentiveEligible: u.IncentiveEligible,
- LastProposed: u.LastProposed,
- LastHeartbeat: u.LastHeartbeat,
- }
-}
-
// VotingStake returns the amount of MicroAlgos associated with the user's account
// for the purpose of participating in the Algorand protocol. It assumes the
// caller has already updated rewards appropriately using WithUpdatedRewards().
diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go
index cc4ba8056a..52b9c54c94 100644
--- a/data/basics/userBalance_test.go
+++ b/data/basics/userBalance_test.go
@@ -24,8 +24,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
- "github.com/algorand/go-algorand/crypto"
- "github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -98,40 +96,6 @@ func TestWithUpdatedRewardsPanics(t *testing.T) {
})
}
-func makeString(len int) string {
- s := ""
- for i := 0; i < len; i++ {
- s += string(byte(i))
- }
- return s
-}
-
-func getSampleAccountData() AccountData {
- oneTimeSecrets := crypto.GenerateOneTimeSignatureSecrets(0, 1)
- vrfSecrets := crypto.GenerateVRFSecrets()
- var stateProofID merklesignature.Commitment
- crypto.RandBytes(stateProofID[:])
-
- return AccountData{
- Status: NotParticipating,
- MicroAlgos: MicroAlgos{},
- RewardsBase: 0x1234123412341234,
- RewardedMicroAlgos: MicroAlgos{},
- VoteID: oneTimeSecrets.OneTimeSignatureVerifier,
- SelectionID: vrfSecrets.PK,
- StateProofID: stateProofID,
- VoteFirstValid: Round(0x1234123412341234),
- VoteLastValid: Round(0x1234123412341234),
- VoteKeyDilution: 0x1234123412341234,
- AssetParams: make(map[AssetIndex]AssetParams),
- Assets: make(map[AssetIndex]AssetHolding),
- AppLocalStates: make(map[AppIndex]AppLocalState),
- AppParams: make(map[AppIndex]AppParams),
- AuthAddr: Address(crypto.Hash([]byte{1, 2, 3, 4})),
- IncentiveEligible: true,
- }
-}
-
func TestEncodedAccountAllocationBounds(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -175,21 +139,3 @@ func TestAppIndexHashing(t *testing.T) {
i = AppIndex(77)
require.Equal(t, "PCYUFPA2ZTOYWTP43MX2MOX2OWAIAXUDNC2WFCXAGMRUZ3DYD6BWFDL5YM", i.Address().String())
}
-
-func TestOnlineAccountData(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- ad := getSampleAccountData()
- ad.MicroAlgos.Raw = 1000000
- ad.Status = Offline
-
- oad := ad.OnlineAccountData()
- require.Empty(t, oad)
-
- ad.Status = Online
- oad = ad.OnlineAccountData()
- require.Equal(t, ad.MicroAlgos, oad.MicroAlgosWithRewards)
- require.Equal(t, ad.VoteID, oad.VoteID)
- require.Equal(t, ad.SelectionID, oad.SelectionID)
- require.Equal(t, ad.IncentiveEligible, oad.IncentiveEligible)
-}
diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go
index 46d08a9ee7..afce45e899 100644
--- a/data/bookkeeping/block.go
+++ b/data/bookkeeping/block.go
@@ -262,6 +262,59 @@ type (
}
)
+// TxnDeadError defines an error type which indicates a transaction is outside of the
+// round validity window.
+type TxnDeadError struct {
+ Round basics.Round
+ FirstValid basics.Round
+ LastValid basics.Round
+ Early bool
+}
+
+func (err *TxnDeadError) Error() string {
+ return fmt.Sprintf("txn dead: round %d outside of %d--%d", err.Round, err.FirstValid, err.LastValid)
+}
+
+// Alive checks to see if the transaction is still alive (can be applied) at the specified Round.
+func (bh BlockHeader) Alive(tx transactions.Header) error {
+ // Check round validity
+ round := bh.Round
+ if round < tx.FirstValid || round > tx.LastValid {
+ return &TxnDeadError{
+ Round: round,
+ FirstValid: tx.FirstValid,
+ LastValid: tx.LastValid,
+ Early: round < tx.FirstValid,
+ }
+ }
+
+ // Check genesis ID
+ proto := config.Consensus[bh.CurrentProtocol]
+ genesisID := bh.GenesisID
+ if tx.GenesisID != "" && tx.GenesisID != genesisID {
+ return fmt.Errorf("tx.GenesisID <%s> does not match expected <%s>",
+ tx.GenesisID, genesisID)
+ }
+
+ // Check genesis hash
+ if proto.SupportGenesisHash {
+ genesisHash := bh.GenesisHash
+ if tx.GenesisHash != (crypto.Digest{}) && tx.GenesisHash != genesisHash {
+ return fmt.Errorf("tx.GenesisHash <%s> does not match expected <%s>",
+ tx.GenesisHash, genesisHash)
+ }
+ if proto.RequireGenesisHash && tx.GenesisHash == (crypto.Digest{}) {
+ return fmt.Errorf("required tx.GenesisHash is missing")
+ }
+ } else {
+ if tx.GenesisHash != (crypto.Digest{}) {
+ return fmt.Errorf("tx.GenesisHash <%s> not allowed", tx.GenesisHash)
+ }
+ }
+
+ return nil
+}
+
// Hash returns the hash of a block header.
// The hash of a block is the hash of its header.
func (bh BlockHeader) Hash() BlockHash {
diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go
index f47de03df7..89bf96a5be 100644
--- a/data/bookkeeping/block_test.go
+++ b/data/bookkeeping/block_test.go
@@ -1069,3 +1069,45 @@ func TestFirstYearsBonus(t *testing.T) {
// declined to about 72% (but foundation funding probably gone anyway)
a.InDelta(0.72, float64(bonus)/float64(plan.BaseAmount), 0.01)
}
+
+func TestAlive(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ bh := BlockHeader{
+ Round: 0,
+ GenesisHash: crypto.Digest{0x42},
+ }
+ bh.CurrentProtocol = protocol.ConsensusCurrentVersion
+
+ header := transactions.Header{
+ FirstValid: 5000,
+ LastValid: 5050,
+ GenesisID: bh.GenesisID,
+ GenesisHash: bh.GenesisHash,
+ }
+
+ bh.Round = header.FirstValid + 1
+ if err := bh.Alive(header); err != nil {
+ t.Errorf("transaction not alive during lifetime %v", err)
+ }
+
+ bh.Round = header.FirstValid
+ if err := bh.Alive(header); err != nil {
+ t.Errorf("transaction not alive at issuance %v", err)
+ }
+
+ bh.Round = header.LastValid
+ if err := bh.Alive(header); err != nil {
+ t.Errorf("transaction not alive at expiry %v", err)
+ }
+
+ bh.Round = header.FirstValid - 1
+ if bh.Alive(header) == nil {
+ t.Errorf("premature transaction alive %v", header)
+ }
+
+ bh.Round = header.LastValid + 1
+ if bh.Alive(header) == nil {
+ t.Errorf("expired transaction alive %v", header)
+ }
+}
diff --git a/data/committee/common_test.go b/data/committee/common_test.go
index 98437117db..f6dd60ecf2 100644
--- a/data/committee/common_test.go
+++ b/data/committee/common_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/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/protocol"
)
@@ -97,7 +98,7 @@ func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, seedGen io.Reader
if !ok {
return false, BalanceRecord{}, Seed{}, basics.MicroAlgos{Raw: 0}
}
- return true, BalanceRecord{Addr: addr, OnlineAccountData: data.OnlineAccountData()}, seed, total
+ return true, BalanceRecord{Addr: addr, OnlineAccountData: basics_testing.OnlineAccountData(data)}, seed, total
}
selParamsList := func(addrs []basics.Address) (ok bool, records []BalanceRecord, seed Seed, total basics.MicroAlgos) {
diff --git a/data/ledger.go b/data/ledger.go
index 131aa8dd2f..01e2065191 100644
--- a/data/ledger.go
+++ b/data/ledger.go
@@ -118,25 +118,21 @@ func LoadLedger[T string | ledger.DirsAndPrefix](
return l, nil
}
-// AddressTxns returns the list of transactions to/from a given address in specific round
-func (l *Ledger) AddressTxns(id basics.Address, r basics.Round) ([]transactions.SignedTxnWithAD, error) {
+// TxnsFrom returns the list of transactions sent by a given address in a round
+func (l *Ledger) TxnsFrom(id basics.Address, r basics.Round) ([]transactions.Transaction, error) {
blk, err := l.Block(r)
if err != nil {
return nil, err
}
- spec := transactions.SpecialAddresses{
- FeeSink: blk.FeeSink,
- RewardsPool: blk.RewardsPool,
- }
- var res []transactions.SignedTxnWithAD
+ var res []transactions.Transaction
payset, err := blk.DecodePaysetFlat()
if err != nil {
return nil, err
}
for _, tx := range payset {
- if tx.Txn.MatchAddress(id, spec) {
- res = append(res, tx)
+ if id == tx.Txn.Sender {
+ res = append(res, tx.Txn)
}
}
return res, nil
@@ -289,7 +285,7 @@ func (l *Ledger) ConsensusVersion(r basics.Round) (protocol.ConsensusVersion, er
// no protocol upgrade taking place, we have *at least* UpgradeVoteRounds before the protocol version would get changed.
// it's safe to ignore the error case here since we know that we couldn't reached to this "known" round
// without having the binary supporting this protocol version.
- currentConsensusParams, _ := config.Consensus[latestBlockhdr.CurrentProtocol]
+ currentConsensusParams := config.Consensus[latestBlockhdr.CurrentProtocol]
// we're using <= here since there is no current upgrade on this round, and if there will be one on the subsequent round
// it would still be correct until (latestBlockhdr.Round + currentConsensusParams.UpgradeVoteRounds)
if r <= latestBlockhdr.Round+basics.Round(currentConsensusParams.UpgradeVoteRounds) {
diff --git a/data/ledger_test.go b/data/ledger_test.go
index 0112940c9e..50efbd0339 100644
--- a/data/ledger_test.go
+++ b/data/ledger_test.go
@@ -682,6 +682,10 @@ func TestLedgerErrorValidate(t *testing.T) {
require.LessOrEqual(t, attemptedRound, dbRound)
require.GreaterOrEqual(t, int(l.Latest()), dbRound+int(cfg.MaxAcctLookback))
um = ""
+ } else if strings.Contains(um, "rolling back failed commitRound") {
+ // this in-memory ledger is expected to hit "database table is locked" errors
+ // so that retry logic is exercised and trackers notified of the rollback
+ um = ""
}
require.Empty(t, um, um)
default:
diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go
index 4c36bba6f1..dfdc633262 100644
--- a/data/pools/transactionPool.go
+++ b/data/pools/transactionPool.go
@@ -617,7 +617,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio
r := pool.pendingBlockEvaluator.Round() + pool.numPendingWholeBlocks
for _, tx := range txgroup {
if tx.Txn.LastValid < r {
- return &transactions.TxnDeadError{
+ return &bookkeeping.TxnDeadError{
Round: r,
FirstValid: tx.Txn.FirstValid,
LastValid: tx.Txn.LastValid,
@@ -779,7 +779,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIDs map[transact
case *ledgercore.TransactionInLedgerError:
asmStats.CommittedCount++
stats.RemovedInvalidCount++
- case *transactions.TxnDeadError:
+ case *bookkeeping.TxnDeadError:
if int(terr.LastValid-terr.FirstValid) > 20 {
// cutoff value here is picked as a somewhat arbitrary cutoff trying to separate longer lived transactions from very short lived ones
asmStats.ExpiredLongLivedCount++
diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go
index f0bff7a69a..e3bf0a378f 100644
--- a/data/pools/transactionPool_test.go
+++ b/data/pools/transactionPool_test.go
@@ -19,6 +19,7 @@ package pools
import (
"bufio"
"bytes"
+ "context"
"fmt"
"math/rand"
"os"
@@ -1438,7 +1439,7 @@ func TestStateProofLogging(t *testing.T) {
// Set the logging to capture the telemetry Metrics into logging
logger := logging.TestingLog(t)
logger.SetLevel(logging.Info)
- logger.EnableTelemetry(logging.TelemetryConfig{Enable: true, SendToLog: true})
+ logger.EnableTelemetryContext(context.Background(), logging.TelemetryConfig{Enable: true, SendToLog: true})
var buf bytes.Buffer
logger.SetOutput(&buf)
diff --git a/data/transactions/application.go b/data/transactions/application.go
index 70c0189394..21793b1976 100644
--- a/data/transactions/application.go
+++ b/data/transactions/application.go
@@ -20,6 +20,7 @@ import (
"fmt"
"slices"
+ "github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
)
@@ -163,7 +164,11 @@ type ApplicationCallTxnFields struct {
// ExtraProgramPages specifies the additional app program len requested in pages.
// A page is MaxAppProgramLen bytes. This field enables execution of app programs
// larger than the default config, MaxAppProgramLen.
- ExtraProgramPages uint32 `codec:"apep,omitempty"`
+ ExtraProgramPages uint32 `codec:"apep"`
+
+ // RejectVersion is the lowest application version for which this
+ // transaction should immediately fail. 0 indicates that no version check should be performed.
+ RejectVersion uint64 `codec:"aprv"`
// If you add any fields here, remember you MUST modify the Empty
// method below!
@@ -216,9 +221,139 @@ func (ac *ApplicationCallTxnFields) Empty() bool {
if ac.ExtraProgramPages != 0 {
return false
}
+ if ac.RejectVersion != 0 {
+ return false
+ }
return true
}
+// wellFormed performs some stateless checks on the ApplicationCall transaction
+func (ac ApplicationCallTxnFields) wellFormed(proto config.ConsensusParams) error {
+
+ // Ensure requested action is valid
+ switch ac.OnCompletion {
+ case NoOpOC, OptInOC, CloseOutOC, ClearStateOC, UpdateApplicationOC, DeleteApplicationOC:
+ /* ok */
+ default:
+ return fmt.Errorf("invalid application OnCompletion")
+ }
+
+ if !proto.EnableAppVersioning && ac.RejectVersion > 0 {
+ return fmt.Errorf("tx.RejectVersion is not supported")
+ }
+
+ if ac.RejectVersion > 0 && ac.ApplicationID == 0 {
+ return fmt.Errorf("tx.RejectVersion cannot be set during creation")
+ }
+
+ // Programs may only be set for creation or update
+ if ac.ApplicationID != 0 && ac.OnCompletion != UpdateApplicationOC {
+ if len(ac.ApprovalProgram) != 0 || len(ac.ClearStateProgram) != 0 {
+ return fmt.Errorf("programs may only be specified during application creation or update")
+ }
+ } else {
+ // This will check version matching, but not downgrading. That
+ // depends on chain state (so we pass an empty AppParams)
+ err := CheckContractVersions(ac.ApprovalProgram, ac.ClearStateProgram, basics.AppParams{}, &proto)
+ if err != nil {
+ return err
+ }
+ }
+
+ effectiveEPP := ac.ExtraProgramPages
+ // Schemas and ExtraProgramPages may only be set during application creation
+ if ac.ApplicationID != 0 {
+ if ac.LocalStateSchema != (basics.StateSchema{}) ||
+ ac.GlobalStateSchema != (basics.StateSchema{}) {
+ return fmt.Errorf("local and global state schemas are immutable")
+ }
+ if ac.ExtraProgramPages != 0 {
+ return fmt.Errorf("tx.ExtraProgramPages is immutable")
+ }
+
+ if proto.EnableExtraPagesOnAppUpdate {
+ effectiveEPP = uint32(proto.MaxExtraAppProgramPages)
+ }
+
+ }
+
+ // Limit total number of arguments
+ if len(ac.ApplicationArgs) > proto.MaxAppArgs {
+ return fmt.Errorf("too many application args, max %d", proto.MaxAppArgs)
+ }
+
+ // Sum up argument lengths
+ var argSum uint64
+ for _, arg := range ac.ApplicationArgs {
+ argSum = basics.AddSaturate(argSum, uint64(len(arg)))
+ }
+
+ // Limit total length of all arguments
+ if argSum > uint64(proto.MaxAppTotalArgLen) {
+ return fmt.Errorf("application args total length too long, max len %d bytes", proto.MaxAppTotalArgLen)
+ }
+
+ // Limit number of accounts referred to in a single ApplicationCall
+ if len(ac.Accounts) > proto.MaxAppTxnAccounts {
+ return fmt.Errorf("tx.Accounts too long, max number of accounts is %d", proto.MaxAppTxnAccounts)
+ }
+
+ // Limit number of other app global states referred to
+ if len(ac.ForeignApps) > proto.MaxAppTxnForeignApps {
+ return fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is %d", proto.MaxAppTxnForeignApps)
+ }
+
+ if len(ac.ForeignAssets) > proto.MaxAppTxnForeignAssets {
+ return fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is %d", proto.MaxAppTxnForeignAssets)
+ }
+
+ if len(ac.Boxes) > proto.MaxAppBoxReferences {
+ return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences)
+ }
+
+ // Limit the sum of all types of references that bring in account records
+ if len(ac.Accounts)+len(ac.ForeignApps)+len(ac.ForeignAssets)+len(ac.Boxes) > proto.MaxAppTotalTxnReferences {
+ return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences)
+ }
+
+ if ac.ExtraProgramPages > uint32(proto.MaxExtraAppProgramPages) {
+ return fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", proto.MaxExtraAppProgramPages)
+ }
+
+ lap := len(ac.ApprovalProgram)
+ lcs := len(ac.ClearStateProgram)
+ pages := int(1 + effectiveEPP)
+ if lap > pages*proto.MaxAppProgramLen {
+ return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
+ }
+ if lcs > pages*proto.MaxAppProgramLen {
+ return fmt.Errorf("clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
+ }
+ if lap+lcs > pages*proto.MaxAppTotalProgramLen {
+ return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen)
+ }
+
+ for i, br := range ac.Boxes {
+ // recall 0 is the current app so indexes are shifted, thus test is for greater than, not gte.
+ if br.Index > uint64(len(ac.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 ac.LocalStateSchema.NumEntries() > proto.MaxLocalSchemaEntries {
+ return fmt.Errorf("tx.LocalStateSchema too large, max number of keys is %d", proto.MaxLocalSchemaEntries)
+ }
+
+ if ac.GlobalStateSchema.NumEntries() > proto.MaxGlobalSchemaEntries {
+ return fmt.Errorf("tx.GlobalStateSchema too large, max number of keys is %d", proto.MaxGlobalSchemaEntries)
+ }
+
+ return nil
+}
+
// AddressByIndex converts an integer index into an address associated with the
// transaction. Index 0 corresponds to the transaction sender, and an index > 0
// corresponds to an offset into txn.Accounts. Returns an error if the index is
diff --git a/data/transactions/application_test.go b/data/transactions/application_test.go
index ce6d700e75..3f7f7e8d17 100644
--- a/data/transactions/application_test.go
+++ b/data/transactions/application_test.go
@@ -17,13 +17,17 @@
package transactions
import (
+ "fmt"
"reflect"
+ "strings"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -33,7 +37,7 @@ func TestApplicationCallFieldsNotChanged(t *testing.T) {
af := ApplicationCallTxnFields{}
s := reflect.ValueOf(&af).Elem()
- if s.NumField() != 13 {
+ if s.NumField() != 14 {
t.Errorf("You added or removed a field from transactions.ApplicationCallTxnFields. " +
"Please ensure you have updated the Empty() method and then " +
"fix this test")
@@ -60,6 +64,10 @@ func TestApplicationCallFieldsEmpty(t *testing.T) {
a.False(ac.Empty())
ac.ApplicationArgs = nil
+ ac.RejectVersion = 1
+ a.False(ac.Empty())
+
+ ac.RejectVersion = 0
ac.Accounts = make([]basics.Address, 1)
a.False(ac.Empty())
@@ -124,3 +132,507 @@ func TestEncodedAppTxnAllocationBounds(t *testing.T) {
}
}
}
+
+func TestAppCallVersioningWellFormed(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ preVersion := protocol.ConsensusV40
+ v5 := []byte{0x05}
+
+ cases := []struct {
+ expectedError string
+ cv protocol.ConsensusVersion // defaults to future if not set
+ ac ApplicationCallTxnFields
+ }{
+ {
+ cv: preVersion,
+ ac: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ RejectVersion: 0,
+ },
+ },
+ {
+ expectedError: "tx.RejectVersion is not supported",
+ cv: preVersion,
+ ac: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ RejectVersion: 1,
+ },
+ },
+ {
+ ac: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ RejectVersion: 0,
+ },
+ },
+ {
+ ac: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ RejectVersion: 1,
+ },
+ },
+ {
+ ac: ApplicationCallTxnFields{
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ RejectVersion: 0,
+ },
+ },
+ {
+ expectedError: "tx.RejectVersion cannot be set during creation",
+ ac: ApplicationCallTxnFields{
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ RejectVersion: 1,
+ },
+ },
+ }
+ for i, tc := range cases {
+ name := fmt.Sprintf("i=%d", i)
+ if tc.expectedError != "" {
+ name = tc.expectedError
+ }
+ t.Run(name, func(t *testing.T) {
+ cv := tc.cv
+ if cv == "" {
+ cv = protocol.ConsensusFuture
+ }
+ err := tc.ac.wellFormed(config.Consensus[cv])
+ if tc.expectedError != "" {
+ require.ErrorContains(t, err, tc.expectedError)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestAppCallCreateWellFormed(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ futureProto := config.Consensus[protocol.ConsensusFuture]
+ addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+ v5 := []byte{0x05}
+ v6 := []byte{0x06}
+
+ usecases := []struct {
+ tx Transaction
+ proto config.ConsensusParams
+ expectedError string
+ }{
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ },
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ },
+ },
+ proto: curProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ },
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 0,
+ },
+ },
+ proto: curProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ },
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 3,
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ },
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 0,
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ },
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApprovalProgram: v5,
+ ClearStateProgram: v6,
+ },
+ },
+ proto: futureProto,
+ expectedError: "mismatch",
+ },
+ }
+ for i, usecase := range usecases {
+ t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
+ err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto)
+ if usecase.expectedError != "" {
+ require.Error(t, err)
+ require.Contains(t, err.Error(), usecase.expectedError)
+ } else {
+ require.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestWellFormedErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ futureProto := config.Consensus[protocol.ConsensusFuture]
+ 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}
+ okHeader := Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ }
+ usecases := []struct {
+ tx Transaction
+ proto config.ConsensusParams
+ expectedError error
+ }{
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0, // creation
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 1,
+ },
+ },
+ proto: protoV27,
+ expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", protoV27.MaxExtraAppProgramPages),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0, // creation
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte("Xjunk"),
+ },
+ },
+ proto: protoV27,
+ expectedError: fmt.Errorf("approval program too long. max len 1024 bytes"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0, // creation
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte("Xjunk"),
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0, // creation
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("app programs too long. max total len 2048 bytes"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0, // creation
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ ExtraProgramPages: 1,
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 1,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 0,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 4,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", futureProto.MaxExtraAppProgramPages),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ForeignApps: []basics.AppIndex{10, 11},
+ },
+ },
+ proto: protoV27,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ForeignApps: []basics.AppIndex{10, 11, 12},
+ },
+ },
+ proto: protoV27,
+ expectedError: fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is 2"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ForeignApps: []basics.AppIndex{10, 11, 12, 13, 14, 15, 16, 17},
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ForeignAssets: []basics.AssetIndex{14, 15, 16, 17, 18, 19, 20, 21, 22},
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is 8"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ Accounts: []basics.Address{{}, {}, {}},
+ ForeignApps: []basics.AppIndex{14, 15, 16, 17},
+ ForeignAssets: []basics.AssetIndex{14, 15, 16, 17},
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ ExtraProgramPages: 0,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ proto: protoV28,
+ expectedError: fmt.Errorf("app programs too long. max total len %d bytes", curProto.MaxAppProgramLen),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApprovalProgram: []byte(strings.Repeat("X", 1025)),
+ ClearStateProgram: []byte(strings.Repeat("X", 1025)),
+ ExtraProgramPages: 0,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ ApprovalProgram: v5,
+ ClearStateProgram: v5,
+ ApplicationArgs: [][]byte{
+ []byte("write"),
+ },
+ ExtraProgramPages: 1,
+ OnCompletion: UpdateApplicationOC,
+ },
+ },
+ proto: protoV28,
+ expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.Boxes[0].Index is 1. Exceeds len(tx.ForeignApps)"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
+ ForeignApps: []basics.AppIndex{1},
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.ApplicationCallTx,
+ Header: okHeader,
+ ApplicationCallTxnFields: ApplicationCallTxnFields{
+ ApplicationID: 1,
+ Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
+ ForeignApps: []basics.AppIndex{1},
+ },
+ },
+ 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(SpecialAddresses{}, usecase.proto)
+ assert.Equal(t, usecase.expectedError, err)
+ }
+}
diff --git a/data/transactions/common_test.go b/data/transactions/common_test.go
index 08ce7cc950..016973eb3f 100644
--- a/data/transactions/common_test.go
+++ b/data/transactions/common_test.go
@@ -25,7 +25,7 @@ import (
"github.com/algorand/go-algorand/protocol"
)
-func generateTestObjects(numTxs, numAccs int) ([]Transaction, []SignedTxn, []*crypto.SignatureSecrets, []basics.Address) {
+func generateSignedTxns(numTxs, numAccs int) []SignedTxn {
txs := make([]Transaction, numTxs)
signed := make([]SignedTxn, numTxs)
secrets := make([]*crypto.SignatureSecrets, numAccs)
@@ -62,5 +62,5 @@ func generateTestObjects(numTxs, numAccs int) ([]Transaction, []SignedTxn, []*cr
signed[i] = txs[i].Sign(secrets[s])
}
- return txs, signed, secrets, addresses
+ return signed
}
diff --git a/data/transactions/error.go b/data/transactions/error.go
index 2cd3e0beeb..33a279576e 100644
--- a/data/transactions/error.go
+++ b/data/transactions/error.go
@@ -18,8 +18,6 @@ package transactions
import (
"fmt"
-
- "github.com/algorand/go-algorand/data/basics"
)
// MinFeeError defines an error type which could be returned from the method WellFormed
@@ -35,16 +33,3 @@ func makeMinFeeErrorf(format string, args ...interface{}) *MinFeeError {
err := MinFeeError(fmt.Sprintf(format, args...))
return &err
}
-
-// TxnDeadError defines an error type which indicates a transaction is outside of the
-// round validity window.
-type TxnDeadError struct {
- Round basics.Round
- FirstValid basics.Round
- LastValid basics.Round
- Early bool
-}
-
-func (err *TxnDeadError) Error() string {
- return fmt.Sprintf("txn dead: round %d outside of %d--%d", err.Round, err.FirstValid, err.LastValid)
-}
diff --git a/data/transactions/heartbeat.go b/data/transactions/heartbeat.go
index 9dd870d59d..e5f6d7faf5 100644
--- a/data/transactions/heartbeat.go
+++ b/data/transactions/heartbeat.go
@@ -17,6 +17,10 @@
package transactions
import (
+ "errors"
+ "fmt"
+
+ "github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/committee"
@@ -47,3 +51,38 @@ type HeartbeatTxnFields struct {
// HbKeyDilution must match HbAddress account's current KeyDilution.
HbKeyDilution uint64 `codec:"kd"`
}
+
+// wellFormed performs some stateless checks on the Heartbeat transaction
+func (hb HeartbeatTxnFields) wellFormed(header Header, proto config.ConsensusParams) error {
+ // If this is a free/cheap heartbeat, it must be very simple.
+ if header.Fee.Raw < proto.MinTxnFee && header.Group.IsZero() {
+ kind := "free"
+ if header.Fee.Raw > 0 {
+ kind = "cheap"
+ }
+
+ if len(header.Note) > 0 {
+ return fmt.Errorf("tx.Note is set in %s heartbeat", kind)
+ }
+ if header.Lease != [32]byte{} {
+ return fmt.Errorf("tx.Lease is set in %s heartbeat", kind)
+ }
+ if !header.RekeyTo.IsZero() {
+ return fmt.Errorf("tx.RekeyTo is set in %s heartbeat", kind)
+ }
+ }
+
+ if (hb.HbProof == crypto.HeartbeatProof{}) {
+ return errors.New("tx.HbProof is empty")
+ }
+ if (hb.HbSeed == committee.Seed{}) {
+ return errors.New("tx.HbSeed is empty")
+ }
+ if hb.HbVoteID.IsEmpty() {
+ return errors.New("tx.HbVoteID is empty")
+ }
+ if hb.HbKeyDilution == 0 {
+ return errors.New("tx.HbKeyDilution is zero")
+ }
+ return nil
+}
diff --git a/data/transactions/heartbeat_test.go b/data/transactions/heartbeat_test.go
new file mode 100644
index 0000000000..64f637f228
--- /dev/null
+++ b/data/transactions/heartbeat_test.go
@@ -0,0 +1,202 @@
+// Copyright (C) 2019-2025 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 transactions
+
+import (
+ "fmt"
+ "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/committee"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestWellFormedHeartbeatErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ futureProto := config.Consensus[protocol.ConsensusFuture]
+ protoV36 := config.Consensus[protocol.ConsensusV36]
+ addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+ okHeader := Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 105,
+ FirstValid: 100,
+ }
+ usecases := []struct {
+ tx Transaction
+ proto config.ConsensusParams
+ expectedError error
+ }{
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ },
+ proto: protoV36,
+ expectedError: fmt.Errorf("heartbeat transaction not supported"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.HbProof is empty"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.HbSeed is empty"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.HbVoteID is empty"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.HbKeyDilution is zero"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: okHeader,
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 100},
+ LastValid: 105,
+ FirstValid: 100,
+ Note: []byte{0x01},
+ },
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.Note is set in cheap heartbeat"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 100},
+ LastValid: 105,
+ FirstValid: 100,
+ Lease: [32]byte{0x01},
+ },
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.Lease is set in cheap heartbeat"),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.HeartbeatTx,
+ Header: Header{
+ Sender: addr1,
+ LastValid: 105,
+ FirstValid: 100,
+ RekeyTo: [32]byte{0x01},
+ },
+ HeartbeatTxnFields: &HeartbeatTxnFields{
+ HbProof: crypto.HeartbeatProof{
+ Sig: [64]byte{0x01},
+ },
+ HbSeed: committee.Seed{0x02},
+ HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
+ HbKeyDilution: 10,
+ },
+ },
+ proto: futureProto,
+ expectedError: fmt.Errorf("tx.RekeyTo is set in free heartbeat"),
+ },
+ }
+ for _, usecase := range usecases {
+ err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto)
+ assert.Equal(t, usecase.expectedError, err)
+ }
+}
diff --git a/data/transactions/keyreg.go b/data/transactions/keyreg.go
index 56789eda43..c4d0ed2628 100644
--- a/data/transactions/keyreg.go
+++ b/data/transactions/keyreg.go
@@ -17,6 +17,10 @@
package transactions
import (
+ "errors"
+ "fmt"
+
+ "github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/data/basics"
@@ -34,3 +38,110 @@ type KeyregTxnFields struct {
VoteKeyDilution uint64 `codec:"votekd"`
Nonparticipation bool `codec:"nonpart"`
}
+
+var errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound = errors.New("transaction first voting round need to be less than its last voting round")
+var errKeyregTxnNonCoherentVotingKeys = errors.New("the following transaction fields need to be clear/set together : votekey, selkey, votekd")
+var errKeyregTxnOfflineTransactionHasVotingRounds = errors.New("on going offline key registration transaction, the vote first and vote last fields should not be set")
+var errKeyregTxnUnsupportedSwitchToNonParticipating = errors.New("transaction tries to mark an account as nonparticipating, but that transaction is not supported")
+var errKeyregTxnGoingOnlineWithNonParticipating = errors.New("transaction tries to register keys to go online, but nonparticipatory flag is set")
+var errKeyregTxnGoingOnlineWithZeroVoteLast = errors.New("transaction tries to register keys to go online, but vote last is set to zero")
+var errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid = errors.New("transaction tries to register keys to go online, but first voting round is beyond the round after last valid round")
+var errKeyRegEmptyStateProofPK = errors.New("online keyreg transaction cannot have empty field StateProofPK")
+var errKeyregTxnNotEmptyStateProofPK = errors.New("transaction field StateProofPK should be empty in this consensus version")
+var errKeyregTxnNonParticipantShouldBeEmptyStateProofPK = errors.New("non participation keyreg transactions should contain empty stateProofPK")
+var errKeyregTxnOfflineShouldBeEmptyStateProofPK = errors.New("offline keyreg transactions should contain empty stateProofPK")
+var errKeyRegTxnValidityPeriodTooLong = errors.New("validity period for keyreg transaction is too long")
+
+// wellFormed performs some stateless checks on the KeyReg transaction
+func (keyreg KeyregTxnFields) wellFormed(header Header, spec SpecialAddresses, proto config.ConsensusParams) error {
+ if header.Sender == spec.FeeSink {
+ return fmt.Errorf("cannot register participation key for fee sink's address %v", header.Sender)
+ }
+
+ if proto.EnableKeyregCoherencyCheck {
+ // ensure that the VoteLast is greater or equal to the VoteFirst
+ if keyreg.VoteFirst > keyreg.VoteLast {
+ return errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound
+ }
+
+ // The trio of [VotePK, SelectionPK, VoteKeyDilution] needs to be all zeros or all non-zero for the transaction to be valid.
+ if !((keyreg.VotePK.IsEmpty() && keyreg.SelectionPK.IsEmpty() && keyreg.VoteKeyDilution == 0) ||
+ (!keyreg.VotePK.IsEmpty() && !keyreg.SelectionPK.IsEmpty() && keyreg.VoteKeyDilution != 0)) {
+ return errKeyregTxnNonCoherentVotingKeys
+ }
+
+ // if it's a going offline transaction
+ if keyreg.VoteKeyDilution == 0 {
+ // check that we don't have any VoteFirst/VoteLast fields.
+ if keyreg.VoteFirst != 0 || keyreg.VoteLast != 0 {
+ return errKeyregTxnOfflineTransactionHasVotingRounds
+ }
+ } else {
+ // going online
+ if keyreg.VoteLast == 0 {
+ return errKeyregTxnGoingOnlineWithZeroVoteLast
+ }
+ if keyreg.VoteFirst > header.LastValid+1 {
+ return errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid
+ }
+ }
+ }
+
+ // check that, if this tx is marking an account nonparticipating,
+ // it supplies no key (as though it were trying to go offline)
+ if keyreg.Nonparticipation {
+ if !proto.SupportBecomeNonParticipatingTransactions {
+ // if the transaction has the Nonparticipation flag high, but the protocol does not support
+ // that type of transaction, it is invalid.
+ return errKeyregTxnUnsupportedSwitchToNonParticipating
+ }
+ suppliesNullKeys := keyreg.VotePK.IsEmpty() || keyreg.SelectionPK.IsEmpty()
+ if !suppliesNullKeys {
+ return errKeyregTxnGoingOnlineWithNonParticipating
+ }
+ }
+
+ if err := keyreg.stateProofPKWellFormed(proto); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (keyreg KeyregTxnFields) stateProofPKWellFormed(proto config.ConsensusParams) error {
+ isEmpty := keyreg.StateProofPK.IsEmpty()
+ if !proto.EnableStateProofKeyregCheck {
+ // make certain empty key is stored.
+ if !isEmpty {
+ return errKeyregTxnNotEmptyStateProofPK
+ }
+ return nil
+ }
+
+ if proto.MaxKeyregValidPeriod != 0 && uint64(keyreg.VoteLast.SubSaturate(keyreg.VoteFirst)) > proto.MaxKeyregValidPeriod {
+ return errKeyRegTxnValidityPeriodTooLong
+ }
+
+ if keyreg.Nonparticipation {
+ // make certain that set offline request clears the stateProofPK.
+ if !isEmpty {
+ return errKeyregTxnNonParticipantShouldBeEmptyStateProofPK
+ }
+ return nil
+ }
+
+ if keyreg.VotePK.IsEmpty() || keyreg.SelectionPK.IsEmpty() {
+ if !isEmpty {
+ return errKeyregTxnOfflineShouldBeEmptyStateProofPK
+ }
+ return nil
+ }
+
+ // online transactions:
+ // setting online cannot set an empty stateProofPK
+ if isEmpty {
+ return errKeyRegEmptyStateProofPK
+ }
+
+ return nil
+}
diff --git a/data/transactions/keyreg_test.go b/data/transactions/keyreg_test.go
new file mode 100644
index 0000000000..fd0d4b4a92
--- /dev/null
+++ b/data/transactions/keyreg_test.go
@@ -0,0 +1,792 @@
+// Copyright (C) 2019-2025 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 transactions
+
+import (
+ "flag"
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/crypto/merklesignature"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+var generateFlag = flag.Bool("generate", false, "")
+
+// running test with -generate would generate the matrix used in the test ( without the "correct" errors )
+func TestWellFormedKeyRegistrationTx(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ flag.Parse()
+
+ // addr has no significance here other than being a normal valid address
+ addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+
+ tx := generateDummyGoNonparticpatingTransaction(addr)
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ if !curProto.SupportBecomeNonParticipatingTransactions {
+ t.Skipf("Skipping rest of test because current protocol version %v does not support become-nonparticipating transactions", protocol.ConsensusCurrentVersion)
+ }
+
+ // this tx is well-formed
+ err = tx.WellFormed(SpecialAddresses{}, curProto)
+ require.NoError(t, err)
+
+ type keyRegTestCase struct {
+ votePK crypto.OneTimeSignatureVerifier
+ selectionPK crypto.VRFVerifier
+ stateProofPK merklesignature.Commitment
+ voteFirst basics.Round
+ voteLast basics.Round
+ lastValid basics.Round
+ voteKeyDilution uint64
+ nonParticipation bool
+ supportBecomeNonParticipatingTransactions bool
+ enableKeyregCoherencyCheck bool
+ enableStateProofKeyregCheck bool
+ err error
+ }
+ votePKValue := crypto.OneTimeSignatureVerifier{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}
+ selectionPKValue := crypto.VRFVerifier{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}
+
+ stateProofPK := merklesignature.Commitment([merklesignature.MerkleSignatureSchemeRootSize]byte{1})
+ maxValidPeriod := config.Consensus[protocol.ConsensusCurrentVersion].MaxKeyregValidPeriod
+
+ runTestCase := func(testCase keyRegTestCase) error {
+
+ tx.KeyregTxnFields.VotePK = testCase.votePK
+ tx.KeyregTxnFields.SelectionPK = testCase.selectionPK
+ tx.KeyregTxnFields.VoteFirst = testCase.voteFirst
+ tx.KeyregTxnFields.VoteLast = testCase.voteLast
+ tx.KeyregTxnFields.VoteKeyDilution = testCase.voteKeyDilution
+ tx.KeyregTxnFields.Nonparticipation = testCase.nonParticipation
+ tx.LastValid = testCase.lastValid
+ tx.KeyregTxnFields.StateProofPK = testCase.stateProofPK
+
+ curProto.SupportBecomeNonParticipatingTransactions = testCase.supportBecomeNonParticipatingTransactions
+ curProto.EnableKeyregCoherencyCheck = testCase.enableKeyregCoherencyCheck
+ curProto.EnableStateProofKeyregCheck = testCase.enableStateProofKeyregCheck
+ curProto.MaxKeyregValidPeriod = maxValidPeriod // TODO: remove this when MaxKeyregValidPeriod is in CurrentVersion
+ return tx.WellFormed(SpecialAddresses{}, curProto)
+ }
+
+ if *generateFlag == true {
+ fmt.Printf("keyRegTestCases := []keyRegTestCase{\n")
+ idx := 0
+ for _, votePK := range []crypto.OneTimeSignatureVerifier{{}, votePKValue} {
+ for _, selectionPK := range []crypto.VRFVerifier{{}, selectionPKValue} {
+ for _, voteFirst := range []basics.Round{basics.Round(0), basics.Round(5)} {
+ for _, voteLast := range []basics.Round{basics.Round(0), basics.Round(10)} {
+ for _, lastValid := range []basics.Round{basics.Round(4), basics.Round(3)} {
+ for _, voteKeyDilution := range []uint64{0, 10000} {
+ for _, nonParticipation := range []bool{false, true} {
+ for _, supportBecomeNonParticipatingTransactions := range []bool{false, true} {
+ for _, enableKeyregCoherencyCheck := range []bool{false, true} {
+ for _, enableStateProofKeyregCheck := range []bool{false, true} {
+ outcome := runTestCase(keyRegTestCase{
+ votePK,
+ selectionPK,
+ stateProofPK,
+ voteFirst,
+ voteLast,
+ lastValid,
+ voteKeyDilution,
+ nonParticipation,
+ supportBecomeNonParticipatingTransactions,
+ enableKeyregCoherencyCheck,
+ enableStateProofKeyregCheck,
+ nil})
+ errStr := "nil"
+ switch outcome {
+ case errKeyregTxnUnsupportedSwitchToNonParticipating:
+ errStr = "errKeyregTxnUnsupportedSwitchToNonParticipating"
+ case errKeyregTxnGoingOnlineWithNonParticipating:
+ errStr = "errKeyregTxnGoingOnlineWithNonParticipating"
+ case errKeyregTxnNonCoherentVotingKeys:
+ errStr = "errKeyregTxnNonCoherentVotingKeys"
+ case errKeyregTxnOfflineTransactionHasVotingRounds:
+ errStr = "errKeyregTxnOfflineTransactionHasVotingRounds"
+ case errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound:
+ errStr = "errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound"
+ case errKeyregTxnGoingOnlineWithZeroVoteLast:
+ errStr = "errKeyregTxnGoingOnlineWithZeroVoteLast"
+ case errKeyregTxnGoingOnlineWithNonParticipating:
+ errStr = "errKeyregTxnGoingOnlineWithNonParticipating"
+ case errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid:
+ errStr = "errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid"
+ default:
+ require.Nil(t, outcome)
+
+ }
+ s := "/* %3d */ keyRegTestCase{votePK:"
+ if votePK == votePKValue {
+ s += "votePKValue"
+ } else {
+ s += "crypto.OneTimeSignatureVerifier{}"
+ }
+ s += ", selectionPK:"
+ if selectionPK == selectionPKValue {
+ s += "selectionPKValue"
+ } else {
+ s += "crypto.VRFVerifier{}"
+ }
+ s = fmt.Sprintf("%s, voteFirst:basics.Round(%2d), voteLast:basics.Round(%2d), lastValid:basics.Round(%2d), voteKeyDilution: %5d, nonParticipation: %v,supportBecomeNonParticipatingTransactions:%v, enableKeyregCoherencyCheck:%v, err:%s},\n",
+ s, voteFirst, voteLast, lastValid, voteKeyDilution, nonParticipation, supportBecomeNonParticipatingTransactions, enableKeyregCoherencyCheck, errStr)
+ fmt.Printf(s, idx)
+ idx++
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ fmt.Printf("}\n")
+ return
+ }
+ keyRegTestCases := []keyRegTestCase{
+ /* 0 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 1 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
+ /* 2 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 3 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 4 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 5 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 6 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 7 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 8 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 9 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 10 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 11 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 12 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 13 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 14 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 15 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 16 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 17 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
+ /* 18 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 19 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 20 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 21 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 22 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 23 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 24 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 25 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 26 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 27 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 28 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 29 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 30 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 31 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 32 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 33 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 34 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 35 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 36 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 37 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 38 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 39 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 40 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 41 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 42 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 43 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 44 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 45 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 46 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 47 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 48 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 49 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 50 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 51 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 52 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 53 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 54 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 55 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 56 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 57 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 58 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 59 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 60 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 61 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 62 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 63 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 64 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 65 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 66 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 67 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 68 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 69 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 70 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 71 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 72 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 73 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 74 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 75 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 76 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 77 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 78 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 79 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 80 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 81 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 82 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 83 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 84 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 85 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 86 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 87 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 88 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 89 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 90 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 91 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 92 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 93 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 94 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 95 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 96 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 97 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 98 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 99 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 100 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 101 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 102 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 103 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 104 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 105 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 106 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 107 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 108 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 109 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 110 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 111 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 112 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 113 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 114 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 115 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 116 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 117 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 118 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 119 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
+ /* 120 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 121 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 122 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 123 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 124 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 125 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 126 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 127 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 128 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 129 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 130 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 131 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 132 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 133 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 134 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 135 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 136 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 137 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 138 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 139 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 140 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 141 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 142 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 143 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 144 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 145 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 146 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 147 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 148 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 149 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 150 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 151 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 152 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 153 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 154 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 155 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 156 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 157 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 158 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 159 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 160 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 161 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 162 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 163 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 164 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 165 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 166 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 167 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 168 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 169 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 170 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 171 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 172 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 173 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 174 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 175 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 176 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 177 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 178 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 179 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 180 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 181 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 182 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 183 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 184 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 185 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 186 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 187 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 188 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 189 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 190 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 191 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 192 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 193 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 194 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 195 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 196 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 197 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 198 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 199 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 200 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 201 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 202 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 203 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 204 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 205 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 206 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 207 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 208 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 209 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 210 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 211 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 212 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 213 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 214 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 215 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 216 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 217 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 218 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 219 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 220 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 221 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 222 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 223 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 224 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 225 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 226 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 227 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 228 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 229 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 230 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 231 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 232 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 233 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 234 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 235 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 236 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 237 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 238 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 239 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 240 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 241 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 242 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 243 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 244 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 245 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 246 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 247 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 248 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 249 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 250 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 251 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 252 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 253 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 254 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 255 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 256 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 257 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 258 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 259 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 260 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 261 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 262 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 263 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 264 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 265 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 266 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 267 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 268 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 269 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 270 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 271 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 272 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 273 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 274 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 275 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 276 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 277 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 278 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 279 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 280 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 281 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 282 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 283 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 284 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 285 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 286 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 287 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 288 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 289 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 290 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 291 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 292 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 293 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 294 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 295 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 296 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 297 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 298 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 299 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 300 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 301 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 302 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 303 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 304 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 305 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 306 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 307 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 308 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 309 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 310 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 311 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 312 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 313 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 314 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 315 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 316 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 317 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 318 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 319 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 320 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 321 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 322 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 323 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 324 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 325 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 326 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 327 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 328 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 329 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 330 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 331 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 332 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 333 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 334 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 335 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 336 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 337 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 338 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 339 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 340 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 341 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 342 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 343 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 344 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 345 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 346 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 347 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 348 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 349 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 350 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 351 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 352 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 353 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 354 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 355 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 356 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 357 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 358 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 359 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 360 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 361 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 362 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 363 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 364 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 365 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 366 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 367 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 368 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 369 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 370 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 371 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 372 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 373 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 374 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 375 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 376 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 377 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 378 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 379 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 380 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 381 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 382 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 383 */ {votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 384 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 385 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 386 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 387 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 388 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 389 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 390 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 391 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 392 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 393 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 394 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 395 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 396 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 397 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 398 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 399 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 400 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 401 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 402 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 403 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 404 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 405 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 406 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 407 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 408 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 409 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 410 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 411 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 412 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 413 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 414 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 415 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
+ /* 416 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 417 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 418 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 419 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 420 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 421 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 422 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 423 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 424 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 425 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
+ /* 426 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 427 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 428 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 429 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 430 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 431 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 432 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 433 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 434 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 435 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 436 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 437 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 438 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 439 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 440 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 441 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
+ /* 442 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 443 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 444 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 445 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 446 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 447 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 448 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 449 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 450 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 451 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 452 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 453 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 454 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 455 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 456 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 457 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 458 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 459 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 460 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 461 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 462 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 463 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 464 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 465 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 466 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 467 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 468 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 469 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 470 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 471 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 472 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 473 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 474 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 475 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 476 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 477 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 478 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 479 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
+ /* 480 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 481 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 482 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 483 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 484 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 485 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 486 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 487 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 488 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 489 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
+ /* 490 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 491 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
+ /* 492 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 493 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 494 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 495 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 496 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 497 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 498 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 499 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 500 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 501 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 502 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 503 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
+ /* 504 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
+ /* 505 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
+ /* 506 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
+ /* 507 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
+ /* 508 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
+ /* 509 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
+ /* 510 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
+ /* 511 */ {votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
+ /* 512 */ {votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: errKeyregTxnNotEmptyStateProofPK},
+ /* 513 */ {votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: nil},
+ /* 514 */ {votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
+ /* 515 */ {votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyRegEmptyStateProofPK},
+ /* 516 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyregTxnNonParticipantShouldBeEmptyStateProofPK},
+ /* 517 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
+ /* 518 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyregTxnOfflineShouldBeEmptyStateProofPK},
+ /* 519 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
+ /* 520 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
+ /* 521 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
+ /* 522 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10000 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyRegTxnValidityPeriodTooLong},
+ /* 523 */ {votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10000 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: nil},
+ }
+ for testcaseIdx, testCase := range keyRegTestCases {
+ err := runTestCase(testCase)
+
+ require.Equalf(t, testCase.err, err, "index: %d\ntest case: %#v", testcaseIdx, testCase)
+ }
+}
+
+func generateDummyGoNonparticpatingTransaction(addr basics.Address) (tx Transaction) {
+ buf := make([]byte, 10)
+ crypto.RandBytes(buf[:])
+
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+ tx = Transaction{
+ Type: protocol.KeyRegistrationTx,
+ Header: Header{
+ Sender: addr,
+ Fee: basics.MicroAlgos{Raw: proto.MinTxnFee},
+ FirstValid: 1,
+ LastValid: 300,
+ },
+ KeyregTxnFields: KeyregTxnFields{
+ Nonparticipation: true,
+ VoteFirst: 0,
+ VoteLast: 0,
+ },
+ }
+
+ tx.KeyregTxnFields.Nonparticipation = true
+ return tx
+}
+
+func TestGoOnlineGoNonparticipatingContradiction(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // addr has no significance here other than being a normal valid address
+ addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+
+ tx := generateDummyGoNonparticpatingTransaction(addr)
+ // Generate keys, they don't need to be good or secure, just present
+ v := crypto.GenerateOneTimeSignatureSecrets(1, 1)
+ // Also generate a new VRF key
+ vrf := crypto.GenerateVRFSecrets()
+ tx.KeyregTxnFields = KeyregTxnFields{
+ VotePK: v.OneTimeSignatureVerifier,
+ SelectionPK: vrf.PK,
+ VoteKeyDilution: 1,
+ VoteFirst: 1,
+ VoteLast: 100,
+ Nonparticipation: true,
+ }
+ // this tx tries to both register keys to go online, and mark an account as non-participating.
+ // it is not well-formed.
+ err = tx.WellFormed(SpecialAddresses{}, config.Consensus[protocol.ConsensusCurrentVersion])
+ require.ErrorContains(t, err, "tries to register keys to go online, but nonparticipatory flag is set")
+}
+
+func TestGoNonparticipatingWellFormed(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // addr has no significance here other than being a normal valid address
+ addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+
+ tx := generateDummyGoNonparticpatingTransaction(addr)
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+
+ if !curProto.SupportBecomeNonParticipatingTransactions {
+ t.Skipf("Skipping rest of test because current protocol version %v does not support become-nonparticipating transactions", protocol.ConsensusCurrentVersion)
+ }
+
+ // this tx is well-formed
+ err = tx.WellFormed(SpecialAddresses{}, curProto)
+ require.NoError(t, err)
+ // but it should stop being well-formed if the protocol does not support it
+ curProto.SupportBecomeNonParticipatingTransactions = false
+ err = tx.WellFormed(SpecialAddresses{}, curProto)
+ require.ErrorContains(t, err, "mark an account as nonparticipating, but")
+}
+
+func TestKeyregFeeSink(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+ spec := SpecialAddresses{
+ FeeSink: basics.Address{0x01},
+ RewardsPool: basics.Address{0x02},
+ }
+ err := Transaction{
+ Type: protocol.KeyRegistrationTx,
+ Header: Header{
+ Sender: spec.FeeSink,
+ Fee: basics.MicroAlgos{Raw: 100},
+ },
+ }.WellFormed(spec, proto)
+ require.ErrorContains(t, err, "cannot register participation key for fee sink")
+}
diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md
index 8f5fbee163..408eec057f 100644
--- a/data/transactions/logic/README.md
+++ b/data/transactions/logic/README.md
@@ -594,7 +594,7 @@ Some of these have immediate data in the byte or bytes after the opcode.
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/TEAL_opcodes_v10.md b/data/transactions/logic/TEAL_opcodes_v10.md
index 4e4491e095..bfd2c1e36b 100644
--- a/data/transactions/logic/TEAL_opcodes_v10.md
+++ b/data/transactions/logic/TEAL_opcodes_v10.md
@@ -426,7 +426,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/TEAL_opcodes_v11.md b/data/transactions/logic/TEAL_opcodes_v11.md
index f302bca1e5..3e38852ed5 100644
--- a/data/transactions/logic/TEAL_opcodes_v11.md
+++ b/data/transactions/logic/TEAL_opcodes_v11.md
@@ -426,7 +426,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/TEAL_opcodes_v6.md b/data/transactions/logic/TEAL_opcodes_v6.md
index bacca46d45..6621c3c84a 100644
--- a/data/transactions/logic/TEAL_opcodes_v6.md
+++ b/data/transactions/logic/TEAL_opcodes_v6.md
@@ -424,7 +424,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
## global
diff --git a/data/transactions/logic/TEAL_opcodes_v7.md b/data/transactions/logic/TEAL_opcodes_v7.md
index 74314af059..46752e9121 100644
--- a/data/transactions/logic/TEAL_opcodes_v7.md
+++ b/data/transactions/logic/TEAL_opcodes_v7.md
@@ -426,7 +426,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/TEAL_opcodes_v8.md b/data/transactions/logic/TEAL_opcodes_v8.md
index a1059bc50e..2c94de5a56 100644
--- a/data/transactions/logic/TEAL_opcodes_v8.md
+++ b/data/transactions/logic/TEAL_opcodes_v8.md
@@ -426,7 +426,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/TEAL_opcodes_v9.md b/data/transactions/logic/TEAL_opcodes_v9.md
index ac4482ce3e..68afbe47ee 100644
--- a/data/transactions/logic/TEAL_opcodes_v9.md
+++ b/data/transactions/logic/TEAL_opcodes_v9.md
@@ -426,7 +426,7 @@ Fields (see [transaction reference](https://developer.algorand.org/docs/referenc
| 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 |
| 62 | LastLog | []byte | v6 | The last message emitted. Empty bytes if none were emitted. Application mode only |
-| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key |
+| 63 | StateProofPK | [64]byte | v6 | State proof public key |
| 65 | NumApprovalProgramPages | uint64 | v7 | Number of Approval Program pages |
| 67 | NumClearStateProgramPages | uint64 | v7 | Number of ClearState Program pages |
diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go
index 36ec42b54b..910bfd441f 100644
--- a/data/transactions/logic/assembler_test.go
+++ b/data/transactions/logic/assembler_test.go
@@ -632,9 +632,8 @@ func testMatch(t testing.TB, actual, expected string) (ok bool) {
return strings.Contains(actual+"^", expected[3:]+"^")
} else if strings.HasSuffix(expected, "...") {
return strings.Contains("^"+actual, "^"+expected[:len(expected)-3])
- } else {
- return expected == actual
}
+ return expected == actual
}
func assembleWithTrace(text string, ver uint64) (*OpStream, error) {
@@ -1731,6 +1730,7 @@ global PayoutsGoOnlineFee
global PayoutsPercent
global PayoutsMinBalance
global PayoutsMaxBalance
+txn RejectVersion
`, AssemblerMaxVersion)
for _, names := range [][]string{GlobalFieldNames[:], TxnFieldNames[:], blockFieldNames[:]} {
for _, f := range names {
@@ -2839,33 +2839,33 @@ func TestScratchBounds(t *testing.T) {
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))
+ require.Equal(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))
+ require.Equal(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))
+ require.Equal(t, sv.Bound, static(1))
osv := os.known.scratchSpace[0]
require.Equal(t, osv.AVMType, avmUint64)
- require.ElementsMatch(t, osv.Bound, static(0))
+ require.Equal(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))
+ require.Equal(t, sv.Bound, static(0))
osv = os.known.scratchSpace[0]
require.Equal(t, osv.AVMType, avmAny)
- require.ElementsMatch(t, osv.Bound, static(0))
+ require.Equal(t, osv.Bound, static(0))
testProg(t, "byte 0xff; store 1; load 1; return", AssemblerMaxVersion, exp(1, "return arg 0 wanted type uint64 ..."))
}
@@ -2945,6 +2945,9 @@ func TestBadInnerFields(t *testing.T) {
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, exp(1, "...is not allowed."))
+
+ testProg(t, "itxn_begin; int 3; itxn_field RejectVersion", 11, exp(1, "...introduced in v12..."))
+ testProg(t, "itxn_begin; int 2; itxn_field RejectVersion", 12)
}
func TestTypeTracking(t *testing.T) {
diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go
index 2449b96162..a8448a3194 100644
--- a/data/transactions/logic/eval.go
+++ b/data/transactions/logic/eval.go
@@ -1439,9 +1439,6 @@ func (cx *EvalContext) begin(program []byte) error {
if version > cx.Proto.LogicSigVersion {
return fmt.Errorf("program version %d greater than protocol supported version %d", version, cx.Proto.LogicSigVersion)
}
- if err != nil {
- return err
- }
cx.version = version
cx.pc = vlen
@@ -2962,6 +2959,8 @@ func (cx *EvalContext) appParamsToValue(params *basics.AppParams, fs appParamsFi
sv.Uint = params.LocalStateSchema.NumByteSlice
case AppExtraProgramPages:
sv.Uint = uint64(params.ExtraProgramPages)
+ case AppVersion:
+ sv.Uint = params.Version
default:
// The pseudo fields AppCreator and AppAddress are handled before this method
return sv, fmt.Errorf("invalid app_params_get field %d", fs.field)
@@ -3086,11 +3085,10 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t
if inner {
// Before we had inner apps, we did not allow these, since we had no inner groups.
if cx.version < innerAppsEnabledVersion && (fs.field == GroupIndex || fs.field == TxID) {
- err = fmt.Errorf("illegal field for inner transaction %s", fs.field)
- return
+ return sv, fmt.Errorf("illegal field for inner transaction %s", fs.field)
}
}
- err = nil
+
txn := &stxn.SignedTxn.Txn
switch fs.field {
case Sender:
@@ -3161,6 +3159,8 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t
sv.Uint = uint64(txn.ApplicationID)
case OnCompletion:
sv.Uint = uint64(txn.OnCompletion)
+ case RejectVersion:
+ sv.Uint = uint64(txn.RejectVersion)
case ApplicationArgs:
if arrayFieldIdx >= uint64(len(txn.ApplicationArgs)) {
@@ -5431,6 +5431,8 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t
var onc uint64
onc, err = sv.uintMaxed(uint64(transactions.DeleteApplicationOC))
txn.OnCompletion = transactions.OnCompletion(onc)
+ case RejectVersion:
+ txn.RejectVersion, err = sv.uint()
case ApplicationArgs:
if sv.Bytes == nil {
return fmt.Errorf("ApplicationArg is not a byte array")
diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go
index 17240007a1..5c06495973 100644
--- a/data/transactions/logic/evalAppTxn_test.go
+++ b/data/transactions/logic/evalAppTxn_test.go
@@ -1336,10 +1336,31 @@ int 5000; app_params_get AppLocalNumByteSlice; assert; int 2; ==; assert
int 5000; app_params_get AppLocalNumUint; assert; int 3; ==; assert
int 1
`)
+ if v >= 12 {
+ // Version starts at 0
+ test(`int 5000; app_params_get AppVersion; assert; !`)
+ }
// Call it (default OnComplete is NoOp)
test(call)
+ update := `
+itxn_begin
+int appl; itxn_field TypeEnum
+int 5000; itxn_field ApplicationID
+` + approve + `; itxn_field ApprovalProgram
+` + approve + `; itxn_field ClearStateProgram
+int UpdateApplication; itxn_field OnCompletion
+itxn_submit
+int 1
+`
+ test(update)
+
+ if v >= 12 {
+ // Version starts at 0
+ test(`int 5000; app_params_get AppVersion; assert; int 1; ==`)
+ }
+
test(`
itxn_begin
int appl; itxn_field TypeEnum
diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go
index 7b50c3283d..f41b537692 100644
--- a/data/transactions/logic/evalStateful_test.go
+++ b/data/transactions/logic/evalStateful_test.go
@@ -44,6 +44,7 @@ func makeApp(li uint64, lb uint64, gi uint64, gb uint64) basics.AppParams {
GlobalStateSchema: basics.StateSchema{NumUint: gi, NumByteSlice: gb},
},
ExtraProgramPages: 0,
+ Version: 0,
}
}
diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go
index cf6a04aa1f..f5bb656ef2 100644
--- a/data/transactions/logic/eval_test.go
+++ b/data/transactions/logic/eval_test.go
@@ -1805,7 +1805,8 @@ int 1
const testTxnProgramTextV12 = testTxnProgramTextV11 + `
assert
-int 1
+txn RejectVersion
+!
`
func makeSampleTxn() transactions.SignedTxn {
diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go
index e35cd794dd..4e6f07bcae 100644
--- a/data/transactions/logic/fields.go
+++ b/data/transactions/logic/fields.go
@@ -213,6 +213,9 @@ const (
// NumClearStateProgramPages = len(ClearStateProgramPages) // 4096
NumClearStateProgramPages
+ // RejectVersion uint64
+ RejectVersion
+
invalidTxnField // compile-time constant for number of fields
)
@@ -344,7 +347,7 @@ var txnFieldSpecs = [...]txnFieldSpec{
{LastLog, StackBytes, false, 6, 0, true, "The last message emitted. Empty bytes if none were emitted"},
// Not an effect. Just added after the effects fields.
- {StateProofPK, StackBytes, false, 6, 6, false, "64 byte state proof public key"},
+ {StateProofPK, StackBytes64, false, 6, 6, false, "State proof public key"},
// Pseudo-fields to aid access to large programs (bigger than TEAL values)
// reading in a txn seems not *super* useful, but setting in `itxn` is critical to inner app factories
@@ -352,6 +355,8 @@ var txnFieldSpecs = [...]txnFieldSpec{
{NumApprovalProgramPages, StackUint64, false, 7, 0, false, "Number of Approval Program pages"},
{ClearStateProgramPages, StackBytes, true, 7, 7, false, "ClearState Program as an array of pages"},
{NumClearStateProgramPages, StackUint64, false, 7, 0, false, "Number of ClearState Program pages"},
+
+ {RejectVersion, StackUint64, false, 12, 12, false, "Application version for which the txn must reject"},
}
// TxnFields contains info on the arguments to the txn* family of opcodes
@@ -1326,6 +1331,9 @@ const (
// AppAddress is also not *in* the Params, but can be derived
AppAddress
+ // AppVersion begins at 0 and increasing each time either program changes
+ AppVersion
+
invalidAppParamsField // compile-time constant for number of fields
)
@@ -1364,6 +1372,7 @@ var appParamsFieldSpecs = [...]appParamsFieldSpec{
{AppExtraProgramPages, StackUint64, 5, "Number of Extra Program Pages of code space"},
{AppCreator, StackAddress, 5, "Creator address"},
{AppAddress, StackAddress, 5, "Address for which this application has authority"},
+ {AppVersion, StackUint64, 12, "Version of the app, incremented each time the approval or clear program changes"},
}
func appParamsFieldSpecByField(f AppParamsField) (appParamsFieldSpec, bool) {
diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go
index 81f1b33d29..a9059e56f3 100644
--- a/data/transactions/logic/fields_string.go
+++ b/data/transactions/logic/fields_string.go
@@ -76,12 +76,13 @@ func _() {
_ = x[NumApprovalProgramPages-65]
_ = x[ClearStateProgramPages-66]
_ = x[NumClearStateProgramPages-67]
- _ = x[invalidTxnField-68]
+ _ = x[RejectVersion-68]
+ _ = x[invalidTxnField-69]
}
-const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationLogsNumLogsCreatedAssetIDCreatedApplicationIDLastLogStateProofPKApprovalProgramPagesNumApprovalProgramPagesClearStateProgramPagesNumClearStateProgramPagesinvalidTxnField"
+const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceExtraProgramPagesNonparticipationLogsNumLogsCreatedAssetIDCreatedApplicationIDLastLogStateProofPKApprovalProgramPagesNumApprovalProgramPagesClearStateProgramPagesNumClearStateProgramPagesRejectVersioninvalidTxnField"
-var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 718, 725, 739, 759, 766, 778, 798, 821, 843, 868, 883}
+var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 698, 714, 718, 725, 739, 759, 766, 778, 798, 821, 843, 868, 881, 896}
func (i TxnField) String() string {
if i < 0 || i >= TxnField(len(_TxnField_index)-1) {
@@ -171,12 +172,13 @@ func _() {
_ = x[AppExtraProgramPages-6]
_ = x[AppCreator-7]
_ = x[AppAddress-8]
- _ = x[invalidAppParamsField-9]
+ _ = x[AppVersion-9]
+ _ = x[invalidAppParamsField-10]
}
-const _AppParamsField_name = "AppApprovalProgramAppClearStateProgramAppGlobalNumUintAppGlobalNumByteSliceAppLocalNumUintAppLocalNumByteSliceAppExtraProgramPagesAppCreatorAppAddressinvalidAppParamsField"
+const _AppParamsField_name = "AppApprovalProgramAppClearStateProgramAppGlobalNumUintAppGlobalNumByteSliceAppLocalNumUintAppLocalNumByteSliceAppExtraProgramPagesAppCreatorAppAddressAppVersioninvalidAppParamsField"
-var _AppParamsField_index = [...]uint8{0, 18, 38, 54, 75, 90, 110, 130, 140, 150, 171}
+var _AppParamsField_index = [...]uint8{0, 18, 38, 54, 75, 90, 110, 130, 140, 150, 160, 181}
func (i AppParamsField) String() string {
if i < 0 || i >= AppParamsField(len(_AppParamsField_index)-1) {
diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go
index f353068b71..67cf826dee 100644
--- a/data/transactions/logic/fields_test.go
+++ b/data/transactions/logic/fields_test.go
@@ -18,6 +18,7 @@ package logic
import (
"fmt"
+ "strings"
"testing"
"github.com/stretchr/testify/require"
@@ -206,6 +207,89 @@ func TestTxnFieldVersions(t *testing.T) {
}
}
+// ensure itxn_field works properly (only) in the versions it's supposed to work
+func TestITxnFieldVersions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ var fields []txnFieldSpec
+ for _, fs := range txnFieldSpecs {
+ if fs.itxVersion > 0 {
+ fields = append(fields, fs)
+ }
+ }
+ require.Greater(t, len(fields), 1)
+
+ txn := makeSampleTxn()
+ txgroup := makeSampleTxnGroup(txn)
+ asmDefaultError := "...was introduced in ..."
+
+ specialArgs := map[string]string{
+ "Type": `byte "pay"`,
+ // assets must be available
+ "XferAsset": `int 55`,
+ "ConfigAsset": `int 55`,
+ "FreezeAsset": `int 55`,
+ "Assets": `int 55`,
+ // applications must be available
+ "ApplicationID": `int 111`,
+ "Applications": `int 111`,
+ }
+ for _, fs := range fields {
+ field := fs.field.String()
+ t.Run(field, func(t *testing.T) {
+ text := "itxn_begin; itxn_field " + field + "; int 1"
+ asmError := asmDefaultError
+
+ if arg, ok := specialArgs[field]; ok {
+ text = arg + ";" + text
+ } else {
+ // prepend a generic value
+ switch fs.ftype.AVMType {
+ case avmUint64:
+ text = "int 1;" + text
+ case avmBytes:
+ if fs.ftype == StackAddress {
+ text = "txn Sender;" + text
+ } else {
+ text = fmt.Sprintf("byte 0x%s;", strings.Repeat("aa", int(fs.ftype.Bound[0]))) + text
+ }
+ case avmAny:
+ text = "error;"
+ }
+ }
+
+ // check assembler fails if version before introduction
+ testLine(t, text, assemblerNoVersion, asmError)
+ for v := uint64(0); v < fs.itxVersion; v++ {
+ testLine(t, text, v, asmError)
+ }
+ t.Log(text)
+ testLine(t, text, fs.itxVersion, "")
+
+ // First, make sure it works when it should
+ ops := testProg(t, text, fs.itxVersion)
+ ep := defaultAppParamsWithVersion(fs.itxVersion, txgroup...)
+ testAppBytes(t, ops.Program, ep)
+
+ // And now make sure it doesn't when it shouldn't
+ preVersion := fs.itxVersion - 1
+ ep = defaultAppParamsWithVersion(preVersion, txgroup...)
+
+ // we change the program version so can run, but itxn_field opcode
+ // still won't.
+ ops.Program[0] = byte(preVersion) // set version
+ checkErr := ""
+ evalErr := "invalid itxn_field " + field
+ if preVersion < 5 { // when inners and `itxn_field` were introduced
+ checkErr = "illegal opcode"
+ evalErr = "illegal opcode"
+ }
+ testAppBytes(t, ops.Program, ep, checkErr, evalErr)
+ })
+ }
+}
+
// TestTxnEffectsAvailable ensures that LogicSigs can not use "effects" fields
// (ever). And apps can only use effects fields with `gtxn` after
// txnEffectsVersion. (itxn could use them earlier)
@@ -283,12 +367,11 @@ func TestAssetParamsFieldsVersions(t *testing.T) {
}
func TestFieldVersions(t *testing.T) {
- // This test is weird, it confirms that we don't need to
- // bother with a "good" test for AssetHolding and AppParams
- // fields. It will fail if we add a field that has a
- // different debut version, and then we'll need a test
- // like TestAssetParamsFieldsVersions that checks the field is
- // unavailable before its debut.
+ // This test is weird, it confirms that we don't need to bother with a
+ // "good" test for AssetHolding fields. It will fail if we add a field that
+ // has a different debut version, and then we'll need a test like
+ // TestAppParamsFieldsVersions that checks the field is unavailable before
+ // its debut.
partitiontest.PartitionTest(t)
t.Parallel()
@@ -296,9 +379,47 @@ func TestFieldVersions(t *testing.T) {
for _, fs := range assetHoldingFieldSpecs {
require.Equal(t, uint64(2), fs.version)
}
+}
+
+// TestAppParamsFieldsVersions tests types and accessibility of various app
+// fields over different versions.
+func TestAppParamsFieldsVersions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ for _, field := range appParamsFieldSpecs {
+ testLogicRange(t, 2, 0, func(t *testing.T, ep *EvalParams, txn *transactions.Transaction, ledger *Ledger) {
+ text := fmt.Sprintf("int 56; app_params_get %s; assert;", field.field)
+ 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
+ }
- for _, fs := range appParamsFieldSpecs {
- require.Equal(t, uint64(5), fs.version)
+ v := ep.Proto.LogicSigVersion
+ ledger.NewApp(txn.Sender, txn.ForeignApps[0], basics.AppParams{
+ ApprovalProgram: []byte("ap"),
+ ClearStateProgram: []byte("cs"),
+ GlobalState: map[string]basics.TealValue{},
+ StateSchemas: basics.StateSchemas{},
+ ExtraProgramPages: 2,
+ Version: 6,
+ })
+ if field.version > v {
+ // check assembler fails if version before introduction
+ 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 < 5 {
+ testAppBytes(t, ops.Program, ep, "illegal opcode", "illegal opcode")
+ } else {
+ testAppBytes(t, ops.Program, ep, "invalid app_params_get field")
+ }
+ } else {
+ testProg(t, text, v)
+ testApp(t, text, ep)
+ }
+ })
}
}
diff --git a/data/transactions/logic/langspec_v10.json b/data/transactions/logic/langspec_v10.json
index dffcd9e328..51a83e7250 100644
--- a/data/transactions/logic/langspec_v10.json
+++ b/data/transactions/logic/langspec_v10.json
@@ -1124,7 +1124,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1348,7 +1348,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1656,7 +1656,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3737,7 +3737,7 @@
"uint64",
"uint64",
"bool",
- "[]byte",
+ "[64]byte",
"[]byte",
"[]byte"
],
@@ -3910,7 +3910,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -4129,7 +4129,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
diff --git a/data/transactions/logic/langspec_v11.json b/data/transactions/logic/langspec_v11.json
index 2ff7187f3c..970ff01702 100644
--- a/data/transactions/logic/langspec_v11.json
+++ b/data/transactions/logic/langspec_v11.json
@@ -1124,7 +1124,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1358,7 +1358,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1666,7 +1666,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3793,7 +3793,7 @@
"uint64",
"uint64",
"bool",
- "[]byte",
+ "[64]byte",
"[]byte",
"[]byte"
],
@@ -3966,7 +3966,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -4185,7 +4185,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
diff --git a/data/transactions/logic/langspec_v6.json b/data/transactions/logic/langspec_v6.json
index 5cd1de7fc3..09e2476a06 100644
--- a/data/transactions/logic/langspec_v6.json
+++ b/data/transactions/logic/langspec_v6.json
@@ -1115,7 +1115,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "field F of current transaction",
@@ -1323,7 +1323,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "field F of the Tth transaction in the current group",
@@ -1613,7 +1613,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "field F of the Ath transaction in the current group",
@@ -3311,7 +3311,7 @@
"uint64",
"uint64",
"bool",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "set field F of the current inner transaction to A",
@@ -3476,7 +3476,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "field F of the last inner transaction",
@@ -3681,7 +3681,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte"
+ "[64]byte"
],
"DocCost": "1",
"Doc": "field F of the Tth transaction in the last inner group submitted",
diff --git a/data/transactions/logic/langspec_v7.json b/data/transactions/logic/langspec_v7.json
index 4152b01675..f0148b970f 100644
--- a/data/transactions/logic/langspec_v7.json
+++ b/data/transactions/logic/langspec_v7.json
@@ -1124,7 +1124,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1342,7 +1342,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1650,7 +1650,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3509,7 +3509,7 @@
"uint64",
"uint64",
"bool",
- "[]byte",
+ "[64]byte",
"[]byte",
"[]byte"
],
@@ -3682,7 +3682,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3901,7 +3901,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
diff --git a/data/transactions/logic/langspec_v8.json b/data/transactions/logic/langspec_v8.json
index d667cbb0d1..fddcc13a31 100644
--- a/data/transactions/logic/langspec_v8.json
+++ b/data/transactions/logic/langspec_v8.json
@@ -1124,7 +1124,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1342,7 +1342,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1650,7 +1650,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3731,7 +3731,7 @@
"uint64",
"uint64",
"bool",
- "[]byte",
+ "[64]byte",
"[]byte",
"[]byte"
],
@@ -3904,7 +3904,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -4123,7 +4123,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
diff --git a/data/transactions/logic/langspec_v9.json b/data/transactions/logic/langspec_v9.json
index 0d5dedf63c..28f3e9f995 100644
--- a/data/transactions/logic/langspec_v9.json
+++ b/data/transactions/logic/langspec_v9.json
@@ -1124,7 +1124,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1342,7 +1342,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -1650,7 +1650,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -3731,7 +3731,7 @@
"uint64",
"uint64",
"bool",
- "[]byte",
+ "[64]byte",
"[]byte",
"[]byte"
],
@@ -3904,7 +3904,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
@@ -4123,7 +4123,7 @@
"uint64",
"uint64",
"[]byte",
- "[]byte",
+ "[64]byte",
"[]byte",
"uint64",
"[]byte",
diff --git a/data/transactions/logic/ledger_test.go b/data/transactions/logic/ledger_test.go
index 7757895f4d..7a43019ecc 100644
--- a/data/transactions/logic/ledger_test.go
+++ b/data/transactions/logic/ledger_test.go
@@ -853,6 +853,7 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF
},
},
ExtraProgramPages: appl.ExtraProgramPages,
+ Version: 0,
}
l.NewApp(from, aid, params)
ad.ApplicationID = aid
@@ -915,6 +916,7 @@ func (l *Ledger) appl(from basics.Address, appl transactions.ApplicationCallTxnF
}
app.ApprovalProgram = appl.ApprovalProgram
app.ClearStateProgram = appl.ClearStateProgram
+ app.Version++
l.applications[aid] = app
}
return nil
diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go
index c275395fc4..e201171f7f 100644
--- a/data/transactions/logic/opcodes.go
+++ b/data/transactions/logic/opcodes.go
@@ -892,9 +892,8 @@ func init() {
for v := uint64(2); v <= LogicVersion; v++ {
// Copy opcodes from lower version
OpsByName[v] = maps.Clone(OpsByName[v-1])
- for op, oi := range opsByOpcode[v-1] {
- opsByOpcode[v][op] = oi
- }
+ // Copy array with direct assignment instead of a loop
+ opsByOpcode[v] = opsByOpcode[v-1]
// Update tables with opcodes from the current version
for _, oi := range OpSpecs {
diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go
index ceb12375be..fe1d52680e 100644
--- a/data/transactions/msgp_gen.go
+++ b/data/transactions/msgp_gen.go
@@ -235,8 +235,8 @@ import (
func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) {
o = msgp.Require(b, z.Msgsize())
// omitempty: check for empty values
- zb0006Len := uint32(12)
- var zb0006Mask uint16 /* 13 bits */
+ zb0006Len := uint32(13)
+ var zb0006Mask uint16 /* 14 bits */
if len((*z).ApplicationArgs) == 0 {
zb0006Len--
zb0006Mask |= 0x2
@@ -281,10 +281,14 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) {
zb0006Len--
zb0006Mask |= 0x800
}
- if len((*z).ClearStateProgram) == 0 {
+ if (*z).RejectVersion == 0 {
zb0006Len--
zb0006Mask |= 0x1000
}
+ if len((*z).ClearStateProgram) == 0 {
+ zb0006Len--
+ zb0006Mask |= 0x2000
+ }
// variable map header, size zb0006Len
o = append(o, 0x80|uint8(zb0006Len))
if zb0006Len != 0 {
@@ -401,6 +405,11 @@ func (z *ApplicationCallTxnFields) MarshalMsg(b []byte) (o []byte) {
o = (*z).LocalStateSchema.MarshalMsg(o)
}
if (zb0006Mask & 0x1000) == 0 { // if not empty
+ // string "aprv"
+ o = append(o, 0xa4, 0x61, 0x70, 0x72, 0x76)
+ o = msgp.AppendUint64(o, (*z).RejectVersion)
+ }
+ if (zb0006Mask & 0x2000) == 0 { // if not empty
// string "apsu"
o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75)
o = msgp.AppendBytes(o, (*z).ClearStateProgram)
@@ -741,6 +750,14 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm
return
}
}
+ if zb0006 > 0 {
+ zb0006--
+ (*z).RejectVersion, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "RejectVersion")
+ return
+ }
+ }
if zb0006 > 0 {
err = msgp.ErrTooManyArrayFields(zb0006)
if err != nil {
@@ -1049,6 +1066,12 @@ func (z *ApplicationCallTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.Unm
err = msgp.WrapError(err, "ExtraProgramPages")
return
}
+ case "aprv":
+ (*z).RejectVersion, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "RejectVersion")
+ return
+ }
default:
err = msgp.ErrNoField(string(field))
if err != nil {
@@ -1092,13 +1115,13 @@ func (z *ApplicationCallTxnFields) Msgsize() (s int) {
for zb0005 := range (*z).ForeignAssets {
s += (*z).ForeignAssets[zb0005].Msgsize()
}
- s += 5 + (*z).LocalStateSchema.Msgsize() + 5 + (*z).GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 5 + msgp.Uint32Size
+ s += 5 + (*z).LocalStateSchema.Msgsize() + 5 + (*z).GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size
return
}
// MsgIsZero returns whether this is a zero value
func (z *ApplicationCallTxnFields) MsgIsZero() bool {
- return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).Boxes) == 0) && (len((*z).ForeignAssets) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0)
+ return ((*z).ApplicationID.MsgIsZero()) && ((*z).OnCompletion == 0) && (len((*z).ApplicationArgs) == 0) && (len((*z).Accounts) == 0) && (len((*z).ForeignApps) == 0) && (len((*z).Boxes) == 0) && (len((*z).ForeignAssets) == 0) && ((*z).LocalStateSchema.MsgIsZero()) && ((*z).GlobalStateSchema.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).ExtraProgramPages == 0) && ((*z).RejectVersion == 0)
}
// MaxSize returns a maximum valid message size for this message type
@@ -1117,7 +1140,7 @@ func ApplicationCallTxnFieldsMaxSize() (s int) {
s += 5
// Calculating size of slice: z.ForeignAssets
s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize()))
- s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size
+ s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size
return
}
@@ -5205,8 +5228,8 @@ func StateProofTxnFieldsMaxSize() (s int) {
func (z *Transaction) MarshalMsg(b []byte) (o []byte) {
o = msgp.Require(b, z.Msgsize())
// omitempty: check for empty values
- zb0007Len := uint32(47)
- var zb0007Mask uint64 /* 56 bits */
+ zb0007Len := uint32(48)
+ var zb0007Mask uint64 /* 57 bits */
if (*z).AssetTransferTxnFields.AssetAmount == 0 {
zb0007Len--
zb0007Mask |= 0x200
@@ -5271,130 +5294,134 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) {
zb0007Len--
zb0007Mask |= 0x1000000
}
- if len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0 {
+ if (*z).ApplicationCallTxnFields.RejectVersion == 0 {
zb0007Len--
zb0007Mask |= 0x2000000
}
- if (*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero() {
+ if len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0 {
zb0007Len--
zb0007Mask |= 0x4000000
}
- if (*z).AssetTransferTxnFields.AssetSender.MsgIsZero() {
+ if (*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x8000000
}
- if (*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero() {
+ if (*z).AssetTransferTxnFields.AssetSender.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x10000000
}
- if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() {
+ if (*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x20000000
}
- if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() {
+ if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x40000000
}
- if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() {
+ if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x80000000
}
- if (*z).Header.Fee.MsgIsZero() {
+ if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x100000000
}
- if (*z).Header.FirstValid.MsgIsZero() {
+ if (*z).Header.Fee.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x200000000
}
- if (*z).Header.GenesisID == "" {
+ if (*z).Header.FirstValid.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x400000000
}
- if (*z).Header.GenesisHash.MsgIsZero() {
+ if (*z).Header.GenesisID == "" {
zb0007Len--
zb0007Mask |= 0x800000000
}
- if (*z).Header.Group.MsgIsZero() {
+ if (*z).Header.GenesisHash.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x1000000000
}
- if (*z).HeartbeatTxnFields == nil {
+ if (*z).Header.Group.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x2000000000
}
- if (*z).Header.LastValid.MsgIsZero() {
+ if (*z).HeartbeatTxnFields == nil {
zb0007Len--
zb0007Mask |= 0x4000000000
}
- if (*z).Header.Lease == ([32]byte{}) {
+ if (*z).Header.LastValid.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x8000000000
}
- if (*z).KeyregTxnFields.Nonparticipation == false {
+ if (*z).Header.Lease == ([32]byte{}) {
zb0007Len--
zb0007Mask |= 0x10000000000
}
- if len((*z).Header.Note) == 0 {
+ if (*z).KeyregTxnFields.Nonparticipation == false {
zb0007Len--
zb0007Mask |= 0x20000000000
}
- if (*z).PaymentTxnFields.Receiver.MsgIsZero() {
+ if len((*z).Header.Note) == 0 {
zb0007Len--
zb0007Mask |= 0x40000000000
}
- if (*z).Header.RekeyTo.MsgIsZero() {
+ if (*z).PaymentTxnFields.Receiver.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x80000000000
}
- if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() {
+ if (*z).Header.RekeyTo.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x100000000000
}
- if (*z).Header.Sender.MsgIsZero() {
+ if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x200000000000
}
- if (*z).StateProofTxnFields.StateProof.MsgIsZero() {
+ if (*z).Header.Sender.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x400000000000
}
- if (*z).StateProofTxnFields.Message.MsgIsZero() {
+ if (*z).StateProofTxnFields.StateProof.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x800000000000
}
- if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() {
+ if (*z).StateProofTxnFields.Message.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x1000000000000
}
- if (*z).StateProofTxnFields.StateProofType.MsgIsZero() {
+ if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x2000000000000
}
- if (*z).Type.MsgIsZero() {
+ if (*z).StateProofTxnFields.StateProofType.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x4000000000000
}
- if (*z).KeyregTxnFields.VoteFirst.MsgIsZero() {
+ if (*z).Type.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x8000000000000
}
- if (*z).KeyregTxnFields.VoteKeyDilution == 0 {
+ if (*z).KeyregTxnFields.VoteFirst.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x10000000000000
}
- if (*z).KeyregTxnFields.VotePK.MsgIsZero() {
+ if (*z).KeyregTxnFields.VoteKeyDilution == 0 {
zb0007Len--
zb0007Mask |= 0x20000000000000
}
- if (*z).KeyregTxnFields.VoteLast.MsgIsZero() {
+ if (*z).KeyregTxnFields.VotePK.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x40000000000000
}
- if (*z).AssetTransferTxnFields.XferAsset.MsgIsZero() {
+ if (*z).KeyregTxnFields.VoteLast.MsgIsZero() {
zb0007Len--
zb0007Mask |= 0x80000000000000
}
+ if (*z).AssetTransferTxnFields.XferAsset.MsgIsZero() {
+ zb0007Len--
+ zb0007Mask |= 0x100000000000000
+ }
// variable map header, size zb0007Len
o = msgp.AppendMapHeader(o, zb0007Len)
if zb0007Len != 0 {
@@ -5536,66 +5563,71 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) {
o = (*z).ApplicationCallTxnFields.LocalStateSchema.MarshalMsg(o)
}
if (zb0007Mask & 0x2000000) == 0 { // if not empty
+ // string "aprv"
+ o = append(o, 0xa4, 0x61, 0x70, 0x72, 0x76)
+ o = msgp.AppendUint64(o, (*z).ApplicationCallTxnFields.RejectVersion)
+ }
+ if (zb0007Mask & 0x4000000) == 0 { // if not empty
// string "apsu"
o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75)
o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ClearStateProgram)
}
- if (zb0007Mask & 0x4000000) == 0 { // if not empty
+ if (zb0007Mask & 0x8000000) == 0 { // if not empty
// string "arcv"
o = append(o, 0xa4, 0x61, 0x72, 0x63, 0x76)
o = (*z).AssetTransferTxnFields.AssetReceiver.MarshalMsg(o)
}
- if (zb0007Mask & 0x8000000) == 0 { // if not empty
+ if (zb0007Mask & 0x10000000) == 0 { // if not empty
// string "asnd"
o = append(o, 0xa4, 0x61, 0x73, 0x6e, 0x64)
o = (*z).AssetTransferTxnFields.AssetSender.MarshalMsg(o)
}
- if (zb0007Mask & 0x10000000) == 0 { // if not empty
+ if (zb0007Mask & 0x20000000) == 0 { // if not empty
// string "caid"
o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64)
o = (*z).AssetConfigTxnFields.ConfigAsset.MarshalMsg(o)
}
- if (zb0007Mask & 0x20000000) == 0 { // if not empty
+ if (zb0007Mask & 0x40000000) == 0 { // if not empty
// string "close"
o = append(o, 0xa5, 0x63, 0x6c, 0x6f, 0x73, 0x65)
o = (*z).PaymentTxnFields.CloseRemainderTo.MarshalMsg(o)
}
- if (zb0007Mask & 0x40000000) == 0 { // if not empty
+ if (zb0007Mask & 0x80000000) == 0 { // if not empty
// string "fadd"
o = append(o, 0xa4, 0x66, 0x61, 0x64, 0x64)
o = (*z).AssetFreezeTxnFields.FreezeAccount.MarshalMsg(o)
}
- if (zb0007Mask & 0x80000000) == 0 { // if not empty
+ if (zb0007Mask & 0x100000000) == 0 { // if not empty
// string "faid"
o = append(o, 0xa4, 0x66, 0x61, 0x69, 0x64)
o = (*z).AssetFreezeTxnFields.FreezeAsset.MarshalMsg(o)
}
- if (zb0007Mask & 0x100000000) == 0 { // if not empty
+ if (zb0007Mask & 0x200000000) == 0 { // if not empty
// string "fee"
o = append(o, 0xa3, 0x66, 0x65, 0x65)
o = (*z).Header.Fee.MarshalMsg(o)
}
- if (zb0007Mask & 0x200000000) == 0 { // if not empty
+ if (zb0007Mask & 0x400000000) == 0 { // if not empty
// string "fv"
o = append(o, 0xa2, 0x66, 0x76)
o = (*z).Header.FirstValid.MarshalMsg(o)
}
- if (zb0007Mask & 0x400000000) == 0 { // if not empty
+ if (zb0007Mask & 0x800000000) == 0 { // if not empty
// string "gen"
o = append(o, 0xa3, 0x67, 0x65, 0x6e)
o = msgp.AppendString(o, (*z).Header.GenesisID)
}
- if (zb0007Mask & 0x800000000) == 0 { // if not empty
+ if (zb0007Mask & 0x1000000000) == 0 { // if not empty
// string "gh"
o = append(o, 0xa2, 0x67, 0x68)
o = (*z).Header.GenesisHash.MarshalMsg(o)
}
- if (zb0007Mask & 0x1000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x2000000000) == 0 { // if not empty
// string "grp"
o = append(o, 0xa3, 0x67, 0x72, 0x70)
o = (*z).Header.Group.MarshalMsg(o)
}
- if (zb0007Mask & 0x2000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x4000000000) == 0 { // if not empty
// string "hb"
o = append(o, 0xa2, 0x68, 0x62)
if (*z).HeartbeatTxnFields == nil {
@@ -5604,92 +5636,92 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) {
o = (*z).HeartbeatTxnFields.MarshalMsg(o)
}
}
- if (zb0007Mask & 0x4000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x8000000000) == 0 { // if not empty
// string "lv"
o = append(o, 0xa2, 0x6c, 0x76)
o = (*z).Header.LastValid.MarshalMsg(o)
}
- if (zb0007Mask & 0x8000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x10000000000) == 0 { // if not empty
// string "lx"
o = append(o, 0xa2, 0x6c, 0x78)
o = msgp.AppendBytes(o, ((*z).Header.Lease)[:])
}
- if (zb0007Mask & 0x10000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x20000000000) == 0 { // if not empty
// string "nonpart"
o = append(o, 0xa7, 0x6e, 0x6f, 0x6e, 0x70, 0x61, 0x72, 0x74)
o = msgp.AppendBool(o, (*z).KeyregTxnFields.Nonparticipation)
}
- if (zb0007Mask & 0x20000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x40000000000) == 0 { // if not empty
// string "note"
o = append(o, 0xa4, 0x6e, 0x6f, 0x74, 0x65)
o = msgp.AppendBytes(o, (*z).Header.Note)
}
- if (zb0007Mask & 0x40000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x80000000000) == 0 { // if not empty
// string "rcv"
o = append(o, 0xa3, 0x72, 0x63, 0x76)
o = (*z).PaymentTxnFields.Receiver.MarshalMsg(o)
}
- if (zb0007Mask & 0x80000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x100000000000) == 0 { // if not empty
// string "rekey"
o = append(o, 0xa5, 0x72, 0x65, 0x6b, 0x65, 0x79)
o = (*z).Header.RekeyTo.MarshalMsg(o)
}
- if (zb0007Mask & 0x100000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x200000000000) == 0 { // if not empty
// string "selkey"
o = append(o, 0xa6, 0x73, 0x65, 0x6c, 0x6b, 0x65, 0x79)
o = (*z).KeyregTxnFields.SelectionPK.MarshalMsg(o)
}
- if (zb0007Mask & 0x200000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x400000000000) == 0 { // if not empty
// string "snd"
o = append(o, 0xa3, 0x73, 0x6e, 0x64)
o = (*z).Header.Sender.MarshalMsg(o)
}
- if (zb0007Mask & 0x400000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x800000000000) == 0 { // if not empty
// string "sp"
o = append(o, 0xa2, 0x73, 0x70)
o = (*z).StateProofTxnFields.StateProof.MarshalMsg(o)
}
- if (zb0007Mask & 0x800000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x1000000000000) == 0 { // if not empty
// string "spmsg"
o = append(o, 0xa5, 0x73, 0x70, 0x6d, 0x73, 0x67)
o = (*z).StateProofTxnFields.Message.MarshalMsg(o)
}
- if (zb0007Mask & 0x1000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x2000000000000) == 0 { // if not empty
// string "sprfkey"
o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x66, 0x6b, 0x65, 0x79)
o = (*z).KeyregTxnFields.StateProofPK.MarshalMsg(o)
}
- if (zb0007Mask & 0x2000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x4000000000000) == 0 { // if not empty
// string "sptype"
o = append(o, 0xa6, 0x73, 0x70, 0x74, 0x79, 0x70, 0x65)
o = (*z).StateProofTxnFields.StateProofType.MarshalMsg(o)
}
- if (zb0007Mask & 0x4000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x8000000000000) == 0 { // if not empty
// string "type"
o = append(o, 0xa4, 0x74, 0x79, 0x70, 0x65)
o = (*z).Type.MarshalMsg(o)
}
- if (zb0007Mask & 0x8000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x10000000000000) == 0 { // if not empty
// string "votefst"
o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x66, 0x73, 0x74)
o = (*z).KeyregTxnFields.VoteFirst.MarshalMsg(o)
}
- if (zb0007Mask & 0x10000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x20000000000000) == 0 { // if not empty
// string "votekd"
o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x64)
o = msgp.AppendUint64(o, (*z).KeyregTxnFields.VoteKeyDilution)
}
- if (zb0007Mask & 0x20000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x40000000000000) == 0 { // if not empty
// string "votekey"
o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x65, 0x79)
o = (*z).KeyregTxnFields.VotePK.MarshalMsg(o)
}
- if (zb0007Mask & 0x40000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x80000000000000) == 0 { // if not empty
// string "votelst"
o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6c, 0x73, 0x74)
o = (*z).KeyregTxnFields.VoteLast.MarshalMsg(o)
}
- if (zb0007Mask & 0x80000000000000) == 0 { // if not empty
+ if (zb0007Mask & 0x100000000000000) == 0 { // if not empty
// string "xaid"
o = append(o, 0xa4, 0x78, 0x61, 0x69, 0x64)
o = (*z).AssetTransferTxnFields.XferAsset.MarshalMsg(o)
@@ -6298,6 +6330,14 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState)
return
}
}
+ if zb0007 > 0 {
+ zb0007--
+ (*z).ApplicationCallTxnFields.RejectVersion, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "RejectVersion")
+ return
+ }
+ }
if zb0007 > 0 {
zb0007--
bts, err = (*z).StateProofTxnFields.StateProofType.UnmarshalMsgWithState(bts, st)
@@ -6855,6 +6895,12 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState)
err = msgp.WrapError(err, "ExtraProgramPages")
return
}
+ case "aprv":
+ (*z).ApplicationCallTxnFields.RejectVersion, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "RejectVersion")
+ return
+ }
case "sptype":
bts, err = (*z).StateProofTxnFields.StateProofType.UnmarshalMsgWithState(bts, st)
if err != nil {
@@ -6933,7 +6979,7 @@ func (z *Transaction) Msgsize() (s int) {
for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets {
s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].Msgsize()
}
- s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 7 + (*z).StateProofTxnFields.StateProofType.Msgsize() + 3 + (*z).StateProofTxnFields.StateProof.Msgsize() + 6 + (*z).StateProofTxnFields.Message.Msgsize() + 3
+ s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size + 7 + (*z).StateProofTxnFields.StateProofType.Msgsize() + 3 + (*z).StateProofTxnFields.StateProof.Msgsize() + 6 + (*z).StateProofTxnFields.Message.Msgsize() + 3
if (*z).HeartbeatTxnFields == nil {
s += msgp.NilSize
} else {
@@ -6944,7 +6990,7 @@ func (z *Transaction) Msgsize() (s int) {
// MsgIsZero returns whether this is a zero value
func (z *Transaction) MsgIsZero() bool {
- return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) && ((*z).HeartbeatTxnFields == nil)
+ return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).ApplicationCallTxnFields.RejectVersion == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) && ((*z).HeartbeatTxnFields == nil)
}
// MaxSize returns a maximum valid message size for this message type
@@ -6966,7 +7012,7 @@ func TransactionMaxSize() (s int) {
s += 5
// Calculating size of slice: z.ApplicationCallTxnFields.ForeignAssets
s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize()))
- s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 7 + protocol.StateProofTypeMaxSize() + 3 + stateproof.StateProofMaxSize() + 6 + stateproofmsg.MessageMaxSize() + 3
+ s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 5 + msgp.Uint64Size + 7 + protocol.StateProofTypeMaxSize() + 3 + stateproof.StateProofMaxSize() + 6 + stateproofmsg.MessageMaxSize() + 3
s += HeartbeatTxnFieldsMaxSize()
return
}
diff --git a/data/transactions/payment.go b/data/transactions/payment.go
index 7db066773f..59b54d8075 100644
--- a/data/transactions/payment.go
+++ b/data/transactions/payment.go
@@ -37,8 +37,8 @@ type PaymentTxnFields struct {
CloseRemainderTo basics.Address `codec:"close"`
}
-// checkSpender performs some stateless checks on the Sender of a pay transaction
-func (payment PaymentTxnFields) checkSpender(header Header, spec SpecialAddresses, proto config.ConsensusParams) error {
+// wellFormed performs some stateless checks on the Sender of a pay transaction
+func (payment PaymentTxnFields) wellFormed(header Header, spec SpecialAddresses, proto config.ConsensusParams) error {
if header.Sender == payment.CloseRemainderTo {
return fmt.Errorf("transaction cannot close account to its sender %v", header.Sender)
}
diff --git a/data/transactions/payment_test.go b/data/transactions/payment_test.go
index 5c24bab195..6f65a30195 100644
--- a/data/transactions/payment_test.go
+++ b/data/transactions/payment_test.go
@@ -17,8 +17,10 @@
package transactions
import (
+ "fmt"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/algorand/go-algorand/config"
@@ -70,7 +72,7 @@ func TestAlgosEncoding(t *testing.T) {
}
}
-func TestCheckSpender(t *testing.T) {
+func TestPaymentWellFormed(t *testing.T) {
partitiontest.PartitionTest(t)
v7 := config.Consensus[protocol.ConsensusV7]
@@ -105,30 +107,83 @@ func TestCheckSpender(t *testing.T) {
}
tx.Sender = feeSink
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v7),
"to non incentive pool address")
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v39),
"to non incentive pool address")
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, vFuture),
"cannot spend from fee sink")
tx.Receiver = poolAddr
- require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7))
- require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39))
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture),
+ require.NoError(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v7))
+ require.NoError(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v39))
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, vFuture),
"cannot spend from fee sink")
tx.CloseRemainderTo = poolAddr
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v7),
"cannot close fee sink")
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v39),
"cannot close fee sink")
- require.ErrorContains(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture),
+ require.ErrorContains(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, vFuture),
"cannot spend from fee sink")
// When not sending from fee sink, everything's fine
tx.Sender = src
- require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v7))
- require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, v39))
- require.NoError(t, tx.PaymentTxnFields.checkSpender(tx.Header, spec, vFuture))
+ require.NoError(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v7))
+ require.NoError(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, v39))
+ require.NoError(t, tx.PaymentTxnFields.wellFormed(tx.Header, spec, vFuture))
+}
+
+func TestWellFormedPaymentErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ protoV27 := config.Consensus[protocol.ConsensusV27]
+ addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+ usecases := []struct {
+ tx Transaction
+ proto config.ConsensusParams
+ expectedError error
+ }{
+ {
+ tx: Transaction{
+ Type: protocol.PaymentTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 100},
+ },
+ },
+ proto: protoV27,
+ expectedError: makeMinFeeErrorf("transaction had fee %d, which is less than the minimum %d", 100, curProto.MinTxnFee),
+ },
+ {
+ tx: Transaction{
+ Type: protocol.PaymentTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 100},
+ },
+ },
+ proto: curProto,
+ },
+ {
+ tx: Transaction{
+ Type: protocol.PaymentTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 1000},
+ LastValid: 100,
+ FirstValid: 105,
+ },
+ },
+ proto: curProto,
+ expectedError: fmt.Errorf("transaction invalid range (%d--%d)", 105, 100),
+ },
+ }
+ for _, usecase := range usecases {
+ err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto)
+ assert.Equal(t, usecase.expectedError, err)
+ }
}
diff --git a/data/transactions/payset_test.go b/data/transactions/payset_test.go
index 796ae81d04..7a86439a7f 100644
--- a/data/transactions/payset_test.go
+++ b/data/transactions/payset_test.go
@@ -23,22 +23,22 @@ import (
"github.com/stretchr/testify/require"
)
-func preparePayset(txnCount, acctCount int) Payset {
- _, stxns, _, _ := generateTestObjects(txnCount, acctCount)
- var stxnb []SignedTxnInBlock
- for _, stxn := range stxns {
- stxnb = append(stxnb, SignedTxnInBlock{
+func generatePayset(txnCount, acctCount int) Payset {
+ stxnb := make([]SignedTxnInBlock, txnCount)
+ for i, stxn := range generateSignedTxns(txnCount, acctCount) {
+ stxnb[i] = SignedTxnInBlock{
SignedTxnWithAD: SignedTxnWithAD{
SignedTxn: stxn,
},
- })
+ }
}
return Payset(stxnb)
}
+
func TestPaysetCommitsToTxnOrder(t *testing.T) {
partitiontest.PartitionTest(t)
- payset := preparePayset(50, 50)
+ payset := generatePayset(50, 50)
commit1 := payset.CommitFlat()
payset[0], payset[1] = payset[1], payset[0]
commit2 := payset.CommitFlat()
@@ -63,8 +63,7 @@ func TestEmptyPaysetCommitment(t *testing.T) {
}
func BenchmarkCommit(b *testing.B) {
- payset := preparePayset(5000, 50)
-
+ payset := generatePayset(5000, 50)
b.ResetTimer()
for i := 0; i < b.N; i++ {
payset.CommitFlat()
@@ -73,7 +72,7 @@ func BenchmarkCommit(b *testing.B) {
}
func BenchmarkToBeHashed(b *testing.B) {
- payset := preparePayset(5000, 50)
+ payset := generatePayset(5000, 50)
b.ResetTimer()
for i := 0; i < b.N; i++ {
payset.ToBeHashed()
diff --git a/data/transactions/stateproof.go b/data/transactions/stateproof.go
index 15c69e71bd..cdc8bc916b 100644
--- a/data/transactions/stateproof.go
+++ b/data/transactions/stateproof.go
@@ -17,6 +17,8 @@
package transactions
import (
+ "errors"
+
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/stateproof"
"github.com/algorand/go-algorand/data/basics"
@@ -33,6 +35,40 @@ type StateProofTxnFields struct {
Message stateproofmsg.Message `codec:"spmsg"`
}
+var errBadSenderInStateProofTxn = errors.New("sender must be the state-proof sender")
+var errFeeMustBeZeroInStateproofTxn = errors.New("fee must be zero in state-proof transaction")
+var errNoteMustBeEmptyInStateproofTxn = errors.New("note must be empty in state-proof transaction")
+var errGroupMustBeZeroInStateproofTxn = errors.New("group must be zero in state-proof transaction")
+var errRekeyToMustBeZeroInStateproofTxn = errors.New("rekey must be zero in state-proof transaction")
+var errLeaseMustBeZeroInStateproofTxn = errors.New("lease must be zero in state-proof transaction")
+
+// wellFormed performs stateless checks on the StateProof transaction
+func (sp StateProofTxnFields) wellFormed(header Header) error {
+ // This is a placeholder transaction used to store state proofs
+ // on the ledger, and ensure they are broadly available. Most of
+ // the fields must be empty. It must be issued from a special
+ // sender address.
+ if header.Sender != StateProofSender {
+ return errBadSenderInStateProofTxn
+ }
+ if !header.Fee.IsZero() {
+ return errFeeMustBeZeroInStateproofTxn
+ }
+ if len(header.Note) != 0 {
+ return errNoteMustBeEmptyInStateproofTxn
+ }
+ if !header.Group.IsZero() {
+ return errGroupMustBeZeroInStateproofTxn
+ }
+ if !header.RekeyTo.IsZero() {
+ return errRekeyToMustBeZeroInStateproofTxn
+ }
+ if header.Lease != [32]byte{} {
+ return errLeaseMustBeZeroInStateproofTxn
+ }
+ return nil
+}
+
// specialAddr is used to form a unique address that will send out state proofs.
//
//msgp:ignore specialAddr
diff --git a/data/transactions/stateproof_test.go b/data/transactions/stateproof_test.go
new file mode 100644
index 0000000000..9090c90352
--- /dev/null
+++ b/data/transactions/stateproof_test.go
@@ -0,0 +1,148 @@
+// Copyright (C) 2019-2025 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 transactions
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/algorand/go-algorand/config"
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/crypto/stateproof"
+ "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/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+type stateproofTxnTestCase struct {
+ expectedError error
+
+ StateProofInterval uint64
+ fee basics.MicroAlgos
+ note []byte
+ group crypto.Digest
+ lease [32]byte
+ rekeyValue basics.Address
+ sender basics.Address
+}
+
+func TestUnsupportedStateProof(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ curProto.StateProofInterval = 0
+ err := Transaction{
+ Type: protocol.StateProofTx,
+ Header: Header{
+ Sender: StateProofSender,
+ Fee: basics.MicroAlgos{Raw: 100},
+ },
+ StateProofTxnFields: StateProofTxnFields{},
+ }.WellFormed(SpecialAddresses{}, curProto)
+ require.ErrorContains(t, err, "state proofs not supported")
+}
+
+func (s *stateproofTxnTestCase) runIsWellFormedForTestCase() error {
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+
+ // edit txn params. wanted
+ return Transaction{
+ Type: protocol.StateProofTx,
+ Header: Header{
+ Sender: s.sender,
+ Fee: s.fee,
+ Note: s.note,
+ Group: s.group,
+ Lease: s.lease,
+ RekeyTo: s.rekeyValue,
+ },
+ StateProofTxnFields: StateProofTxnFields{},
+ }.WellFormed(SpecialAddresses{}, curProto)
+}
+
+func TestWellFormedStateProofTxn(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ // want to create different Txns, run on all of these cases the check, and have an expected result
+ cases := []stateproofTxnTestCase{
+ /* 0 */ {expectedError: errBadSenderInStateProofTxn, sender: basics.Address{1, 2, 3, 4}},
+ /* 1 */ {expectedError: errFeeMustBeZeroInStateproofTxn, sender: StateProofSender, fee: basics.MicroAlgos{Raw: 1}},
+ /* 2 */ {expectedError: errNoteMustBeEmptyInStateproofTxn, sender: StateProofSender, note: []byte{1, 2, 3}},
+ /* 3 */ {expectedError: errGroupMustBeZeroInStateproofTxn, sender: StateProofSender, group: crypto.Digest{1, 2, 3}},
+ /* 4 */ {expectedError: errRekeyToMustBeZeroInStateproofTxn, sender: StateProofSender, rekeyValue: basics.Address{1, 2, 3, 4}},
+ /* 5 */ {expectedError: errLeaseMustBeZeroInStateproofTxn, sender: StateProofSender, lease: [32]byte{1, 2, 3, 4}},
+ /* 6 */ {expectedError: nil, fee: basics.MicroAlgos{Raw: 0}, note: nil, group: crypto.Digest{}, lease: [32]byte{}, rekeyValue: basics.Address{}, sender: StateProofSender},
+ }
+ for i, testCase := range cases {
+ t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
+ t.Parallel()
+ require.Equal(t, testCase.expectedError, testCase.runIsWellFormedForTestCase())
+ })
+ }
+}
+
+func TestStateProofTxnShouldBeZero(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
+ require.NoError(t, err)
+
+ curProto := config.Consensus[protocol.ConsensusCurrentVersion]
+ curProto.StateProofInterval = 256
+ txn := Transaction{
+ Type: protocol.PaymentTx,
+ Header: Header{
+ Sender: addr1,
+ Fee: basics.MicroAlgos{Raw: 100},
+ FirstValid: 0,
+ LastValid: 0,
+ Note: []byte{0, 1},
+ GenesisID: "",
+ GenesisHash: crypto.Digest{},
+ },
+ StateProofTxnFields: StateProofTxnFields{},
+ }
+
+ const erroMsg = "type pay has non-zero fields for type stpf"
+ txn.StateProofType = 1
+ err = txn.WellFormed(SpecialAddresses{}, curProto)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), erroMsg)
+
+ txn.StateProofType = 0
+ txn.Message = stateproofmsg.Message{FirstAttestedRound: 1}
+ err = txn.WellFormed(SpecialAddresses{}, curProto)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), erroMsg)
+
+ txn.Message = stateproofmsg.Message{}
+ txn.StateProof = stateproof.StateProof{SignedWeight: 100}
+ err = txn.WellFormed(SpecialAddresses{}, curProto)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), erroMsg)
+
+ txn.StateProof = stateproof.StateProof{}
+ txn.Message.LastAttestedRound = 512
+ err = txn.WellFormed(SpecialAddresses{}, curProto)
+ require.Error(t, err)
+ require.Contains(t, err.Error(), erroMsg)
+
+ txn.Message.LastAttestedRound = 0
+ err = txn.WellFormed(SpecialAddresses{}, curProto)
+ require.NoError(t, err)
+}
diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go
index c4548809c5..9223b3640a 100644
--- a/data/transactions/transaction.go
+++ b/data/transactions/transaction.go
@@ -21,13 +21,11 @@ import (
"encoding/binary"
"errors"
"fmt"
- "slices"
"sync"
"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/committee"
"github.com/algorand/go-algorand/protocol"
)
@@ -288,287 +286,80 @@ func (tx Header) TxFee() basics.MicroAlgos {
return tx.Fee
}
-// Alive checks to see if the transaction is still alive (can be applied) at the specified Round.
-func (tx Header) Alive(tc TxnContext) error {
- // Check round validity
- round := tc.Round()
- if round < tx.FirstValid || round > tx.LastValid {
- return &TxnDeadError{
- Round: round,
- FirstValid: tx.FirstValid,
- LastValid: tx.LastValid,
- Early: round < tx.FirstValid,
- }
- }
-
- // Check genesis ID
- proto := tc.ConsensusProtocol()
- genesisID := tc.GenesisID()
- if tx.GenesisID != "" && tx.GenesisID != genesisID {
- return fmt.Errorf("tx.GenesisID <%s> does not match expected <%s>",
- tx.GenesisID, genesisID)
+// MatchAddress checks if the transaction touches a given address. The feesink
+// and rewards pool are not considered matches.
+func (tx Transaction) MatchAddress(addr basics.Address) bool {
+ if addr == tx.Sender {
+ return true
}
- // Check genesis hash
- if proto.SupportGenesisHash {
- genesisHash := tc.GenesisHash()
- if tx.GenesisHash != (crypto.Digest{}) && tx.GenesisHash != genesisHash {
- return fmt.Errorf("tx.GenesisHash <%s> does not match expected <%s>",
- tx.GenesisHash, genesisHash)
+ switch tx.Type {
+ case protocol.PaymentTx:
+ if addr == tx.PaymentTxnFields.Receiver {
+ return true
+ }
+ if !tx.PaymentTxnFields.CloseRemainderTo.IsZero() &&
+ addr == tx.PaymentTxnFields.CloseRemainderTo {
+ return true
+ }
+ case protocol.AssetTransferTx:
+ if addr == tx.AssetTransferTxnFields.AssetReceiver {
+ return true
}
- if proto.RequireGenesisHash && tx.GenesisHash == (crypto.Digest{}) {
- return fmt.Errorf("required tx.GenesisHash is missing")
+ if !tx.AssetTransferTxnFields.AssetCloseTo.IsZero() &&
+ addr == tx.AssetTransferTxnFields.AssetCloseTo {
+ return true
}
- } else {
- if tx.GenesisHash != (crypto.Digest{}) {
- return fmt.Errorf("tx.GenesisHash <%s> not allowed", tx.GenesisHash)
+ if !tx.AssetTransferTxnFields.AssetSender.IsZero() &&
+ addr == tx.AssetTransferTxnFields.AssetSender {
+ return true
+ }
+ case protocol.HeartbeatTx:
+ if addr == tx.HeartbeatTxnFields.HbAddress {
+ return true
}
}
-
- return nil
+ return false
}
-// MatchAddress checks if the transaction touches a given address.
-func (tx Transaction) MatchAddress(addr basics.Address, spec SpecialAddresses) bool {
- return slices.Contains(tx.relevantAddrs(spec), addr)
-}
-
-var errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound = errors.New("transaction first voting round need to be less than its last voting round")
-var errKeyregTxnNonCoherentVotingKeys = errors.New("the following transaction fields need to be clear/set together : votekey, selkey, votekd")
-var errKeyregTxnOfflineTransactionHasVotingRounds = errors.New("on going offline key registration transaction, the vote first and vote last fields should not be set")
-var errKeyregTxnUnsupportedSwitchToNonParticipating = errors.New("transaction tries to mark an account as nonparticipating, but that transaction is not supported")
-var errKeyregTxnGoingOnlineWithNonParticipating = errors.New("transaction tries to register keys to go online, but nonparticipatory flag is set")
-var errKeyregTxnGoingOnlineWithZeroVoteLast = errors.New("transaction tries to register keys to go online, but vote last is set to zero")
-var errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid = errors.New("transaction tries to register keys to go online, but first voting round is beyond the round after last valid round")
-var errKeyRegEmptyStateProofPK = errors.New("online keyreg transaction cannot have empty field StateProofPK")
-var errKeyregTxnNotEmptyStateProofPK = errors.New("transaction field StateProofPK should be empty in this consensus version")
-var errKeyregTxnNonParticipantShouldBeEmptyStateProofPK = errors.New("non participation keyreg transactions should contain empty stateProofPK")
-var errKeyregTxnOfflineShouldBeEmptyStateProofPK = errors.New("offline keyreg transactions should contain empty stateProofPK")
-var errKeyRegTxnValidityPeriodTooLong = errors.New("validity period for keyreg transaction is too long")
-var errStateProofNotSupported = errors.New("state proofs not supported")
-var errBadSenderInStateProofTxn = errors.New("sender must be the state-proof sender")
-var errFeeMustBeZeroInStateproofTxn = errors.New("fee must be zero in state-proof transaction")
-var errNoteMustBeEmptyInStateproofTxn = errors.New("note must be empty in state-proof transaction")
-var errGroupMustBeZeroInStateproofTxn = errors.New("group must be zero in state-proof transaction")
-var errRekeyToMustBeZeroInStateproofTxn = errors.New("rekey must be zero in state-proof transaction")
-var errLeaseMustBeZeroInStateproofTxn = errors.New("lease must be zero in state-proof transaction")
-
// WellFormed checks that the transaction looks reasonable on its own (but not necessarily valid against the actual ledger). It does not check signatures.
func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusParams) error {
switch tx.Type {
case protocol.PaymentTx:
- // in case that the fee sink is spending, check that this spend is to a valid address
- err := tx.checkSpender(tx.Header, spec, proto)
+ err := tx.PaymentTxnFields.wellFormed(tx.Header, spec, proto)
if err != nil {
return err
}
case protocol.KeyRegistrationTx:
- if proto.EnableKeyregCoherencyCheck {
- // ensure that the VoteLast is greater or equal to the VoteFirst
- if tx.KeyregTxnFields.VoteFirst > tx.KeyregTxnFields.VoteLast {
- return errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound
- }
-
- // The trio of [VotePK, SelectionPK, VoteKeyDilution] needs to be all zeros or all non-zero for the transaction to be valid.
- if !((tx.KeyregTxnFields.VotePK.IsEmpty() && tx.KeyregTxnFields.SelectionPK.IsEmpty() && tx.KeyregTxnFields.VoteKeyDilution == 0) ||
- (!tx.KeyregTxnFields.VotePK.IsEmpty() && !tx.KeyregTxnFields.SelectionPK.IsEmpty() && tx.KeyregTxnFields.VoteKeyDilution != 0)) {
- return errKeyregTxnNonCoherentVotingKeys
- }
-
- // if it's a going offline transaction
- if tx.KeyregTxnFields.VoteKeyDilution == 0 {
- // check that we don't have any VoteFirst/VoteLast fields.
- if tx.KeyregTxnFields.VoteFirst != 0 || tx.KeyregTxnFields.VoteLast != 0 {
- return errKeyregTxnOfflineTransactionHasVotingRounds
- }
- } else {
- // going online
- if tx.KeyregTxnFields.VoteLast == 0 {
- return errKeyregTxnGoingOnlineWithZeroVoteLast
- }
- if tx.KeyregTxnFields.VoteFirst > tx.LastValid+1 {
- return errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid
- }
- }
- }
-
- // check that, if this tx is marking an account nonparticipating,
- // it supplies no key (as though it were trying to go offline)
- if tx.KeyregTxnFields.Nonparticipation {
- if !proto.SupportBecomeNonParticipatingTransactions {
- // if the transaction has the Nonparticipation flag high, but the protocol does not support
- // that type of transaction, it is invalid.
- return errKeyregTxnUnsupportedSwitchToNonParticipating
- }
- suppliesNullKeys := tx.KeyregTxnFields.VotePK.IsEmpty() || tx.KeyregTxnFields.SelectionPK.IsEmpty()
- if !suppliesNullKeys {
- return errKeyregTxnGoingOnlineWithNonParticipating
- }
- }
-
- if err := tx.stateProofPKWellFormed(proto); err != nil {
+ err := tx.KeyregTxnFields.wellFormed(tx.Header, spec, proto)
+ if err != nil {
return err
}
- case protocol.AssetConfigTx:
+ case protocol.AssetConfigTx, protocol.AssetTransferTx, protocol.AssetFreezeTx:
if !proto.Asset {
return fmt.Errorf("asset transaction not supported")
}
- case protocol.AssetTransferTx:
- if !proto.Asset {
- return fmt.Errorf("asset transaction not supported")
- }
-
- case protocol.AssetFreezeTx:
- if !proto.Asset {
- return fmt.Errorf("asset transaction not supported")
- }
case protocol.ApplicationCallTx:
if !proto.Application {
return fmt.Errorf("application transaction not supported")
}
- // Ensure requested action is valid
- switch tx.OnCompletion {
- case NoOpOC, OptInOC, CloseOutOC, ClearStateOC, UpdateApplicationOC, DeleteApplicationOC:
- /* ok */
- default:
- return fmt.Errorf("invalid application OnCompletion")
- }
-
- // Programs may only be set for creation or update
- if tx.ApplicationID != 0 && tx.OnCompletion != UpdateApplicationOC {
- if len(tx.ApprovalProgram) != 0 || len(tx.ClearStateProgram) != 0 {
- return fmt.Errorf("programs may only be specified during application creation or update")
- }
- } else {
- // This will check version matching, but not downgrading. That
- // depends on chain state (so we pass an empty AppParams)
- err := CheckContractVersions(tx.ApprovalProgram, tx.ClearStateProgram, basics.AppParams{}, &proto)
- if err != nil {
- return err
- }
- }
-
- effectiveEPP := tx.ExtraProgramPages
- // Schemas and ExtraProgramPages may only be set during application creation
- if tx.ApplicationID != 0 {
- if tx.LocalStateSchema != (basics.StateSchema{}) ||
- tx.GlobalStateSchema != (basics.StateSchema{}) {
- return fmt.Errorf("local and global state schemas are immutable")
- }
- if tx.ExtraProgramPages != 0 {
- return fmt.Errorf("tx.ExtraProgramPages is immutable")
- }
-
- if proto.EnableExtraPagesOnAppUpdate {
- effectiveEPP = uint32(proto.MaxExtraAppProgramPages)
- }
-
- }
-
- // Limit total number of arguments
- if len(tx.ApplicationArgs) > proto.MaxAppArgs {
- return fmt.Errorf("too many application args, max %d", proto.MaxAppArgs)
- }
-
- // Sum up argument lengths
- var argSum uint64
- for _, arg := range tx.ApplicationArgs {
- argSum = basics.AddSaturate(argSum, uint64(len(arg)))
- }
-
- // Limit total length of all arguments
- if argSum > uint64(proto.MaxAppTotalArgLen) {
- return fmt.Errorf("application args total length too long, max len %d bytes", proto.MaxAppTotalArgLen)
- }
-
- // Limit number of accounts referred to in a single ApplicationCall
- if len(tx.Accounts) > proto.MaxAppTxnAccounts {
- return fmt.Errorf("tx.Accounts too long, max number of accounts is %d", proto.MaxAppTxnAccounts)
- }
-
- // Limit number of other app global states referred to
- if len(tx.ForeignApps) > proto.MaxAppTxnForeignApps {
- return fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is %d", proto.MaxAppTxnForeignApps)
- }
-
- if len(tx.ForeignAssets) > proto.MaxAppTxnForeignAssets {
- return fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is %d", proto.MaxAppTxnForeignAssets)
- }
-
- if len(tx.Boxes) > proto.MaxAppBoxReferences {
- return fmt.Errorf("tx.Boxes too long, max number of box references is %d", proto.MaxAppBoxReferences)
- }
-
- // Limit the sum of all types of references that bring in account records
- if len(tx.Accounts)+len(tx.ForeignApps)+len(tx.ForeignAssets)+len(tx.Boxes) > proto.MaxAppTotalTxnReferences {
- return fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = %d", proto.MaxAppTotalTxnReferences)
- }
-
- if tx.ExtraProgramPages > uint32(proto.MaxExtraAppProgramPages) {
- return fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", proto.MaxExtraAppProgramPages)
- }
-
- lap := len(tx.ApprovalProgram)
- lcs := len(tx.ClearStateProgram)
- pages := int(1 + effectiveEPP)
- if lap > pages*proto.MaxAppProgramLen {
- return fmt.Errorf("approval program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
- }
- if lcs > pages*proto.MaxAppProgramLen {
- return fmt.Errorf("clear state program too long. max len %d bytes", pages*proto.MaxAppProgramLen)
- }
- if lap+lcs > pages*proto.MaxAppTotalProgramLen {
- return fmt.Errorf("app programs too long. max total len %d bytes", pages*proto.MaxAppTotalProgramLen)
- }
-
- for i, br := range tx.Boxes {
- // recall 0 is the current app so indexes are shifted, thus test is for greater than, not gte.
- 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 {
- return fmt.Errorf("tx.LocalStateSchema too large, max number of keys is %d", proto.MaxLocalSchemaEntries)
- }
-
- if tx.GlobalStateSchema.NumEntries() > proto.MaxGlobalSchemaEntries {
- return fmt.Errorf("tx.GlobalStateSchema too large, max number of keys is %d", proto.MaxGlobalSchemaEntries)
+ err := tx.ApplicationCallTxnFields.wellFormed(proto)
+ if err != nil {
+ return err
}
case protocol.StateProofTx:
if proto.StateProofInterval == 0 {
- return errStateProofNotSupported
+ return fmt.Errorf("state proofs not supported")
}
- // This is a placeholder transaction used to store state proofs
- // on the ledger, and ensure they are broadly available. Most of
- // the fields must be empty. It must be issued from a special
- // sender address.
- if tx.Sender != StateProofSender {
- return errBadSenderInStateProofTxn
- }
- if !tx.Fee.IsZero() {
- return errFeeMustBeZeroInStateproofTxn
- }
- if len(tx.Note) != 0 {
- return errNoteMustBeEmptyInStateproofTxn
- }
- if !tx.Group.IsZero() {
- return errGroupMustBeZeroInStateproofTxn
- }
- if !tx.RekeyTo.IsZero() {
- return errRekeyToMustBeZeroInStateproofTxn
- }
- if tx.Lease != [32]byte{} {
- return errLeaseMustBeZeroInStateproofTxn
+ err := tx.StateProofTxnFields.wellFormed(tx.Header)
+ if err != nil {
+ return err
}
case protocol.HeartbeatTx:
@@ -576,35 +367,9 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
return fmt.Errorf("heartbeat transaction not supported")
}
- // If this is a free/cheap heartbeat, it must be very simple.
- if tx.Fee.Raw < proto.MinTxnFee && tx.Group.IsZero() {
- kind := "free"
- if tx.Fee.Raw > 0 {
- kind = "cheap"
- }
-
- if len(tx.Note) > 0 {
- return fmt.Errorf("tx.Note is set in %s heartbeat", kind)
- }
- if tx.Lease != [32]byte{} {
- return fmt.Errorf("tx.Lease is set in %s heartbeat", kind)
- }
- if !tx.RekeyTo.IsZero() {
- return fmt.Errorf("tx.RekeyTo is set in %s heartbeat", kind)
- }
- }
-
- if (tx.HbProof == crypto.HeartbeatProof{}) {
- return errors.New("tx.HbProof is empty")
- }
- if (tx.HbSeed == committee.Seed{}) {
- return errors.New("tx.HbSeed is empty")
- }
- if tx.HbVoteID.IsEmpty() {
- return errors.New("tx.HbVoteID is empty")
- }
- if tx.HbKeyDilution == 0 {
- return errors.New("tx.HbKeyDilution is zero")
+ err := tx.HeartbeatTxnFields.wellFormed(tx.Header, proto)
+ if err != nil {
+ return err
}
default:
@@ -697,84 +462,6 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa
return nil
}
-func (tx Transaction) stateProofPKWellFormed(proto config.ConsensusParams) error {
- isEmpty := tx.KeyregTxnFields.StateProofPK.IsEmpty()
- if !proto.EnableStateProofKeyregCheck {
- // make certain empty key is stored.
- if !isEmpty {
- return errKeyregTxnNotEmptyStateProofPK
- }
- return nil
- }
-
- if proto.MaxKeyregValidPeriod != 0 && uint64(tx.VoteLast.SubSaturate(tx.VoteFirst)) > proto.MaxKeyregValidPeriod {
- return errKeyRegTxnValidityPeriodTooLong
- }
-
- if tx.Nonparticipation {
- // make certain that set offline request clears the stateProofPK.
- if !isEmpty {
- return errKeyregTxnNonParticipantShouldBeEmptyStateProofPK
- }
- return nil
- }
-
- if tx.VotePK.IsEmpty() || tx.SelectionPK.IsEmpty() {
- if !isEmpty {
- return errKeyregTxnOfflineShouldBeEmptyStateProofPK
- }
- return nil
- }
-
- // online transactions:
- // setting online cannot set an empty stateProofPK
- if isEmpty {
- return errKeyRegEmptyStateProofPK
- }
-
- return nil
-}
-
-// Aux returns the note associated with this transaction
-func (tx Header) Aux() []byte {
- return tx.Note
-}
-
-// First returns the first round this transaction is valid
-func (tx Header) First() basics.Round {
- return tx.FirstValid
-}
-
-// Last returns the first round this transaction is valid
-func (tx Header) Last() basics.Round {
- return tx.LastValid
-}
-
-// relevantAddrs returns the addresses whose balance records this transaction will need to access.
-func (tx Transaction) relevantAddrs(spec SpecialAddresses) []basics.Address {
- addrs := []basics.Address{tx.Sender, spec.FeeSink}
-
- switch tx.Type {
- case protocol.PaymentTx:
- addrs = append(addrs, tx.PaymentTxnFields.Receiver)
- if !tx.PaymentTxnFields.CloseRemainderTo.IsZero() {
- addrs = append(addrs, tx.PaymentTxnFields.CloseRemainderTo)
- }
- case protocol.AssetTransferTx:
- addrs = append(addrs, tx.AssetTransferTxnFields.AssetReceiver)
- if !tx.AssetTransferTxnFields.AssetCloseTo.IsZero() {
- addrs = append(addrs, tx.AssetTransferTxnFields.AssetCloseTo)
- }
- if !tx.AssetTransferTxnFields.AssetSender.IsZero() {
- addrs = append(addrs, tx.AssetTransferTxnFields.AssetSender)
- }
- case protocol.HeartbeatTx:
- addrs = append(addrs, tx.HeartbeatTxnFields.HbAddress)
- }
-
- return addrs
-}
-
// TxAmount returns the amount paid to the recipient in this payment
func (tx Transaction) TxAmount() basics.MicroAlgos {
switch tx.Type {
@@ -811,17 +498,6 @@ func (tx Transaction) EstimateEncodedSize() int {
return stx.GetEncodedLength()
}
-// TxnContext describes the context in which a transaction can appear
-// (pretty much, a block, but we don't have the definition of a block
-// here, since that would be a circular dependency). This is used to
-// decide if a transaction is alive or not.
-type TxnContext interface {
- Round() basics.Round
- ConsensusProtocol() config.ConsensusParams
- GenesisID() string
- GenesisHash() crypto.Digest
-}
-
// ProgramVersion extracts the version of an AVM program from its bytecode
func ProgramVersion(bytecode []byte) (version uint64, length int, err error) {
if len(bytecode) == 0 {
@@ -878,32 +554,3 @@ func CheckContractVersions(approval []byte, clear []byte, previous basics.AppPar
}
return nil
}
-
-// ExplicitTxnContext is a struct that implements TxnContext with
-// explicit fields for everything.
-type ExplicitTxnContext struct {
- ExplicitRound basics.Round
- Proto config.ConsensusParams
- GenID string
- GenHash crypto.Digest
-}
-
-// Round implements the TxnContext interface
-func (tc ExplicitTxnContext) Round() basics.Round {
- return tc.ExplicitRound
-}
-
-// ConsensusProtocol implements the TxnContext interface
-func (tc ExplicitTxnContext) ConsensusProtocol() config.ConsensusParams {
- return tc.Proto
-}
-
-// GenesisID implements the TxnContext interface
-func (tc ExplicitTxnContext) GenesisID() string {
- return tc.GenID
-}
-
-// GenesisHash implements the TxnContext interface
-func (tc ExplicitTxnContext) GenesisHash() crypto.Digest {
- return tc.GenHash
-}
diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go
index a024cf3b5a..289e5f018b 100644
--- a/data/transactions/transaction_test.go
+++ b/data/transactions/transaction_test.go
@@ -17,21 +17,13 @@
package transactions
import (
- "flag"
- "fmt"
- "strings"
"testing"
- "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/committee"
- "github.com/algorand/go-algorand/data/stateproofmsg"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -64,688 +56,6 @@ func TestTransaction_EstimateEncodedSize(t *testing.T) {
require.Equal(t, 200, tx.EstimateEncodedSize())
}
-func generateDummyGoNonparticpatingTransaction(addr basics.Address) (tx Transaction) {
- buf := make([]byte, 10)
- crypto.RandBytes(buf[:])
-
- proto := config.Consensus[protocol.ConsensusCurrentVersion]
- tx = Transaction{
- Type: protocol.KeyRegistrationTx,
- Header: Header{
- Sender: addr,
- Fee: basics.MicroAlgos{Raw: proto.MinTxnFee},
- FirstValid: 1,
- LastValid: 300,
- },
- KeyregTxnFields: KeyregTxnFields{
- Nonparticipation: true,
- VoteFirst: 0,
- VoteLast: 0,
- },
- }
-
- tx.KeyregTxnFields.Nonparticipation = true
- return tx
-}
-
-func TestGoOnlineGoNonparticipatingContradiction(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- // addr has no significance here other than being a normal valid address
- addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
- require.NoError(t, err)
-
- tx := generateDummyGoNonparticpatingTransaction(addr)
- // Generate keys, they don't need to be good or secure, just present
- v := crypto.GenerateOneTimeSignatureSecrets(1, 1)
- // Also generate a new VRF key
- vrf := crypto.GenerateVRFSecrets()
- tx.KeyregTxnFields = KeyregTxnFields{
- VotePK: v.OneTimeSignatureVerifier,
- SelectionPK: vrf.PK,
- VoteKeyDilution: 1,
- VoteFirst: 1,
- VoteLast: 100,
- Nonparticipation: true,
- }
- // this tx tries to both register keys to go online, and mark an account as non-participating.
- // it is not well-formed.
- err = tx.WellFormed(SpecialAddresses{}, config.Consensus[protocol.ConsensusCurrentVersion])
- require.ErrorContains(t, err, "tries to register keys to go online, but nonparticipatory flag is set")
-}
-
-func TestGoNonparticipatingWellFormed(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- // addr has no significance here other than being a normal valid address
- addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
- require.NoError(t, err)
-
- tx := generateDummyGoNonparticpatingTransaction(addr)
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
-
- if !curProto.SupportBecomeNonParticipatingTransactions {
- t.Skipf("Skipping rest of test because current protocol version %v does not support become-nonparticipating transactions", protocol.ConsensusCurrentVersion)
- }
-
- // this tx is well-formed
- err = tx.WellFormed(SpecialAddresses{}, curProto)
- require.NoError(t, err)
- // but it should stop being well-formed if the protocol does not support it
- curProto.SupportBecomeNonParticipatingTransactions = false
- err = tx.WellFormed(SpecialAddresses{}, curProto)
- require.ErrorContains(t, err, "mark an account as nonparticipating, but")
-}
-
-func TestAppCallCreateWellFormed(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
- futureProto := config.Consensus[protocol.ConsensusFuture]
- addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
- require.NoError(t, err)
- v5 := []byte{0x05}
- v6 := []byte{0x06}
-
- usecases := []struct {
- tx Transaction
- proto config.ConsensusParams
- expectedError string
- }{
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- },
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- },
- },
- proto: curProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- },
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 0,
- },
- },
- proto: curProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- },
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 3,
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- },
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 0,
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- },
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApprovalProgram: v5,
- ClearStateProgram: v6,
- },
- },
- proto: futureProto,
- expectedError: "mismatch",
- },
- }
- for i, usecase := range usecases {
- t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) {
- err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto)
- if usecase.expectedError != "" {
- require.Error(t, err)
- require.Contains(t, err.Error(), usecase.expectedError)
- } else {
- require.NoError(t, err)
- }
- })
- }
-}
-
-func TestWellFormedErrors(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
- futureProto := config.Consensus[protocol.ConsensusFuture]
- 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}
- okHeader := Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 105,
- FirstValid: 100,
- }
- usecases := []struct {
- tx Transaction
- proto config.ConsensusParams
- expectedError error
- }{
- {
- tx: Transaction{
- Type: protocol.PaymentTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 100},
- },
- },
- proto: protoV27,
- expectedError: makeMinFeeErrorf("transaction had fee %d, which is less than the minimum %d", 100, curProto.MinTxnFee),
- },
- {
- tx: Transaction{
- Type: protocol.PaymentTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 100},
- },
- },
- proto: curProto,
- },
- {
- tx: Transaction{
- Type: protocol.PaymentTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 1000},
- LastValid: 100,
- FirstValid: 105,
- },
- },
- proto: curProto,
- expectedError: fmt.Errorf("transaction invalid range (%d--%d)", 105, 100),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0, // creation
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 1,
- },
- },
- proto: protoV27,
- expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", protoV27.MaxExtraAppProgramPages),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0, // creation
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte("Xjunk"),
- },
- },
- proto: protoV27,
- expectedError: fmt.Errorf("approval program too long. max len 1024 bytes"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0, // creation
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte("Xjunk"),
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0, // creation
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte(strings.Repeat("X", 1025)),
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("app programs too long. max total len 2048 bytes"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0, // creation
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte(strings.Repeat("X", 1025)),
- ExtraProgramPages: 1,
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 1,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 0,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 4,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.ExtraProgramPages exceeds MaxExtraAppProgramPages = %d", futureProto.MaxExtraAppProgramPages),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ForeignApps: []basics.AppIndex{10, 11},
- },
- },
- proto: protoV27,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ForeignApps: []basics.AppIndex{10, 11, 12},
- },
- },
- proto: protoV27,
- expectedError: fmt.Errorf("tx.ForeignApps too long, max number of foreign apps is 2"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ForeignApps: []basics.AppIndex{10, 11, 12, 13, 14, 15, 16, 17},
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ForeignAssets: []basics.AssetIndex{14, 15, 16, 17, 18, 19, 20, 21, 22},
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.ForeignAssets too long, max number of foreign assets is 8"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- Accounts: []basics.Address{{}, {}, {}},
- ForeignApps: []basics.AppIndex{14, 15, 16, 17},
- ForeignAssets: []basics.AssetIndex{14, 15, 16, 17},
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx references exceed MaxAppTotalTxnReferences = 8"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte(strings.Repeat("X", 1025)),
- ExtraProgramPages: 0,
- OnCompletion: UpdateApplicationOC,
- },
- },
- proto: protoV28,
- expectedError: fmt.Errorf("app programs too long. max total len %d bytes", curProto.MaxAppProgramLen),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ApprovalProgram: []byte(strings.Repeat("X", 1025)),
- ClearStateProgram: []byte(strings.Repeat("X", 1025)),
- ExtraProgramPages: 0,
- OnCompletion: UpdateApplicationOC,
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- ApprovalProgram: v5,
- ClearStateProgram: v5,
- ApplicationArgs: [][]byte{
- []byte("write"),
- },
- ExtraProgramPages: 1,
- OnCompletion: UpdateApplicationOC,
- },
- },
- proto: protoV28,
- expectedError: fmt.Errorf("tx.ExtraProgramPages is immutable"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.Boxes[0].Index is 1. Exceeds len(tx.ForeignApps)"),
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
- ForeignApps: []basics.AppIndex{1},
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.ApplicationCallTx,
- Header: okHeader,
- ApplicationCallTxnFields: ApplicationCallTxnFields{
- ApplicationID: 1,
- Boxes: []BoxRef{{Index: 1, Name: []byte("junk")}},
- ForeignApps: []basics.AppIndex{1},
- },
- },
- 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,
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- },
- proto: protoV36,
- expectedError: fmt.Errorf("heartbeat transaction not supported"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.HbProof is empty"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.HbSeed is empty"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.HbVoteID is empty"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.HbKeyDilution is zero"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: okHeader,
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 100},
- LastValid: 105,
- FirstValid: 100,
- Note: []byte{0x01},
- },
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.Note is set in cheap heartbeat"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 100},
- LastValid: 105,
- FirstValid: 100,
- Lease: [32]byte{0x01},
- },
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.Lease is set in cheap heartbeat"),
- },
- {
- tx: Transaction{
- Type: protocol.HeartbeatTx,
- Header: Header{
- Sender: addr1,
- LastValid: 105,
- FirstValid: 100,
- RekeyTo: [32]byte{0x01},
- },
- HeartbeatTxnFields: &HeartbeatTxnFields{
- HbProof: crypto.HeartbeatProof{
- Sig: [64]byte{0x01},
- },
- HbSeed: committee.Seed{0x02},
- HbVoteID: crypto.OneTimeSignatureVerifier{0x03},
- HbKeyDilution: 10,
- },
- },
- proto: futureProto,
- expectedError: fmt.Errorf("tx.RekeyTo is set in free heartbeat"),
- },
- }
- for _, usecase := range usecases {
- err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto)
- assert.Equal(t, usecase.expectedError, err)
- }
-}
-
// TestTransactionHash checks that Transaction.ID() is equivalent to the old simpler crypto.HashObj() implementation.
func TestTransactionHash(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -764,780 +74,44 @@ func TestTransactionHash(t *testing.T) {
require.Equal(t, txid3, txid2)
}
-var generateFlag = flag.Bool("generate", false, "")
-
-// running test with -generate would generate the matrix used in the test ( without the "correct" errors )
-func TestWellFormedKeyRegistrationTx(t *testing.T) {
+func TestTransactionIDChanges(t *testing.T) {
partitiontest.PartitionTest(t)
- flag.Parse()
-
- // addr has no significance here other than being a normal valid address
- addr, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
- require.NoError(t, err)
-
- tx := generateDummyGoNonparticpatingTransaction(addr)
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
- if !curProto.SupportBecomeNonParticipatingTransactions {
- t.Skipf("Skipping rest of test because current protocol version %v does not support become-nonparticipating transactions", protocol.ConsensusCurrentVersion)
- }
-
- // this tx is well-formed
- err = tx.WellFormed(SpecialAddresses{}, curProto)
- require.NoError(t, err)
-
- type keyRegTestCase struct {
- votePK crypto.OneTimeSignatureVerifier
- selectionPK crypto.VRFVerifier
- stateProofPK merklesignature.Commitment
- voteFirst basics.Round
- voteLast basics.Round
- lastValid basics.Round
- voteKeyDilution uint64
- nonParticipation bool
- supportBecomeNonParticipatingTransactions bool
- enableKeyregCoherencyCheck bool
- enableStateProofKeyregCheck bool
- err error
- }
- votePKValue := crypto.OneTimeSignatureVerifier{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}
- selectionPKValue := crypto.VRFVerifier{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}
-
- stateProofPK := merklesignature.Commitment([merklesignature.MerkleSignatureSchemeRootSize]byte{1})
- maxValidPeriod := config.Consensus[protocol.ConsensusCurrentVersion].MaxKeyregValidPeriod
-
- runTestCase := func(testCase keyRegTestCase) error {
-
- tx.KeyregTxnFields.VotePK = testCase.votePK
- tx.KeyregTxnFields.SelectionPK = testCase.selectionPK
- tx.KeyregTxnFields.VoteFirst = testCase.voteFirst
- tx.KeyregTxnFields.VoteLast = testCase.voteLast
- tx.KeyregTxnFields.VoteKeyDilution = testCase.voteKeyDilution
- tx.KeyregTxnFields.Nonparticipation = testCase.nonParticipation
- tx.LastValid = testCase.lastValid
- tx.KeyregTxnFields.StateProofPK = testCase.stateProofPK
-
- curProto.SupportBecomeNonParticipatingTransactions = testCase.supportBecomeNonParticipatingTransactions
- curProto.EnableKeyregCoherencyCheck = testCase.enableKeyregCoherencyCheck
- curProto.EnableStateProofKeyregCheck = testCase.enableStateProofKeyregCheck
- curProto.MaxKeyregValidPeriod = maxValidPeriod // TODO: remove this when MaxKeyregValidPeriod is in CurrentVersion
- return tx.WellFormed(SpecialAddresses{}, curProto)
- }
-
- if *generateFlag == true {
- fmt.Printf("keyRegTestCases := []keyRegTestCase{\n")
- idx := 0
- for _, votePK := range []crypto.OneTimeSignatureVerifier{crypto.OneTimeSignatureVerifier{}, votePKValue} {
- for _, selectionPK := range []crypto.VRFVerifier{crypto.VRFVerifier{}, selectionPKValue} {
- for _, voteFirst := range []basics.Round{basics.Round(0), basics.Round(5)} {
- for _, voteLast := range []basics.Round{basics.Round(0), basics.Round(10)} {
- for _, lastValid := range []basics.Round{basics.Round(4), basics.Round(3)} {
- for _, voteKeyDilution := range []uint64{0, 10000} {
- for _, nonParticipation := range []bool{false, true} {
- for _, supportBecomeNonParticipatingTransactions := range []bool{false, true} {
- for _, enableKeyregCoherencyCheck := range []bool{false, true} {
- for _, enableStateProofKeyregCheck := range []bool{false, true} {
- outcome := runTestCase(keyRegTestCase{
- votePK,
- selectionPK,
- stateProofPK,
- voteFirst,
- voteLast,
- lastValid,
- voteKeyDilution,
- nonParticipation,
- supportBecomeNonParticipatingTransactions,
- enableKeyregCoherencyCheck,
- enableStateProofKeyregCheck,
- nil})
- errStr := "nil"
- switch outcome {
- case errKeyregTxnUnsupportedSwitchToNonParticipating:
- errStr = "errKeyregTxnUnsupportedSwitchToNonParticipating"
- case errKeyregTxnGoingOnlineWithNonParticipating:
- errStr = "errKeyregTxnGoingOnlineWithNonParticipating"
- case errKeyregTxnNonCoherentVotingKeys:
- errStr = "errKeyregTxnNonCoherentVotingKeys"
- case errKeyregTxnOfflineTransactionHasVotingRounds:
- errStr = "errKeyregTxnOfflineTransactionHasVotingRounds"
- case errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound:
- errStr = "errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound"
- case errKeyregTxnGoingOnlineWithZeroVoteLast:
- errStr = "errKeyregTxnGoingOnlineWithZeroVoteLast"
- case errKeyregTxnGoingOnlineWithNonParticipating:
- errStr = "errKeyregTxnGoingOnlineWithNonParticipating"
- case errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid:
- errStr = "errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid"
- default:
- require.Nil(t, outcome)
-
- }
- s := "/* %3d */ keyRegTestCase{votePK:"
- if votePK == votePKValue {
- s += "votePKValue"
- } else {
- s += "crypto.OneTimeSignatureVerifier{}"
- }
- s += ", selectionPK:"
- if selectionPK == selectionPKValue {
- s += "selectionPKValue"
- } else {
- s += "crypto.VRFVerifier{}"
- }
- s = fmt.Sprintf("%s, voteFirst:basics.Round(%2d), voteLast:basics.Round(%2d), lastValid:basics.Round(%2d), voteKeyDilution: %5d, nonParticipation: %v,supportBecomeNonParticipatingTransactions:%v, enableKeyregCoherencyCheck:%v, err:%s},\n",
- s, voteFirst, voteLast, lastValid, voteKeyDilution, nonParticipation, supportBecomeNonParticipatingTransactions, enableKeyregCoherencyCheck, errStr)
- fmt.Printf(s, idx)
- idx++
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- fmt.Printf("}\n")
- return
- }
- keyRegTestCases := []keyRegTestCase{
- /* 0 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 1 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
- /* 2 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 3 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 4 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 5 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 6 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 7 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 8 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 9 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 10 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 11 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 12 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 13 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 14 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 15 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 16 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 17 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
- /* 18 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 19 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 20 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 21 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 22 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 23 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 24 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 25 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 26 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 27 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 28 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 29 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 30 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 31 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 32 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 33 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 34 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 35 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 36 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 37 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 38 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 39 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 40 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 41 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 42 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 43 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 44 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 45 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 46 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 47 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 48 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 49 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 50 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 51 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 52 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 53 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 54 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 55 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 56 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 57 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 58 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 59 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 60 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 61 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 62 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 63 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 64 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 65 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 66 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 67 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 68 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 69 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 70 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 71 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 72 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 73 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 74 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 75 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 76 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 77 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 78 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 79 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 80 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 81 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 82 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 83 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 84 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 85 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 86 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 87 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 88 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 89 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 90 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 91 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 92 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 93 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 94 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 95 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 96 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 97 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 98 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 99 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 100 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 101 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 102 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 103 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 104 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 105 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 106 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 107 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 108 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 109 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 110 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 111 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 112 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 113 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 114 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 115 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 116 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 117 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 118 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 119 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnOfflineTransactionHasVotingRounds},
- /* 120 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 121 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 122 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 123 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 124 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 125 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 126 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 127 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 128 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 129 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 130 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 131 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 132 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 133 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 134 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 135 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 136 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 137 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 138 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 139 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 140 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 141 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 142 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 143 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 144 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 145 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 146 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 147 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 148 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 149 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 150 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 151 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 152 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 153 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 154 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 155 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 156 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 157 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 158 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 159 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 160 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 161 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 162 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 163 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 164 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 165 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 166 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 167 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 168 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 169 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 170 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 171 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 172 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 173 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 174 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 175 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 176 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 177 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 178 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 179 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 180 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 181 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 182 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 183 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 184 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 185 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 186 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 187 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 188 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 189 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 190 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 191 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 192 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 193 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 194 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 195 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 196 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 197 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 198 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 199 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 200 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 201 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 202 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 203 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 204 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 205 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 206 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 207 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 208 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 209 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 210 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 211 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 212 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 213 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 214 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 215 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 216 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 217 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 218 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 219 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 220 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 221 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 222 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 223 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 224 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 225 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 226 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 227 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 228 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 229 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 230 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 231 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 232 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 233 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 234 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 235 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 236 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 237 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 238 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 239 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 240 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 241 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 242 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 243 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 244 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 245 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 246 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 247 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 248 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 249 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 250 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 251 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 252 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 253 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 254 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 255 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 256 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 257 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 258 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 259 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 260 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 261 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 262 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 263 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 264 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 265 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 266 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 267 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 268 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 269 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 270 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 271 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 272 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 273 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 274 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 275 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 276 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 277 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 278 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 279 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 280 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 281 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 282 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 283 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 284 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 285 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 286 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 287 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 288 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 289 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 290 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 291 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 292 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 293 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 294 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 295 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 296 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 297 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 298 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 299 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 300 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 301 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 302 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 303 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 304 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 305 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 306 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 307 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 308 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 309 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 310 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 311 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 312 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 313 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 314 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 315 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 316 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 317 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 318 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 319 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 320 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 321 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 322 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 323 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 324 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 325 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 326 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 327 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 328 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 329 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 330 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 331 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 332 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 333 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 334 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 335 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 336 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 337 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 338 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 339 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 340 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 341 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 342 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 343 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 344 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 345 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 346 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 347 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 348 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 349 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 350 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 351 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 352 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 353 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 354 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 355 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 356 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 357 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 358 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 359 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 360 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 361 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 362 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 363 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 364 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 365 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 366 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 367 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 368 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 369 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 370 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 371 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 372 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 373 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 374 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 375 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 376 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 377 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 378 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 379 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 380 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 381 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 382 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 383 */ keyRegTestCase{votePK: votePKValue, selectionPK: crypto.VRFVerifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 384 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 385 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 386 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 387 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 388 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 389 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 390 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 391 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 392 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 393 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 394 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 395 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 396 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 397 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 398 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 399 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 400 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 401 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 402 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 403 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 404 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 405 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 406 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 407 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 408 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 409 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 410 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 411 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 412 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 413 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 414 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 415 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithZeroVoteLast},
- /* 416 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 417 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 418 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 419 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 420 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 421 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 422 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 423 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 424 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 425 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
- /* 426 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 427 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 428 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 429 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 430 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 431 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 432 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 433 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 434 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 435 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 436 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 437 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 438 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 439 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 440 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 441 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
- /* 442 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 443 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 444 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 445 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 446 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 447 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(0), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 448 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 449 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 450 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 451 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 452 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 453 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 454 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 455 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 456 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 457 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 458 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 459 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 460 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 461 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 462 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 463 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 464 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 465 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 466 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 467 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 468 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 469 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 470 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 471 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 472 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 473 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 474 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 475 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 476 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 477 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 478 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 479 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(0), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnFirstVotingRoundGreaterThanLastVotingRound},
- /* 480 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 481 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 482 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 483 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 484 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 485 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 486 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 487 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 488 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 489 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: nil},
- /* 490 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 491 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: nil},
- /* 492 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 493 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 494 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 495 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(4), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 496 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 497 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 498 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 499 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 500 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 501 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 502 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 503 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnNonCoherentVotingKeys},
- /* 504 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: nil},
- /* 505 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
- /* 506 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: nil},
- /* 507 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
- /* 508 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: false, err: errKeyregTxnUnsupportedSwitchToNonParticipating},
- /* 509 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: false, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
- /* 510 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, err: errKeyregTxnGoingOnlineWithNonParticipating},
- /* 511 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: true, err: errKeyregTxnGoingOnlineWithFirstVoteAfterLastValid},
- /* 512 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: errKeyregTxnNotEmptyStateProofPK},
- /* 513 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: nil},
- /* 514 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
- /* 515 */ keyRegTestCase{votePK: votePKValue, selectionPK: selectionPKValue, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyRegEmptyStateProofPK},
- /* 516 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyregTxnNonParticipantShouldBeEmptyStateProofPK},
- /* 517 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
- /* 518 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: stateProofPK, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyregTxnOfflineShouldBeEmptyStateProofPK},
- /* 519 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
- /* 520 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 0, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
- /* 521 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil},
- /* 522 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10000 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyRegTxnValidityPeriodTooLong},
- /* 523 */ keyRegTestCase{votePK: crypto.OneTimeSignatureVerifier{}, selectionPK: crypto.VRFVerifier{}, stateProofPK: merklesignature.Commitment{}, voteFirst: basics.Round(10), voteLast: basics.Round(10000 + maxValidPeriod), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: nil},
- }
- for testcaseIdx, testCase := range keyRegTestCases {
- err := runTestCase(testCase)
-
- require.Equalf(t, testCase.err, err, "index: %d\ntest case: %#v", testcaseIdx, testCase)
- }
-}
-
-type stateproofTxnTestCase struct {
- expectedError error
-
- StateProofInterval uint64
- fee basics.MicroAlgos
- note []byte
- group crypto.Digest
- lease [32]byte
- rekeyValue basics.Address
- sender basics.Address
-}
-
-func (s *stateproofTxnTestCase) runIsWellFormedForTestCase() error {
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
- curProto.StateProofInterval = s.StateProofInterval
-
- // edit txn params. wanted
- return Transaction{
- Type: protocol.StateProofTx,
+ txn := Transaction{
+ Type: "pay",
Header: Header{
- Sender: s.sender,
- Fee: s.fee,
- FirstValid: 0,
- LastValid: 0,
- Note: s.note,
- GenesisID: "",
- GenesisHash: crypto.Digest{},
- Group: s.group,
- Lease: s.lease,
- RekeyTo: s.rekeyValue,
+ Sender: [32]byte{0x01},
+ Fee: basics.MicroAlgos{Raw: 10_000},
+ FirstValid: 100,
+ LastValid: 200,
+ Note: []byte{0x02},
},
- StateProofTxnFields: StateProofTxnFields{},
- }.WellFormed(SpecialAddresses{}, curProto)
-}
+ PaymentTxnFields: PaymentTxnFields{
+ Receiver: [32]byte{0x03},
+ Amount: basics.MicroAlgos{Raw: 200_000},
+ CloseRemainderTo: [32]byte{0x04},
+ },
+ }
-func TestWellFormedStateProofTxn(t *testing.T) {
- partitiontest.PartitionTest(t)
- // want to create different Txns, run on all of these cases the check, and have an expected result
- cases := []stateproofTxnTestCase{
- /* 0 */ {expectedError: errStateProofNotSupported}, // StateProofInterval == 0 leads to error
- /* 1 */ {expectedError: errBadSenderInStateProofTxn, StateProofInterval: 256, sender: basics.Address{1, 2, 3, 4}},
- /* 2 */ {expectedError: errFeeMustBeZeroInStateproofTxn, StateProofInterval: 256, sender: StateProofSender, fee: basics.MicroAlgos{Raw: 1}},
- /* 3 */ {expectedError: errNoteMustBeEmptyInStateproofTxn, StateProofInterval: 256, sender: StateProofSender, note: []byte{1, 2, 3}},
- /* 4 */ {expectedError: errGroupMustBeZeroInStateproofTxn, StateProofInterval: 256, sender: StateProofSender, group: crypto.Digest{1, 2, 3}},
- /* 5 */ {expectedError: errRekeyToMustBeZeroInStateproofTxn, StateProofInterval: 256, sender: StateProofSender, rekeyValue: basics.Address{1, 2, 3, 4}},
- /* 6 */ {expectedError: errLeaseMustBeZeroInStateproofTxn, StateProofInterval: 256, sender: StateProofSender, lease: [32]byte{1, 2, 3, 4}},
- /* 7 */ {expectedError: nil, StateProofInterval: 256, fee: basics.MicroAlgos{Raw: 0}, note: nil, group: crypto.Digest{}, lease: [32]byte{}, rekeyValue: basics.Address{}, sender: StateProofSender},
+ // Make a copy of txn, change some fields, be sure the TXID changes. This is not exhaustive.
+ txn2 := txn
+ txn2.Note = []byte{42}
+ if txn2.ID() == txn.ID() {
+ t.Errorf("txid does not depend on note")
}
- for i, testCase := range cases {
- cpyTestCase := testCase
- t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
- t.Parallel()
- require.Equal(t, cpyTestCase.expectedError, cpyTestCase.runIsWellFormedForTestCase())
- })
+ txn2 = txn
+ txn2.Amount.Raw++
+ if txn2.ID() == txn.ID() {
+ t.Errorf("txid does not depend on amount")
}
-}
-
-func TestStateProofTxnShouldBeZero(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- addr1, err := basics.UnmarshalChecksumAddress("NDQCJNNY5WWWFLP4GFZ7MEF2QJSMZYK6OWIV2AQ7OMAVLEFCGGRHFPKJJA")
- require.NoError(t, err)
-
- curProto := config.Consensus[protocol.ConsensusCurrentVersion]
- curProto.StateProofInterval = 256
- txn := Transaction{
- Type: protocol.PaymentTx,
- Header: Header{
- Sender: addr1,
- Fee: basics.MicroAlgos{Raw: 100},
- FirstValid: 0,
- LastValid: 0,
- Note: []byte{0, 1},
- GenesisID: "",
- GenesisHash: crypto.Digest{},
- },
- StateProofTxnFields: StateProofTxnFields{},
+ txn2 = txn
+ txn2.Fee.Raw++
+ if txn2.ID() == txn.ID() {
+ t.Errorf("txid does not depend on fee")
+ }
+ txn2 = txn
+ txn2.LastValid++
+ if txn2.ID() == txn.ID() {
+ t.Errorf("txid does not depend on lastvalid")
}
-
- const erroMsg = "type pay has non-zero fields for type stpf"
- txn.StateProofType = 1
- err = txn.WellFormed(SpecialAddresses{}, curProto)
- require.Error(t, err)
- require.Contains(t, err.Error(), erroMsg)
-
- txn.StateProofType = 0
- txn.Message = stateproofmsg.Message{FirstAttestedRound: 1}
- err = txn.WellFormed(SpecialAddresses{}, curProto)
- require.Error(t, err)
- require.Contains(t, err.Error(), erroMsg)
-
- txn.Message = stateproofmsg.Message{}
- txn.StateProof = stateproof.StateProof{SignedWeight: 100}
- err = txn.WellFormed(SpecialAddresses{}, curProto)
- require.Error(t, err)
- require.Contains(t, err.Error(), erroMsg)
-
- txn.StateProof = stateproof.StateProof{}
- txn.Message.LastAttestedRound = 512
- err = txn.WellFormed(SpecialAddresses{}, curProto)
- require.Error(t, err)
- require.Contains(t, err.Error(), erroMsg)
-
- txn.Message.LastAttestedRound = 0
- err = txn.WellFormed(SpecialAddresses{}, curProto)
- require.NoError(t, err)
}
diff --git a/data/transactions/verify/verifiedTxnCache.go b/data/transactions/verify/verifiedTxnCache.go
index b1220f9aa2..73a33b9761 100644
--- a/data/transactions/verify/verifiedTxnCache.go
+++ b/data/transactions/verify/verifiedTxnCache.go
@@ -49,12 +49,12 @@ var errTooManyPinnedEntries = &VerifiedTxnCacheError{errors.New("Too many pinned
// errMissingPinnedEntry is being generated when we're trying to pin a transaction that does not appear in the cache
var errMissingPinnedEntry = &VerifiedTxnCacheError{errors.New("Missing pinned entry")}
-// VerifiedTransactionCache provides a cached store of recently verified transactions. The cache is desiged two have two separate "levels". On the
+// VerifiedTransactionCache provides a cached store of recently verified transactions. The cache is designed to have two separate "levels". On the
// bottom tier, the cache would be using a cyclic buffer, where old transactions would end up overridden by new ones. In order to support transactions
// that goes into the transaction pool, we have a higher tier of pinned cache. Pinned transactions would not be cycled-away by new incoming transactions,
// and would only get eliminated by updates to the transaction pool, which would inform the cache of updates to the pinned items.
type VerifiedTransactionCache interface {
- // Add adds a given transaction group and it's associated group context to the cache. If any of the transactions already appear
+ // Add adds a given transaction group and its associated group context to the cache. If any of the transactions already appear
// in the cache, the new entry overrides the old one.
Add(txgroup []transactions.SignedTxn, groupCtx *GroupContext)
// AddPayset works in a similar way to Add, but is intended for adding an array of transaction groups, along with their corresponding contexts.
@@ -262,7 +262,6 @@ type mockedCache struct {
}
func (v *mockedCache) Add(txgroup []transactions.SignedTxn, groupCtx *GroupContext) {
- return
}
func (v *mockedCache) AddPayset(txgroup [][]transactions.SignedTxn, groupCtxs []*GroupContext) {
diff --git a/data/txDupCache.go b/data/txDupCache.go
index f05f89fbd8..b9981a4574 100644
--- a/data/txDupCache.go
+++ b/data/txDupCache.go
@@ -31,7 +31,7 @@ import (
)
// digestCache is a rotating cache of size N accepting crypto.Digest as a key
-// and keeping up to 2*N elements in memory
+// and keeping up to 2*maxSize elements in memory
type digestCache struct {
cur map[crypto.Digest]struct{}
prev map[crypto.Digest]struct{}
@@ -49,11 +49,11 @@ func makeDigestCache(size int) *digestCache {
}
// check if digest d is in a cache.
-// locking semantic: write lock must be taken
-func (c *digestCache) check(d *crypto.Digest) bool {
- _, found := c.cur[*d]
+// locking semantic: read lock must be taken
+func (c *digestCache) check(d crypto.Digest) bool {
+ _, found := c.cur[d]
if !found {
- _, found = c.prev[*d]
+ _, found = c.prev[d]
}
return found
}
@@ -67,15 +67,15 @@ func (c *digestCache) swap() {
// put adds digest d into a cache.
// locking semantic: write lock must be taken
-func (c *digestCache) put(d *crypto.Digest) {
+func (c *digestCache) put(d crypto.Digest) {
if len(c.cur) >= c.maxSize {
c.swap()
}
- c.cur[*d] = struct{}{}
+ c.cur[d] = struct{}{}
}
// CheckAndPut adds digest d into a cache if not found
-func (c *digestCache) CheckAndPut(d *crypto.Digest) bool {
+func (c *digestCache) CheckAndPut(d crypto.Digest) bool {
c.mu.Lock()
defer c.mu.Unlock()
if c.check(d) {
@@ -94,11 +94,11 @@ func (c *digestCache) Len() int {
}
// Delete from the cache
-func (c *digestCache) Delete(d *crypto.Digest) {
+func (c *digestCache) Delete(d crypto.Digest) {
c.mu.Lock()
defer c.mu.Unlock()
- delete(c.cur, *d)
- delete(c.prev, *d)
+ delete(c.cur, d)
+ delete(c.prev, d)
}
// txSaltedCache is a digest cache with a rotating salt
@@ -179,8 +179,8 @@ func (c *txSaltedCache) innerSwap(scheduled bool) {
}
// innerCheck returns true if exists, and the current salted hash if does not.
-// locking semantic: write lock must be held
-func (c *txSaltedCache) innerCheck(msg []byte) (*crypto.Digest, bool) {
+// locking semantic: READ lock must be held, cache is not mutated
+func (c *txSaltedCache) innerCheck(msg []byte) (crypto.Digest, bool) {
ptr := saltedPool.Get()
defer saltedPool.Put(ptr)
@@ -193,7 +193,7 @@ func (c *txSaltedCache) innerCheck(msg []byte) (*crypto.Digest, bool) {
_, found := c.cur[d]
if found {
- return nil, true
+ return crypto.Digest{}, true
}
toBeHashed = append(toBeHashed[:len(msg)], c.prevSalt[:]...)
@@ -201,14 +201,14 @@ func (c *txSaltedCache) innerCheck(msg []byte) (*crypto.Digest, bool) {
pd := crypto.Digest(blake2b.Sum256(toBeHashed))
_, found = c.prev[pd]
if found {
- return nil, true
+ return crypto.Digest{}, true
}
- return &d, false
+ return d, false
}
// CheckAndPut adds msg into a cache if not found
// returns a hashing key used for insertion if the message not found.
-func (c *txSaltedCache) CheckAndPut(msg []byte) (*crypto.Digest, bool) {
+func (c *txSaltedCache) CheckAndPut(msg []byte) (crypto.Digest, bool) {
c.mu.RLock()
d, found := c.innerCheck(msg)
salt := c.curSalt
@@ -231,7 +231,7 @@ func (c *txSaltedCache) CheckAndPut(msg []byte) (*crypto.Digest, bool) {
} else {
// Do another check to see if another copy of the transaction won the race to write it to the cache
// Only check current to save a lookup since swaps are rare and no need to re-hash
- if _, found := c.cur[*d]; found {
+ if _, found := c.cur[d]; found {
return d, found
}
}
@@ -246,16 +246,15 @@ func (c *txSaltedCache) CheckAndPut(msg []byte) (*crypto.Digest, bool) {
toBeHashed = append(toBeHashed, c.curSalt[:]...)
toBeHashed = toBeHashed[:len(msg)+len(c.curSalt)]
- dn := crypto.Digest(blake2b.Sum256(toBeHashed))
- d = &dn
+ d = crypto.Digest(blake2b.Sum256(toBeHashed))
}
- c.cur[*d] = struct{}{}
+ c.cur[d] = struct{}{}
return d, false
}
// DeleteByKey from the cache by using a key used for insertion
-func (c *txSaltedCache) DeleteByKey(d *crypto.Digest) {
+func (c *txSaltedCache) DeleteByKey(d crypto.Digest) {
c.digestCache.Delete(d)
}
diff --git a/data/txDupCache_test.go b/data/txDupCache_test.go
index bf10bade52..aee303fc54 100644
--- a/data/txDupCache_test.go
+++ b/data/txDupCache_test.go
@@ -45,10 +45,10 @@ func TestTxHandlerDigestCache(t *testing.T) {
var ds [size]crypto.Digest
for i := 0; i < size; i++ {
crypto.RandBytes([]byte(ds[i][:]))
- exist := cache.CheckAndPut(&ds[i])
+ exist := cache.CheckAndPut(ds[i])
require.False(t, exist)
- exist = cache.check(&ds[i])
+ exist = cache.check(ds[i])
require.True(t, exist)
}
@@ -56,7 +56,7 @@ func TestTxHandlerDigestCache(t *testing.T) {
// try to re-add, ensure not added
for i := 0; i < size; i++ {
- exist := cache.CheckAndPut(&ds[i])
+ exist := cache.CheckAndPut(ds[i])
require.True(t, exist)
}
@@ -66,10 +66,10 @@ func TestTxHandlerDigestCache(t *testing.T) {
var ds2 [size]crypto.Digest
for i := 0; i < size; i++ {
crypto.RandBytes(ds2[i][:])
- exist := cache.CheckAndPut(&ds2[i])
+ exist := cache.CheckAndPut(ds2[i])
require.False(t, exist)
- exist = cache.check(&ds2[i])
+ exist = cache.check(ds2[i])
require.True(t, exist)
}
@@ -77,34 +77,34 @@ func TestTxHandlerDigestCache(t *testing.T) {
var d crypto.Digest
crypto.RandBytes(d[:])
- exist := cache.CheckAndPut(&d)
+ exist := cache.CheckAndPut(d)
require.False(t, exist)
- exist = cache.check(&d)
+ exist = cache.check(d)
require.True(t, exist)
require.Equal(t, size+1, cache.Len())
// ensure hashes from the prev batch are still there
for i := 0; i < size; i++ {
- exist := cache.check(&ds2[i])
+ exist := cache.check(ds2[i])
require.True(t, exist)
}
// ensure hashes from the first batch are gone
for i := 0; i < size; i++ {
- exist := cache.check(&ds[i])
+ exist := cache.check(ds[i])
require.False(t, exist)
}
// check deletion works
for i := 0; i < size; i++ {
- cache.Delete(&ds[i])
- cache.Delete(&ds2[i])
+ cache.Delete(ds[i])
+ cache.Delete(ds2[i])
}
require.Equal(t, 1, cache.Len())
- cache.Delete(&d)
+ cache.Delete(d)
require.Equal(t, 0, cache.Len())
}
@@ -125,7 +125,7 @@ func TestTxHandlerSaltedCacheBasic(t *testing.T) {
// add some unique random
var ds [size][8]byte
- var ks [size]*crypto.Digest
+ var ks [size]crypto.Digest
var exist bool
for i := 0; i < size; i++ {
crypto.RandBytes([]byte(ds[i][:]))
@@ -150,7 +150,7 @@ func TestTxHandlerSaltedCacheBasic(t *testing.T) {
// add some more and ensure capacity switch
var ds2 [size][8]byte
- var ks2 [size]*crypto.Digest
+ var ks2 [size]crypto.Digest
for i := 0; i < size; i++ {
crypto.RandBytes(ds2[i][:])
ks2[i], exist = cache.CheckAndPut(ds2[i][:])
@@ -309,7 +309,7 @@ func (p *digestCachePusher) push() {
var d [crypto.DigestSize]byte
crypto.RandBytes(d[:])
h := crypto.Digest(blake2b.Sum256(d[:])) // digestCache does not hashes so calculate hash here
- p.c.CheckAndPut(&h)
+ p.c.CheckAndPut(h)
}
func (p *saltedCachePusher) push() {
@@ -342,6 +342,7 @@ func BenchmarkDigestCaches(b *testing.B) {
}
for _, bench := range benchmarks {
b.Run(fmt.Sprintf("%T/threads=%d", bench.maker, bench.numThreads), func(b *testing.B) {
+ b.ReportAllocs()
benchmarkDigestCache(b, bench.maker, bench.numThreads)
})
}
diff --git a/data/txHandler.go b/data/txHandler.go
index 65fd869d7e..59194210c5 100644
--- a/data/txHandler.go
+++ b/data/txHandler.go
@@ -38,6 +38,7 @@ import (
"github.com/algorand/go-algorand/util"
"github.com/algorand/go-algorand/util/execpool"
"github.com/algorand/go-algorand/util/metrics"
+ "github.com/algorand/go-deadlock"
)
var transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled)
@@ -102,20 +103,19 @@ const (
// The txBacklogMsg structure used to track a single incoming transaction from the gossip network,
type txBacklogMsg struct {
- rawmsg *network.IncomingMessage // the raw message from the network
- unverifiedTxGroup []transactions.SignedTxn // the unverified ( and signed ) transaction group
- rawmsgDataHash *crypto.Digest // hash (if any) of raw message data from the network
- unverifiedTxGroupHash *crypto.Digest // hash (if any) of the unverifiedTxGroup
- verificationErr error // The verification error generated by the verification function, if any.
- capguard *util.ErlCapacityGuard // the structure returned from the elastic rate limiter, to be released when dequeued
+ rawmsg *network.IncomingMessage // the raw message from the network
+ unverifiedTxGroup []transactions.SignedTxn // the unverified ( and signed ) transaction group
+ rawmsgDataHash crypto.Digest // hash (or IsZero) of raw message data from the network
+ unverifiedTxGroupHash crypto.Digest // hash (or IsZero) of the unverifiedTxGroup
+ verificationErr error // The verification error generated by the verification function, if any.
+ capguard *util.ErlCapacityGuard // the structure returned from the elastic rate limiter, to be released when dequeued
+ syncCh chan network.ForwardingPolicy // channel to signal the synchronous mode and its ops completion
}
// TxHandler handles transaction messages
type TxHandler struct {
txPool *pools.TransactionPool
ledger *Ledger
- genesisID string
- genesisHash crypto.Digest
txVerificationPool execpool.BacklogPool
backlogQueue chan *txBacklogMsg
backlogCongestionThreshold float64
@@ -130,6 +130,7 @@ type TxHandler struct {
streamVerifierChan chan execpool.InputJob
streamVerifierDropped chan *verify.UnverifiedTxnSigJob
erl *util.ElasticRateLimiter
+ erlClientMapper erlClientMapper
appLimiter *appRateLimiter
appLimiterBacklogThreshold int
appLimiterCountERLDrops bool
@@ -144,8 +145,6 @@ type TxHandlerOpts struct {
ExecutionPool execpool.BacklogPool
Ledger *Ledger
Net network.GossipNode
- GenesisID string
- GenesisHash crypto.Digest
Config config.Local
}
@@ -173,8 +172,6 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) {
handler := &TxHandler{
txPool: opts.TxPool,
- genesisID: opts.GenesisID,
- genesisHash: opts.GenesisHash,
ledger: opts.Ledger,
txVerificationPool: opts.ExecutionPool,
backlogQueue: make(chan *txBacklogMsg, txBacklogSize),
@@ -206,6 +203,10 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) {
time.Duration(opts.Config.TxBacklogServiceRateWindowSeconds)*time.Second,
txBacklogDroppedCongestionManagement,
)
+ handler.erlClientMapper = erlClientMapper{
+ mapping: make(map[string]*erlIPClient),
+ maxClients: opts.Config.MaxConnectionsPerIP,
+ }
}
if opts.Config.EnableTxBacklogAppRateLimiting {
handler.appLimiter = makeAppRateLimiter(
@@ -220,14 +221,13 @@ func MakeTxHandler(opts TxHandlerOpts) (*TxHandler, error) {
}
// prepare the batch processor for pubsub synchronous verification
- var err0 error
- handler.batchVerifier, err0 = verify.MakeSigVerifier(handler.ledger, handler.ledger.VerifiedTransactionCache())
- if err0 != nil {
- return nil, err0
+ var err error
+ handler.batchVerifier, err = verify.MakeSigVerifier(handler.ledger, handler.ledger.VerifiedTransactionCache())
+ if err != nil {
+ return nil, err
}
// prepare the transaction stream verifier
- var err error
txnElementProcessor, err := verify.MakeSigVerifyJobProcessor(handler.ledger, handler.ledger.VerifiedTransactionCache(),
handler.postVerificationQueue, handler.streamVerifierDropped)
if err != nil {
@@ -353,6 +353,10 @@ func (handler *TxHandler) backlogWorker() {
if wi.capguard != nil {
wi.capguard.Served()
}
+ // if in synchronous mode, signal the completion of the operation
+ if wi.syncCh != nil {
+ wi.syncCh <- network.Ignore
+ }
continue
}
// handler.streamVerifierChan does not receive if ctx is cancelled
@@ -360,6 +364,10 @@ func (handler *TxHandler) backlogWorker() {
case handler.streamVerifierChan <- &verify.UnverifiedTxnSigJob{TxnGroup: wi.unverifiedTxGroup, BacklogMessage: wi}:
case <-handler.ctx.Done():
transactionMessagesDroppedFromBacklog.Inc(nil)
+ // if in synchronous mode, signal the completion of the operation
+ if wi.syncCh != nil {
+ wi.syncCh <- network.Ignore
+ }
return
}
if wi.capguard != nil {
@@ -373,7 +381,6 @@ func (handler *TxHandler) backlogWorker() {
m := wi.BacklogMessage.(*txBacklogMsg)
m.verificationErr = wi.Err
handler.postProcessCheckedTxn(m)
-
case <-handler.ctx.Done():
return
}
@@ -414,7 +421,7 @@ func (handler *TxHandler) checkReportErrors(err error) {
case *ledgercore.TxnNotWellFormedError:
transactionMessageTxPoolCheckCounter.Add(txPoolRememberTagTxnNotWellFormed, 1)
return
- case *transactions.TxnDeadError:
+ case *bookkeeping.TxnDeadError:
if err.Early {
transactionMessageTxPoolCheckCounter.Add(txPoolRememberTagTxnEarly, 1)
} else {
@@ -476,7 +483,7 @@ func (handler *TxHandler) rememberReportErrors(err error) {
case *pools.ErrTxPoolFeeError:
transactionMessageTxPoolRememberCounter.Add(txPoolRememberTagFee, 1)
return
- case *transactions.TxnDeadError:
+ case *bookkeeping.TxnDeadError:
if err.Early {
transactionMessageTxPoolRememberCounter.Add(txPoolRememberTagTxnEarly, 1)
} else {
@@ -515,6 +522,11 @@ func (handler *TxHandler) postProcessCheckedTxn(wi *txBacklogMsg) {
// disconnect from peer.
handler.postProcessReportErrors(wi.verificationErr)
logging.Base().Warnf("Received a malformed tx group %v: %v", wi.unverifiedTxGroup, wi.verificationErr)
+ // if in synchronous mode, signal the completion of the operation
+ if wi.syncCh != nil {
+ wi.syncCh <- network.Disconnect
+ return
+ }
handler.net.Disconnect(wi.rawmsg.Sender)
return
}
@@ -530,6 +542,10 @@ func (handler *TxHandler) postProcessCheckedTxn(wi *txBacklogMsg) {
if err != nil {
handler.rememberReportErrors(err)
logging.Base().Debugf("could not remember tx: %v", err)
+ // if in synchronous mode, signal the completion of the operation
+ if wi.syncCh != nil {
+ wi.syncCh <- network.Ignore
+ }
return
}
@@ -540,24 +556,29 @@ func (handler *TxHandler) postProcessCheckedTxn(wi *txBacklogMsg) {
if err != nil {
logging.Base().Infof("unable to pin transaction: %v", err)
}
+ // if in synchronous mode, signal the completion of the operation
+ if wi.syncCh != nil {
+ wi.syncCh <- network.Accept
+ return
+ }
// We reencode here instead of using rawmsg.Data to avoid broadcasting non-canonical encodings
handler.net.Relay(handler.ctx, protocol.TxnTag, reencode(verifiedTxGroup), false, wi.rawmsg.Sender)
}
-func (handler *TxHandler) deleteFromCaches(msgKey *crypto.Digest, canonicalKey *crypto.Digest) {
- if handler.txCanonicalCache != nil && canonicalKey != nil {
+func (handler *TxHandler) deleteFromCaches(msgKey crypto.Digest, canonicalKey crypto.Digest) {
+ if handler.txCanonicalCache != nil && !canonicalKey.IsZero() {
handler.txCanonicalCache.Delete(canonicalKey)
}
- if handler.msgCache != nil && msgKey != nil {
+ if handler.msgCache != nil && !msgKey.IsZero() {
handler.msgCache.DeleteByKey(msgKey)
}
}
// dedupCanonical checks if the transaction group has been seen before after reencoding to canonical representation.
// returns a key used for insertion if the group was not found.
-func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.SignedTxn, consumed int) (key *crypto.Digest, reencoded []byte, isDup bool) {
+func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.SignedTxn, consumed int) (key crypto.Digest, reencoded []byte, isDup bool) {
// consider situations where someone want to censor transactions A
// 1. Txn A is not part of a group => txn A with a valid signature is OK
// Censorship attempts are:
@@ -580,8 +601,8 @@ func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.Signed
// a single transaction => cache/dedup canonical txn with its signature
enc := unverifiedTxGroup[0].MarshalMsg(nil)
d = crypto.Hash(enc)
- if handler.txCanonicalCache.CheckAndPut(&d) {
- return nil, nil, true
+ if handler.txCanonicalCache.CheckAndPut(d) {
+ return crypto.Digest{}, nil, true
}
reencodedBuf = enc
} else {
@@ -594,23 +615,23 @@ func (handler *TxHandler) dedupCanonical(unverifiedTxGroup []transactions.Signed
// reallocated, some assumption on size was wrong
// log and skip
logging.Base().Warnf("Decoded size %d does not match to encoded %d", consumed, len(encodeBuf))
- return nil, nil, false
+ return crypto.Digest{}, nil, false
}
d = crypto.Hash(encodeBuf)
- if handler.txCanonicalCache.CheckAndPut(&d) {
- return nil, nil, true
+ if handler.txCanonicalCache.CheckAndPut(d) {
+ return crypto.Digest{}, nil, true
}
reencodedBuf = encodeBuf
}
- return &d, reencodedBuf, false
+ return d, reencodedBuf, false
}
// incomingMsgDupCheck runs the duplicate check on a raw incoming message.
// Returns:
// - the key used for insertion if the message was not found in the cache
// - a boolean indicating if the message was a duplicate
-func (handler *TxHandler) incomingMsgDupCheck(data []byte) (*crypto.Digest, bool) {
- var msgKey *crypto.Digest
+func (handler *TxHandler) incomingMsgDupCheck(data []byte) (crypto.Digest, bool) {
+ var msgKey crypto.Digest
var isDup bool
if handler.msgCache != nil {
// check for duplicate messages
@@ -623,32 +644,116 @@ func (handler *TxHandler) incomingMsgDupCheck(data []byte) (*crypto.Digest, bool
return msgKey, false
}
+// erlClientMapper handles erlIPClient creation from erlClient
+// in order to map multiple clients to a single IP address.
+// Then that meta erlIPClient is supposed to be supplied to ERL
+type erlClientMapper struct {
+ m deadlock.RWMutex
+ mapping map[string]*erlIPClient
+ maxClients int
+}
+
+// getClient returns erlIPClient for a given sender
+func (mp *erlClientMapper) getClient(sender network.DisconnectableAddressablePeer) util.ErlClient {
+ addr := string(sender.RoutingAddr())
+ ec := sender.(util.ErlClient)
+
+ // check if the client is already known
+ // typically one client sends lots of messages so more much more reads than writes.
+ // handle with a quick read lock, and if not found, create a new one with a write lock
+ mp.m.RLock()
+ ipClient, has := mp.mapping[addr]
+ mp.m.RUnlock()
+
+ if !has {
+ ipClient = mp.getClientByAddr(addr)
+ }
+
+ ipClient.register(ec)
+ return ipClient
+}
+
+// getClientByAddr is internal helper to get or create a new erlIPClient
+// with write lock held
+func (mp *erlClientMapper) getClientByAddr(addr string) *erlIPClient {
+ mp.m.Lock()
+ defer mp.m.Unlock()
+
+ ipClient, has := mp.mapping[addr]
+ if !has {
+ ipClient = &erlIPClient{
+ clients: make(map[util.ErlClient]struct{}, mp.maxClients),
+ }
+ mp.mapping[addr] = ipClient
+ }
+ return ipClient
+}
+
+type erlIPClient struct {
+ util.ErlClient
+ m deadlock.RWMutex
+ clients map[util.ErlClient]struct{}
+ closer func()
+}
+
+func (eic *erlIPClient) OnClose(f func()) {
+ eic.m.Lock()
+ defer eic.m.Unlock()
+ eic.closer = f
+}
+
+// register registers a new client to the erlIPClient
+// by adding a helper closer function to track connection closures
+func (eic *erlIPClient) register(ec util.ErlClient) {
+ eic.m.Lock()
+ defer eic.m.Unlock()
+ if _, has := eic.clients[ec]; has {
+ // this peer is known => noop
+ return
+ }
+ eic.clients[ec] = struct{}{}
+
+ ec.OnClose(func() {
+ eic.connClosed(ec)
+ })
+}
+
+// connClosed is called when a connection is closed so that
+// erlIPClient removes the client from its list of clients
+// and calls the closer function if there are no more clients
+func (eic *erlIPClient) connClosed(ec util.ErlClient) {
+ eic.m.Lock()
+ defer eic.m.Unlock()
+ delete(eic.clients, ec)
+ empty := len(eic.clients) == 0
+ // if no elements left, call the closer
+ if empty && eic.closer != nil {
+ eic.closer()
+ eic.closer = nil
+ }
+}
+
// incomingMsgErlCheck runs the rate limiting check on a sender.
// Returns:
// - the capacity guard returned by the elastic rate limiter
// - a boolean indicating if the sender is rate limited
-func (handler *TxHandler) incomingMsgErlCheck(sender network.DisconnectableAddressablePeer) (*util.ErlCapacityGuard, bool) {
- var capguard *util.ErlCapacityGuard
- var isCMEnabled bool
- var err error
- if handler.erl != nil {
- congestedERL := float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue))
- // consume a capacity unit
- // if the elastic rate limiter cannot vend a capacity, the error it returns
- // is sufficient to indicate that we should enable Congestion Control, because
- // an issue in vending capacity indicates the underlying resource (TXBacklog) is full
- capguard, isCMEnabled, err = handler.erl.ConsumeCapacity(sender.(util.ErlClient))
- if err != nil || // did ERL ask to enable congestion control?
- (!isCMEnabled && congestedERL) { // is CM not currently enabled, but queue is congested?
- handler.erl.EnableCongestionControl()
- // if there is no capacity, it is the same as if we failed to put the item onto the backlog, so report such
- transactionMessagesDroppedFromBacklog.Inc(nil)
- return capguard, true
- }
+func (handler *TxHandler) incomingMsgErlCheck(sender util.ErlClient) (*util.ErlCapacityGuard, bool) {
+ congestedERL := float64(cap(handler.backlogQueue))*handler.backlogCongestionThreshold < float64(len(handler.backlogQueue))
+ // consume a capacity unit
+ // if the elastic rate limiter cannot vend a capacity, the error it returns
+ // is sufficient to indicate that we should enable Congestion Control, because
+ // an issue in vending capacity indicates the underlying resource (TXBacklog) is full
+ capguard, isCMEnabled, err := handler.erl.ConsumeCapacity(sender)
+ if err != nil { // did ERL ask to enable congestion control?
+ handler.erl.EnableCongestionControl()
+ // if there is no capacity, it is the same as if we failed to put the item onto the backlog, so report such
+ transactionMessagesDroppedFromBacklog.Inc(nil)
+ return capguard, true
+ } else if !isCMEnabled && congestedERL { // is CM not currently enabled, but queue is congested?
+ handler.erl.EnableCongestionControl()
+ } else if !congestedERL {
// if the backlog Queue has 50% of its buffer back, turn congestion control off
- if !congestedERL {
- handler.erl.DisableCongestionControl()
- }
+ handler.erl.DisableCongestionControl()
}
return capguard, false
}
@@ -701,14 +806,14 @@ func decodeMsg(data []byte) (unverifiedTxGroup []transactions.SignedTxn, consume
// incomingTxGroupCanonicalDedup checks if the incoming transaction group has been seen before after reencoding to canonical representation.
// It also return canonical representation of the transaction group allowing the caller to compare it with the input.
-func (handler *TxHandler) incomingTxGroupCanonicalDedup(unverifiedTxGroup []transactions.SignedTxn, encodedExpectedSize int) (*crypto.Digest, []byte, bool) {
- var canonicalKey *crypto.Digest
+func (handler *TxHandler) incomingTxGroupCanonicalDedup(unverifiedTxGroup []transactions.SignedTxn, encodedExpectedSize int) (crypto.Digest, []byte, bool) {
+ var canonicalKey crypto.Digest
var reencoded []byte
if handler.txCanonicalCache != nil {
var isDup bool
if canonicalKey, reencoded, isDup = handler.dedupCanonical(unverifiedTxGroup, encodedExpectedSize); isDup {
transactionMessagesDupCanonical.Inc(nil)
- return nil, nil, true
+ return crypto.Digest{}, nil, true
}
}
return canonicalKey, reencoded, false
@@ -738,7 +843,11 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net
return network.OutgoingMessage{Action: network.Ignore}
}
- capguard, shouldDrop := handler.incomingMsgErlCheck(rawmsg.Sender)
+ var capguard *util.ErlCapacityGuard
+ if handler.erl != nil {
+ client := handler.erlClientMapper.getClient(rawmsg.Sender)
+ capguard, shouldDrop = handler.incomingMsgErlCheck(client)
+ }
accepted := false
defer func() {
// if we failed to put the item onto the backlog, we should release the capacity if any
@@ -800,11 +909,6 @@ func (handler *TxHandler) processIncomingTxn(rawmsg network.IncomingMessage) net
// validateIncomingTxMessage is the validator for the MessageProcessor implementation used by P2PNetwork.
func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessage) network.OutgoingMessage {
- msgKey, isDup := handler.incomingMsgDupCheck(rawmsg.Data)
- if isDup {
- return network.OutgoingMessage{Action: network.Ignore}
- }
-
unverifiedTxGroup, consumed, invalid := decodeMsg(rawmsg.Data)
if invalid {
// invalid encoding or exceeding txgroup, disconnect from this peer
@@ -834,44 +938,25 @@ func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessa
wi := &txBacklogMsg{
rawmsg: &rawmsg,
unverifiedTxGroup: unverifiedTxGroup,
- rawmsgDataHash: msgKey,
unverifiedTxGroupHash: canonicalKey,
capguard: nil,
+ syncCh: make(chan network.ForwardingPolicy, 1),
}
- if handler.checkAlreadyCommitted(wi) {
- transactionMessagesAlreadyCommitted.Inc(nil)
- return network.OutgoingMessage{
- Action: network.Ignore,
- }
- }
-
- err := handler.batchVerifier.Verify(wi.unverifiedTxGroup)
- if err != nil {
- handler.postProcessReportErrors(err)
- logging.Base().Warnf("Received a malformed tx group %v: %v", wi.unverifiedTxGroup, err)
- return network.OutgoingMessage{
- Action: network.Disconnect,
- }
- }
- verifiedTxGroup := wi.unverifiedTxGroup
-
- // save the transaction, if it has high enough fee and not already in the cache
- err = handler.txPool.Remember(verifiedTxGroup)
- if err != nil {
- handler.rememberReportErrors(err)
- logging.Base().Debugf("could not remember tx: %v", err)
- return network.OutgoingMessage{
- Action: network.Ignore,
- }
- }
+ var action network.ForwardingPolicy
+ select {
+ case handler.backlogQueue <- wi:
+ action = <-wi.syncCh
+ default:
+ // if we failed here we want to increase the corresponding metric. It might suggest that we
+ // want to increase the queue size.
+ transactionMessagesDroppedFromBacklog.Inc(nil)
- transactionMessagesRemember.Inc(nil)
+ // additionally, remove the txn from duplicate caches to ensure it can be re-submitted
+ handler.deleteFromCaches(crypto.Digest{}, canonicalKey)
- // if we remembered without any error ( i.e. txpool wasn't full ), then we should pin these transactions.
- err = handler.ledger.VerifiedTransactionCache().Pin(verifiedTxGroup)
- if err != nil {
- logging.Base().Infof("unable to pin transaction: %v", err)
+ // queue is full, do not if the message valid or not so ignore
+ action = network.Ignore
}
if hybridNet, ok := handler.net.(HybridRelayer); ok {
@@ -879,7 +964,7 @@ func (handler *TxHandler) validateIncomingTxMessage(rawmsg network.IncomingMessa
}
return network.OutgoingMessage{
- Action: network.Accept,
+ Action: action,
}
}
@@ -891,11 +976,9 @@ var errBackLogFullLocal = errors.New("backlog full")
func (handler *TxHandler) LocalTransaction(txgroup []transactions.SignedTxn) error {
select {
case handler.backlogQueue <- &txBacklogMsg{
- rawmsg: &network.IncomingMessage{},
- unverifiedTxGroup: txgroup,
- rawmsgDataHash: nil,
- unverifiedTxGroupHash: nil,
- capguard: nil,
+ rawmsg: &network.IncomingMessage{},
+ unverifiedTxGroup: txgroup,
+ capguard: nil,
}:
default:
transactionMessagesDroppedFromBacklog.Inc(nil)
diff --git a/data/txHandler_test.go b/data/txHandler_test.go
index 67e82468fa..28ad141020 100644
--- a/data/txHandler_test.go
+++ b/data/txHandler_test.go
@@ -51,6 +51,7 @@ import (
"github.com/algorand/go-algorand/network"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/util"
"github.com/algorand/go-algorand/util/execpool"
"github.com/algorand/go-algorand/util/metrics"
)
@@ -825,7 +826,7 @@ func makeTestTxHandler(dl *Ledger, cfg config.Local) (*TxHandler, error) {
tp := pools.MakeTransactionPool(dl.Ledger, cfg, logging.Base(), nil)
backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil)
opts := TxHandlerOpts{
- tp, backlogPool, dl, &mocks.MockNetwork{}, "", crypto.Digest{}, cfg,
+ tp, backlogPool, dl, &mocks.MockNetwork{}, cfg,
}
return MakeTxHandler(opts)
}
@@ -2349,19 +2350,19 @@ func TestTxHandlerRememberReportErrorsWithTxPool(t *testing.T) { //nolint:parall
func TestMakeTxHandlerErrors(t *testing.T) {
partitiontest.PartitionTest(t)
opts := TxHandlerOpts{
- nil, nil, nil, &mocks.MockNetwork{}, "", crypto.Digest{}, config.Local{},
+ nil, nil, nil, &mocks.MockNetwork{}, config.Local{},
}
_, err := MakeTxHandler(opts)
require.Error(t, err, ErrInvalidTxPool)
opts = TxHandlerOpts{
- &pools.TransactionPool{}, nil, nil, &mocks.MockNetwork{}, "", crypto.Digest{}, config.Local{},
+ &pools.TransactionPool{}, nil, nil, &mocks.MockNetwork{}, config.Local{},
}
_, err = MakeTxHandler(opts)
require.Error(t, err, ErrInvalidLedger)
// it is not possible to test MakeStreamVerifier returning an error, because it is not possible to
- // get the leger return an error for returining the header of its latest round
+ // get the ledger to return an error for returining the header of its latest round
}
// TestTxHandlerRestartWithBacklogAndTxPool starts txHandler, sends transactions,
@@ -2774,6 +2775,8 @@ func TestTxHandlerValidateIncomingTxMessage(t *testing.T) {
handler, err := makeTestTxHandler(ledger, cfg)
require.NoError(t, err)
+ handler.Start()
+ defer handler.Stop()
// valid message
_, blob := makeTxns(addresses, secrets, 1, 2, genesisHash)
@@ -2807,6 +2810,8 @@ func TestTxHandlerValidateIncomingTxMessage(t *testing.T) {
require.True(t, cfg.TxFilterCanonicalEnabled())
handler, err := makeTestTxHandler(ledger, cfg)
require.NoError(t, err)
+ handler.Start()
+ defer handler.Stop()
// valid message
_, blob := makeTxns(addresses, secrets, 1, 2, genesisHash)
@@ -2822,3 +2827,178 @@ func TestTxHandlerValidateIncomingTxMessage(t *testing.T) {
require.Equal(t, outmsg.Action, network.Disconnect)
})
}
+
+// Create mock types to satisfy interfaces
+type erlMockPeer struct {
+ network.DisconnectableAddressablePeer
+ util.ErlClient
+ addr string
+ closer func()
+}
+
+func newErlMockPeer(addr string) *erlMockPeer {
+ return &erlMockPeer{
+ addr: addr,
+ }
+}
+
+// Implement required interface methods
+func (m *erlMockPeer) RoutingAddr() []byte { return []byte(m.addr) }
+func (m *erlMockPeer) OnClose(f func()) { m.closer = f }
+
+func TestTxHandlerErlClientMapper(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ t.Run("Same routing address clients share erlIPClient", func(t *testing.T) {
+ mapper := erlClientMapper{
+ mapping: make(map[string]*erlIPClient),
+ maxClients: 4,
+ }
+
+ peer1 := newErlMockPeer("192.168.1.1")
+ peer2 := newErlMockPeer("192.168.1.1")
+
+ client1 := mapper.getClient(peer1)
+ client2 := mapper.getClient(peer2)
+
+ // Verify both peers got same erlIPClient
+ require.Equal(t, client1, client2, "Expected same erlIPClient for same routing address")
+ require.Equal(t, 1, len(mapper.mapping))
+
+ ipClient := mapper.mapping["192.168.1.1"]
+ require.Equal(t, 2, len(ipClient.clients))
+ })
+
+ t.Run("Different routing addresses get different erlIPClients", func(t *testing.T) {
+ mapper := erlClientMapper{
+ mapping: make(map[string]*erlIPClient),
+ maxClients: 4,
+ }
+
+ peer1 := newErlMockPeer("192.168.1.1")
+ peer2 := newErlMockPeer("192.168.1.2")
+
+ client1 := mapper.getClient(peer1)
+ client2 := mapper.getClient(peer2)
+
+ // Verify peers got different erlIPClients
+ require.NotEqual(t, client1, client2, "Expected different erlIPClients for different routing addresses")
+ require.Equal(t, 2, len(mapper.mapping))
+ })
+
+ t.Run("Client cleanup on connection close", func(t *testing.T) {
+ mapper := erlClientMapper{
+ mapping: make(map[string]*erlIPClient),
+ maxClients: 4,
+ }
+
+ peer1 := newErlMockPeer("192.168.1.1")
+ peer2 := newErlMockPeer("192.168.1.1")
+
+ // Register clients for both peers
+ mapper.getClient(peer1)
+ mapper.getClient(peer2)
+
+ ipClient := mapper.mapping["192.168.1.1"]
+ closerCalled := false
+ ipClient.OnClose(func() {
+ closerCalled = true
+ })
+
+ require.Equal(t, 2, len(ipClient.clients))
+
+ // Simulate connection close for peer1
+ peer1.closer()
+ require.Equal(t, 1, len(ipClient.clients))
+ require.False(t, closerCalled)
+
+ // Simulate connection close for peer2
+ peer2.closer()
+ require.Equal(t, 0, len(ipClient.clients))
+ require.True(t, closerCalled)
+ })
+}
+
+// TestTxHandlerERLIPClient checks that ERL properly handles sender with the same and different addresses:
+// Configure ERL in following way:
+// 1. Small maxCapacity=10 fully shared by two IP senders (TxBacklogReservedCapacityPerPeer=5, IncomingConnectionsLimit=0)
+// 2. Submit one from both IP senders to initalize per peer-queues and exhaust shared capacity
+// 3. Make sure the third peer does not come through
+// 4. Make sure extra messages from the first peer and second peer are accepted
+func TestTxHandlerERLIPClient(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ // technically we don't need any users for this test
+ // but we need to create the genesis accounts to prevent this warning:
+ // "cannot start evaluator: overflowed subtracting rewards for block 1"
+ _, _, genesis := makeTestGenesisAccounts(t, 0)
+ genBal := bookkeeping.MakeGenesisBalances(genesis, sinkAddr, poolAddr)
+ ledgerName := fmt.Sprintf("%s-mem", t.Name())
+ const inMem = true
+
+ log := logging.TestingLog(t)
+ log.SetLevel(logging.Panic)
+
+ const backlogSize = 10 // to have targetRateRefreshTicks: bsize / 10 != 0 in NewREDCongestionManager
+ cfg := config.GetDefaultLocal()
+ cfg.TxIncomingFilteringFlags = 0 // disable duplicate filtering to simplify the test
+ cfg.IncomingConnectionsLimit = 0 // disable incoming connections limit to have TxBacklogSize controlled
+ cfg.EnableTxBacklogRateLimiting = true
+ cfg.EnableTxBacklogAppRateLimiting = false
+ cfg.TxBacklogServiceRateWindowSeconds = 100 // large window
+ cfg.TxBacklogRateLimitingCongestionPct = 0 // always congested
+ cfg.TxBacklogReservedCapacityPerPeer = 5 // 5 messages per peer (IP address in our case)
+ cfg.TxBacklogSize = backlogSize
+ l, err := LoadLedger(log, ledgerName, inMem, protocol.ConsensusCurrentVersion, genBal, genesisID, genesisHash, cfg)
+ require.NoError(t, err)
+ defer l.Close()
+
+ handler, err := makeTestTxHandler(l, cfg)
+ require.NoError(t, err)
+ defer handler.txVerificationPool.Shutdown()
+ defer close(handler.streamVerifierDropped)
+ require.NotNil(t, handler.erl)
+ require.Nil(t, handler.appLimiter)
+ handler.erl.Start()
+ defer handler.erl.Stop()
+
+ var addr1, addr2 basics.Address
+ crypto.RandBytes(addr1[:])
+ crypto.RandBytes(addr2[:])
+
+ tx := getTransaction(addr1, addr2, 1)
+
+ signedTx := tx.Sign(keypair()) // some random key
+ blob := protocol.Encode(&signedTx)
+ sender1 := newErlMockPeer("1")
+ sender2 := newErlMockPeer("2")
+ sender3 := newErlMockPeer("3")
+
+ // initialize peer queues
+ action := handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender1})
+ require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action)
+ require.Equal(t, 1, len(handler.backlogQueue))
+
+ action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender2})
+ require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action)
+ require.Equal(t, 2, len(handler.backlogQueue))
+
+ // make sure the third peer does not come through
+ action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender3})
+ require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action)
+ require.Equal(t, 2, len(handler.backlogQueue))
+
+ // make sure messages from other sender objects with the same IP are accepted
+ sender11 := newErlMockPeer("1")
+ sender21 := newErlMockPeer("2")
+
+ action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender11})
+ require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action)
+ require.Equal(t, 3, len(handler.backlogQueue))
+
+ action = handler.processIncomingTxn(network.IncomingMessage{Data: blob, Sender: sender21})
+ require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, action)
+ require.Equal(t, 4, len(handler.backlogQueue))
+}
diff --git a/docker/README.md b/docker/README.md
index e86729b604..a5c2ff2fb8 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -34,10 +34,10 @@ The following config.json overrides are applied:
The following environment variables can be supplied. Except when noted, it is possible to reconfigure deployments even after the data directory has been initialized.
-| Variable | Description |
-| -------- | ----------- |
+| 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. |
-| PROFILE | If set, initializes the config.json file according to the given profile. |
+| 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. |
| 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. |
@@ -45,10 +45,11 @@ The following environment variables can be supplied. Except when noted, it is po
| ADMIN_TOKEN | If set, overrides the REST API admin token. |
| KMD_TOKEN | If set along with `START_KMD`, override the KMD REST API token. |
| TELEMETRY_NAME | If set on a public network, telemetry is reported with this name. |
+| TELEMETRY_URL | The URL to send telemetry to if enabled (TELEMETRY_NAME is also required). Defaults to sending to Algorand. |
| NUM_ROUNDS | If set on a private network, override default of 30000 participation keys. |
-| GENESIS_ADDRESS | If set, use this API address to initialize the genesis file. |
+| GENESIS_ADDRESS | If set, use this API address to initialize the genesis file. |
| PEER_ADDRESS | If set, override phonebook with peer ip:port (or semicolon separated list: ip:port;ip:port;ip:port...) |
-| GOSSIP_PORT | If set, configure the node to listen for external connections on this address. For example "4161" |
+| GOSSIP_PORT | If set, configure the node to listen for external connections on this address. For example "4161" |
### Special Files
diff --git a/docker/files/run/run.sh b/docker/files/run/run.sh
index d92e7e1d1f..d7ded94b52 100755
--- a/docker/files/run/run.sh
+++ b/docker/files/run/run.sh
@@ -95,6 +95,9 @@ function configure_data_dir() {
# configure telemetry
if [ "$TELEMETRY_NAME" != "" ]; then
diagcfg telemetry name -n "$TELEMETRY_NAME" -d "$ALGORAND_DATA"
+ if [ "$TELEMETRY_URL" != "" ]; then
+ diagcfg telemetry endpoint -e "$TELEMETRY_URL" -d "$ALGORAND_DATA"
+ fi
diagcfg telemetry enable -d "$ALGORAND_DATA"
elif ! [ -f "/etc/algorand/logging.config" ]; then
diagcfg telemetry disable
@@ -213,6 +216,7 @@ 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 " TELEMETRY_URL: $TELEMETRY_URL"
echo " NUM_ROUNDS: $NUM_ROUNDS"
echo " GENESIS_ADDRESS: $GENESIS_ADDRESS"
echo " PEER_ADDRESS: $PEER_ADDRESS"
diff --git a/gen/generate_test.go b/gen/generate_test.go
index 7c314883e8..79b931e2b5 100644
--- a/gen/generate_test.go
+++ b/gen/generate_test.go
@@ -53,8 +53,8 @@ func TestLoadMultiRootKeyConcurrent(t *testing.T) {
defer wg.Done()
wallet := filepath.Join(tempDir, fmt.Sprintf("wallet%d", idx+1))
rootDB, err := db.MakeErasableAccessor(wallet)
- defer rootDB.Close()
a.NoError(err)
+ defer rootDB.Close()
_, err = account.GenerateRoot(rootDB)
a.NoError(err)
}(i)
diff --git a/go.mod b/go.mod
index 0f45d6b6a7..4a8c36580b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/algorand/go-algorand
-go 1.23
+go 1.23.0
toolchain go1.23.3
@@ -27,12 +27,14 @@ require (
github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.6.0
github.com/google/go-querystring v1.0.0
+ github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/ipfs/go-log v1.0.5
github.com/ipfs/go-log/v2 v2.5.1
github.com/jmoiron/sqlx v1.2.0
github.com/jsimonetti/rtnetlink v1.4.2
- github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c
+ github.com/karalabe/hid v1.0.1-0.20240919124526-821c38d2678e
+ github.com/klauspost/cpuid/v2 v2.2.8
github.com/labstack/echo/v4 v4.9.1
github.com/libp2p/go-libp2p v0.37.0
github.com/libp2p/go-libp2p-kad-dht v0.28.0
@@ -50,13 +52,13 @@ require (
github.com/stretchr/testify v1.9.0
go.opencensus.io v0.24.0
go.uber.org/zap v1.27.0
- golang.org/x/crypto v0.31.0
+ golang.org/x/crypto v0.35.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
- golang.org/x/sync v0.10.0
- golang.org/x/sys v0.28.0
- golang.org/x/text v0.21.0
+ golang.org/x/sync v0.11.0
+ golang.org/x/sys v0.30.0
+ golang.org/x/text v0.22.0
gopkg.in/sohlich/elogrus.v3 v3.0.0-20180410122755-1fa29e2f2009
- pgregory.net/rapid v0.6.2
+ pgregory.net/rapid v1.2.0
)
require (
@@ -92,7 +94,6 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect
- github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -112,7 +113,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
- github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
@@ -196,8 +196,8 @@ require (
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.21.0 // indirect
- golang.org/x/net v0.33.0 // indirect
- golang.org/x/term v0.27.0 // indirect
+ golang.org/x/net v0.36.0 // indirect
+ golang.org/x/term v0.29.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
diff --git a/go.sum b/go.sum
index 42c6ef5005..e5d42b917d 100644
--- a/go.sum
+++ b/go.sum
@@ -323,8 +323,8 @@ github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPci
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c h1:AqsttAyEyIEsNz5WLRwuRwjiT5CMDUfLk6cFJDVPebs=
-github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
+github.com/karalabe/hid v1.0.1-0.20240919124526-821c38d2678e h1:ryNJIEs1fyZNVwJ/Dsz7+EFZTow0ggBdnAIVo8SAs+A=
+github.com/karalabe/hid v1.0.1-0.20240919124526-821c38d2678e/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8=
github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
@@ -733,8 +733,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
@@ -784,8 +784,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -801,8 +801,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -843,8 +843,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -852,8 +852,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
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=
@@ -864,8 +864,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -970,8 +970,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
-pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
-pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
+pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
+pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
diff --git a/installer/config.json.example b/installer/config.json.example
index 1e7937d455..fec7d7bf6d 100644
--- a/installer/config.json.example
+++ b/installer/config.json.example
@@ -1,5 +1,5 @@
{
- "Version": 35,
+ "Version": 36,
"AccountUpdatesStatsInterval": 5000000000,
"AccountsRebuildSynchronousMode": 1,
"AgreementIncomingBundlesQueueLength": 15,
@@ -68,6 +68,7 @@
"EnableTxnEvalTracer": false,
"EnableUsageLog": false,
"EnableVerbosedTransactionSyncLogging": false,
+ "EnableVoteCompression": true,
"EndpointAddress": "127.0.0.1:0",
"FallbackDNSResolverAddress": "",
"ForceFetchTransactions": false,
diff --git a/installer/external/node_exporter-stable-darwin-arm64.tar.gz b/installer/external/node_exporter-stable-darwin-arm64.tar.gz
index 56d41a144d..7b49a771bd 100644
Binary files a/installer/external/node_exporter-stable-darwin-arm64.tar.gz and b/installer/external/node_exporter-stable-darwin-arm64.tar.gz differ
diff --git a/installer/external/node_exporter-stable-darwin-universal.tar.gz b/installer/external/node_exporter-stable-darwin-universal.tar.gz
new file mode 100644
index 0000000000..4612327b41
Binary files /dev/null and b/installer/external/node_exporter-stable-darwin-universal.tar.gz differ
diff --git a/installer/external/node_exporter-stable-darwin-x86_64.tar.gz b/installer/external/node_exporter-stable-darwin-x86_64.tar.gz
index f847ea6662..2ad329fc78 100644
Binary files a/installer/external/node_exporter-stable-darwin-x86_64.tar.gz and b/installer/external/node_exporter-stable-darwin-x86_64.tar.gz differ
diff --git a/installer/external/node_exporter-stable-linux-riscv64.tar.gz b/installer/external/node_exporter-stable-linux-riscv64.tar.gz
new file mode 100644
index 0000000000..56d41a144d
Binary files /dev/null and b/installer/external/node_exporter-stable-linux-riscv64.tar.gz differ
diff --git a/installer/external/node_exporter-stable-linux-x86_64.tar.gz b/installer/external/node_exporter-stable-linux-x86_64.tar.gz
index ef0da17fd0..f0065067b9 100644
Binary files a/installer/external/node_exporter-stable-linux-x86_64.tar.gz and b/installer/external/node_exporter-stable-linux-x86_64.tar.gz differ
diff --git a/ledger/acctdeltas.go b/ledger/acctdeltas.go
index 37021f3bfd..d8f53b6b22 100644
--- a/ledger/acctdeltas.go
+++ b/ledger/acctdeltas.go
@@ -1032,7 +1032,12 @@ func onlineAccountsNewRoundImpl(
prevAcct = updated
}
} else {
- if prevAcct.AccountData.IsVotingEmpty() && newAcct.IsVotingEmpty() {
+ if prevAcct.AccountData.IsVotingEmpty() && newStatus != basics.Online {
+ // we are not using newAcct.IsVotingEmpty because new account comes from deltas,
+ // and deltas are base (full) accounts, so that it can have status=offline and non-empty voting data
+ // for suspended accounts.
+ // it is not the same for online accounts where empty all offline accounts are stored with empty voting data.
+
// if both old and new are offline, ignore
// otherwise the following could happen:
// 1. there are multiple offline account deltas so all of them could be inserted
diff --git a/ledger/acctdeltas_test.go b/ledger/acctdeltas_test.go
index c13656ffb6..be9772d724 100644
--- a/ledger/acctdeltas_test.go
+++ b/ledger/acctdeltas_test.go
@@ -945,16 +945,15 @@ func TestLookupKeysByPrefix(t *testing.T) {
for index, testCase := range testCases {
t.Run("lookupKVByPrefix-testcase-"+strconv.Itoa(index), func(t *testing.T) {
- actual := make(map[string]bool)
- _, err := qs.LookupKeysByPrefix(string(testCase.prefix), uint64(len(kvPairDBPrepareSet)), actual, 0)
+ _, actual, _, err := qs.LookupKeysByPrefix(string(testCase.prefix), "", len(kvPairDBPrepareSet), 1_000_000, false)
if err != nil {
require.NotEmpty(t, testCase.err, testCase.prefix)
require.Contains(t, err.Error(), testCase.err)
} else {
require.Empty(t, testCase.err)
- expected := make(map[string]bool)
+ expected := make(map[string]string)
for _, name := range testCase.expectedNames {
- expected[string(name)] = true
+ expected[string(name)] = ""
}
require.Equal(t, actual, expected)
}
@@ -1023,8 +1022,7 @@ func BenchmarkLookupKeyByPrefix(b *testing.B) {
b.Run("lookupKVByPrefix-DBsize"+strconv.Itoa(currentDBSize), func(b *testing.B) {
for i := 0; i < b.N; i++ {
- results := make(map[string]bool)
- _, err := qs.LookupKeysByPrefix(prefix, uint64(currentDBSize), results, 0)
+ _, results, _, err := qs.LookupKeysByPrefix(prefix, "", currentDBSize, 1_000_000, false)
require.NoError(b, err)
require.True(b, len(results) >= 1)
}
@@ -3386,6 +3384,7 @@ func randomAppResourceData() trackerdb.ResourcesData {
GlobalStateSchemaNumUint: crypto.RandUint64(),
GlobalStateSchemaNumByteSlice: crypto.RandUint64(),
ExtraProgramPages: uint32(crypto.RandUint63() % uint64(math.MaxUint32)),
+ Version: crypto.RandUint64(),
ResourceFlags: 255,
UpdateRound: crypto.RandUint64(),
@@ -3599,3 +3598,89 @@ func TestOnlineAccountsExceedOfflineRows(t *testing.T) {
require.True(t, history[1].AccountData.IsVotingEmpty())
require.Equal(t, basics.Round(5), history[1].UpdRound)
}
+
+// TestOnlineAccountsSuspended checks that transfer to suspended account does not produce extra rows
+// in online accounts table. The test is similar to TestOnlineAccountsExceedOfflineRows.
+func TestOnlineAccountsSuspended(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ dbs, _ := storetesting.DbOpenTest(t, true)
+ storetesting.SetDbLogging(t, dbs)
+ defer dbs.Close()
+
+ tx, err := dbs.Wdb.Handle.Begin()
+ require.NoError(t, err)
+ defer tx.Rollback()
+
+ proto := config.Consensus[protocol.ConsensusCurrentVersion]
+
+ var accts map[basics.Address]basics.AccountData
+ sqlitedriver.AccountsInitTest(t, tx, accts, protocol.ConsensusCurrentVersion)
+
+ addrA := ledgertesting.RandomAddress()
+
+ deltaA := onlineAccountDelta{
+ address: addrA,
+ newAcct: []trackerdb.BaseOnlineAccountData{
+ {
+ MicroAlgos: basics.MicroAlgos{Raw: 100_000_000},
+ IncentiveEligible: true, // does not matter for commit logic but makes the test intent clearer
+ BaseVotingData: trackerdb.BaseVotingData{VoteFirstValid: 1, VoteLastValid: 5},
+ },
+ // suspend, offline but non-empty voting data
+ {
+ MicroAlgos: basics.MicroAlgos{Raw: 100_000_000},
+ IncentiveEligible: false,
+ BaseVotingData: trackerdb.BaseVotingData{VoteFirstValid: 1, VoteLastValid: 5},
+ },
+ },
+ updRound: []uint64{1, 2},
+ newStatus: []basics.Status{basics.Online, basics.Offline},
+ }
+ updates := compactOnlineAccountDeltas{}
+ updates.deltas = append(updates.deltas, deltaA)
+ writer, err := sqlitedriver.MakeOnlineAccountsSQLWriter(tx, updates.len() > 0)
+ require.NoError(t, err)
+ defer writer.Close()
+
+ lastUpdateRound := basics.Round(2)
+ updated, err := onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound)
+ require.NoError(t, err)
+ require.Len(t, updated, 2)
+
+ var baseOnlineAccounts lruOnlineAccounts
+ baseOnlineAccounts.init(logging.TestingLog(t), 1000, 800)
+ for _, persistedAcct := range updated {
+ baseOnlineAccounts.write(persistedAcct)
+ }
+
+ // make sure baseOnlineAccounts has the entry
+ entry, has := baseOnlineAccounts.read(addrA)
+ require.True(t, has)
+ require.True(t, entry.AccountData.IsVotingEmpty())
+ require.Equal(t, basics.Round(2), entry.UpdRound)
+
+ acctDelta := ledgercore.AccountDeltas{}
+
+ // simulate transfer to suspended account
+ ad := ledgercore.AccountData{
+ AccountBaseData: ledgercore.AccountBaseData{
+ Status: basics.Offline,
+ MicroAlgos: basics.MicroAlgos{Raw: 100_000_000 - 1},
+ },
+ VotingData: basics.VotingData{
+ VoteFirstValid: 1,
+ VoteLastValid: 5,
+ },
+ }
+ acctDelta.Upsert(addrA, ad)
+ deltas := []ledgercore.AccountDeltas{acctDelta}
+ updates = makeCompactOnlineAccountDeltas(deltas, 3, baseOnlineAccounts)
+
+ // insert and make sure no new rows are inserted
+ lastUpdateRound = basics.Round(3)
+ updated, err = onlineAccountsNewRoundImpl(writer, updates, proto, lastUpdateRound)
+ require.NoError(t, err)
+ require.Len(t, updated, 0)
+}
diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go
index 4e8f7b1759..0335a20f6f 100644
--- a/ledger/acctonline_test.go
+++ b/ledger/acctonline_test.go
@@ -27,6 +27,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
@@ -2021,12 +2022,12 @@ func TestAcctOnline_ExpiredOnlineCirculation(t *testing.T) {
stakeA := allAccts[0].MicroAlgos
statesA := map[acctState]ledgercore.AccountData{
acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeA}, VotingData: basics.VotingData{}},
- acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeA}, VotingData: basics.VotingData(allAccts[0].OnlineAccountData().VotingData)},
+ acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeA}, VotingData: basics.VotingData(basics_testing.OnlineAccountData(allAccts[0].AccountData).VotingData)},
}
addrB := allAccts[1].Addr
stakeB := allAccts[1].MicroAlgos
- votingDataB := allAccts[1].OnlineAccountData().VotingData
+ votingDataB := basics_testing.OnlineAccountData(allAccts[1].AccountData).VotingData
statesB := map[acctState]ledgercore.AccountData{
acctStateOffline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: stakeB}, VotingData: basics.VotingData{}},
acctStateOnline: {AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: stakeB}, VotingData: basics.VotingData(votingDataB)},
diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go
index ca9b0c28e3..c36d4b517f 100644
--- a/ledger/acctupdates.go
+++ b/ledger/acctupdates.go
@@ -22,7 +22,6 @@ import (
"fmt"
"io"
"sort"
- "strings"
"sync"
"time"
@@ -457,132 +456,13 @@ func (au *accountUpdates) lookupKv(rnd basics.Round, key string, synchronized bo
}
}
-func (au *accountUpdates) LookupKeysByPrefix(round basics.Round, keyPrefix string, maxKeyNum uint64) ([]string, error) {
- return au.lookupKeysByPrefix(round, keyPrefix, maxKeyNum, true /* take lock */)
-}
-
-func (au *accountUpdates) lookupKeysByPrefix(round basics.Round, keyPrefix string, maxKeyNum uint64, synchronized bool) (resultKeys []string, err error) {
- var results map[string]bool
- // keep track of the number of result key with value
- var resultCount uint64
-
- needUnlock := false
- if synchronized {
- au.accountsMu.RLock()
- needUnlock = true
- }
- defer func() {
- if needUnlock {
- au.accountsMu.RUnlock()
- }
- // preparation of result happens in deferring function
- // prepare result only when err != nil
- if err == nil {
- resultKeys = make([]string, 0, resultCount)
- for resKey, present := range results {
- if present {
- resultKeys = append(resultKeys, resKey)
- }
- }
- }
- }()
-
- // TODO: This loop and round handling is copied from other routines like
- // lookupResource. I believe that it is overly cautious, as it always reruns
- // the lookup if the DB round does not match the expected round. However, as
- // long as the db round has not advanced too far (greater than `rnd`), I
- // believe it would be valid to use. In the interest of minimizing changes,
- // I'm not doing that now.
-
- for {
- currentDBRound := au.cachedDBRound
- currentDeltaLen := len(au.deltas)
- offset, rndErr := au.roundOffset(round)
- if rndErr != nil {
- return nil, rndErr
- }
-
- // reset `results` to be empty each iteration
- // if db round does not match the round number returned from DB query, start over again
- // NOTE: `results` is maintained as we walk backwards from the latest round, to DB
- // IT IS NOT SIMPLY A SET STORING KEY NAMES!
- // - if the boolean for the key is true: we consider the key is still valid in later round
- // - otherwise, we consider that the key is deleted in later round, and we will not return it as part of result
- // Thus: `resultCount` keeps track of how many VALID keys in the `results`
- // DO NOT TRY `len(results)` TO SEE NUMBER OF VALID KEYS!
- results = map[string]bool{}
- resultCount = 0
-
- for offset > 0 {
- offset--
- for keyInRound, mv := range au.deltas[offset].KvMods {
- if !strings.HasPrefix(keyInRound, keyPrefix) {
- continue
- }
- // whether it is set or deleted in later round, if such modification exists in later round
- // we just ignore the earlier insert
- if _, ok := results[keyInRound]; ok {
- continue
- }
- if mv.Data == nil {
- results[keyInRound] = false
- } else {
- // set such key to be valid with value
- results[keyInRound] = true
- resultCount++
- // check if the size of `results` reaches `maxKeyNum`
- // if so just return the list of keys
- if resultCount == maxKeyNum {
- return
- }
- }
- }
- }
-
- round = currentDBRound + basics.Round(currentDeltaLen)
-
- // after this line, we should dig into DB I guess
- // OTHER LOOKUPS USE "base" caches here.
- if synchronized {
- au.accountsMu.RUnlock()
- needUnlock = false
- }
-
- // NOTE: the kv cache isn't used here because the data structure doesn't support range
- // queries. It may be preferable to increase the SQLite cache size if these reads become
- // too slow.
-
- // Finishing searching updates of this account in kvDeltas, keep going: use on-disk DB
- // to find the rest matching keys in DB.
- dbRound, dbErr := au.accountsq.LookupKeysByPrefix(keyPrefix, maxKeyNum, results, resultCount)
- if dbErr != nil {
- return nil, dbErr
- }
- if dbRound == currentDBRound {
- return
- }
+func (au *accountUpdates) LookupKeysByPrefix(prefix, next string, maxKeyNum, maxBytes int, values bool) (basics.Round, map[string]string, string, error) {
+ // We only use the DB, not deltas. That means our answer is a bit out of
+ // date, but it is otherwise extremely difficult to integrate answers from
+ // the deltas and from the DB, while respecting the limits and the prefix.
+ // This seems reasonable for it's purpose - just a REST API.
- // The DB round is unexpected... '_>'?
- if synchronized {
- if dbRound < currentDBRound {
- // does not make sense if DB round is earlier than it should be
- au.log.Errorf("accountUpdates.lookupKvPair: database round %d is behind in-memory round %d", dbRound, currentDBRound)
- err = &StaleDatabaseRoundError{databaseRound: dbRound, memoryRound: currentDBRound}
- return
- }
- // The DB round is higher than expected, so a write-into-DB must have happened. Start over again.
- au.accountsMu.RLock()
- needUnlock = true
- // WHY BOTH - seems the goal is just to wait until the au is aware of progress. au.cachedDBRound should be enough?
- for currentDBRound >= au.cachedDBRound && currentDeltaLen == len(au.deltas) {
- au.accountsReadCond.Wait()
- }
- } else {
- au.log.Errorf("accountUpdates.lookupKvPair: database round %d mismatching in-memory round %d", dbRound, currentDBRound)
- err = &MismatchingDatabaseRoundError{databaseRound: dbRound, memoryRound: currentDBRound}
- return
- }
- }
+ return au.accountsq.LookupKeysByPrefix(prefix, next, maxKeyNum, maxBytes, values)
}
// LookupWithoutRewards returns the account data for a given address at a given round.
diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go
index e72dea9444..e07c8449b0 100644
--- a/ledger/acctupdates_test.go
+++ b/ledger/acctupdates_test.go
@@ -990,19 +990,25 @@ func TestBoxNamesByAppIDs(t *testing.T) {
`他们丢失了四季,惶惑之行开始`,
`这颗行星所有的酒馆,无法听到远方的呼喊`,
`野心勃勃的灯火,瞬间吞没黑暗的脸庞`,
+
+ // useful for testing prefix
+ "xxABC",
+ "xxBBB",
+ "xxBBC",
+ "xxCBA",
}
- appIDset := make(map[basics.AppIndex]struct{}, len(testingBoxNames))
- boxNameToAppID := make(map[string]basics.AppIndex, len(testingBoxNames))
- var currentRound basics.Round
+ appIDset := make(map[uint64]struct{}, len(testingBoxNames))
+ boxNameToAppID := make(map[string]uint64, len(testingBoxNames))
+ const allBoxesAppID = 777
// keep adding one box key and one random appID (non-duplicated)
for i, boxName := range testingBoxNames {
- currentRound = basics.Round(i + 1)
+ currentRound := au.latest() + 1
- var appID basics.AppIndex
+ var appID uint64
for {
- appID = basics.AppIndex(crypto.RandUint64())
+ appID = crypto.RandUint64()
_, preExisting := appIDset[appID]
if !preExisting {
break
@@ -1012,45 +1018,122 @@ func TestBoxNamesByAppIDs(t *testing.T) {
appIDset[appID] = struct{}{}
boxNameToAppID[boxName] = appID
- boxChange := ledgercore.KvValueDelta{Data: []byte(boxName)}
auNewBlock(t, currentRound, au, accts, opts, map[string]ledgercore.KvValueDelta{
- apps.MakeBoxKey(uint64(appID), boxName): boxChange,
+ apps.MakeBoxKey(appID, boxName): {Data: []byte(boxName)},
+ apps.MakeBoxKey(allBoxesAppID, boxName): {Data: []byte(boxName)},
})
auCommitSync(t, currentRound, au, ml)
- // ensure rounds
- rnd := au.latest()
- require.Equal(t, currentRound, rnd)
- if uint64(currentRound) > conf.MaxAcctLookback {
- require.Equal(t, basics.Round(uint64(currentRound)-conf.MaxAcctLookback), au.cachedDBRound)
- } else {
- require.Equal(t, basics.Round(0), au.cachedDBRound)
+ // Advance conf.MaxAcctLookback rounds because LookupKeysByPrefix only
+ // sees kvs that make it to the DB. The deltas are not examined. It
+ // might be nice to do so, but it's complicated. If we decide to do so,
+ // we can stop advancing here
+
+ target := basics.Round(uint64(currentRound) + conf.MaxAcctLookback)
+ for au.latest() < target {
+ auNewBlock(t, au.latest()+1, au, accts, opts, nil)
+ auCommitSync(t, au.latest()+1, au, ml)
}
- // check input, see all present keys are all still there
+ // check that each box ended up in its unique app
for _, storedBoxName := range testingBoxNames[:i+1] {
- res, err := au.LookupKeysByPrefix(currentRound, apps.MakeBoxKey(uint64(boxNameToAppID[storedBoxName]), ""), 10000)
+ _, res, _, err := au.LookupKeysByPrefix(apps.MakeBoxKey(boxNameToAppID[storedBoxName], ""), "", 10000, 100_000, false)
+
require.NoError(t, err)
require.Len(t, res, 1)
- require.Equal(t, apps.MakeBoxKey(uint64(boxNameToAppID[storedBoxName]), storedBoxName), res[0])
+ require.Contains(t, res, apps.MakeBoxKey(boxNameToAppID[storedBoxName], storedBoxName))
+ }
+
+ // check that the allBoxesAppID has all of them
+ _, res, next, err := au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, ""), "", 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, i+1)
+ require.Empty(t, next)
+ for _, storedBoxName := range testingBoxNames[:i+1] {
+ require.Contains(t, res, apps.MakeBoxKey(allBoxesAppID, storedBoxName))
}
}
+ // all boxes have been created.
+
+ // show that when row limit is too low, paging kicks in
+ _, res, next, err := au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, ""), "", 10, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 10)
+ require.NotEmpty(t, next)
+
+ // get the rest
+ _, res, next, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, ""), next, 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, len(testingBoxNames)-10)
+ require.Empty(t, next)
+
+ // show that when byte limit is too low, paging kicks in
+ _, res, next, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, ""), "", 10000, 1_000, false)
+ require.NoError(t, err)
+ boxCount := len(res)
+ require.Less(t, boxCount, len(testingBoxNames))
+ count := 0
+ for key, val := range res {
+ count += len(key) + len(val)
+ }
+ require.Less(t, count, 1_001)
+ require.Greater(t, count, 950) // got close though, right?
+ require.NotEmpty(t, next)
+
+ // get the rest
+ _, res, next, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, ""), next, 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, len(testingBoxNames)-boxCount)
+ require.Empty(t, next)
+
+ // check that prefix works properly
+ _, res, _, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, "x"), "", 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 4)
+
+ _, res, _, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, "xx"), "", 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 4)
+
+ _, res, _, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, "xxB"), "", 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 2) // xxBBB, xxBBC
+
+ // check that "next" works.
+ _, res, _, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, "xx"), apps.MakeBoxKey(allBoxesAppID, "xxB"), 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 3) // xxBBB, xxBBC, XXCBA
+
+ // check that "next" works precisely
+ _, res, _, err = au.LookupKeysByPrefix(apps.MakeBoxKey(allBoxesAppID, "xx"), apps.MakeBoxKey(allBoxesAppID, "xxBBB"), 10000, 100_000, false)
+ require.NoError(t, err)
+ require.Len(t, res, 3) // xxBBB, xxBBC, XXCBA
+
// removing inserted boxes
for _, boxName := range testingBoxNames {
- currentRound++
+ currentRound := au.latest() + 1
// remove inserted box
appID := boxNameToAppID[boxName]
auNewBlock(t, currentRound, au, accts, opts, map[string]ledgercore.KvValueDelta{
- apps.MakeBoxKey(uint64(appID), boxName): {},
+ // to make a delete actually hit the DB, OldData must not be nil.
+ apps.MakeBoxKey(uint64(appID), boxName): {OldData: []byte{0x01}},
})
auCommitSync(t, currentRound, au, ml)
- // ensure recently removed key is not present, and it is not part of the result
- res, err := au.LookupKeysByPrefix(currentRound, apps.MakeBoxKey(uint64(boxNameToAppID[boxName]), ""), 10000)
+ // as we did during inserts, advance until the deletion is "visible" in the DB
+ target := basics.Round(uint64(currentRound) + conf.MaxAcctLookback)
+ for au.latest() < target {
+ auNewBlock(t, au.latest()+1, au, accts, opts, nil)
+ auCommitSync(t, au.latest()+1, au, ml)
+ }
+
+ // ensure recently removed key is not present, and it is not part of the
+ // result. We are grabbing all boxes under this (unique) appID.
+ _, res, _, err := au.LookupKeysByPrefix(apps.MakeBoxKey(uint64(appID), ""), "", 10000, 100_000, false)
require.NoError(t, err)
- require.Len(t, res, 0)
+ require.Empty(t, res)
}
}
diff --git a/ledger/applications_test.go b/ledger/applications_test.go
index 2375808b64..8be15bae91 100644
--- a/ledger/applications_test.go
+++ b/ledger/applications_test.go
@@ -1069,13 +1069,13 @@ func testAppAccountDeltaIndicesCompatibility(t *testing.T, source string, accoun
a.Equal(blk.Payset[0].ApplyData.EvalDelta.LocalDeltas[accountIdx]["lk1"].Bytes, "local1")
}
-// TestParitalDeltaWrites checks account data consistency when app global state or app local state
+// TestPartialDeltaWrites checks account data consistency when app global state or app local state
// accessed in a block where app creator and local user do not have any state changes expect app storage
// Block 1: create app
// Block 2: opt in
// Block 3: write to global state (goes into creator's AD), write to local state of txn.Account[1] (not a txn sender)
// In this case StateDelta will not have base record modification, only storage
-func TestParitalDeltaWrites(t *testing.T) {
+func TestPartialDeltaWrites(t *testing.T) {
partitiontest.PartitionTest(t)
source := `#pragma version 2
diff --git a/ledger/apply/application.go b/ledger/apply/application.go
index 7d18aa5fa7..397c43d68e 100644
--- a/ledger/apply/application.go
+++ b/ledger/apply/application.go
@@ -97,6 +97,7 @@ func createApplication(ac *transactions.ApplicationCallTxnFields, balances Balan
GlobalStateSchema: ac.GlobalStateSchema,
},
ExtraProgramPages: ac.ExtraProgramPages,
+ Version: 0,
}
// Update the cached TotalStateSchema for this account, used
@@ -213,6 +214,9 @@ func updateApplication(ac *transactions.ApplicationCallTxnFields, balances Balan
params.ApprovalProgram = ac.ApprovalProgram
params.ClearStateProgram = ac.ClearStateProgram
+ if proto.EnableAppVersioning {
+ params.Version++
+ }
return balances.PutAppParams(creator, appIdx, params)
}
@@ -376,6 +380,10 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio
return err
}
+ if ac.RejectVersion > 0 && params.Version >= ac.RejectVersion {
+ return fmt.Errorf("app version (%d) >= reject version (%d)", params.Version, ac.RejectVersion)
+ }
+
// Ensure that the only operation we can do is ClearState if the application
// does not exist
if !exists && ac.OnCompletion != transactions.ClearStateOC {
@@ -424,9 +432,8 @@ func ApplicationCall(ac transactions.ApplicationCallTxnFields, header transactio
// Fill in applyData, so that consumers don't have to implement a
// stateful TEAL interpreter to apply state changes
ad.EvalDelta = evalDelta
- } else {
- // Ignore logic eval errors and rejections from the ClearStateProgram
}
+ // Ignore logic eval errors and rejections from the ClearStateProgram
}
return closeOutApplication(balances, header.Sender, appIdx)
diff --git a/ledger/apply/asset_test.go b/ledger/apply/asset_test.go
index 0c1e800041..2e988a7b59 100644
--- a/ledger/apply/asset_test.go
+++ b/ledger/apply/asset_test.go
@@ -24,6 +24,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
+ ledgertesting "github.com/algorand/go-algorand/ledger/testing"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
@@ -31,15 +32,9 @@ import (
func TestAssetTransfer(t *testing.T) {
partitiontest.PartitionTest(t)
- // Creator
- secretSrc := keypair()
- src := basics.Address(secretSrc.SignatureVerifier)
-
- secretDst := keypair()
- dst := basics.Address(secretDst.SignatureVerifier)
-
- secretCls := keypair()
- cls := basics.Address(secretCls.SignatureVerifier)
+ src := ledgertesting.RandomAddress()
+ dst := ledgertesting.RandomAddress()
+ cls := ledgertesting.RandomAddress()
var total, toSend, dstAmount uint64
total = 1000000
diff --git a/ledger/apply/keyreg.go b/ledger/apply/keyreg.go
index 571a683d44..f1d0ff630c 100644
--- a/ledger/apply/keyreg.go
+++ b/ledger/apply/keyreg.go
@@ -30,10 +30,6 @@ var errKeyregGoingOnlineFirstVotingInFuture = errors.New("transaction tries to m
// Keyreg applies a KeyRegistration transaction using the Balances interface.
func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, balances Balances, spec transactions.SpecialAddresses, ad *transactions.ApplyData, round basics.Round) error {
- if header.Sender == spec.FeeSink {
- return fmt.Errorf("cannot register participation key for fee sink's address %v", header.Sender)
- }
-
// Get the user's balance entry
record, err := balances.Get(header.Sender, false)
if err != nil {
diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go
index 7732949ac5..16b0bfbcdf 100644
--- a/ledger/apply/keyreg_test.go
+++ b/ledger/apply/keyreg_test.go
@@ -28,6 +28,7 @@ import (
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
"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"
"github.com/algorand/go-algorand/util/db"
@@ -117,21 +118,14 @@ func (balances keyregTestBalances) StatefulEval(int, *logic.EvalParams, basics.A
func TestKeyregApply(t *testing.T) {
partitiontest.PartitionTest(t)
- secretSrc := keypair()
- src := basics.Address(secretSrc.SignatureVerifier)
+ src := ledgertesting.RandomAddress()
vrfSecrets := crypto.GenerateVRFSecrets()
- secretParticipation := keypair()
+ sigVerifier := crypto.SignatureVerifier{0x02, 0x03, 0x04}
- tx := createTestTxn(t, src, secretParticipation, vrfSecrets)
- err := Keyreg(tx.KeyregTxnFields, tx.Header, makeMockBalances(protocol.ConsensusCurrentVersion), transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0))
+ tx := createTestKeyreg(t, src, sigVerifier, vrfSecrets)
+ err := Keyreg(tx.KeyregTxnFields, tx.Header, makeMockBalances(protocol.ConsensusCurrentVersion), spec, nil, basics.Round(0))
require.NoError(t, err)
- tx.Sender = feeSink
- err = Keyreg(tx.KeyregTxnFields, tx.Header, makeMockBalances(protocol.ConsensusCurrentVersion), transactions.SpecialAddresses{FeeSink: feeSink}, nil, basics.Round(0))
- require.Error(t, err)
-
- tx.Sender = src
-
mockBal := newKeyregTestBalances()
// Going from offline to online should be okay
@@ -163,7 +157,7 @@ func TestKeyregApply(t *testing.T) {
LastValid: basics.Round(1200),
},
KeyregTxnFields: transactions.KeyregTxnFields{
- VotePK: crypto.OneTimeSignatureVerifier(secretParticipation.SignatureVerifier),
+ VotePK: crypto.OneTimeSignatureVerifier(sigVerifier),
SelectionPK: vrfSecrets.PK,
VoteKeyDilution: 1000,
VoteFirst: 500,
@@ -208,12 +202,11 @@ func testStateProofPKBeingStored(t *testing.T, tx transactions.Transaction, mock
func TestStateProofPKKeyReg(t *testing.T) {
partitiontest.PartitionTest(t)
- secretSrc := keypair()
- src := basics.Address(secretSrc.SignatureVerifier)
+ src := ledgertesting.RandomAddress()
vrfSecrets := crypto.GenerateVRFSecrets()
- secretParticipation := keypair()
+ sigVerifier := crypto.SignatureVerifier{0x01, 0x02}
- tx := createTestTxn(t, src, secretParticipation, vrfSecrets)
+ tx := createTestKeyreg(t, src, sigVerifier, vrfSecrets)
mockBal := makeMockBalances(protocol.ConsensusV30)
err := Keyreg(tx.KeyregTxnFields, tx.Header, mockBal, transactions.SpecialAddresses{}, nil, basics.Round(0))
require.NoError(t, err)
@@ -258,11 +251,11 @@ func TestStateProofPKKeyReg(t *testing.T) {
}
-func createTestTxn(t *testing.T, src basics.Address, secretParticipation *crypto.SignatureSecrets, vrfSecrets *crypto.VRFSecrets) transactions.Transaction {
- return createTestTxnWithPeriod(t, src, secretParticipation, vrfSecrets, defaultParticipationFirstRound, defaultParticipationLastRound)
+func createTestKeyreg(t *testing.T, src basics.Address, sigVerifier crypto.SignatureVerifier, vrfSecrets *crypto.VRFSecrets) transactions.Transaction {
+ return createTestKeyregWithPeriod(t, src, sigVerifier, vrfSecrets, defaultParticipationFirstRound, defaultParticipationLastRound)
}
-func createTestTxnWithPeriod(t *testing.T, src basics.Address, secretParticipation *crypto.SignatureSecrets, vrfSecrets *crypto.VRFSecrets, firstRound basics.Round, lastRound basics.Round) transactions.Transaction {
+func createTestKeyregWithPeriod(t *testing.T, src basics.Address, sigVerifier crypto.SignatureVerifier, vrfSecrets *crypto.VRFSecrets, firstRound basics.Round, lastRound basics.Round) transactions.Transaction {
store, err := db.MakeAccessor("test-DB", false, true)
require.NoError(t, err)
defer store.Close()
@@ -280,7 +273,7 @@ func createTestTxnWithPeriod(t *testing.T, src basics.Address, secretParticipati
LastValid: basics.Round(defaultParticipationLastRound),
},
KeyregTxnFields: transactions.KeyregTxnFields{
- VotePK: crypto.OneTimeSignatureVerifier(secretParticipation.SignatureVerifier),
+ VotePK: crypto.OneTimeSignatureVerifier(sigVerifier),
SelectionPK: vrfSecrets.PK,
StateProofPK: signer.GetVerifier().Commitment,
VoteFirst: 0,
diff --git a/ledger/apply/payment_test.go b/ledger/apply/payment_test.go
index eb559f66a5..82c8f28f72 100644
--- a/ledger/apply/payment_test.go
+++ b/ledger/apply/payment_test.go
@@ -17,31 +17,24 @@
package apply
import (
- "math/rand"
+ "math/rand/v2"
"testing"
+ "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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/transactions"
+ ledgertesting "github.com/algorand/go-algorand/ledger/testing"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)
-var poolAddr = basics.Address{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
-
var spec = transactions.SpecialAddresses{
- FeeSink: feeSink,
- RewardsPool: poolAddr,
-}
-
-func keypair() *crypto.SignatureSecrets {
- var seed crypto.Seed
- crypto.RandBytes(seed[:])
- s := crypto.GenerateSignatureSecrets(seed)
- return s
+ FeeSink: ledgertesting.RandomAddress(),
+ RewardsPool: ledgertesting.RandomAddress(),
}
func TestAlgosEncoding(t *testing.T) {
@@ -82,27 +75,21 @@ func TestAlgosEncoding(t *testing.T) {
func TestPaymentApply(t *testing.T) {
partitiontest.PartitionTest(t)
- mockBalV0 := makeMockBalances(protocol.ConsensusCurrentVersion)
-
- secretSrc := keypair()
- src := basics.Address(secretSrc.SignatureVerifier)
-
- secretDst := keypair()
- dst := basics.Address(secretDst.SignatureVerifier)
-
tx := transactions.Transaction{
Type: protocol.PaymentTx,
Header: transactions.Header{
- Sender: src,
+ Sender: ledgertesting.RandomAddress(),
Fee: basics.MicroAlgos{Raw: 1},
FirstValid: basics.Round(100),
LastValid: basics.Round(1000),
},
PaymentTxnFields: transactions.PaymentTxnFields{
- Receiver: dst,
+ Receiver: ledgertesting.RandomAddress(),
Amount: basics.MicroAlgos{Raw: uint64(50)},
},
}
+
+ mockBalV0 := makeMockBalances(protocol.ConsensusCurrentVersion)
var ad transactions.ApplyData
err := Payment(tx.PaymentTxnFields, tx.Header, mockBalV0, transactions.SpecialAddresses{}, &ad)
require.NoError(t, err)
@@ -111,160 +98,80 @@ func TestPaymentApply(t *testing.T) {
func TestPaymentValidation(t *testing.T) {
partitiontest.PartitionTest(t)
- payments, _, _, _ := generateTestObjects(100, 50)
- genHash := crypto.Digest{0x42}
- for i, txn := range payments {
- txn.GenesisHash = genHash
- payments[i] = txn
- }
- tcpast := transactions.ExplicitTxnContext{
- Proto: config.Consensus[protocol.ConsensusV27],
- GenHash: genHash,
- }
- tc := transactions.ExplicitTxnContext{
- Proto: config.Consensus[protocol.ConsensusCurrentVersion],
- GenHash: genHash,
- }
- for _, txn := range payments {
- // Lifetime window
- tc.ExplicitRound = txn.First() + 1
- if txn.Alive(tc) != nil {
- t.Errorf("transaction not alive during lifetime %v", txn)
- }
-
- tc.ExplicitRound = txn.First()
- if txn.Alive(tc) != nil {
- t.Errorf("transaction not alive at issuance %v", txn)
- }
-
- tc.ExplicitRound = txn.Last()
- if txn.Alive(tc) != nil {
- t.Errorf("transaction not alive at expiry %v", txn)
- }
-
- tc.ExplicitRound = txn.First() - 1
- if txn.Alive(tc) == nil {
- t.Errorf("premature transaction alive %v", txn)
- }
-
- tc.ExplicitRound = txn.Last() + 1
- if txn.Alive(tc) == nil {
- t.Errorf("expired transaction alive %v", txn)
- }
-
- // Make a copy of txn, change some fields, be sure the TXID changes. This is not exhaustive.
- var txn2 transactions.Transaction
- txn2 = txn
- txn2.Note = []byte{42}
- if txn2.ID() == txn.ID() {
- t.Errorf("txid does not depend on note")
- }
- txn2 = txn
- txn2.Amount.Raw++
- if txn2.ID() == txn.ID() {
- t.Errorf("txid does not depend on amount")
- }
- txn2 = txn
- txn2.Fee.Raw++
- if txn2.ID() == txn.ID() {
- t.Errorf("txid does not depend on fee")
- }
- txn2 = txn
- txn2.LastValid++
- if txn2.ID() == txn.ID() {
- t.Errorf("txid does not depend on lastvalid")
- }
-
+ current := config.Consensus[protocol.ConsensusCurrentVersion]
+ for _, txn := range generateTestPays(100) {
// Check malformed transactions
largeWindow := txn
- largeWindow.LastValid += basics.Round(tc.Proto.MaxTxnLife)
- if largeWindow.WellFormed(spec, tc.Proto) == nil {
+ largeWindow.LastValid += basics.Round(current.MaxTxnLife)
+ if largeWindow.WellFormed(spec, current) == nil {
t.Errorf("transaction with large window %#v verified incorrectly", largeWindow)
}
badWindow := txn
badWindow.LastValid = badWindow.FirstValid - 1
- if badWindow.WellFormed(spec, tc.Proto) == nil {
+ if badWindow.WellFormed(spec, current) == nil {
t.Errorf("transaction with bad window %#v verified incorrectly", badWindow)
}
badFee := txn
badFee.Fee = basics.MicroAlgos{}
- if badFee.WellFormed(spec, tcpast.Proto) == nil {
+ if badFee.WellFormed(spec, config.Consensus[protocol.ConsensusV27]) == nil {
t.Errorf("transaction with no fee %#v verified incorrectly", badFee)
}
- require.Nil(t, badFee.WellFormed(spec, tc.Proto))
+ assert.NoError(t, badFee.WellFormed(spec, current))
badFee.Fee.Raw = 1
- if badFee.WellFormed(spec, tcpast.Proto) == nil {
+ if badFee.WellFormed(spec, config.Consensus[protocol.ConsensusV27]) == nil {
t.Errorf("transaction with low fee %#v verified incorrectly", badFee)
}
- require.Nil(t, badFee.WellFormed(spec, tc.Proto))
+ assert.NoError(t, badFee.WellFormed(spec, current))
}
}
func TestPaymentSelfClose(t *testing.T) {
partitiontest.PartitionTest(t)
- secretSrc := keypair()
- src := basics.Address(secretSrc.SignatureVerifier)
-
- secretDst := keypair()
- dst := basics.Address(secretDst.SignatureVerifier)
+ self := ledgertesting.RandomAddress()
tx := transactions.Transaction{
Type: protocol.PaymentTx,
Header: transactions.Header{
- Sender: src,
+ Sender: self,
Fee: basics.MicroAlgos{Raw: config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee},
FirstValid: basics.Round(100),
LastValid: basics.Round(1000),
},
PaymentTxnFields: transactions.PaymentTxnFields{
- Receiver: dst,
+ Receiver: ledgertesting.RandomAddress(),
Amount: basics.MicroAlgos{Raw: uint64(50)},
- CloseRemainderTo: src,
+ CloseRemainderTo: self,
},
}
require.Error(t, tx.WellFormed(spec, config.Consensus[protocol.ConsensusCurrentVersion]))
}
-func generateTestObjects(numTxs, numAccs int) ([]transactions.Transaction, []transactions.SignedTxn, []*crypto.SignatureSecrets, []basics.Address) {
+func generateTestPays(numTxs int) []transactions.Transaction {
txs := make([]transactions.Transaction, numTxs)
- signed := make([]transactions.SignedTxn, numTxs)
- secrets := make([]*crypto.SignatureSecrets, numAccs)
- addresses := make([]basics.Address, numAccs)
-
- for i := 0; i < numAccs; i++ {
- secret := keypair()
- addr := basics.Address(secret.SignatureVerifier)
- secrets[i] = secret
- addresses[i] = addr
- }
-
- for i := 0; i < numTxs; i++ {
- s := rand.Intn(numAccs)
- r := rand.Intn(numAccs)
- a := rand.Intn(1000)
- f := config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee + uint64(rand.Intn(10))
- iss := 50 + rand.Intn(30)
+ for i := range numTxs {
+ a := rand.IntN(1000)
+ f := config.Consensus[protocol.ConsensusCurrentVersion].MinTxnFee + uint64(rand.IntN(10))
+ iss := 50 + rand.IntN(30)
exp := iss + 10
txs[i] = transactions.Transaction{
Type: protocol.PaymentTx,
Header: transactions.Header{
- Sender: addresses[s],
- Fee: basics.MicroAlgos{Raw: f},
- FirstValid: basics.Round(iss),
- LastValid: basics.Round(exp),
+ Sender: ledgertesting.RandomAddress(),
+ Fee: basics.MicroAlgos{Raw: f},
+ FirstValid: basics.Round(iss),
+ LastValid: basics.Round(exp),
+ GenesisHash: crypto.Digest{0x02},
},
PaymentTxnFields: transactions.PaymentTxnFields{
- Receiver: addresses[r],
+ Receiver: ledgertesting.RandomAddress(),
Amount: basics.MicroAlgos{Raw: uint64(a)},
},
}
- signed[i] = txs[i].Sign(secrets[s])
}
-
- return txs, signed, secrets, addresses
+ return txs
}
diff --git a/ledger/catchpointfilewriter_test.go b/ledger/catchpointfilewriter_test.go
index d436e0de8c..69d4c803b1 100644
--- a/ledger/catchpointfilewriter_test.go
+++ b/ledger/catchpointfilewriter_test.go
@@ -905,11 +905,10 @@ func testExactAccountChunk(t *testing.T, proto protocol.ConsensusVersion, extraB
dl.fullBlock(&selfpay)
}
- genR, _ := testCatchpointFlushRound(dl.generator)
- valR, _ := testCatchpointFlushRound(dl.validator)
- require.Equal(t, genR, valR)
- require.EqualValues(t, BalancesPerCatchpointFileChunk-12+extraBlocks, genR)
+ testCatchpointFlushRound(dl.generator)
+ testCatchpointFlushRound(dl.validator)
+ // wait for the two ledgers to finish committing and be in sync
require.Eventually(t, func() bool {
dl.generator.accts.accountsMu.RLock()
dlg := len(dl.generator.accts.deltas)
@@ -920,6 +919,10 @@ func testExactAccountChunk(t *testing.T, proto protocol.ConsensusVersion, extraB
dl.validator.accts.accountsMu.RUnlock()
return dlg == dlv && dl.generator.Latest() == dl.validator.Latest()
}, 10*time.Second, 100*time.Millisecond)
+ genR, _ := dl.generator.LatestCommitted()
+ valR, _ := dl.validator.LatestCommitted()
+ require.Equal(t, genR, valR)
+ require.EqualValues(t, BalancesPerCatchpointFileChunk-12+extraBlocks, genR)
tempDir := t.TempDir()
@@ -1037,7 +1040,7 @@ func TestCatchpointAfterTxns(t *testing.T) {
l := testNewLedgerFromCatchpoint(t, dl.validator.trackerDB(), catchpointFilePath)
defer l.Close()
- values, err := l.LookupKeysByPrefix(l.Latest(), "bx:", 10)
+ _, values, _, err := l.LookupKeysByPrefix("bx:", "", 10, 10_000, false)
require.NoError(t, err)
require.Len(t, values, 1)
@@ -1069,7 +1072,7 @@ func TestCatchpointAfterTxns(t *testing.T) {
l = testNewLedgerFromCatchpoint(t, dl.validator.trackerDB(), catchpointFilePath)
defer l.Close()
- values, err = l.LookupKeysByPrefix(l.Latest(), "bx:", 10)
+ _, values, _, err = l.LookupKeysByPrefix("bx:", "", 10, 10_000, false)
require.NoError(t, err)
require.Len(t, values, 1)
v, err := l.LookupKv(l.Latest(), apps.MakeBoxKey(uint64(boxApp), "xxx"))
@@ -1378,7 +1381,7 @@ func TestCatchpointAfterBoxTxns(t *testing.T) {
l := testNewLedgerFromCatchpoint(t, dl.generator.trackerDB(), catchpointFilePath)
defer l.Close()
- values, err := l.LookupKeysByPrefix(l.Latest(), "bx:", 10)
+ _, values, _, err := l.LookupKeysByPrefix("bx:", "", 10, 10_000, false)
require.NoError(t, err)
require.Len(t, values, 1)
v, err := l.LookupKv(l.Latest(), apps.MakeBoxKey(uint64(boxApp), "xxx"))
diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go
index 2814499620..8b422af70f 100644
--- a/ledger/eval/eval.go
+++ b/ledger/eval/eval.go
@@ -990,7 +990,7 @@ func (eval *BlockEvaluator) TestTransactionGroup(txgroup []transactions.SignedTx
// evaluator, or modify the block evaluator state in any other visible way.
func (eval *BlockEvaluator) TestTransaction(txn transactions.SignedTxn) error {
// Transaction valid (not expired)?
- err := txn.Txn.Alive(eval.block)
+ err := eval.block.Alive(txn.Txn.Header)
if err != nil {
return err
}
@@ -1003,7 +1003,7 @@ func (eval *BlockEvaluator) TestTransaction(txn transactions.SignedTxn) error {
// Transaction already in the ledger?
txid := txn.ID()
- err = eval.state.checkDup(txn.Txn.First(), txn.Txn.Last(), txid, ledgercore.Txlease{Sender: txn.Txn.Sender, Lease: txn.Txn.Lease})
+ err = eval.state.checkDup(txn.Txn.FirstValid, txn.Txn.LastValid, txid, ledgercore.Txlease{Sender: txn.Txn.Sender, Lease: txn.Txn.Lease})
if err != nil {
return err
}
@@ -1179,13 +1179,13 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams *
txid := txn.ID()
if eval.validate {
- err = txn.Txn.Alive(eval.block)
+ err = eval.block.Alive(txn.Txn.Header)
if err != nil {
return err
}
// Transaction already in the ledger?
- err = cow.checkDup(txn.Txn.First(), txn.Txn.Last(), txid, ledgercore.Txlease{Sender: txn.Txn.Sender, Lease: txn.Txn.Lease})
+ err = cow.checkDup(txn.Txn.FirstValid, txn.Txn.LastValid, txid, ledgercore.Txlease{Sender: txn.Txn.Sender, Lease: txn.Txn.Lease})
if err != nil {
return err
}
@@ -2034,7 +2034,7 @@ func (validator *evalTxValidator) run() {
signedTxnGroup := make([]transactions.SignedTxn, len(group))
for j, txn := range group {
signedTxnGroup[j] = txn.SignedTxn
- err := txn.SignedTxn.Txn.Alive(validator.block)
+ err := validator.block.Alive(txn.SignedTxn.Txn.Header)
if err != nil {
validator.done <- err
return
diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go
index d375feeea3..b90530b1e3 100644
--- a/ledger/eval/eval_test.go
+++ b/ledger/eval/eval_test.go
@@ -797,7 +797,7 @@ func (ledger *evalTestLedger) GetKnockOfflineCandidates(rnd basics.Round, _ conf
ret := make(map[basics.Address]basics.OnlineAccountData)
for addr, data := range ledger.roundBalances[rnd] {
if data.Status == basics.Online && !data.MicroAlgos.IsZero() {
- ret[addr] = data.OnlineAccountData()
+ ret[addr] = basics_testing.OnlineAccountData(data)
}
}
return ret, nil
diff --git a/ledger/eval_simple_test.go b/ledger/eval_simple_test.go
index 5bd11bd10a..b7817e4e36 100644
--- a/ledger/eval_simple_test.go
+++ b/ledger/eval_simple_test.go
@@ -32,6 +32,7 @@ import (
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/committee"
"github.com/algorand/go-algorand/data/transactions"
@@ -849,8 +850,8 @@ func TestDoubleLedgerGetKnockoffCandidates(t *testing.T) {
for addr, ad := range genBalances.Balances {
if ad.Status == basics.Online {
onlineCnt++
- genesisOnlineAccts[addr] = ad.OnlineAccountData()
- afterPayTxnOnlineAccts[addr] = ad.OnlineAccountData()
+ genesisOnlineAccts[addr] = basics_testing.OnlineAccountData(ad)
+ afterPayTxnOnlineAccts[addr] = basics_testing.OnlineAccountData(ad)
}
}
diff --git a/ledger/ledger.go b/ledger/ledger.go
index 8e0114f222..a13ee6ac29 100644
--- a/ledger/ledger.go
+++ b/ledger/ledger.go
@@ -625,11 +625,11 @@ func (l *Ledger) LookupKv(rnd basics.Round, key string) ([]byte, error) {
// LookupKeysByPrefix searches keys with specific prefix, up to `maxKeyNum`
// if `maxKeyNum` == 0, then it loads all keys with such prefix
-func (l *Ledger) LookupKeysByPrefix(round basics.Round, keyPrefix string, maxKeyNum uint64) ([]string, error) {
+func (l *Ledger) LookupKeysByPrefix(prefix, next string, boxLimit, byteLimit int, values bool) (basics.Round, map[string]string, string, error) {
l.trackerMu.RLock()
defer l.trackerMu.RUnlock()
- return l.accts.LookupKeysByPrefix(round, keyPrefix, maxKeyNum)
+ return l.accts.LookupKeysByPrefix(prefix, next, boxLimit, byteLimit, values)
}
// LookupAgreement returns account data used by agreement.
diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go
index 8528ea5052..633fc4397d 100644
--- a/ledger/ledger_test.go
+++ b/ledger/ledger_test.go
@@ -37,6 +37,7 @@ import (
"github.com/algorand/go-algorand/crypto/stateproof"
"github.com/algorand/go-algorand/data/account"
"github.com/algorand/go-algorand/data/basics"
+ basics_testing "github.com/algorand/go-algorand/data/basics/testing"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
@@ -1973,7 +1974,7 @@ func TestLookupAgreement(t *testing.T) {
ad, _, _, err := ledger.LookupLatest(addrOnline)
require.NoError(t, err)
require.NotEmpty(t, ad)
- require.Equal(t, oad, ad.OnlineAccountData())
+ require.Equal(t, oad, basics_testing.OnlineAccountData(ad))
require.NoError(t, err)
oad, err = ledger.LookupAgreement(0, addrOffline)
@@ -1982,7 +1983,7 @@ func TestLookupAgreement(t *testing.T) {
ad, _, _, err = ledger.LookupLatest(addrOffline)
require.NoError(t, err)
require.NotEmpty(t, ad)
- require.Equal(t, oad, ad.OnlineAccountData())
+ require.Equal(t, oad, basics_testing.OnlineAccountData(ad))
}
func TestGetKnockOfflineCandidates(t *testing.T) {
@@ -2007,7 +2008,7 @@ func TestGetKnockOfflineCandidates(t *testing.T) {
for addr, ad := range genesisInitState.Accounts {
if ad.Status == basics.Online {
onlineCnt++
- onlineAddrs[addr] = ad.OnlineAccountData()
+ onlineAddrs[addr] = basics_testing.OnlineAccountData(ad)
}
}
require.Len(t, accts, onlineCnt)
diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go
index 4297afc329..88bd8b39c4 100644
--- a/ledger/ledgercore/statedelta.go
+++ b/ledger/ledgercore/statedelta.go
@@ -81,7 +81,7 @@ type KvValueDelta struct {
// Data stores the most recent value (nil == deleted)
Data []byte
- // OldData stores the previous vlaue (nil == didn't exist)
+ // OldData stores the previous value (nil == didn't exist)
OldData []byte
}
diff --git a/ledger/store/trackerdb/data.go b/ledger/store/trackerdb/data.go
index e25d5c8a4e..b548f7d6bb 100644
--- a/ledger/store/trackerdb/data.go
+++ b/ledger/store/trackerdb/data.go
@@ -132,6 +132,8 @@ type ResourcesData struct {
// consensus parameter is being set. Once the above consensus takes place, this field would be populated with the
// correct round number.
UpdateRound uint64 `codec:"z"`
+
+ Version uint64 `codec:"A"`
}
// BaseVotingData is the base struct used to store voting data
@@ -552,7 +554,8 @@ func (rd *ResourcesData) IsEmptyAppFields() bool {
rd.LocalStateSchemaNumByteSlice == 0 &&
rd.GlobalStateSchemaNumUint == 0 &&
rd.GlobalStateSchemaNumByteSlice == 0 &&
- rd.ExtraProgramPages == 0
+ rd.ExtraProgramPages == 0 &&
+ rd.Version == 0
}
// IsApp returns true if the flag is ResourceFlagsEmptyApp and the fields are not empty.
@@ -730,6 +733,7 @@ func (rd *ResourcesData) ClearAppParams() {
rd.GlobalStateSchemaNumUint = 0
rd.GlobalStateSchemaNumByteSlice = 0
rd.ExtraProgramPages = 0
+ rd.Version = 0
hadHolding := (rd.ResourceFlags & ResourceFlagsNotHolding) == ResourceFlagsHolding
rd.ResourceFlags -= rd.ResourceFlags & ResourceFlagsOwnership
rd.ResourceFlags &= ^ResourceFlagsEmptyApp
@@ -748,6 +752,7 @@ func (rd *ResourcesData) SetAppParams(ap basics.AppParams, haveHoldings bool) {
rd.GlobalStateSchemaNumUint = ap.GlobalStateSchema.NumUint
rd.GlobalStateSchemaNumByteSlice = ap.GlobalStateSchema.NumByteSlice
rd.ExtraProgramPages = ap.ExtraProgramPages
+ rd.Version = ap.Version
rd.ResourceFlags |= ResourceFlagsOwnership
if !haveHoldings {
rd.ResourceFlags |= ResourceFlagsNotHolding
@@ -775,6 +780,7 @@ func (rd *ResourcesData) GetAppParams() basics.AppParams {
},
},
ExtraProgramPages: rd.ExtraProgramPages,
+ Version: rd.Version,
}
}
diff --git a/ledger/store/trackerdb/dualdriver/accounts_reader.go b/ledger/store/trackerdb/dualdriver/accounts_reader.go
index 8220a70f33..2081275552 100644
--- a/ledger/store/trackerdb/dualdriver/accounts_reader.go
+++ b/ledger/store/trackerdb/dualdriver/accounts_reader.go
@@ -142,21 +142,20 @@ func (ar *accountsReader) LookupKeyValue(key string) (pv trackerdb.PersistedKVDa
}
// LookupKeysByPrefix implements trackerdb.AccountsReader
-func (ar *accountsReader) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) {
- roundP, errP := ar.primary.LookupKeysByPrefix(prefix, maxKeyNum, results, resultCount)
- roundS, errS := ar.secondary.LookupKeysByPrefix(prefix, maxKeyNum, results, resultCount)
+func (ar *accountsReader) LookupKeysByPrefix(prefix, next string, maxBoxes, maxBytes int, values bool) (basics.Round, map[string]string, string, error) {
+ roundP, resP, nextP, errP := ar.primary.LookupKeysByPrefix(prefix, next, maxBoxes, maxBytes, values)
+ roundS, resS, nextS, errS := ar.secondary.LookupKeysByPrefix(prefix, next, maxBoxes, maxBytes, values)
// coalesce errors
- err = coalesceErrors(errP, errS)
+ err := coalesceErrors(errP, errS)
if err != nil {
- return
+ return 0, nil, "", err
}
// check results match
- if roundP != roundS {
- err = ErrInconsistentResult
- return
+ if roundP != roundS || !cmp.Equal(resP, resS) || nextP != nextS {
+ return 0, nil, "", ErrInconsistentResult
}
// return primary results
- return roundP, nil
+ return roundP, resP, nextP, nil
}
// LookupResources implements trackerdb.AccountsReader
diff --git a/ledger/store/trackerdb/generickv/accounts_reader.go b/ledger/store/trackerdb/generickv/accounts_reader.go
index 15bbe4aa9d..7b43da93f8 100644
--- a/ledger/store/trackerdb/generickv/accounts_reader.go
+++ b/ledger/store/trackerdb/generickv/accounts_reader.go
@@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"io"
+ "strings"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
@@ -223,7 +224,7 @@ func keyPrefixIntervalPreprocessing(prefix []byte) ([]byte, []byte) {
return prefix, nil
}
-func (r *accountsReader) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) {
+func (r *accountsReader) LookupKeysByPrefix(prefix, next string, rowLimit, byteLimit int, values bool) (basics.Round, map[string]string, string, error) {
// SQL at time of writing:
//
// SELECT acctrounds.rnd, kvstore.key
@@ -231,41 +232,49 @@ func (r *accountsReader) LookupKeysByPrefix(prefix string, maxKeyNum uint64, res
// WHERE id='acctbase'
// read the current db round
- round, err = r.AccountsRound()
+ round, err := r.AccountsRound()
if err != nil {
- return
+ return 0, nil, "", err
}
start, end := keyPrefixIntervalPreprocessing([]byte(prefix))
+ if next != "" {
+ if !strings.HasPrefix(next, prefix) {
+ return 0, nil, "", fmt.Errorf("next %#v is not prefixed by %#v", next, prefix)
+ }
+ start = []byte(next)
+ next = "" // If we don't exceed limits, we want next=""
+ }
iter := r.kvr.NewIter(start, end, false)
defer iter.Close()
- var value []byte
-
+ boxes := make(map[string]string)
for iter.Next() {
- // end iteration if we reached max results
- if resultCount == maxKeyNum {
- return
- }
-
// read the key
key := string(iter.Key())
-
- // get value for current item in the iterator
- value, err = iter.Value()
- if err != nil {
- return
+ value := ""
+ if values {
+ valBytes, err := iter.Value()
+ if err != nil {
+ return 0, nil, "", err
+ }
+ value = string(valBytes)
}
-
- // mark if the key has data on the result map
- results[key] = len(value) > 0
-
- // inc results in range
- resultCount++
+ rowLimit--
+ byteLimit -= len(key)
+ if values {
+ byteLimit -= len(value)
+ }
+ // If including this box would exceed limits, set `next` and return
+ if rowLimit < 0 || byteLimit < 0 {
+ next = key
+ break
+ }
+ boxes[key] = value
}
- return
+ return round, boxes, next, nil
}
func (r *accountsReader) LookupCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (addr basics.Address, ok bool, dbRound basics.Round, err error) {
diff --git a/ledger/store/trackerdb/interface.go b/ledger/store/trackerdb/interface.go
index 90f0ab5f63..cfa85d719f 100644
--- a/ledger/store/trackerdb/interface.go
+++ b/ledger/store/trackerdb/interface.go
@@ -108,7 +108,7 @@ type AccountsReader interface {
LookupLimitedResources(addr basics.Address, minIdx basics.CreatableIndex, maxCreatables uint64, ctype basics.CreatableType) (data []PersistedResourcesDataWithCreator, rnd basics.Round, err error)
LookupKeyValue(key string) (pv PersistedKVData, err error)
- LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error)
+ LookupKeysByPrefix(prefix, next string, maxKeyNum, maxBytes int, values bool) (basics.Round, map[string]string, string, error)
LookupCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (addr basics.Address, ok bool, dbRound basics.Round, err error)
diff --git a/ledger/store/trackerdb/msgp_gen.go b/ledger/store/trackerdb/msgp_gen.go
index 3c3206e71b..c120d20e25 100644
--- a/ledger/store/trackerdb/msgp_gen.go
+++ b/ledger/store/trackerdb/msgp_gen.go
@@ -1744,241 +1744,250 @@ func ResourceFlagsMaxSize() (s int) {
func (z *ResourcesData) MarshalMsg(b []byte) (o []byte) {
o = msgp.Require(b, z.Msgsize())
// omitempty: check for empty values
- zb0002Len := uint32(26)
- var zb0002Mask uint32 /* 27 bits */
+ zb0002Len := uint32(27)
+ var zb0002Mask uint32 /* 28 bits */
+ if (*z).Version == 0 {
+ zb0002Len--
+ zb0002Mask |= 0x1
+ }
if (*z).Total == 0 {
zb0002Len--
- zb0002Mask |= 0x2
+ zb0002Mask |= 0x4
}
if (*z).Decimals == 0 {
zb0002Len--
- zb0002Mask |= 0x4
+ zb0002Mask |= 0x8
}
if (*z).DefaultFrozen == false {
zb0002Len--
- zb0002Mask |= 0x8
+ zb0002Mask |= 0x10
}
if (*z).UnitName == "" {
zb0002Len--
- zb0002Mask |= 0x10
+ zb0002Mask |= 0x20
}
if (*z).AssetName == "" {
zb0002Len--
- zb0002Mask |= 0x20
+ zb0002Mask |= 0x40
}
if (*z).URL == "" {
zb0002Len--
- zb0002Mask |= 0x40
+ zb0002Mask |= 0x80
}
if (*z).MetadataHash == ([32]byte{}) {
zb0002Len--
- zb0002Mask |= 0x80
+ zb0002Mask |= 0x100
}
if (*z).Manager.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x100
+ zb0002Mask |= 0x200
}
if (*z).Reserve.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x200
+ zb0002Mask |= 0x400
}
if (*z).Freeze.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x400
+ zb0002Mask |= 0x800
}
if (*z).Clawback.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x800
+ zb0002Mask |= 0x1000
}
if (*z).Amount == 0 {
zb0002Len--
- zb0002Mask |= 0x1000
+ zb0002Mask |= 0x2000
}
if (*z).Frozen == false {
zb0002Len--
- zb0002Mask |= 0x2000
+ zb0002Mask |= 0x4000
}
if (*z).SchemaNumUint == 0 {
zb0002Len--
- zb0002Mask |= 0x4000
+ zb0002Mask |= 0x8000
}
if (*z).SchemaNumByteSlice == 0 {
zb0002Len--
- zb0002Mask |= 0x8000
+ zb0002Mask |= 0x10000
}
if (*z).KeyValue.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x10000
+ zb0002Mask |= 0x20000
}
if len((*z).ApprovalProgram) == 0 {
zb0002Len--
- zb0002Mask |= 0x20000
+ zb0002Mask |= 0x40000
}
if len((*z).ClearStateProgram) == 0 {
zb0002Len--
- zb0002Mask |= 0x40000
+ zb0002Mask |= 0x80000
}
if (*z).GlobalState.MsgIsZero() {
zb0002Len--
- zb0002Mask |= 0x80000
+ zb0002Mask |= 0x100000
}
if (*z).LocalStateSchemaNumUint == 0 {
zb0002Len--
- zb0002Mask |= 0x100000
+ zb0002Mask |= 0x200000
}
if (*z).LocalStateSchemaNumByteSlice == 0 {
zb0002Len--
- zb0002Mask |= 0x200000
+ zb0002Mask |= 0x400000
}
if (*z).GlobalStateSchemaNumUint == 0 {
zb0002Len--
- zb0002Mask |= 0x400000
+ zb0002Mask |= 0x800000
}
if (*z).GlobalStateSchemaNumByteSlice == 0 {
zb0002Len--
- zb0002Mask |= 0x800000
+ zb0002Mask |= 0x1000000
}
if (*z).ExtraProgramPages == 0 {
zb0002Len--
- zb0002Mask |= 0x1000000
+ zb0002Mask |= 0x2000000
}
if (*z).ResourceFlags == 0 {
zb0002Len--
- zb0002Mask |= 0x2000000
+ zb0002Mask |= 0x4000000
}
if (*z).UpdateRound == 0 {
zb0002Len--
- zb0002Mask |= 0x4000000
+ zb0002Mask |= 0x8000000
}
// variable map header, size zb0002Len
o = msgp.AppendMapHeader(o, zb0002Len)
if zb0002Len != 0 {
- if (zb0002Mask & 0x2) == 0 { // if not empty
+ if (zb0002Mask & 0x1) == 0 { // if not empty
+ // string "A"
+ o = append(o, 0xa1, 0x41)
+ o = msgp.AppendUint64(o, (*z).Version)
+ }
+ if (zb0002Mask & 0x4) == 0 { // if not empty
// string "a"
o = append(o, 0xa1, 0x61)
o = msgp.AppendUint64(o, (*z).Total)
}
- if (zb0002Mask & 0x4) == 0 { // if not empty
+ if (zb0002Mask & 0x8) == 0 { // if not empty
// string "b"
o = append(o, 0xa1, 0x62)
o = msgp.AppendUint32(o, (*z).Decimals)
}
- if (zb0002Mask & 0x8) == 0 { // if not empty
+ if (zb0002Mask & 0x10) == 0 { // if not empty
// string "c"
o = append(o, 0xa1, 0x63)
o = msgp.AppendBool(o, (*z).DefaultFrozen)
}
- if (zb0002Mask & 0x10) == 0 { // if not empty
+ if (zb0002Mask & 0x20) == 0 { // if not empty
// string "d"
o = append(o, 0xa1, 0x64)
o = msgp.AppendString(o, (*z).UnitName)
}
- if (zb0002Mask & 0x20) == 0 { // if not empty
+ if (zb0002Mask & 0x40) == 0 { // if not empty
// string "e"
o = append(o, 0xa1, 0x65)
o = msgp.AppendString(o, (*z).AssetName)
}
- if (zb0002Mask & 0x40) == 0 { // if not empty
+ if (zb0002Mask & 0x80) == 0 { // if not empty
// string "f"
o = append(o, 0xa1, 0x66)
o = msgp.AppendString(o, (*z).URL)
}
- if (zb0002Mask & 0x80) == 0 { // if not empty
+ if (zb0002Mask & 0x100) == 0 { // if not empty
// string "g"
o = append(o, 0xa1, 0x67)
o = msgp.AppendBytes(o, ((*z).MetadataHash)[:])
}
- if (zb0002Mask & 0x100) == 0 { // if not empty
+ if (zb0002Mask & 0x200) == 0 { // if not empty
// string "h"
o = append(o, 0xa1, 0x68)
o = (*z).Manager.MarshalMsg(o)
}
- if (zb0002Mask & 0x200) == 0 { // if not empty
+ if (zb0002Mask & 0x400) == 0 { // if not empty
// string "i"
o = append(o, 0xa1, 0x69)
o = (*z).Reserve.MarshalMsg(o)
}
- if (zb0002Mask & 0x400) == 0 { // if not empty
+ if (zb0002Mask & 0x800) == 0 { // if not empty
// string "j"
o = append(o, 0xa1, 0x6a)
o = (*z).Freeze.MarshalMsg(o)
}
- if (zb0002Mask & 0x800) == 0 { // if not empty
+ if (zb0002Mask & 0x1000) == 0 { // if not empty
// string "k"
o = append(o, 0xa1, 0x6b)
o = (*z).Clawback.MarshalMsg(o)
}
- if (zb0002Mask & 0x1000) == 0 { // if not empty
+ if (zb0002Mask & 0x2000) == 0 { // if not empty
// string "l"
o = append(o, 0xa1, 0x6c)
o = msgp.AppendUint64(o, (*z).Amount)
}
- if (zb0002Mask & 0x2000) == 0 { // if not empty
+ if (zb0002Mask & 0x4000) == 0 { // if not empty
// string "m"
o = append(o, 0xa1, 0x6d)
o = msgp.AppendBool(o, (*z).Frozen)
}
- if (zb0002Mask & 0x4000) == 0 { // if not empty
+ if (zb0002Mask & 0x8000) == 0 { // if not empty
// string "n"
o = append(o, 0xa1, 0x6e)
o = msgp.AppendUint64(o, (*z).SchemaNumUint)
}
- if (zb0002Mask & 0x8000) == 0 { // if not empty
+ if (zb0002Mask & 0x10000) == 0 { // if not empty
// string "o"
o = append(o, 0xa1, 0x6f)
o = msgp.AppendUint64(o, (*z).SchemaNumByteSlice)
}
- if (zb0002Mask & 0x10000) == 0 { // if not empty
+ if (zb0002Mask & 0x20000) == 0 { // if not empty
// string "p"
o = append(o, 0xa1, 0x70)
o = (*z).KeyValue.MarshalMsg(o)
}
- if (zb0002Mask & 0x20000) == 0 { // if not empty
+ if (zb0002Mask & 0x40000) == 0 { // if not empty
// string "q"
o = append(o, 0xa1, 0x71)
o = msgp.AppendBytes(o, (*z).ApprovalProgram)
}
- if (zb0002Mask & 0x40000) == 0 { // if not empty
+ if (zb0002Mask & 0x80000) == 0 { // if not empty
// string "r"
o = append(o, 0xa1, 0x72)
o = msgp.AppendBytes(o, (*z).ClearStateProgram)
}
- if (zb0002Mask & 0x80000) == 0 { // if not empty
+ if (zb0002Mask & 0x100000) == 0 { // if not empty
// string "s"
o = append(o, 0xa1, 0x73)
o = (*z).GlobalState.MarshalMsg(o)
}
- if (zb0002Mask & 0x100000) == 0 { // if not empty
+ if (zb0002Mask & 0x200000) == 0 { // if not empty
// string "t"
o = append(o, 0xa1, 0x74)
o = msgp.AppendUint64(o, (*z).LocalStateSchemaNumUint)
}
- if (zb0002Mask & 0x200000) == 0 { // if not empty
+ if (zb0002Mask & 0x400000) == 0 { // if not empty
// string "u"
o = append(o, 0xa1, 0x75)
o = msgp.AppendUint64(o, (*z).LocalStateSchemaNumByteSlice)
}
- if (zb0002Mask & 0x400000) == 0 { // if not empty
+ if (zb0002Mask & 0x800000) == 0 { // if not empty
// string "v"
o = append(o, 0xa1, 0x76)
o = msgp.AppendUint64(o, (*z).GlobalStateSchemaNumUint)
}
- if (zb0002Mask & 0x800000) == 0 { // if not empty
+ if (zb0002Mask & 0x1000000) == 0 { // if not empty
// string "w"
o = append(o, 0xa1, 0x77)
o = msgp.AppendUint64(o, (*z).GlobalStateSchemaNumByteSlice)
}
- if (zb0002Mask & 0x1000000) == 0 { // if not empty
+ if (zb0002Mask & 0x2000000) == 0 { // if not empty
// string "x"
o = append(o, 0xa1, 0x78)
o = msgp.AppendUint32(o, (*z).ExtraProgramPages)
}
- if (zb0002Mask & 0x2000000) == 0 { // if not empty
+ if (zb0002Mask & 0x4000000) == 0 { // if not empty
// string "y"
o = append(o, 0xa1, 0x79)
o = msgp.AppendUint8(o, uint8((*z).ResourceFlags))
}
- if (zb0002Mask & 0x4000000) == 0 { // if not empty
+ if (zb0002Mask & 0x8000000) == 0 { // if not empty
// string "z"
o = append(o, 0xa1, 0x7a)
o = msgp.AppendUint64(o, (*z).UpdateRound)
@@ -2242,6 +2251,14 @@ func (z *ResourcesData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState
return
}
}
+ if zb0002 > 0 {
+ zb0002--
+ (*z).Version, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "struct-from-array", "Version")
+ return
+ }
+ }
if zb0002 > 0 {
err = msgp.ErrTooManyArrayFields(zb0002)
if err != nil {
@@ -2445,6 +2462,12 @@ func (z *ResourcesData) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState
err = msgp.WrapError(err, "UpdateRound")
return
}
+ case "A":
+ (*z).Version, bts, err = msgp.ReadUint64Bytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, "Version")
+ return
+ }
default:
err = msgp.ErrNoField(string(field))
if err != nil {
@@ -2468,13 +2491,13 @@ func (_ *ResourcesData) CanUnmarshalMsg(z interface{}) bool {
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
func (z *ResourcesData) Msgsize() (s int) {
- s = 3 + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.BoolSize + 2 + msgp.StringPrefixSize + len((*z).UnitName) + 2 + msgp.StringPrefixSize + len((*z).AssetName) + 2 + msgp.StringPrefixSize + len((*z).URL) + 2 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 2 + (*z).Manager.Msgsize() + 2 + (*z).Reserve.Msgsize() + 2 + (*z).Freeze.Msgsize() + 2 + (*z).Clawback.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + (*z).KeyValue.Msgsize() + 2 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 2 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 2 + (*z).GlobalState.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint8Size + 2 + msgp.Uint64Size
+ s = 3 + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.BoolSize + 2 + msgp.StringPrefixSize + len((*z).UnitName) + 2 + msgp.StringPrefixSize + len((*z).AssetName) + 2 + msgp.StringPrefixSize + len((*z).URL) + 2 + msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) + 2 + (*z).Manager.Msgsize() + 2 + (*z).Reserve.Msgsize() + 2 + (*z).Freeze.Msgsize() + 2 + (*z).Clawback.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + (*z).KeyValue.Msgsize() + 2 + msgp.BytesPrefixSize + len((*z).ApprovalProgram) + 2 + msgp.BytesPrefixSize + len((*z).ClearStateProgram) + 2 + (*z).GlobalState.Msgsize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint8Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size
return
}
// MsgIsZero returns whether this is a zero value
func (z *ResourcesData) MsgIsZero() bool {
- return ((*z).Total == 0) && ((*z).Decimals == 0) && ((*z).DefaultFrozen == false) && ((*z).UnitName == "") && ((*z).AssetName == "") && ((*z).URL == "") && ((*z).MetadataHash == ([32]byte{})) && ((*z).Manager.MsgIsZero()) && ((*z).Reserve.MsgIsZero()) && ((*z).Freeze.MsgIsZero()) && ((*z).Clawback.MsgIsZero()) && ((*z).Amount == 0) && ((*z).Frozen == false) && ((*z).SchemaNumUint == 0) && ((*z).SchemaNumByteSlice == 0) && ((*z).KeyValue.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).GlobalState.MsgIsZero()) && ((*z).LocalStateSchemaNumUint == 0) && ((*z).LocalStateSchemaNumByteSlice == 0) && ((*z).GlobalStateSchemaNumUint == 0) && ((*z).GlobalStateSchemaNumByteSlice == 0) && ((*z).ExtraProgramPages == 0) && ((*z).ResourceFlags == 0) && ((*z).UpdateRound == 0)
+ return ((*z).Total == 0) && ((*z).Decimals == 0) && ((*z).DefaultFrozen == false) && ((*z).UnitName == "") && ((*z).AssetName == "") && ((*z).URL == "") && ((*z).MetadataHash == ([32]byte{})) && ((*z).Manager.MsgIsZero()) && ((*z).Reserve.MsgIsZero()) && ((*z).Freeze.MsgIsZero()) && ((*z).Clawback.MsgIsZero()) && ((*z).Amount == 0) && ((*z).Frozen == false) && ((*z).SchemaNumUint == 0) && ((*z).SchemaNumByteSlice == 0) && ((*z).KeyValue.MsgIsZero()) && (len((*z).ApprovalProgram) == 0) && (len((*z).ClearStateProgram) == 0) && ((*z).GlobalState.MsgIsZero()) && ((*z).LocalStateSchemaNumUint == 0) && ((*z).LocalStateSchemaNumByteSlice == 0) && ((*z).GlobalStateSchemaNumUint == 0) && ((*z).GlobalStateSchemaNumByteSlice == 0) && ((*z).ExtraProgramPages == 0) && ((*z).ResourceFlags == 0) && ((*z).UpdateRound == 0) && ((*z).Version == 0)
}
// MaxSize returns a maximum valid message size for this message type
@@ -2488,7 +2511,7 @@ func ResourcesDataMaxSize() (s int) {
s += 2
// Calculating size of array: z.MetadataHash
s += msgp.ArrayHeaderSize + ((32) * (msgp.ByteSize))
- s += 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + basics.TealKeyValueMaxSize() + 2 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 2 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 2 + basics.TealKeyValueMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint8Size + 2 + msgp.Uint64Size
+ s += 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + basics.AddressMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.BoolSize + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + basics.TealKeyValueMaxSize() + 2 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 2 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 2 + basics.TealKeyValueMaxSize() + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint32Size + 2 + msgp.Uint8Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size
return
}
diff --git a/ledger/store/trackerdb/sqlitedriver/sql.go b/ledger/store/trackerdb/sqlitedriver/sql.go
index f69363536b..b05f5da2c7 100644
--- a/ledger/store/trackerdb/sqlitedriver/sql.go
+++ b/ledger/store/trackerdb/sqlitedriver/sql.go
@@ -19,6 +19,8 @@ package sqlitedriver
import (
"database/sql"
"fmt"
+ "strings"
+
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/ledger/store/trackerdb"
@@ -34,7 +36,7 @@ type accountsDbQueries struct {
lookupAllResourcesStmt *sql.Stmt
lookupLimitedResourcesStmt *sql.Stmt
lookupKvPairStmt *sql.Stmt
- lookupKeysByRangeStmt *sql.Stmt
+ lookupKvByRangeStmt *sql.Stmt
lookupCreatorStmt *sql.Stmt
}
@@ -115,7 +117,7 @@ func AccountsInitDbQueries(q db.Queryable) (*accountsDbQueries, error) {
return nil, err
}
- qs.lookupKeysByRangeStmt, err = q.Prepare("SELECT acctrounds.rnd, kvstore.key FROM acctrounds LEFT JOIN kvstore ON kvstore.key >= ? AND kvstore.key < ? WHERE id='acctbase'")
+ qs.lookupKvByRangeStmt, err = q.Prepare("SELECT acctrounds.rnd, kvstore.key, kvstore.value FROM acctrounds LEFT JOIN kvstore ON kvstore.key >= ? AND kvstore.key < ? WHERE id='acctbase'")
if err != nil {
return nil, err
}
@@ -270,43 +272,63 @@ func (qs *accountsDbQueries) LookupKeyValue(key string) (pv trackerdb.PersistedK
return
}
-// LookupKeysByPrefix returns a set of application boxed values matching the prefix.
-func (qs *accountsDbQueries) LookupKeysByPrefix(prefix string, maxKeyNum uint64, results map[string]bool, resultCount uint64) (round basics.Round, err error) {
+// LookupKeysByPrefix returns a set of application boxes matching the `prefix`, beginning with `next`.
+func (qs *accountsDbQueries) LookupKeysByPrefix(prefix, next string, rowLimit, byteLimit int, values bool) (basics.Round, map[string]string, string, error) {
start, end := keyPrefixIntervalPreprocessing([]byte(prefix))
if end == nil {
// Not an expected use case, it's asking for all keys, or all keys
- // prefixed by some number of 0xFF bytes.
- return 0, fmt.Errorf("lookup by strange prefix %#v", prefix)
+ // prefixed by some number of 0xFF bytes. That's not possible because
+ // there's always (at least) a "bx:" prefix.
+ return 0, nil, "", fmt.Errorf("lookup by strange prefix %#v", prefix)
}
- err = db.Retry(func() error {
+ if next != "" {
+ if !strings.HasPrefix(next, prefix) {
+ return 0, nil, "", fmt.Errorf("next %#v is not prefixed by %#v", next, prefix)
+ }
+ start = []byte(next)
+ next = "" // If we don't exceed limits, we want next=""
+ }
+ round := basics.Round(0)
+ boxes := make(map[string]string)
+ err0 := db.Retry(func() error {
var rows *sql.Rows
- rows, err = qs.lookupKeysByRangeStmt.Query(start, end)
+ rows, err := qs.lookupKvByRangeStmt.Query(start, end)
if err != nil {
return err
}
defer rows.Close()
- var v sql.NullString
-
for rows.Next() {
- if resultCount == maxKeyNum {
- return nil
- }
- err = rows.Scan(&round, &v)
+ var k sql.NullString
+ var v sql.NullString
+ err = rows.Scan(&round, &k, &v)
if err != nil {
return err
}
- if v.Valid {
- if _, ok := results[v.String]; ok {
- continue
+ if k.Valid {
+ rowLimit--
+ byteLimit -= len(k.String)
+ if values {
+ byteLimit -= len(v.String)
+ }
+ // If including this box would exceed limits, set `next` and return
+ if rowLimit < 0 || byteLimit < 0 {
+ next = k.String
+ return nil
+ }
+ if values {
+ boxes[k.String] = v.String
+ } else {
+ boxes[k.String] = ""
}
- results[v.String] = true
- resultCount++
}
}
return nil
})
- return
+ if err0 != nil {
+ return 0, nil, "", err0
+ }
+ return round, boxes, next, nil
}
// keyPrefixIntervalPreprocessing is implemented to generate an interval for DB queries that look up keys by prefix.
@@ -649,7 +671,7 @@ func (qs *accountsDbQueries) Close() {
&qs.lookupAllResourcesStmt,
&qs.lookupLimitedResourcesStmt,
&qs.lookupKvPairStmt,
- &qs.lookupKeysByRangeStmt,
+ &qs.lookupKvByRangeStmt,
&qs.lookupCreatorStmt,
}
for _, preparedQuery := range preparedQueries {
diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go
index 948a58b7a9..da711198af 100644
--- a/ledger/testing/randomAccounts.go
+++ b/ledger/testing/randomAccounts.go
@@ -24,8 +24,6 @@ import (
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/protocol"
- //"github.com/algorand/go-algorand/data/bookkeeping"
-
"github.com/algorand/go-algorand/ledger/ledgercore"
)
@@ -181,6 +179,7 @@ func RandomAppParams() basics.AppParams {
GlobalState: make(basics.TealKeyValue),
StateSchemas: schemas,
ExtraProgramPages: uint32(crypto.RandUint64() % 4),
+ Version: crypto.RandUint64() % 10,
}
if len(ap.ApprovalProgram) > 0 {
crypto.RandBytes(ap.ApprovalProgram[:])
@@ -227,7 +226,6 @@ func RandomAppParams() basics.AppParams {
if len(ap.GlobalState) == 0 {
ap.GlobalState = nil
}
- ap.ExtraProgramPages = uint32(crypto.RandUint64() % 4)
return ap
}
diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go
index fa0630a4d7..bb167d999f 100644
--- a/libgoal/libgoal.go
+++ b/libgoal/libgoal.go
@@ -774,10 +774,10 @@ func (c *Client) ApplicationInformation(index uint64) (resp model.Application, e
}
// ApplicationBoxes takes an app's index and returns the names of boxes under it
-func (c *Client) ApplicationBoxes(appID uint64, maxBoxNum uint64) (resp model.BoxesResponse, err error) {
+func (c *Client) ApplicationBoxes(appID uint64, prefix string, next *string, limit uint64, values bool) (resp model.BoxesResponse, err error) {
algod, err := c.ensureAlgodClient()
if err == nil {
- resp, err = algod.ApplicationBoxes(appID, maxBoxNum)
+ resp, err = algod.ApplicationBoxes(appID, prefix, next, limit, values)
}
return
}
diff --git a/libgoal/transactions.go b/libgoal/transactions.go
index 21b95e28f5..3c80b69dea 100644
--- a/libgoal/transactions.go
+++ b/libgoal/transactions.go
@@ -526,52 +526,53 @@ func (c *Client) FillUnsignedTxTemplate(sender string, firstValid, lastValid, fe
// MakeUnsignedAppCreateTx makes a transaction for creating an application
func (c *Client) MakeUnsignedAppCreateTx(onComplete transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, extrapages uint32) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(0, appArgs, accounts, foreignApps, foreignAssets, boxes, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages)
+ return c.MakeUnsignedApplicationCallTx(0, appArgs, accounts, foreignApps, foreignAssets, boxes, onComplete, approvalProg, clearProg, globalSchema, localSchema, extrapages, 0)
}
// MakeUnsignedAppUpdateTx makes a transaction for updating an application's programs
-func (c *Client) MakeUnsignedAppUpdateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, approvalProg []byte, clearProg []byte) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppUpdateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, approvalProg []byte, clearProg []byte, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.UpdateApplicationOC, approvalProg, clearProg, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedAppDeleteTx makes a transaction for deleting an application
-func (c *Client) MakeUnsignedAppDeleteTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppDeleteTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.DeleteApplicationOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedAppOptInTx makes a transaction for opting in to (allocating
// some account-specific state for) an application
-func (c *Client) MakeUnsignedAppOptInTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppOptInTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.OptInOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedAppCloseOutTx makes a transaction for closing out of
// (deallocating all account-specific state for) an application
-func (c *Client) MakeUnsignedAppCloseOutTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppCloseOutTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.CloseOutOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedAppClearStateTx makes a transaction for clearing out all
// account-specific state for an application. It may not be rejected by the
// application's logic.
-func (c *Client) MakeUnsignedAppClearStateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppClearStateTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.ClearStateOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedAppNoOpTx makes a transaction for interacting with an existing
// application, potentially updating any account-specific local state and
// global state associated with it.
-func (c *Client) MakeUnsignedAppNoOpTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef) (tx transactions.Transaction, err error) {
- return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0)
+func (c *Client) MakeUnsignedAppNoOpTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, rejectVersion uint64) (tx transactions.Transaction, err error) {
+ return c.MakeUnsignedApplicationCallTx(appIdx, appArgs, accounts, foreignApps, foreignAssets, boxes, transactions.NoOpOC, nil, nil, emptySchema, emptySchema, 0, rejectVersion)
}
// MakeUnsignedApplicationCallTx is a helper for the above ApplicationCall
// transaction constructors. A fully custom ApplicationCall transaction may
// be constructed using this method.
-func (c *Client) MakeUnsignedApplicationCallTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32) (tx transactions.Transaction, err error) {
+func (c *Client) MakeUnsignedApplicationCallTx(appIdx uint64, appArgs [][]byte, accounts []string, foreignApps []uint64, foreignAssets []uint64, boxes []transactions.BoxRef, onCompletion transactions.OnCompletion, approvalProg []byte, clearProg []byte, globalSchema basics.StateSchema, localSchema basics.StateSchema, extrapages uint32, rejectVersion uint64) (tx transactions.Transaction, err error) {
tx.Type = protocol.ApplicationCallTx
tx.ApplicationID = basics.AppIndex(appIdx)
tx.OnCompletion = onCompletion
+ tx.RejectVersion = rejectVersion
tx.ApplicationArgs = appArgs
tx.Accounts, err = parseTxnAccounts(accounts)
diff --git a/libgoal/wallets.go b/libgoal/wallets.go
index 57049777f7..394f22a9ab 100644
--- a/libgoal/wallets.go
+++ b/libgoal/wallets.go
@@ -45,6 +45,24 @@ func (c *Client) CreateWallet(name []byte, password []byte, mdk crypto.MasterDer
return []byte(resp.Wallet.ID), nil
}
+// RenameWallet renames a kmd wallet
+func (c *Client) RenameWallet(wid []byte, name []byte, password []byte) error {
+ // Pull the list of all wallets from kmd
+ kmd, err := c.ensureKmdClient()
+ if err != nil {
+ return err
+ }
+
+ // Rename the wallet
+ _, err = kmd.RenameWallet(wid, name, password)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
// GetWalletHandleToken inits the wallet with the given id, returning a wallet handle token
func (c *Client) GetWalletHandleToken(wid, pw []byte) ([]byte, error) {
kmd, err := c.ensureKmdClient()
diff --git a/logging/log.go b/logging/log.go
index 770bf08bb9..2878e5d7f7 100644
--- a/logging/log.go
+++ b/logging/log.go
@@ -35,6 +35,7 @@ logger.Info("New wallet was created")
package logging
import (
+ "context"
"io"
"runtime"
"runtime/debug"
@@ -154,7 +155,7 @@ type Logger interface {
// Adds a hook to the logger
AddHook(hook logrus.Hook)
- EnableTelemetry(cfg TelemetryConfig) error
+ EnableTelemetryContext(ctx context.Context, cfg TelemetryConfig) error
UpdateTelemetryURI(uri string) error
GetTelemetryEnabled() bool
GetTelemetryUploadingEnabled() bool
@@ -389,11 +390,11 @@ func RegisterExitHandler(handler func()) {
logrus.RegisterExitHandler(handler)
}
-func (l logger) EnableTelemetry(cfg TelemetryConfig) (err error) {
+func (l logger) EnableTelemetryContext(ctx context.Context, cfg TelemetryConfig) (err error) {
if l.loggerState.telemetry != nil || (!cfg.Enable && !cfg.SendToLog) {
return nil
}
- return EnableTelemetry(cfg, &l)
+ return EnableTelemetryContext(ctx, cfg, &l)
}
func (l logger) UpdateTelemetryURI(uri string) (err error) {
diff --git a/logging/telemetry.go b/logging/telemetry.go
index 35c4f773cf..fa4af559ba 100644
--- a/logging/telemetry.go
+++ b/logging/telemetry.go
@@ -17,6 +17,7 @@
package logging
import (
+ "context"
"fmt"
"io"
"os"
@@ -35,9 +36,9 @@ const telemetryPrefix = "/"
const telemetrySeparator = "/"
const logBufferDepth = 2
-// EnableTelemetry configures and enables telemetry based on the config provided
-func EnableTelemetry(cfg TelemetryConfig, l *logger) (err error) {
- telemetry, err := makeTelemetryState(cfg, createElasticHook)
+// EnableTelemetryContext configures and enables telemetry based on the config provided
+func EnableTelemetryContext(ctx context.Context, cfg TelemetryConfig, l *logger) (err error) {
+ telemetry, err := makeTelemetryStateContext(ctx, cfg, createElasticHookContext)
if err != nil {
return
}
@@ -70,14 +71,14 @@ func makeLevels(min logrus.Level) []logrus.Level {
return levels
}
-func makeTelemetryState(cfg TelemetryConfig, hookFactory hookFactory) (*telemetryState, error) {
+func makeTelemetryStateContext(ctx context.Context, cfg TelemetryConfig, hookFactory hookFactory) (*telemetryState, error) {
telemetry := &telemetryState{}
telemetry.history = createLogBuffer(logBufferDepth)
if cfg.Enable {
if cfg.SessionGUID == "" {
cfg.SessionGUID = uuid.New()
}
- hook, err := createTelemetryHook(cfg, telemetry.history, hookFactory)
+ hook, err := createTelemetryHookContext(ctx, cfg, telemetry.history, hookFactory)
if err != nil {
return nil, err
}
diff --git a/logging/telemetryCommon.go b/logging/telemetryCommon.go
index 1c11a1a404..cbcd1c4e1c 100644
--- a/logging/telemetryCommon.go
+++ b/logging/telemetryCommon.go
@@ -17,9 +17,11 @@
package logging
import (
+ "context"
+ "sync"
+
"github.com/algorand/go-deadlock"
"github.com/sirupsen/logrus"
- "sync"
)
type telemetryHook interface {
@@ -81,4 +83,4 @@ type asyncTelemetryHook struct {
// A dummy noop type to get rid of checks like telemetry.hook != nil
type dummyHook struct{}
-type hookFactory func(cfg TelemetryConfig) (logrus.Hook, error)
+type hookFactory func(ctx context.Context, cfg TelemetryConfig) (logrus.Hook, error)
diff --git a/logging/telemetryConfig.go b/logging/telemetryConfig.go
index 8a524ce363..f8c34d0e3a 100644
--- a/logging/telemetryConfig.go
+++ b/logging/telemetryConfig.go
@@ -20,7 +20,6 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
- "fmt"
"os"
"strings"
@@ -126,7 +125,7 @@ func (cfg TelemetryConfig) getInstanceName() string {
// NOTE: We used to report HASH:DataDir but DataDir is Personally Identifiable Information (PII)
// So we're removing it entirely to avoid GDPR issues.
- return fmt.Sprintf("%s", pathHashStr[:16])
+ return pathHashStr[:16]
}
// SanitizeTelemetryString applies sanitization rules and returns the sanitized string.
diff --git a/logging/telemetry_test.go b/logging/telemetry_test.go
index 53ac401337..a5a17c703b 100644
--- a/logging/telemetry_test.go
+++ b/logging/telemetry_test.go
@@ -17,13 +17,15 @@
package logging
import (
+ "context"
"encoding/json"
"fmt"
- "github.com/sirupsen/logrus"
- "github.com/stretchr/testify/require"
"os"
"testing"
+ "github.com/sirupsen/logrus"
+ "github.com/stretchr/testify/require"
+
"github.com/algorand/go-deadlock"
"github.com/algorand/go-algorand/config"
@@ -84,7 +86,7 @@ func makeTelemetryTestFixtureWithConfig(minLevel logrus.Level, cfg *TelemetryCon
f.l = Base().(logger)
f.l.SetLevel(Debug) // Ensure logging doesn't filter anything out
- f.telem, _ = makeTelemetryState(lcfg, func(cfg TelemetryConfig) (hook logrus.Hook, err error) {
+ f.telem, _ = makeTelemetryStateContext(context.Background(), lcfg, func(ctx context.Context, cfg TelemetryConfig) (hook logrus.Hook, err error) {
return &f.hook, nil
})
f.l.loggerState.telemetry = f.telem
@@ -138,7 +140,7 @@ func TestCreateHookError(t *testing.T) {
cfg := createTelemetryConfig()
cfg.Enable = true
- telem, err := makeTelemetryState(cfg, func(cfg TelemetryConfig) (hook logrus.Hook, err error) {
+ telem, err := makeTelemetryStateContext(context.Background(), cfg, func(ctx context.Context, cfg TelemetryConfig) (hook logrus.Hook, err error) {
return nil, fmt.Errorf("failed")
})
@@ -291,13 +293,9 @@ func runLogLevelsTest(t *testing.T, minLevel logrus.Level, expected int) {
// f.l.Fatal("fatal") - can't call this - it will os.Exit()
// Protect the call to log.Panic as we don't really want to crash
- func() {
- defer func() {
- if r := recover(); r != nil {
- }
- }()
+ require.Panics(t, func() {
f.l.Panic("panic")
- }()
+ })
// See if we got the expected number of entries
a.Equal(expected, len(f.hookEntries()))
@@ -319,13 +317,9 @@ func TestLogHistoryLevels(t *testing.T) {
f.l.Error("error")
// f.l.Fatal("fatal") - can't call this - it will os.Exit()
// Protect the call to log.Panic as we don't really want to crash
- func() {
- defer func() {
- if r := recover(); r != nil {
- }
- }()
+ require.Panics(t, func() {
f.l.Panic("panic")
- }()
+ })
data := f.hookData()
a.Nil(data[0]["log"]) // Debug
diff --git a/logging/telemetryhook.go b/logging/telemetryhook.go
index 73712ff229..86253879ec 100644
--- a/logging/telemetryhook.go
+++ b/logging/telemetryhook.go
@@ -17,6 +17,7 @@
package logging
import (
+ "context"
"fmt"
"github.com/olivere/elastic"
@@ -228,14 +229,14 @@ func (el elasticClientLogger) Printf(format string, v ...interface{}) {
}
}
-func createElasticHook(cfg TelemetryConfig) (hook logrus.Hook, err error) {
+func createElasticHookContext(ctx context.Context, cfg TelemetryConfig) (hook logrus.Hook, err error) {
// Returning an error here causes issues... need the hooks to be created even if the elastic hook fails so that
// things can recover later.
if cfg.URI == "" {
return nil, nil
}
- client, err := elastic.NewClient(elastic.SetURL(cfg.URI),
+ client, err := elastic.DialContext(ctx, elastic.SetURL(cfg.URI),
elastic.SetBasicAuth(cfg.UserName, cfg.Password),
elastic.SetSniff(false),
elastic.SetGzip(true),
@@ -257,12 +258,12 @@ func createElasticHook(cfg TelemetryConfig) (hook logrus.Hook, err error) {
}
// createTelemetryHook creates the Telemetry log hook, or returns nil if remote logging is not enabled
-func createTelemetryHook(cfg TelemetryConfig, history *logBuffer, hookFactory hookFactory) (hook logrus.Hook, err error) {
+func createTelemetryHookContext(ctx context.Context, cfg TelemetryConfig, history *logBuffer, hookFactory hookFactory) (hook logrus.Hook, err error) {
if !cfg.Enable {
return nil, fmt.Errorf("createTelemetryHook called when telemetry not enabled")
}
- hook, err = hookFactory(cfg)
+ hook, err = hookFactory(ctx, cfg)
if err != nil {
return nil, err
@@ -290,7 +291,8 @@ func (hook *asyncTelemetryHook) UpdateHookURI(uri string) (err error) {
copy := tfh.telemetryConfig
copy.URI = uri
var newHook logrus.Hook
- newHook, err = tfh.factory(copy)
+
+ newHook, err = tfh.factory(context.Background(), copy)
if err == nil && newHook != nil {
tfh.wrappedHook = newHook
diff --git a/logging/usage.go b/logging/usage.go
index 41511394a4..26115f73e3 100644
--- a/logging/usage.go
+++ b/logging/usage.go
@@ -43,7 +43,7 @@ func UsageLogThread(ctx context.Context, log Logger, period time.Duration, wg *s
ticker := time.NewTicker(period)
hasPrev := false
- for true {
+ for {
select {
case <-ticker.C:
case <-ctx.Done():
diff --git a/netdeploy/network.go b/netdeploy/network.go
index 6951969031..ec93b3800e 100644
--- a/netdeploy/network.go
+++ b/netdeploy/network.go
@@ -78,6 +78,13 @@ func OverrideConsensusVersion(ver protocol.ConsensusVersion) TemplateOverride {
}
}
+// OverrideKmdConfig changes the KMD config.
+func OverrideKmdConfig(kmdConfig TemplateKMDConfig) TemplateOverride {
+ return func(template *NetworkTemplate) {
+ template.kmdConfig = kmdConfig
+ }
+}
+
// CreateNetworkFromTemplate uses the specified template to deploy a new private network
// under the specified root directory.
func CreateNetworkFromTemplate(name, rootDir string, templateReader io.Reader, binDir string, importKeys bool, nodeExitCallback nodecontrol.AlgodExitErrorCallback, consensus config.ConsensusProtocols, overrides ...TemplateOverride) (Network, error) {
diff --git a/netdeploy/networkTemplate.go b/netdeploy/networkTemplate.go
index 865edf6ce5..92a7691e16 100644
--- a/netdeploy/networkTemplate.go
+++ b/netdeploy/networkTemplate.go
@@ -29,6 +29,7 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
+ kmdconfig "github.com/algorand/go-algorand/daemon/kmd/config"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/gen"
"github.com/algorand/go-algorand/libgoal"
@@ -42,6 +43,20 @@ type NetworkTemplate struct {
Genesis gen.GenesisData
Nodes []remote.NodeConfigGoal
Consensus config.ConsensusProtocols
+ kmdConfig TemplateKMDConfig // set by OverrideKmdConfig
+}
+
+// TemplateKMDConfig is a subset of the kmd configuration that can be overridden in the network template
+// by using OverrideKmdConfig TemplateOverride opts.
+// The reason why config.KMDConfig cannot be used directly is that it contains DataDir field which is
+// is not known until the template instantiation.
+type TemplateKMDConfig struct {
+ SessionLifetimeSecs uint64
+}
+
+func (c TemplateKMDConfig) apply(cfg kmdconfig.KMDConfig) kmdconfig.KMDConfig {
+ cfg.SessionLifetimeSecs = c.SessionLifetimeSecs
+ return cfg
}
var defaultNetworkTemplate = NetworkTemplate{
@@ -131,10 +146,27 @@ func (t NetworkTemplate) createNodeDirectories(targetFolder string, binDir strin
}
}
+ var kmdDir string
+ if (t.kmdConfig != TemplateKMDConfig{}) {
+ kmdDir = filepath.Join(nodeDir, libgoal.DefaultKMDDataDir)
+ err = os.MkdirAll(kmdDir, 0700) // kmd requires 700 permissions
+ if err != nil {
+ return
+ }
+ err = createKMDConfigFile(t.kmdConfig, kmdDir)
+ if err != nil {
+ return
+ }
+ }
+
if importKeys && hasWallet {
var client libgoal.Client
- client, err = libgoal.MakeClientWithBinDir(binDir, nodeDir, "", libgoal.KmdClient)
- if err != nil {
+ if client, err = libgoal.MakeClientFromConfig(libgoal.ClientConfig{
+ AlgodDataDir: nodeDir,
+ KMDDataDir: kmdDir,
+ CacheDir: "",
+ BinDir: binDir,
+ }, libgoal.KmdClient); err != nil {
return
}
_, err = client.CreateWallet(libgoal.UnencryptedWalletName, nil, crypto.MasterDerivationKey{})
@@ -241,12 +273,12 @@ func (t NetworkTemplate) Validate() error {
return fmt.Errorf("invalid template: at least one relay is required when more than a single node presents")
}
- // Validate JSONOverride decoding
+ // Validate ConfigJSONOverride decoding
for _, cfg := range t.Nodes {
local := config.GetDefaultLocal()
err := decodeJSONOverride(cfg.ConfigJSONOverride, &local)
if err != nil {
- return fmt.Errorf("invalid template: unable to decode JSONOverride: %w", err)
+ return fmt.Errorf("invalid template: unable to decode ConfigJSONOverride: %w", err)
}
}
@@ -293,7 +325,7 @@ func countRelayNodes(nodeCfgs []remote.NodeConfigGoal) (relayCount int) {
return
}
-func decodeJSONOverride(override string, cfg *config.Local) error {
+func decodeJSONOverride[T any](override string, cfg *T) error {
if override != "" {
reader := strings.NewReader(override)
dec := json.NewDecoder(reader)
@@ -340,3 +372,8 @@ func createConfigFile(node remote.NodeConfigGoal, configFile string, numNodes in
return cfg, cfg.SaveToFile(configFile)
}
+
+func createKMDConfigFile(kmdConfig TemplateKMDConfig, kmdDir string) error {
+ cfg := kmdConfig.apply(kmdconfig.DefaultConfig(kmdDir))
+ return kmdconfig.SaveKMDConfig(kmdDir, cfg)
+}
diff --git a/netdeploy/networkTemplates_test.go b/netdeploy/networkTemplates_test.go
index 130667f036..13aff8d842 100644
--- a/netdeploy/networkTemplates_test.go
+++ b/netdeploy/networkTemplates_test.go
@@ -235,7 +235,7 @@ func TestDevModeValidate(t *testing.T) {
},
},
}
- require.ErrorContains(t, tmpl.Validate(), "unable to decode JSONOverride")
+ require.ErrorContains(t, tmpl.Validate(), "unable to decode ConfigJSONOverride")
})
t.Run("ConfigJSONOverride unknown key", func(t *testing.T) {
diff --git a/netdeploy/remote/deployedNetwork.go b/netdeploy/remote/deployedNetwork.go
index 55e1fcaa90..a02f396d6d 100644
--- a/netdeploy/remote/deployedNetwork.go
+++ b/netdeploy/remote/deployedNetwork.go
@@ -295,7 +295,7 @@ func (cfg DeployedNetwork) Validate(buildCfg BuildConfig, rootDir string) (err e
// Validate that the string is a valid filename (we'll use it as part of a directory name somewhere)
func validateFilename(filename string) (err error) {
- if strings.Index(filename, "*") >= 0 {
+ if strings.Contains(filename, "*") {
return ErrDeployedNetworkNameCantIncludeWildcard
}
file, err := os.CreateTemp("", filename)
@@ -494,16 +494,6 @@ func (cfg DeployedNetwork) GenerateDatabaseFiles(fileCfgs BootstrappedNetwork, g
return nil
}
-func getGenesisAlloc(name string, allocation []bookkeeping.GenesisAllocation) bookkeeping.GenesisAllocation {
- name = strings.ToLower(name)
- for _, alloc := range allocation {
- if strings.ToLower(alloc.Comment) == name {
- return alloc
- }
- }
- return bookkeeping.GenesisAllocation{}
-}
-
// deterministicKeypair returns a key based on the provided index
func deterministicKeypair(i uint64) *crypto.SignatureSecrets {
var seed crypto.Seed
diff --git a/network/addr.go b/network/addr.go
index 694a707717..19208c8b50 100644
--- a/network/addr.go
+++ b/network/addr.go
@@ -34,6 +34,6 @@ func (wn *WebsocketNetwork) addrToGossipAddr(a string) (string, error) {
if parsedURL.Scheme == "" {
parsedURL.Scheme = "ws"
}
- parsedURL.Path = strings.Replace(path.Join(parsedURL.Path, GossipNetworkPath), "{genesisID}", wn.GenesisID, -1)
+ parsedURL.Path = strings.Replace(path.Join(parsedURL.Path, GossipNetworkPath), "{genesisID}", wn.genesisID, -1)
return parsedURL.String(), nil
}
diff --git a/network/connPerfMon.go b/network/connPerfMon.go
index fc315e3f70..c6e2b576e3 100644
--- a/network/connPerfMon.go
+++ b/network/connPerfMon.go
@@ -139,7 +139,7 @@ func (pm *connectionPerformanceMonitor) ComparePeers(peers []Peer) bool {
pm.Lock()
defer pm.Unlock()
for _, peer := range peers {
- if pm.monitoredConnections[peer] == false {
+ if !pm.monitoredConnections[peer] {
return false
}
}
@@ -177,10 +177,10 @@ func (pm *connectionPerformanceMonitor) Reset(peers []Peer) {
func (pm *connectionPerformanceMonitor) Notify(msg *IncomingMessage) {
pm.Lock()
defer pm.Unlock()
- if pm.monitoredConnections[msg.Sender] == false {
+ if !pm.monitoredConnections[msg.Sender] {
return
}
- if pm.monitoredMessageTags[msg.Tag] == false {
+ if !pm.monitoredMessageTags[msg.Tag] {
return
}
switch pm.stage {
diff --git a/network/gossipNode.go b/network/gossipNode.go
index e82b5fc0fe..cfd43e48fa 100644
--- a/network/gossipNode.go
+++ b/network/gossipNode.go
@@ -60,6 +60,21 @@ const (
PeersPhonebookArchivalNodes PeerOption = iota
)
+func (po PeerOption) String() string {
+ switch po {
+ case PeersConnectedOut:
+ return "ConnectedOut"
+ case PeersConnectedIn:
+ return "ConnectedIn"
+ case PeersPhonebookRelays:
+ return "PhonebookRelays"
+ case PeersPhonebookArchivalNodes:
+ return "PhonebookArchivalNodes"
+ default:
+ return "Unknown PeerOption"
+ }
+}
+
// GossipNode represents a node in the gossip network
type GossipNode interface {
Address() (string, bool)
@@ -148,11 +163,9 @@ type IncomingMessage struct {
// Tag is a short string (2 bytes) marking a type of message
type Tag = protocol.Tag
-func highPriorityTag(tags []protocol.Tag) bool {
- for _, tag := range tags {
- if tag == protocol.AgreementVoteTag || tag == protocol.ProposalPayloadTag {
- return true
- }
+func highPriorityTag(tag protocol.Tag) bool {
+ if tag == protocol.AgreementVoteTag || tag == protocol.ProposalPayloadTag {
+ return true
}
return false
}
diff --git a/network/msgCompressor.go b/network/msgCompressor.go
index a91dbf69a8..7eec31286d 100644
--- a/network/msgCompressor.go
+++ b/network/msgCompressor.go
@@ -24,6 +24,7 @@ import (
"github.com/DataDog/zstd"
"github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/network/vpack"
"github.com/algorand/go-algorand/protocol"
)
@@ -52,19 +53,39 @@ func zstdCompressMsg(tbytes []byte, d []byte) ([]byte, string) {
return mbytesComp, ""
}
+func vpackCompressVote(tbytes []byte, d []byte) ([]byte, string) {
+ var enc vpack.StatelessEncoder
+ bound := vpack.MaxCompressedVoteSize
+ // Pre-allocate buffer for tag bytes and compressed data
+ mbytesComp := make([]byte, len(tbytes)+bound)
+ copy(mbytesComp, tbytes)
+ comp, err := enc.CompressVote(mbytesComp[len(tbytes):], d)
+ if err != nil {
+ // fallback and reuse non-compressed original data
+ logMsg := fmt.Sprintf("failed to compress vote into buffer of len %d: %v", len(d), err)
+ copied := copy(mbytesComp[len(tbytes):], d)
+ return mbytesComp[:len(tbytes)+copied], logMsg
+ }
+
+ result := mbytesComp[:len(tbytes)+len(comp)]
+ return result, ""
+}
+
// MaxDecompressedMessageSize defines a maximum decompressed data size
// to prevent zip bombs. This depends on MaxTxnBytesPerBlock consensus parameter
// and should be larger.
const MaxDecompressedMessageSize = 20 * 1024 * 1024 // some large enough value
-// wsPeerMsgDataConverter performs optional incoming messages conversion.
-// At the moment it only supports zstd decompression for payload proposal
-type wsPeerMsgDataConverter struct {
+// wsPeerMsgDataDecoder performs optional incoming messages conversion.
+// At the moment it only supports zstd decompression for payload proposal,
+// and vpack decompression for votes.
+type wsPeerMsgDataDecoder struct {
log logging.Logger
origin string
// actual converter(s)
ppdec zstdProposalDecompressor
+ avdec vpackVoteDecompressor
}
type zstdProposalDecompressor struct{}
@@ -73,6 +94,15 @@ func (dec zstdProposalDecompressor) accept(data []byte) bool {
return len(data) > 4 && bytes.Equal(data[:4], zstdCompressionMagic[:])
}
+type vpackVoteDecompressor struct {
+ enabled bool
+ dec *vpack.StatelessDecoder
+}
+
+func (dec vpackVoteDecompressor) convert(data []byte) ([]byte, error) {
+ return dec.dec.DecompressVote(nil, data)
+}
+
func (dec zstdProposalDecompressor) convert(data []byte) ([]byte, error) {
r := zstd.NewReader(bytes.NewReader(data))
defer r.Close()
@@ -96,7 +126,7 @@ func (dec zstdProposalDecompressor) convert(data []byte) ([]byte, error) {
}
}
-func (c *wsPeerMsgDataConverter) convert(tag protocol.Tag, data []byte) ([]byte, error) {
+func (c *wsPeerMsgDataDecoder) convert(tag protocol.Tag, data []byte) ([]byte, error) {
if tag == protocol.ProposalPayloadTag {
// sender might support compressed payload but fail to compress for whatever reason,
// in this case it sends non-compressed payload - the receiver decompress only if it is compressed.
@@ -108,16 +138,33 @@ func (c *wsPeerMsgDataConverter) convert(tag protocol.Tag, data []byte) ([]byte,
return res, nil
}
c.log.Warnf("peer %s supported zstd but sent non-compressed data", c.origin)
+ } else if tag == protocol.AgreementVoteTag {
+ if c.avdec.enabled {
+ res, err := c.avdec.convert(data)
+ if err != nil {
+ c.log.Warnf("peer %s vote decompress error: %v", c.origin, err)
+ // fall back to original data
+ return data, nil
+ }
+ return res, nil
+ }
}
return data, nil
}
-func makeWsPeerMsgDataConverter(wp *wsPeer) *wsPeerMsgDataConverter {
- c := wsPeerMsgDataConverter{
+func makeWsPeerMsgDataDecoder(wp *wsPeer) *wsPeerMsgDataDecoder {
+ c := wsPeerMsgDataDecoder{
log: wp.log,
origin: wp.originAddress,
}
c.ppdec = zstdProposalDecompressor{}
+ // have both ends advertised support for compression?
+ if wp.enableVoteCompression && wp.vpackVoteCompressionSupported() {
+ c.avdec = vpackVoteDecompressor{
+ enabled: true,
+ dec: vpack.NewStatelessDecoder(),
+ }
+ }
return &c
}
diff --git a/network/msgCompressor_test.go b/network/msgCompressor_test.go
index 88273e37cf..d64b8fb54f 100644
--- a/network/msgCompressor_test.go
+++ b/network/msgCompressor_test.go
@@ -76,7 +76,7 @@ func (cl *converterTestLogger) Warnf(s string, args ...interface{}) {
func TestWsPeerMsgDataConverterConvert(t *testing.T) {
partitiontest.PartitionTest(t)
- c := wsPeerMsgDataConverter{}
+ c := wsPeerMsgDataDecoder{}
c.ppdec = zstdProposalDecompressor{}
tag := protocol.AgreementVoteTag
data := []byte("data")
diff --git a/network/msgp_gen.go b/network/msgp_gen.go
index df423c8746..125fa61660 100644
--- a/network/msgp_gen.go
+++ b/network/msgp_gen.go
@@ -3,6 +3,8 @@ package network
// Code generated by github.com/algorand/msgp DO NOT EDIT.
import (
+ "sort"
+
"github.com/algorand/msgp/msgp"
"github.com/algorand/go-algorand/crypto"
@@ -89,6 +91,26 @@ import (
// |-----> (*) MsgIsZero
// |-----> IdentityVerificationMessageSignedMaxSize()
//
+// peerMetaHeaders
+// |-----> MarshalMsg
+// |-----> CanMarshalMsg
+// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
+// |-----> (*) CanUnmarshalMsg
+// |-----> Msgsize
+// |-----> MsgIsZero
+// |-----> PeerMetaHeadersMaxSize()
+//
+// peerMetaValues
+// |-----> MarshalMsg
+// |-----> CanMarshalMsg
+// |-----> (*) UnmarshalMsg
+// |-----> (*) UnmarshalMsgWithState
+// |-----> (*) CanUnmarshalMsg
+// |-----> Msgsize
+// |-----> MsgIsZero
+// |-----> PeerMetaValuesMaxSize()
+//
// MarshalMsg implements msgp.Marshaler
func (z disconnectReason) MarshalMsg(b []byte) (o []byte) {
@@ -1230,3 +1252,237 @@ func IdentityVerificationMessageSignedMaxSize() (s int) {
s += 4 + crypto.SignatureMaxSize()
return
}
+
+// MarshalMsg implements msgp.Marshaler
+func (z peerMetaHeaders) MarshalMsg(b []byte) (o []byte) {
+ o = msgp.Require(b, z.Msgsize())
+ if z == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o = msgp.AppendMapHeader(o, uint32(len(z)))
+ }
+ za0005_keys := make([]string, 0, len(z))
+ for za0005 := range z {
+ za0005_keys = append(za0005_keys, za0005)
+ }
+ sort.Sort(SortString(za0005_keys))
+ for _, za0005 := range za0005_keys {
+ za0006 := z[za0005]
+ _ = za0006
+ o = msgp.AppendString(o, za0005)
+ if za0006 == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o = msgp.AppendArrayHeader(o, uint32(len(za0006)))
+ }
+ for za0007 := range za0006 {
+ o = msgp.AppendString(o, za0006[za0007])
+ }
+ }
+ return
+}
+
+func (_ peerMetaHeaders) CanMarshalMsg(z interface{}) bool {
+ _, ok := (z).(peerMetaHeaders)
+ if !ok {
+ _, ok = (z).(*peerMetaHeaders)
+ }
+ return ok
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *peerMetaHeaders) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
+ var zb0004 int
+ var zb0005 bool
+ zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0004 > maxHeaderKeys {
+ err = msgp.ErrOverflow(uint64(zb0004), uint64(maxHeaderKeys))
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0005 {
+ (*z) = nil
+ } else if (*z) == nil {
+ (*z) = make(peerMetaHeaders, zb0004)
+ }
+ for zb0004 > 0 {
+ var zb0001 string
+ var zb0002 peerMetaValues
+ zb0004--
+ zb0001, bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ var zb0006 int
+ var zb0007 bool
+ zb0006, zb0007, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ if zb0006 > maxHeaderValues {
+ err = msgp.ErrOverflow(uint64(zb0006), uint64(maxHeaderValues))
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ if zb0007 {
+ zb0002 = nil
+ } else if zb0002 != nil && cap(zb0002) >= zb0006 {
+ zb0002 = (zb0002)[:zb0006]
+ } else {
+ zb0002 = make(peerMetaValues, zb0006)
+ }
+ for zb0003 := range zb0002 {
+ zb0002[zb0003], bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001, zb0003)
+ return
+ }
+ }
+ (*z)[zb0001] = zb0002
+ }
+ o = bts
+ return
+}
+
+func (z *peerMetaHeaders) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
+func (_ *peerMetaHeaders) CanUnmarshalMsg(z interface{}) bool {
+ _, ok := (z).(*peerMetaHeaders)
+ return ok
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z peerMetaHeaders) Msgsize() (s int) {
+ s = msgp.MapHeaderSize
+ if z != nil {
+ for za0005, za0006 := range z {
+ _ = za0005
+ _ = za0006
+ s += 0 + msgp.StringPrefixSize + len(za0005) + msgp.ArrayHeaderSize
+ for za0007 := range za0006 {
+ s += msgp.StringPrefixSize + len(za0006[za0007])
+ }
+ }
+ }
+ return
+}
+
+// MsgIsZero returns whether this is a zero value
+func (z peerMetaHeaders) MsgIsZero() bool {
+ return len(z) == 0
+}
+
+// MaxSize returns a maximum valid message size for this message type
+func PeerMetaHeadersMaxSize() (s int) {
+ s += msgp.MapHeaderSize
+ // Adding size of map keys for z
+ s += maxHeaderKeys
+ panic("Unable to determine max size: String type za0005 is unbounded")
+ // Adding size of map values for z
+ s += maxHeaderKeys
+ // Calculating size of slice: za0006
+ s += msgp.ArrayHeaderSize
+ panic("Unable to determine max size: String type is unbounded for za0006[za0007]")
+ return
+}
+
+// MarshalMsg implements msgp.Marshaler
+func (z peerMetaValues) MarshalMsg(b []byte) (o []byte) {
+ o = msgp.Require(b, z.Msgsize())
+ if z == nil {
+ o = msgp.AppendNil(o)
+ } else {
+ o = msgp.AppendArrayHeader(o, uint32(len(z)))
+ }
+ for za0001 := range z {
+ o = msgp.AppendString(o, z[za0001])
+ }
+ return
+}
+
+func (_ peerMetaValues) CanMarshalMsg(z interface{}) bool {
+ _, ok := (z).(peerMetaValues)
+ if !ok {
+ _, ok = (z).(*peerMetaValues)
+ }
+ return ok
+}
+
+// UnmarshalMsg implements msgp.Unmarshaler
+func (z *peerMetaValues) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) {
+ if st.AllowableDepth == 0 {
+ err = msgp.ErrMaxDepthExceeded{}
+ return
+ }
+ st.AllowableDepth--
+ var zb0002 int
+ var zb0003 bool
+ zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0002 > maxHeaderValues {
+ err = msgp.ErrOverflow(uint64(zb0002), uint64(maxHeaderValues))
+ err = msgp.WrapError(err)
+ return
+ }
+ if zb0003 {
+ (*z) = nil
+ } else if (*z) != nil && cap((*z)) >= zb0002 {
+ (*z) = (*z)[:zb0002]
+ } else {
+ (*z) = make(peerMetaValues, zb0002)
+ }
+ for zb0001 := range *z {
+ (*z)[zb0001], bts, err = msgp.ReadStringBytes(bts)
+ if err != nil {
+ err = msgp.WrapError(err, zb0001)
+ return
+ }
+ }
+ o = bts
+ return
+}
+
+func (z *peerMetaValues) UnmarshalMsg(bts []byte) (o []byte, err error) {
+ return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState)
+}
+func (_ *peerMetaValues) CanUnmarshalMsg(z interface{}) bool {
+ _, ok := (z).(*peerMetaValues)
+ return ok
+}
+
+// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
+func (z peerMetaValues) Msgsize() (s int) {
+ s = msgp.ArrayHeaderSize
+ for za0001 := range z {
+ s += msgp.StringPrefixSize + len(z[za0001])
+ }
+ return
+}
+
+// MsgIsZero returns whether this is a zero value
+func (z peerMetaValues) MsgIsZero() bool {
+ return len(z) == 0
+}
+
+// MaxSize returns a maximum valid message size for this message type
+func PeerMetaValuesMaxSize() (s int) {
+ // Calculating size of slice: z
+ s += msgp.ArrayHeaderSize
+ panic("Unable to determine max size: String type is unbounded for z[za0001]")
+ return
+}
diff --git a/network/msgp_gen_test.go b/network/msgp_gen_test.go
index 1046371a1a..0144dea16e 100644
--- a/network/msgp_gen_test.go
+++ b/network/msgp_gen_test.go
@@ -433,3 +433,123 @@ func BenchmarkUnmarshalidentityVerificationMessageSigned(b *testing.B) {
}
}
}
+
+func TestMarshalUnmarshalpeerMetaHeaders(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ v := peerMetaHeaders{}
+ 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 TestRandomizedEncodingpeerMetaHeaders(t *testing.T) {
+ protocol.RunEncodingTest(t, &peerMetaHeaders{})
+}
+
+func BenchmarkMarshalMsgpeerMetaHeaders(b *testing.B) {
+ v := peerMetaHeaders{}
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ v.MarshalMsg(nil)
+ }
+}
+
+func BenchmarkAppendMsgpeerMetaHeaders(b *testing.B) {
+ v := peerMetaHeaders{}
+ 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 BenchmarkUnmarshalpeerMetaHeaders(b *testing.B) {
+ v := peerMetaHeaders{}
+ 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 TestMarshalUnmarshalpeerMetaValues(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ v := peerMetaValues{}
+ 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 TestRandomizedEncodingpeerMetaValues(t *testing.T) {
+ protocol.RunEncodingTest(t, &peerMetaValues{})
+}
+
+func BenchmarkMarshalMsgpeerMetaValues(b *testing.B) {
+ v := peerMetaValues{}
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ v.MarshalMsg(nil)
+ }
+}
+
+func BenchmarkAppendMsgpeerMetaValues(b *testing.B) {
+ v := peerMetaValues{}
+ 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 BenchmarkUnmarshalpeerMetaValues(b *testing.B) {
+ v := peerMetaValues{}
+ 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/network/p2p/capabilities.go b/network/p2p/capabilities.go
index 96691c773a..71a63d4c0c 100644
--- a/network/p2p/capabilities.go
+++ b/network/p2p/capabilities.go
@@ -18,6 +18,7 @@ package p2p
import (
"context"
+ "math/rand/v2"
"sync"
"time"
@@ -109,7 +110,7 @@ func (c *CapabilitiesDiscovery) PeersForCapability(capability Capability, n int)
}
// AdvertiseCapabilities periodically runs the Advertiser interface on the DHT
-// If a capability fails to advertise we will retry every 10 seconds until full success
+// If a capability fails to advertise we will retry every 100 seconds until full success
// This gets rerun every at the minimum ttl or the maxAdvertisementInterval.
func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability) {
c.wg.Add(1)
@@ -121,6 +122,15 @@ func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability
}()
for {
+ // shuffle capabilities to advertise in random order
+ // since the DHT's internal advertisement happens concurrently for peers in its routing table
+ // any peer error does not prevent advertisement of other peers.
+ // on repeated advertisement, we want to avoid the same order to make sure all capabilities are advertised.
+ if len(capabilities) > 1 {
+ rand.Shuffle(len(capabilities), func(i, j int) {
+ capabilities[i], capabilities[j] = capabilities[j], capabilities[i]
+ })
+ }
select {
case <-c.dht.Context().Done():
return
@@ -131,7 +141,7 @@ func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability
ttl, err0 := c.advertise(c.dht.Context(), string(capa))
if err0 != nil {
err = err0
- c.log.Errorf("failed to advertise for capability %s: %v", capa, err0)
+ c.log.Warnf("failed to advertise for capability %s: %v", capa, err0)
break
}
if ttl < advertisementInterval {
@@ -139,9 +149,9 @@ func (c *CapabilitiesDiscovery) AdvertiseCapabilities(capabilities ...Capability
}
c.log.Infof("advertised capability %s", capa)
}
- // If we failed to advertise, retry every 10 seconds until successful
+ // If we failed to advertise, retry every 100 seconds until successful
if err != nil {
- nextExecution = time.After(time.Second * 10)
+ nextExecution = time.After(time.Second * 100)
} else {
// Otherwise, ensure we're at the correct interval
nextExecution = time.After(advertisementInterval)
diff --git a/network/p2p/dht/dht.go b/network/p2p/dht/dht.go
index 22d3cbe391..e53fa8f216 100644
--- a/network/p2p/dht/dht.go
+++ b/network/p2p/dht/dht.go
@@ -69,7 +69,7 @@ func backoffFactory() backoff.BackoffFactory {
return backoff.NewExponentialDecorrelatedJitter(minBackoff, maxBackoff, baseBackoff, rand.NewSource(rand.Int63()))
}
-// MakeDiscovery creates a discovery.Discovery object using backoff and cacching
+// MakeDiscovery creates a discovery.Discovery object using backoff and caching
func MakeDiscovery(r crouting.ContentRouting) (discovery.Discovery, error) {
- return backoff.NewBackoffDiscovery(routing.NewRoutingDiscovery(r), backoffFactory(), backoff.WithBackoffDiscoveryReturnedChannelSize(0), backoff.WithBackoffDiscoverySimultaneousQueryBufferSize(0))
+ return backoff.NewBackoffDiscovery(routing.NewRoutingDiscovery(r), backoffFactory())
}
diff --git a/network/p2p/http.go b/network/p2p/http.go
index e82ac482f4..bb053cdd92 100644
--- a/network/p2p/http.go
+++ b/network/p2p/http.go
@@ -17,6 +17,10 @@
package p2p
import (
+ "bufio"
+ "context"
+ "fmt"
+ "io"
"net/http"
"sync"
"time"
@@ -26,7 +30,10 @@ import (
"github.com/gorilla/mux"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/host"
+ "github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
+ "github.com/libp2p/go-libp2p/core/peerstore"
+ bhost "github.com/libp2p/go-libp2p/p2p/host/basic"
libp2phttp "github.com/libp2p/go-libp2p/p2p/http"
"github.com/multiformats/go-multiaddr"
)
@@ -117,13 +124,16 @@ func makeHTTPClient(addrInfo *peer.AddrInfo, opts ...httpClientOption) (*http.Cl
logging.Base().Debugf("MakeHTTPClient made a new P2P host %s for %s", clientStreamHost.ID(), addrInfo.String())
}
- client := libp2phttp.Host{StreamHost: clientStreamHost}
+ host := &libp2phttp.Host{StreamHost: clientStreamHost}
- // Do not use client.NamespacedClient to prevent it making connection to a well-known handler
+ // Do not use libp2phttp.Host.NamespacedClient to prevent it making connection to a well-known handler
// to make a NamespaceRoundTripper that limits to specific URL paths.
// First, we do not want make requests when listing peers (the main MakeHTTPClient invoker).
// Secondly, this makes unit testing easier - no need to register fake handlers.
- rt, err := client.NewConstrainedRoundTripper(*addrInfo)
+ // Do not use libp2phttp.Host.NewConstrainedRoundTripper because it uses http.Request's context
+ // when establishing new streams so might not be able to bail out in reasonable time when used for catchpoints.
+ // Instead, we use a custom RoundTripper that uses its own context with a timeout when establishing a new stream.
+ rt, err := newP2pHTTPRoundTripper(host, addrInfo)
if err != nil {
return nil, err
}
@@ -131,6 +141,96 @@ func makeHTTPClient(addrInfo *peer.AddrInfo, opts ...httpClientOption) (*http.Cl
return &http.Client{Transport: rt}, nil
}
+// newP2pHTTPRoundTripper is a ligtweight version of libp2phttp.Host.NewConstrainedRoundTripper
+// that returns an own p2pHttpRoundTripper instead of streamRoundTripper.
+func newP2pHTTPRoundTripper(h *libp2phttp.Host, server *peer.AddrInfo) (http.RoundTripper, error) {
+ // Do we have an existing connection to this peer?
+ existingStreamConn := false
+ if server.ID != "" && h.StreamHost != nil {
+ existingStreamConn = len(h.StreamHost.Network().ConnsToPeer(server.ID)) > 0
+ }
+
+ // Otherwise use a stream based transport
+ if h.StreamHost == nil {
+ return nil, fmt.Errorf("can not use the HTTP transport (either no address or PeerID auth is required), and no stream host provided")
+ }
+ if !existingStreamConn {
+ if server.ID == "" {
+ return nil, fmt.Errorf("can not use the HTTP transport, and no server peer ID provided")
+ }
+ }
+
+ return &p2pHTTPRoundTripper{h: h.StreamHost, server: server.ID, serverAddrs: server.Addrs, httpHost: h}, nil
+}
+
+// p2pHTTPRoundTripper is a custom http.RoundTripper that uses libp2p transport.
+// It is a lightweight version of libp2phttp.streamRoundTripper with its own RoundTrip implementation.
+// The main difference and the reason for this custom implementation is that it does not use http.Request's context
+// but uses its own context with a timeout when establishing a new stream.
+type p2pHTTPRoundTripper struct {
+ h host.Host
+ server peer.ID
+ serverAddrs []multiaddr.Multiaddr
+ httpHost *libp2phttp.Host
+ addrsAdded sync.Once
+}
+
+// streamReadCloser is a copy of libp2phttp.streamReadCloser
+type streamReadCloser struct {
+ io.ReadCloser
+ s network.Stream
+}
+
+func (s *streamReadCloser) Close() error {
+ s.s.Close()
+ return s.ReadCloser.Close()
+}
+
+func (rt *p2pHTTPRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
+ rt.addrsAdded.Do(func() {
+ if len(rt.serverAddrs) > 0 {
+ rt.h.Peerstore().AddAddrs(rt.server, rt.serverAddrs, peerstore.TempAddrTTL)
+ }
+ rt.serverAddrs = nil // may as well cleanup
+ })
+
+ ctx, cancel := context.WithTimeout(context.Background(), bhost.DefaultNegotiationTimeout)
+ defer cancel()
+ s, err := rt.h.NewStream(ctx, rt.server, libp2phttp.ProtocolIDForMultistreamSelect)
+ if err != nil {
+ return nil, err
+ }
+
+ // Write connection: close header to ensure the stream is closed after the response
+ r.Header.Add("connection", "close")
+
+ go func() {
+ defer func() {
+ _ = s.CloseWrite()
+ }()
+ _ = r.Write(s)
+ if r.Body != nil {
+ r.Body.Close()
+ }
+ }()
+
+ if deadline, ok := r.Context().Deadline(); ok {
+ err = s.SetReadDeadline(deadline)
+ if err != nil {
+ s.Close()
+ return nil, err
+ }
+ }
+
+ resp, err := http.ReadResponse(bufio.NewReader(s), r)
+ if err != nil {
+ s.Close()
+ return nil, err
+ }
+ resp.Body = &streamReadCloser{resp.Body, s}
+ return resp, nil
+}
+
// makeHTTPClientWithRateLimit creates a http.Client that uses libp2p transport for a given protocol and peer address.
func makeHTTPClientWithRateLimit(addrInfo *peer.AddrInfo, p2pService *serviceImpl, connTimeStore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration) (*http.Client, error) {
cl, err := makeHTTPClient(addrInfo, WithHost(p2pService.host))
diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go
index 3f6737928a..23cb74c9fd 100644
--- a/network/p2p/p2p.go
+++ b/network/p2p/p2p.go
@@ -18,7 +18,6 @@ package p2p
import (
"context"
- "encoding/base32"
"fmt"
"net"
"net/http"
@@ -62,6 +61,8 @@ type Service interface {
ID() peer.ID // return peer.ID for self
IDSigner() *PeerIDChallengeSigner
AddrInfo() peer.AddrInfo // return addrInfo for self
+ NetworkNotify(network.Notifiee)
+ NetworkStopNotify(network.Notifiee)
DialPeersUntilTargetCount(targetConnCount int)
ClosePeer(peer.ID) error
@@ -89,12 +90,13 @@ type serviceImpl struct {
topicsMu deadlock.RWMutex
}
-// AlgorandWsProtocol defines a libp2p protocol name for algorand's websockets messages
-const AlgorandWsProtocol = "/algorand-ws/1.0.0"
+// AlgorandWsProtocolV1 defines a libp2p protocol name for algorand's websockets messages
+// as the very initial release
+const AlgorandWsProtocolV1 = "/algorand-ws/1.0.0"
-// algorandGUIDProtocolPrefix defines a libp2p protocol name for algorand node telemetry GUID exchange
-const algorandGUIDProtocolPrefix = "/algorand-telemetry/1.0.0/"
-const algorandGUIDProtocolTemplate = algorandGUIDProtocolPrefix + "%s/%s"
+// AlgorandWsProtocolV22 defines a libp2p protocol name for algorand's websockets messages
+// as a version supporting peer metadata and wsnet v2.2 protocol features
+const AlgorandWsProtocolV22 = "/algorand-ws/2.2.0"
const dialTimeout = 30 * time.Second
@@ -180,18 +182,24 @@ func configureResourceManager(cfg config.Local) (network.ResourceManager, error)
return rm, err
}
+// StreamHandlerPair is a struct that contains a protocol ID and a StreamHandler
+type StreamHandlerPair struct {
+ ProtoID protocol.ID
+ Handler StreamHandler
+}
+
+// StreamHandlers is an ordered list of StreamHandlerPair
+type StreamHandlers []StreamHandlerPair
+
// MakeService creates a P2P service instance
-func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandler StreamHandler, metricsTracer pubsub.RawTracer) (*serviceImpl, error) {
+func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h host.Host, listenAddr string, wsStreamHandlers StreamHandlers, metricsTracer pubsub.RawTracer) (*serviceImpl, error) {
- sm := makeStreamManager(ctx, log, h, wsStreamHandler, cfg.EnableGossipService)
+ sm := makeStreamManager(ctx, log, h, wsStreamHandlers, cfg.EnableGossipService)
h.Network().Notify(sm)
- h.SetStreamHandler(AlgorandWsProtocol, sm.streamHandler)
- // set an empty handler for telemetryID/telemetryInstance protocol in order to allow other peers to know our telemetryID
- telemetryID := log.GetTelemetryGUID()
- telemetryInstance := log.GetInstanceName()
- telemetryProtoInfo := formatPeerTelemetryInfoProtocolName(telemetryID, telemetryInstance)
- h.SetStreamHandler(protocol.ID(telemetryProtoInfo), func(s network.Stream) { s.Close() })
+ for _, pair := range wsStreamHandlers {
+ h.SetStreamHandler(pair.ProtoID, sm.streamHandler)
+ }
ps, err := makePubSub(ctx, cfg, h, metricsTracer)
if err != nil {
@@ -227,6 +235,7 @@ func (s *serviceImpl) Start() error {
// Close shuts down the P2P service
func (s *serviceImpl) Close() error {
+ s.host.Network().StopNotify(s.streams)
return s.host.Close()
}
@@ -243,7 +252,7 @@ func (s *serviceImpl) IDSigner() *PeerIDChallengeSigner {
// DialPeersUntilTargetCount attempts to establish connections to the provided phonebook addresses
func (s *serviceImpl) DialPeersUntilTargetCount(targetConnCount int) {
ps := s.host.Peerstore().(*pstore.PeerStore)
- addrInfos := ps.GetAddresses(targetConnCount, phonebook.PhoneBookEntryRelayRole)
+ addrInfos := ps.GetAddresses(targetConnCount, phonebook.RelayRole)
conns := s.host.Network().Conns()
var numOutgoingConns int
for _, conn := range conns {
@@ -320,35 +329,6 @@ func netAddressToListenAddress(netAddress string) (string, error) {
return fmt.Sprintf("/ip4/%s/tcp/%s", ip, parts[1]), nil
}
-// GetPeerTelemetryInfo returns the telemetry ID of a peer by looking at its protocols
-func GetPeerTelemetryInfo(peerProtocols []protocol.ID) (telemetryID string, telemetryInstance string) {
- for _, protocol := range peerProtocols {
- if strings.HasPrefix(string(protocol), algorandGUIDProtocolPrefix) {
- telemetryInfo := string(protocol[len(algorandGUIDProtocolPrefix):])
- telemetryInfoParts := strings.Split(telemetryInfo, "/")
- if len(telemetryInfoParts) == 2 {
- telemetryIDBytes, err := base32.StdEncoding.DecodeString(telemetryInfoParts[0])
- if err == nil {
- telemetryID = string(telemetryIDBytes)
- }
- telemetryInstanceBytes, err := base32.StdEncoding.DecodeString(telemetryInfoParts[1])
- if err == nil {
- telemetryInstance = string(telemetryInstanceBytes)
- }
- return telemetryID, telemetryInstance
- }
- }
- }
- return "", ""
-}
-
-func formatPeerTelemetryInfoProtocolName(telemetryID string, telemetryInstance string) string {
- return fmt.Sprintf(algorandGUIDProtocolTemplate,
- base32.StdEncoding.EncodeToString([]byte(telemetryID)),
- base32.StdEncoding.EncodeToString([]byte(telemetryInstance)),
- )
-}
-
var private6 = parseCIDR([]string{
"100::/64",
"2001:2::/48",
@@ -424,3 +404,13 @@ func addressFilter(addrs []multiaddr.Multiaddr) []multiaddr.Multiaddr {
func (s *serviceImpl) GetHTTPClient(addrInfo *peer.AddrInfo, connTimeStore limitcaller.ConnectionTimeStore, queueingTimeout time.Duration) (*http.Client, error) {
return makeHTTPClientWithRateLimit(addrInfo, s, connTimeStore, queueingTimeout)
}
+
+// NetworkNotify registers a notifiee with the host's network
+func (s *serviceImpl) NetworkNotify(n network.Notifiee) {
+ s.host.Network().Notify(n)
+}
+
+// NetworkStopNotify unregisters a notifiee with the host's network
+func (s *serviceImpl) NetworkStopNotify(n network.Notifiee) {
+ s.host.Network().StopNotify(n)
+}
diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go
index 9202dd9c74..9e255f7f33 100644
--- a/network/p2p/p2p_test.go
+++ b/network/p2p/p2p_test.go
@@ -17,15 +17,10 @@
package p2p
import (
- "context"
"fmt"
"net"
"testing"
- "github.com/libp2p/go-libp2p"
- "github.com/libp2p/go-libp2p/core/network"
- "github.com/libp2p/go-libp2p/core/peer"
- "github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
"github.com/stretchr/testify/require"
@@ -86,95 +81,6 @@ func TestNetAddressToListenAddress(t *testing.T) {
}
}
-// TestP2PGetPeerTelemetryInfo tests the GetPeerTelemetryInfo function
-func TestP2PGetPeerTelemetryInfo(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- testCases := []struct {
- name string
- peerProtocols []protocol.ID
- expectedTelemetryID string
- expectedTelemetryInstance string
- }{
- {
- name: "Valid Telemetry Info",
- peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetryID", "telemetryInstance"))},
- expectedTelemetryID: "telemetryID",
- expectedTelemetryInstance: "telemetryInstance",
- },
- {
- name: "Partial Telemetry Info 1",
- peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetryID", ""))},
- expectedTelemetryID: "telemetryID",
- expectedTelemetryInstance: "",
- },
- {
- name: "Partial Telemetry Info 2",
- peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("", "telemetryInstance"))},
- expectedTelemetryID: "",
- expectedTelemetryInstance: "telemetryInstance",
- },
- {
- name: "No Telemetry Info",
- peerProtocols: []protocol.ID{protocol.ID("/some-other-protocol/1.0.0/otherID/otherInstance")},
- expectedTelemetryID: "",
- expectedTelemetryInstance: "",
- },
- {
- name: "Invalid Telemetry Info Format",
- peerProtocols: []protocol.ID{protocol.ID("/algorand-telemetry/1.0.0/invalidFormat")},
- expectedTelemetryID: "",
- expectedTelemetryInstance: "",
- },
- {
- name: "Special Characters Telemetry Info Format",
- peerProtocols: []protocol.ID{protocol.ID(formatPeerTelemetryInfoProtocolName("telemetry/ID", "123-//11-33"))},
- expectedTelemetryID: "telemetry/ID",
- expectedTelemetryInstance: "123-//11-33",
- },
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- telemetryID, telemetryInstance := GetPeerTelemetryInfo(tc.peerProtocols)
- if telemetryID != tc.expectedTelemetryID || telemetryInstance != tc.expectedTelemetryInstance {
- t.Errorf("Expected telemetry ID: %s, telemetry instance: %s, but got telemetry ID: %s, telemetry instance: %s",
- tc.expectedTelemetryID, tc.expectedTelemetryInstance, telemetryID, telemetryInstance)
- }
- })
- }
-}
-
-func TestP2PProtocolAsMeta(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
-
- h1, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
- require.NoError(t, err)
- defer h1.Close()
-
- h1TID := "telemetryID1"
- h1Inst := "telemetryInstance2"
- telemetryProtoInfo := formatPeerTelemetryInfoProtocolName(h1TID, h1Inst)
- h1.SetStreamHandler(protocol.ID(telemetryProtoInfo), func(s network.Stream) { s.Close() })
-
- h2, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"))
- require.NoError(t, err)
- defer h2.Close()
-
- err = h2.Connect(ctx, peer.AddrInfo{ID: h1.ID(), Addrs: h1.Addrs()})
- require.NoError(t, err)
-
- protos, err := h2.Peerstore().GetProtocols(h1.ID())
- require.NoError(t, err)
-
- tid, inst := GetPeerTelemetryInfo(protos)
- require.Equal(t, h1TID, tid)
- require.Equal(t, h1Inst, inst)
-}
-
func TestP2PPrivateAddresses(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
diff --git a/network/p2p/peerstore/peerstore.go b/network/p2p/peerstore/peerstore.go
index 5516db6250..9bb5a46e88 100644
--- a/network/p2p/peerstore/peerstore.go
+++ b/network/p2p/peerstore/peerstore.go
@@ -42,6 +42,7 @@ type PeerStore struct {
peerStoreCAB
connectionsRateLimitingCount uint
connectionsRateLimitingWindow time.Duration
+ lock deadlock.Mutex
}
// addressData: holds the information associated with each phonebook address.
@@ -55,13 +56,9 @@ type addressData struct {
// networkNames: lists the networks to which the given address belongs.
networkNames map[string]bool
- mu *deadlock.RWMutex
- // role is the role that this address serves.
- role phonebook.PhoneBookEntryRoles
-
- // persistent is set true for peers whose record should not be removed for the peer list
- persistent bool
+ // roles is the roles that this address serves.
+ roles phonebook.RoleSet
}
// peerStoreCAB combines the libp2p Peerstore and CertifiedAddrBook interfaces.
@@ -78,7 +75,7 @@ func NewPeerStore(addrInfo []*peer.AddrInfo, network string) (*PeerStore, error)
}
pstore := &PeerStore{peerStoreCAB: ps}
- pstore.AddPersistentPeers(addrInfo, network, phonebook.PhoneBookEntryRelayRole)
+ pstore.AddPersistentPeers(addrInfo, network, phonebook.RelayRole)
return pstore, nil
}
@@ -97,7 +94,7 @@ func MakePhonebook(connectionsRateLimitingCount uint,
}
// GetAddresses returns up to N addresses, but may return fewer
-func (ps *PeerStore) GetAddresses(n int, role phonebook.PhoneBookEntryRoles) []*peer.AddrInfo {
+func (ps *PeerStore) GetAddresses(n int, role phonebook.Role) []*peer.AddrInfo {
return shuffleSelect(ps.filterRetryTime(time.Now(), role), n)
}
@@ -126,8 +123,10 @@ func (ps *PeerStore) UpdateRetryAfter(addr string, retryAfter time.Time) {
// The provisional time should be updated after the connection with UpdateConnectionTime
func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Duration, time.Time) {
curTime := time.Now()
- var timeSince time.Duration
- var numElmtsToRemove int
+
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
peerID := peer.ID(addrOrPeerID)
metadata, err := ps.Get(peerID, addressDataKey)
if err != nil {
@@ -137,7 +136,10 @@ func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Dura
if !ok {
return false, 0 /* not used */, curTime /* not used */
}
+
// Remove from recentConnectionTimes the times later than ConnectionsRateLimitingWindowSeconds
+ numElmtsToRemove := 0
+ timeSince := time.Duration(0)
for numElmtsToRemove < len(ad.recentConnectionTimes) {
timeSince = curTime.Sub(ad.recentConnectionTimes[numElmtsToRemove])
if timeSince >= ps.connectionsRateLimitingWindow {
@@ -149,6 +151,7 @@ func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Dura
// Remove the expired elements from e.data[addr].recentConnectionTimes
ps.popNElements(numElmtsToRemove, peerID)
+
// If there are max number of connections within the time window, wait
metadata, _ = ps.Get(peerID, addressDataKey)
ad, ok = metadata.(addressData)
@@ -160,7 +163,6 @@ func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Dura
return true, /* true */
ps.connectionsRateLimitingWindow - timeSince, curTime /* not used */
}
-
// Else, there is space in connectionsRateLimitingCount. The
// connection request of the caller will proceed
// Update curTime, since it may have significantly changed if waited
@@ -172,6 +174,9 @@ func (ps *PeerStore) GetConnectionWaitTime(addrOrPeerID string) (bool, time.Dura
// UpdateConnectionTime updates the connection time for the given address.
func (ps *PeerStore) UpdateConnectionTime(addrOrPeerID string, provisionalTime time.Time) bool {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
peerID := peer.ID(addrOrPeerID)
metadata, err := ps.Get(peerID, addressDataKey)
if err != nil {
@@ -205,7 +210,13 @@ func (ps *PeerStore) UpdateConnectionTime(addrOrPeerID string, provisionalTime t
}
// ReplacePeerList replaces the peer list for the given networkName and role.
-func (ps *PeerStore) ReplacePeerList(addressesThey []*peer.AddrInfo, networkName string, role phonebook.PhoneBookEntryRoles) {
+// new entries in addressesThey are being added
+// existing items that aren't included in addressesThey are being removed
+// matching entries roles gets updated as needed and persistent peers are not touched
+func (ps *PeerStore) ReplacePeerList(addressesThey []*peer.AddrInfo, networkName string, role phonebook.Role) {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
// prepare a map of items we'd like to remove.
removeItems := make(map[peer.ID]bool, 0)
peerIDs := ps.Peers()
@@ -213,23 +224,30 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []*peer.AddrInfo, networkName
data, _ := ps.Get(pid, addressDataKey)
if data != nil {
ad := data.(addressData)
- ad.mu.RLock()
- if ad.networkNames[networkName] && ad.role == role && !ad.persistent {
- removeItems[pid] = true
+ updated := false
+ if ad.networkNames[networkName] && !ad.roles.IsPersistent(role) {
+ if ad.roles.Is(role) {
+ removeItems[pid] = true
+ } else if ad.roles.Has(role) {
+ ad.roles.Remove(role)
+ updated = true
+ }
+ }
+ if updated {
+ _ = ps.Put(pid, addressDataKey, ad)
}
- ad.mu.RUnlock()
}
}
for _, info := range addressesThey {
data, _ := ps.Get(info.ID, addressDataKey)
if data != nil {
- // we already have this.
- // Update the networkName
+ // we already have this
+ // update the networkName and role
ad := data.(addressData)
- ad.mu.Lock()
ad.networkNames[networkName] = true
- ad.mu.Unlock()
+ ad.roles.Add(role)
+ _ = ps.Put(info.ID, addressDataKey, ad)
// do not remove this entry
delete(removeItems, info.ID)
@@ -248,15 +266,19 @@ func (ps *PeerStore) ReplacePeerList(addressesThey []*peer.AddrInfo, networkName
}
// AddPersistentPeers stores addresses of peers which are persistent.
-// i.e. they won't be replaced by ReplacePeerList calls
-func (ps *PeerStore) AddPersistentPeers(addrInfo []*peer.AddrInfo, networkName string, role phonebook.PhoneBookEntryRoles) {
+// i.e. they won't be replaced by ReplacePeerList calls.
+// If a peer is already in the peerstore, its role will be updated.
+func (ps *PeerStore) AddPersistentPeers(addrInfo []*peer.AddrInfo, networkName string, role phonebook.Role) {
+ ps.lock.Lock()
+ defer ps.lock.Unlock()
+
for _, info := range addrInfo {
data, _ := ps.Get(info.ID, addressDataKey)
if data != nil {
// we already have this.
- // Make sure the persistence field is set to true
+ // Make sure the persistence field is set to true and overwrite the role
ad := data.(addressData)
- ad.persistent = true
+ ad.roles.AddPersistent(role)
_ = ps.Put(info.ID, addressDataKey, ad)
} else {
// we don't have this item. add it.
@@ -273,13 +295,11 @@ func (ps *PeerStore) Length() int {
}
// makePhonebookEntryData creates a new address entry for provided network name and role.
-func makePhonebookEntryData(networkName string, role phonebook.PhoneBookEntryRoles, persistent bool) addressData {
+func makePhonebookEntryData(networkName string, role phonebook.Role, persistent bool) addressData {
pbData := addressData{
networkNames: make(map[string]bool),
- mu: &deadlock.RWMutex{},
recentConnectionTimes: make([]time.Time, 0),
- role: role,
- persistent: persistent,
+ roles: phonebook.MakeRoleSet(role, persistent),
}
pbData.networkNames[networkName] = true
return pbData
@@ -291,17 +311,15 @@ func (ps *PeerStore) deletePhonebookEntry(peerID peer.ID, networkName string) {
return
}
ad := data.(addressData)
- ad.mu.Lock()
delete(ad.networkNames, networkName)
isEmpty := len(ad.networkNames) == 0
- ad.mu.Unlock()
if isEmpty {
ps.ClearAddrs(peerID)
_ = ps.Put(peerID, addressDataKey, nil)
}
}
-// AppendTime adds the current time to recentConnectionTimes in
+// appendTime adds the current time to recentConnectionTimes in
// addressData of addr
func (ps *PeerStore) appendTime(peerID peer.ID, t time.Time) {
data, _ := ps.Get(peerID, addressDataKey)
@@ -310,7 +328,7 @@ func (ps *PeerStore) appendTime(peerID peer.ID, t time.Time) {
_ = ps.Put(peerID, addressDataKey, ad)
}
-// PopEarliestTime removes the earliest time from recentConnectionTimes in
+// popNElements removes the earliest time from recentConnectionTimes in
// addressData for addr
// It is expected to be later than ConnectionsRateLimitingWindow
func (ps *PeerStore) popNElements(n int, peerID peer.ID) {
@@ -320,13 +338,13 @@ func (ps *PeerStore) popNElements(n int, peerID peer.ID) {
_ = ps.Put(peerID, addressDataKey, ad)
}
-func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.PhoneBookEntryRoles) []*peer.AddrInfo {
+func (ps *PeerStore) filterRetryTime(t time.Time, role phonebook.Role) []*peer.AddrInfo {
o := make([]*peer.AddrInfo, 0, len(ps.Peers()))
for _, peerID := range ps.Peers() {
data, _ := ps.Get(peerID, addressDataKey)
if data != nil {
ad := data.(addressData)
- if t.After(ad.retryAfter) && role == ad.role {
+ if t.After(ad.retryAfter) && ad.roles.Has(role) {
mas := ps.Addrs(peerID)
info := peer.AddrInfo{ID: peerID, Addrs: mas}
o = append(o, &info)
diff --git a/network/p2p/peerstore/peerstore_test.go b/network/p2p/peerstore/peerstore_test.go
index 91152d4cd0..ddf5e70fe9 100644
--- a/network/p2p/peerstore/peerstore_test.go
+++ b/network/p2p/peerstore/peerstore_test.go
@@ -32,13 +32,6 @@ import (
"github.com/algorand/go-algorand/test/partitiontest"
)
-// PhoneBookEntryRelayRole used for all the relays that are provided either via the algobootstrap SRV record
-// or via a configuration file.
-const PhoneBookEntryRelayRole = 1
-
-// PhoneBookEntryArchiverRole used for all the archivers that are provided via the archive SRV record.
-const PhoneBookEntryArchiverRole = 2
-
func TestPeerstore(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
@@ -90,7 +83,7 @@ func TestPeerstore(t *testing.T) {
}
func testPhonebookAll(t *testing.T, set []*peer.AddrInfo, ph *PeerStore) {
- actual := ph.GetAddresses(len(set), PhoneBookEntryRelayRole)
+ actual := ph.GetAddresses(len(set), phonebook.RelayRole)
for _, info := range actual {
ok := false
for _, known := range set {
@@ -125,7 +118,7 @@ func testPhonebookUniform(t *testing.T, set []*peer.AddrInfo, ph *PeerStore, get
counts[set[i].ID.String()] = 0
}
for i := 0; i < uniformityTestLength; i++ {
- actual := ph.GetAddresses(getsize, PhoneBookEntryRelayRole)
+ actual := ph.GetAddresses(getsize, phonebook.RelayRole)
for _, info := range actual {
if _, ok := counts[info.ID.String()]; ok {
counts[info.ID.String()]++
@@ -161,7 +154,7 @@ func TestArrayPhonebookAll(t *testing.T) {
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
for _, addr := range set {
- entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ entry := makePhonebookEntryData("", phonebook.RelayRole, false)
info, _ := peerInfoFromDomainPort(addr)
ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL)
ph.Put(info.ID, addressDataKey, entry)
@@ -183,7 +176,7 @@ func TestArrayPhonebookUniform1(t *testing.T) {
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
for _, addr := range set {
- entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ entry := makePhonebookEntryData("", phonebook.RelayRole, false)
info, _ := peerInfoFromDomainPort(addr)
ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL)
ph.Put(info.ID, addressDataKey, entry)
@@ -205,7 +198,7 @@ func TestArrayPhonebookUniform3(t *testing.T) {
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
for _, addr := range set {
- entry := makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ entry := makePhonebookEntryData("", phonebook.RelayRole, false)
info, _ := peerInfoFromDomainPort(addr)
ph.AddAddrs(info.ID, info.Addrs, libp2p.AddressTTL)
ph.Put(info.ID, addressDataKey, entry)
@@ -223,19 +216,12 @@ func TestMultiPhonebook(t *testing.T) {
require.NoError(t, err)
infoSet = append(infoSet, info)
}
- pha := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[:5] {
- pha = append(pha, e)
- }
- phb := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[5:] {
- phb = append(phb, e)
- }
-
+ pha := append([]*peer.AddrInfo{}, infoSet[:5]...)
+ phb := append([]*peer.AddrInfo{}, infoSet[5:]...)
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
- ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ ph.ReplacePeerList(pha, "pha", phonebook.RelayRole)
+ ph.ReplacePeerList(phb, "phb", phonebook.RelayRole)
testPhonebookAll(t, infoSet, ph)
testPhonebookUniform(t, infoSet, ph, 1)
@@ -258,23 +244,17 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) {
infoSet = append(infoSet, info)
}
- pha := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[:5] {
- pha = append(pha, e)
- }
- phb := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[5:] {
- phb = append(phb, e)
- }
+ pha := append([]*peer.AddrInfo{}, infoSet[:5]...)
+ phb := append([]*peer.AddrInfo{}, infoSet[5:]...)
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
- ph.AddPersistentPeers(persistentPeers, "pha", PhoneBookEntryRelayRole)
- ph.AddPersistentPeers(persistentPeers, "phb", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ ph.AddPersistentPeers(persistentPeers, "pha", phonebook.RelayRole)
+ ph.AddPersistentPeers(persistentPeers, "phb", phonebook.RelayRole)
+ ph.ReplacePeerList(pha, "pha", phonebook.RelayRole)
+ ph.ReplacePeerList(phb, "phb", phonebook.RelayRole)
testPhonebookAll(t, append(infoSet, info), ph)
- allAddresses := ph.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole)
+ allAddresses := ph.GetAddresses(len(set)+len(persistentPeers), phonebook.RelayRole)
for _, pp := range persistentPeers {
found := false
for _, addr := range allAddresses {
@@ -285,6 +265,29 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) {
}
require.True(t, found, fmt.Sprintf("%s not found in %v", string(pp.ID), allAddresses))
}
+
+ // check that role of persistent peer gets updated with AddPersistentPeers
+ ph2, err := MakePhonebook(1, 1*time.Millisecond)
+ require.NoError(t, err)
+ ph2.AddPersistentPeers(persistentPeers, "phc", phonebook.RelayRole)
+ ph2.AddPersistentPeers(persistentPeers, "phc", phonebook.ArchivalRole)
+ allAddresses = ph2.GetAddresses(len(set)+len(persistentPeers), phonebook.RelayRole)
+ require.Len(t, allAddresses, 1)
+ allAddresses = ph2.GetAddresses(len(set)+len(persistentPeers), phonebook.ArchivalRole)
+ require.Len(t, allAddresses, 1)
+
+ // check that role of persistent peer survives
+ ph3, err := MakePhonebook(1, 1*time.Millisecond)
+ ph3.AddPersistentPeers(persistentPeers, "phc", phonebook.ArchivalRole)
+ require.NoError(t, err)
+ phc := []*peer.AddrInfo{info}
+ ph3.ReplacePeerList(phc, "phc", phonebook.RelayRole)
+
+ allAddresses = ph3.GetAddresses(len(set)+len(persistentPeers), phonebook.RelayRole)
+ require.Len(t, allAddresses, 1)
+ allAddresses = ph3.GetAddresses(len(set)+len(persistentPeers), phonebook.ArchivalRole)
+ require.Len(t, allAddresses, 1)
+
}
func TestMultiPhonebookDuplicateFiltering(t *testing.T) {
@@ -298,18 +301,12 @@ func TestMultiPhonebookDuplicateFiltering(t *testing.T) {
infoSet = append(infoSet, info)
}
- pha := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[:7] {
- pha = append(pha, e)
- }
- phb := make([]*peer.AddrInfo, 0)
- for _, e := range infoSet[3:] {
- phb = append(phb, e)
- }
+ pha := append([]*peer.AddrInfo{}, infoSet[:7]...)
+ phb := append([]*peer.AddrInfo{}, infoSet[3:]...)
ph, err := MakePhonebook(1, 1*time.Millisecond)
require.NoError(t, err)
- ph.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ ph.ReplacePeerList(pha, "pha", phonebook.RelayRole)
+ ph.ReplacePeerList(phb, "phb", phonebook.RelayRole)
testPhonebookAll(t, infoSet, ph)
testPhonebookUniform(t, infoSet, ph, 1)
@@ -338,7 +335,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
// Test the addresses are populated in the phonebook and a
// time can be added to one of them
- entries.ReplacePeerList([]*peer.AddrInfo{info1, info2}, "default", PhoneBookEntryRelayRole)
+ entries.ReplacePeerList([]*peer.AddrInfo{info1, info2}, "default", phonebook.RelayRole)
addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(string(info1.ID))
require.Equal(t, true, addrInPhonebook)
require.Equal(t, time.Duration(0), waitTime)
@@ -355,7 +352,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
}
// add another value to addr
- addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID))
+ _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID))
require.Equal(t, time.Duration(0), waitTime)
require.Equal(t, true, entries.UpdateConnectionTime(string(info1.ID), provisionalTime))
data, _ = entries.Get(info1.ID, addressDataKey)
@@ -370,7 +367,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
// the first time should be removed and a new one added
// there should not be any wait
- addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID))
+ _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info1.ID))
require.Equal(t, time.Duration(0), waitTime)
require.Equal(t, true, entries.UpdateConnectionTime(string(info1.ID), provisionalTime))
data, _ = entries.Get(info1.ID, addressDataKey)
@@ -416,7 +413,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
require.Equal(t, 3, len(phBookData))
// add another element to trigger wait
- _, waitTime, provisionalTime = entries.GetConnectionWaitTime(string(info2.ID))
+ _, waitTime, _ = entries.GetConnectionWaitTime(string(info2.ID))
require.Greater(t, int64(waitTime), int64(0))
// no element should be removed
data2, _ = entries.Get(info2.ID, addressDataKey)
@@ -469,20 +466,20 @@ func TestPhonebookRoles(t *testing.T) {
ph, err := MakePhonebook(1, 1)
require.NoError(t, err)
- ph.ReplacePeerList(infoRelaySet, "default", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(infoArchiverSet, "default", PhoneBookEntryArchiverRole)
+ ph.ReplacePeerList(infoRelaySet, "default", phonebook.RelayRole)
+ ph.ReplacePeerList(infoArchiverSet, "default", phonebook.ArchivalRole)
require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.Peers()))
require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length())
- for _, role := range []phonebook.PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchiverRole} {
+ for _, role := range []phonebook.Role{phonebook.RelayRole, phonebook.ArchivalRole} {
for k := 0; k < 100; k++ {
for l := 0; l < 3; l++ {
entries := ph.GetAddresses(l, role)
- if role == PhoneBookEntryRelayRole {
+ if role == phonebook.RelayRole {
for _, entry := range entries {
require.Contains(t, string(entry.ID), "relay")
}
- } else if role == PhoneBookEntryArchiverRole {
+ } else if role == phonebook.ArchivalRole {
for _, entry := range entries {
require.Contains(t, string(entry.ID), "archiver")
}
@@ -491,3 +488,125 @@ func TestPhonebookRoles(t *testing.T) {
}
}
}
+
+// TestPhonebookRolesMulti makes sure the same host might have multiple roles
+func TestPhonebookRolesMulti(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ relaysSet := []string{"relay1:4040", "relay2:4041"}
+ archiverSet := []string{"relay1:4040", "archiver1:1111"}
+ const numUnique = 3
+
+ infoRelaySet := make([]*peer.AddrInfo, 0)
+ for _, addr := range relaysSet {
+ info, err := peerInfoFromDomainPort(addr)
+ require.NoError(t, err)
+ infoRelaySet = append(infoRelaySet, info)
+ }
+
+ infoArchiverSet := make([]*peer.AddrInfo, 0)
+ for _, addr := range archiverSet {
+ info, err := peerInfoFromDomainPort(addr)
+ require.NoError(t, err)
+ infoArchiverSet = append(infoArchiverSet, info)
+ }
+
+ ph, err := MakePhonebook(1, 1)
+ require.NoError(t, err)
+ ph.ReplacePeerList(infoRelaySet, "default", phonebook.RelayRole)
+ ph.ReplacePeerList(infoArchiverSet, "default", phonebook.ArchivalRole)
+ require.Equal(t, numUnique, len(ph.Peers()))
+ require.Equal(t, numUnique, ph.Length())
+
+ const maxPeers = 5
+ entries := ph.GetAddresses(maxPeers, phonebook.RelayRole)
+ require.Equal(t, len(relaysSet), len(entries))
+ entries = ph.GetAddresses(maxPeers, phonebook.ArchivalRole)
+ require.Equal(t, len(archiverSet), len(entries))
+}
+
+func TestReplacePeerList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ relaysSet := []string{"a:1", "b:2"}
+ archiverSet := []string{"c:3"}
+ comboSet := []string{"b:2", "c:3"} // b is in both sets
+
+ infoRelaySet := make([]*peer.AddrInfo, 0)
+ for _, addr := range relaysSet {
+ info, err := peerInfoFromDomainPort(addr)
+ require.NoError(t, err)
+ infoRelaySet = append(infoRelaySet, info)
+ }
+
+ infoArchiverSet := make([]*peer.AddrInfo, 0)
+ for _, addr := range archiverSet {
+ info, err := peerInfoFromDomainPort(addr)
+ require.NoError(t, err)
+ infoArchiverSet = append(infoArchiverSet, info)
+ }
+
+ infoComboArchiverSet := make([]*peer.AddrInfo, 0)
+ for _, addr := range comboSet {
+ info, err := peerInfoFromDomainPort(addr)
+ require.NoError(t, err)
+ infoComboArchiverSet = append(infoComboArchiverSet, info)
+ }
+
+ ph, err := MakePhonebook(1, 1)
+ require.NoError(t, err)
+
+ ph.ReplacePeerList(infoRelaySet, "default", phonebook.RelayRole)
+ res := ph.GetAddresses(4, phonebook.RelayRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoRelaySet {
+ require.Contains(t, res, info)
+ }
+
+ ph.ReplacePeerList(infoArchiverSet, "default", phonebook.ArchivalRole)
+ res = ph.GetAddresses(4, phonebook.ArchivalRole)
+ require.Equal(t, 1, len(res))
+ for _, info := range infoArchiverSet {
+ require.Contains(t, res, info)
+ }
+
+ // make b archival in addition to relay
+ ph.ReplacePeerList(infoComboArchiverSet, "default", phonebook.ArchivalRole)
+ res = ph.GetAddresses(4, phonebook.RelayRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoRelaySet {
+ require.Contains(t, res, info)
+ }
+ res = ph.GetAddresses(4, phonebook.ArchivalRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoComboArchiverSet {
+ require.Contains(t, res, info)
+ }
+
+ // update relays
+ ph.ReplacePeerList(infoRelaySet, "default", phonebook.RelayRole)
+ res = ph.GetAddresses(4, phonebook.RelayRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoRelaySet {
+ require.Contains(t, res, info)
+ }
+ res = ph.GetAddresses(4, phonebook.ArchivalRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoComboArchiverSet {
+ require.Contains(t, res, info)
+ }
+
+ // exclude b from archival
+ ph.ReplacePeerList(infoArchiverSet, "default", phonebook.ArchivalRole)
+ res = ph.GetAddresses(4, phonebook.RelayRole)
+ require.Equal(t, 2, len(res))
+ for _, info := range infoRelaySet {
+ require.Contains(t, res, info)
+ }
+ res = ph.GetAddresses(4, phonebook.ArchivalRole)
+ require.Equal(t, 1, len(res))
+ for _, info := range infoArchiverSet {
+ require.Contains(t, res, info)
+ }
+}
diff --git a/network/p2p/streams.go b/network/p2p/streams.go
index f271f00ffa..b44c48c6e3 100644
--- a/network/p2p/streams.go
+++ b/network/p2p/streams.go
@@ -18,6 +18,7 @@ package p2p
import (
"context"
+ "fmt"
"io"
"github.com/algorand/go-algorand/logging"
@@ -25,6 +26,7 @@ import (
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
+ "github.com/libp2p/go-libp2p/core/protocol"
"github.com/multiformats/go-multiaddr"
)
@@ -33,7 +35,7 @@ type streamManager struct {
ctx context.Context
log logging.Logger
host host.Host
- handler StreamHandler
+ handlers StreamHandlers
allowIncomingGossip bool
streams map[peer.ID]network.Stream
@@ -43,12 +45,12 @@ type streamManager struct {
// StreamHandler is called when a new bidirectional stream for a given protocol and peer is opened.
type StreamHandler func(ctx context.Context, pid peer.ID, s network.Stream, incoming bool)
-func makeStreamManager(ctx context.Context, log logging.Logger, h host.Host, handler StreamHandler, allowIncomingGossip bool) *streamManager {
+func makeStreamManager(ctx context.Context, log logging.Logger, h host.Host, handlers StreamHandlers, allowIncomingGossip bool) *streamManager {
return &streamManager{
ctx: ctx,
log: log,
host: h,
- handler: handler,
+ handlers: handlers,
allowIncomingGossip: allowIncomingGossip,
streams: make(map[peer.ID]network.Stream),
}
@@ -83,7 +85,10 @@ func (n *streamManager) streamHandler(stream network.Stream) {
n.streams[stream.Conn().RemotePeer()] = stream
incoming := stream.Conn().Stat().Direction == network.DirInbound
- n.handler(n.ctx, remotePeer, stream, incoming)
+ if err1 := n.dispatch(n.ctx, remotePeer, stream, incoming); err1 != nil {
+ n.log.Errorln(err1.Error())
+ _ = stream.Reset()
+ }
return
}
// otherwise, the old stream is still open, so we can close the new one
@@ -93,51 +98,71 @@ func (n *streamManager) streamHandler(stream network.Stream) {
// no old stream
n.streams[stream.Conn().RemotePeer()] = stream
incoming := stream.Conn().Stat().Direction == network.DirInbound
- n.handler(n.ctx, remotePeer, stream, incoming)
+ if err := n.dispatch(n.ctx, remotePeer, stream, incoming); err != nil {
+ n.log.Errorln(err.Error())
+ _ = stream.Reset()
+ }
+}
+
+// dispatch the stream to the appropriate handler
+func (n *streamManager) dispatch(ctx context.Context, remotePeer peer.ID, stream network.Stream, incoming bool) error {
+ for _, pair := range n.handlers {
+ if pair.ProtoID == stream.Protocol() {
+ pair.Handler(ctx, remotePeer, stream, incoming)
+ return nil
+ }
+ }
+ n.log.Errorf("No handler for protocol %s, peer %s", stream.Protocol(), remotePeer)
+ return fmt.Errorf("%s: no handler for protocol %s, peer %s", n.host.ID().String(), stream.Protocol(), remotePeer)
}
// Connected is called when a connection is opened
// for both incoming (listener -> addConn) and outgoing (dialer -> addConn) connections.
func (n *streamManager) Connected(net network.Network, conn network.Conn) {
- if conn.Stat().Direction == network.DirInbound && !n.allowIncomingGossip {
- n.log.Debugf("ignoring incoming connection from %s", conn.RemotePeer().String())
- return
- }
remotePeer := conn.RemotePeer()
localPeer := n.host.ID()
+ if conn.Stat().Direction == network.DirInbound && !n.allowIncomingGossip {
+ n.log.Debugf("%s: ignoring incoming connection from %s", localPeer.String(), remotePeer.String())
+ return
+ }
+
// ensure that only one of the peers initiates the stream
if localPeer > remotePeer {
+ n.log.Debugf("%s: ignoring a lesser peer ID %s", localPeer.String(), remotePeer.String())
return
}
- needUnlock := true
n.streamsLock.Lock()
- defer func() {
- if needUnlock {
- n.streamsLock.Unlock()
- }
- }()
_, ok := n.streams[remotePeer]
if ok {
+ n.streamsLock.Unlock()
+ n.log.Debugf("%s: already have a stream to/from %s", localPeer.String(), remotePeer.String())
return // there's already an active stream with this peer for our protocol
}
- stream, err := n.host.NewStream(n.ctx, remotePeer, AlgorandWsProtocol)
+ protos := []protocol.ID{}
+ for _, pair := range n.handlers {
+ protos = append(protos, pair.ProtoID)
+ }
+ stream, err := n.host.NewStream(n.ctx, remotePeer, protos...)
if err != nil {
- n.log.Infof("Failed to open stream to %s (%s): %v", remotePeer, conn.RemoteMultiaddr().String(), err)
+ n.log.Infof("%s: failed to open stream to %s (%s): %v", localPeer.String(), remotePeer, conn.RemoteMultiaddr().String(), err)
+ n.streamsLock.Unlock()
return
}
n.streams[remotePeer] = stream
-
- // release the lock to let handler do its thing
- // otherwise reading/writing to the stream will deadlock
- needUnlock = false
n.streamsLock.Unlock()
+ n.log.Infof("%s: using protocol %s with peer %s", localPeer.String(), stream.Protocol(), remotePeer.String())
+
incoming := stream.Conn().Stat().Direction == network.DirInbound
- n.handler(n.ctx, remotePeer, stream, incoming)
+ err = n.dispatch(n.ctx, remotePeer, stream, incoming)
+ if err != nil {
+ n.log.Errorln(err.Error())
+ _ = stream.Reset()
+ }
}
// Disconnected is called when a connection is closed
diff --git a/network/p2pMetainfo.go b/network/p2pMetainfo.go
new file mode 100644
index 0000000000..02a931d259
--- /dev/null
+++ b/network/p2pMetainfo.go
@@ -0,0 +1,126 @@
+// Copyright (C) 2019-2025 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 network
+
+import (
+ "encoding/binary"
+ "fmt"
+ "io"
+ "math"
+ "net/http"
+
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/libp2p/go-libp2p/core/peer"
+)
+
+// peerMetaHeaders holds peer metadata headers similar to wsnet http.Header
+// due to msgp allocbound enforcement we need to limit the number of headers and values
+// but it is cannot be done without msgp modification to accept both map and slice allocbound
+// so that introduce a service peerMetaValues type to have allocbound set and msgp generator satisfied.
+
+const maxHeaderKeys = 64
+const maxHeaderValues = 16
+
+// SortString is a type that implements sort.Interface for sorting strings
+type SortString = basics.SortString
+
+//msgp:allocbound peerMetaValues maxHeaderValues
+type peerMetaValues []string
+
+//msgp:allocbound peerMetaHeaders maxHeaderKeys
+type peerMetaHeaders map[string]peerMetaValues
+
+func peerMetaHeadersToHTTPHeaders(headers peerMetaHeaders) http.Header {
+ httpHeaders := make(http.Header, len(headers))
+ for k, v := range headers {
+ httpHeaders[k] = v
+ }
+ return httpHeaders
+}
+
+func peerMetaHeadersFromHTTPHeaders(headers http.Header) peerMetaHeaders {
+ pmh := make(peerMetaHeaders, len(headers))
+ for k, v := range headers {
+ pmh[k] = v
+ }
+ return pmh
+}
+
+type peerMetaInfo struct {
+ telemetryID string
+ instanceName string
+ version string
+ features string
+}
+
+func readPeerMetaHeaders(stream io.ReadWriter, p2pPeer peer.ID, netProtoSupportedVersions []string) (peerMetaInfo, error) {
+ var msgLenBytes [2]byte
+ rn, err := stream.Read(msgLenBytes[:])
+ if rn != 2 || err != nil {
+ err0 := fmt.Errorf("error reading response message length from peer %s: %w", p2pPeer, err)
+ return peerMetaInfo{}, err0
+ }
+
+ msgLen := binary.BigEndian.Uint16(msgLenBytes[:])
+ msgBytes := make([]byte, msgLen)
+ rn, err = stream.Read(msgBytes[:])
+ if rn != int(msgLen) || err != nil {
+ err0 := fmt.Errorf("error reading response message from peer %s: %w, expected: %d, read: %d", p2pPeer, err, msgLen, rn)
+ return peerMetaInfo{}, err0
+ }
+ var responseHeaders peerMetaHeaders
+ _, err = responseHeaders.UnmarshalMsg(msgBytes[:])
+ if err != nil {
+ err0 := fmt.Errorf("error unmarshaling response message from peer %s: %w", p2pPeer, err)
+ return peerMetaInfo{}, err0
+ }
+ headers := peerMetaHeadersToHTTPHeaders(responseHeaders)
+ matchingVersion, _ := checkProtocolVersionMatch(headers, netProtoSupportedVersions)
+ if matchingVersion == "" {
+ err0 := fmt.Errorf("peer %s does not support any of the supported protocol versions: %v", p2pPeer, netProtoSupportedVersions)
+ return peerMetaInfo{}, err0
+ }
+ return peerMetaInfo{
+ telemetryID: headers.Get(TelemetryIDHeader),
+ instanceName: headers.Get(InstanceNameHeader),
+ version: matchingVersion,
+ features: headers.Get(PeerFeaturesHeader),
+ }, nil
+}
+
+func writePeerMetaHeaders(stream io.ReadWriter, p2pPeer peer.ID, networkProtoVersion string, pmp peerMetadataProvider) error {
+ header := make(http.Header)
+ setHeaders(header, networkProtoVersion, pmp)
+ meta := peerMetaHeadersFromHTTPHeaders(header)
+ data := meta.MarshalMsg(nil)
+ length := len(data)
+ if length > math.MaxUint16 {
+ // 64k is enough for everyone
+ // current headers size is 250 bytes
+ msg := fmt.Sprintf("error writing initial message, too large: %v, peer %s", header, p2pPeer)
+ panic(msg)
+ }
+ metaMsg := make([]byte, 2+length)
+ binary.BigEndian.PutUint16(metaMsg, uint16(length))
+ copy(metaMsg[2:], data)
+ _, err := stream.Write(metaMsg)
+ if err != nil {
+ err0 := fmt.Errorf("error sending initial message: %w", err)
+ return err0
+ }
+ return nil
+}
diff --git a/network/p2pMetainfo_test.go b/network/p2pMetainfo_test.go
new file mode 100644
index 0000000000..08e8ada943
--- /dev/null
+++ b/network/p2pMetainfo_test.go
@@ -0,0 +1,177 @@
+// Copyright (C) 2019-2025 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 network
+
+import (
+ "encoding/binary"
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/algorand/go-algorand/logging"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/libp2p/go-libp2p/core/peer"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+// MockStream is a io.ReaderWriter testing mock
+type MockStream struct {
+ mock.Mock
+}
+
+func (m *MockStream) Read(p []byte) (n int, err error) {
+ args := m.Called(p)
+ arg0 := args.Get(0).([]byte)
+ copy(p, arg0)
+ return len(arg0), args.Error(1)
+}
+
+func (m *MockStream) Write(p []byte) (n int, err error) {
+ args := m.Called(p)
+ return args.Int(0), args.Error(1)
+}
+
+func TestReadPeerMetaHeaders(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ mockStream := new(MockStream)
+ p2pPeer := peer.ID("mockPeer")
+ n := &P2PNetwork{
+ log: logging.Base(),
+ supportedProtocolVersions: []string{"1.0", "2.2"},
+ }
+
+ httpHeaders := make(http.Header)
+ httpHeaders.Set(TelemetryIDHeader, "mockTelemetryID")
+ httpHeaders.Set(InstanceNameHeader, "mockInstanceName")
+ httpHeaders.Set(ProtocolVersionHeader, "1.0")
+ httpHeaders.Set(ProtocolAcceptVersionHeader, "1.0")
+ httpHeaders.Set(PeerFeaturesHeader, "mockFeatures")
+ headers := peerMetaHeadersFromHTTPHeaders(httpHeaders)
+ data := headers.MarshalMsg(nil)
+ length := uint16(len(data))
+ lengthBytes := make([]byte, 2)
+ binary.BigEndian.PutUint16(lengthBytes, length)
+
+ mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once()
+ mockStream.On("Read", mock.Anything).Return(data, nil).Once()
+
+ metaInfo, err := readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.NoError(t, err)
+ assert.Equal(t, "mockTelemetryID", metaInfo.telemetryID)
+ assert.Equal(t, "mockInstanceName", metaInfo.instanceName)
+ assert.Equal(t, "1.0", metaInfo.version)
+ assert.Equal(t, "mockFeatures", metaInfo.features)
+ mockStream.AssertExpectations(t)
+
+ // Error case: incomplete length read
+ mockStream = new(MockStream)
+ mockStream.On("Read", mock.Anything).Return([]byte{1}, nil).Once()
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error reading response message length")
+ mockStream.AssertExpectations(t)
+
+ // Error case: error reading length
+ mockStream = new(MockStream)
+ mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("read error")).Once()
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error reading response message length")
+ mockStream.AssertExpectations(t)
+
+ // Error case: incomplete message read
+ mockStream = new(MockStream)
+ mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once()
+ mockStream.On("Read", mock.Anything).Return(data[:len(data)/2], nil).Once() // Return only half the data
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error reading response message")
+ mockStream.AssertExpectations(t)
+
+ // Error case: error reading message
+ mockStream = new(MockStream)
+ mockStream.On("Read", mock.Anything).Return(lengthBytes, nil).Once()
+ mockStream.On("Read", mock.Anything).Return([]byte{}, fmt.Errorf("read error")).Once()
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error reading response message")
+ mockStream.AssertExpectations(t)
+
+ // Error case: invalid messagepack (unmarshaling error)
+ mockStream = new(MockStream)
+ corruptedMsgpLength := make([]byte, 2)
+ binary.BigEndian.PutUint16(corruptedMsgpLength, uint16(3))
+ mockStream.On("Read", mock.Anything).Return(corruptedMsgpLength, nil).Once()
+ mockStream.On("Read", mock.Anything).Return([]byte{0x99, 0x01, 0x02}, nil).Once()
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error unmarshaling response message")
+ mockStream.AssertExpectations(t)
+
+ // Error case: no matching protocol version
+ mockStream = new(MockStream)
+ incompatibleHeaders := make(http.Header)
+ incompatibleHeaders.Set(ProtocolVersionHeader, "99.0") // Unsupported version
+ incompatibleHeaders.Set(ProtocolAcceptVersionHeader, "99.0")
+ incompatibleData := peerMetaHeadersFromHTTPHeaders(incompatibleHeaders).MarshalMsg(nil)
+ incompatibleLength := uint16(len(incompatibleData))
+ incompatibleLengthBytes := make([]byte, 2)
+ binary.BigEndian.PutUint16(incompatibleLengthBytes, incompatibleLength)
+
+ mockStream.On("Read", mock.Anything).Return(incompatibleLengthBytes, nil).Once()
+ mockStream.On("Read", mock.Anything).Return(incompatibleData, nil).Once()
+ _, err = readPeerMetaHeaders(mockStream, p2pPeer, n.supportedProtocolVersions)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "does not support any of the supported protocol versions")
+ mockStream.AssertExpectations(t)
+}
+
+func TestWritePeerMetaHeaders(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ mockStream := new(MockStream)
+ p2pPeer := peer.ID("mockPeer")
+ n := &P2PNetwork{
+ log: logging.Base(),
+ }
+
+ header := make(http.Header)
+ setHeaders(header, "1.0", n)
+ meta := peerMetaHeadersFromHTTPHeaders(header)
+ data := meta.MarshalMsg(nil)
+ length := uint16(len(data))
+ lengthBytes := make([]byte, 2)
+ binary.BigEndian.PutUint16(lengthBytes, length)
+
+ mockStream.On("Write", append(lengthBytes, data...)).Return(len(lengthBytes)+len(data), nil).Once()
+
+ err := writePeerMetaHeaders(mockStream, p2pPeer, "1.0", n)
+ assert.NoError(t, err)
+ mockStream.AssertExpectations(t)
+
+ // Error case: write error
+ mockStream = new(MockStream)
+ mockStream.On("Write", mock.Anything).Return(0, fmt.Errorf("write error")).Once()
+ err = writePeerMetaHeaders(mockStream, p2pPeer, "1.0", n)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "error sending initial message")
+ mockStream.AssertExpectations(t)
+}
diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go
index 92a8241932..f6408e75ec 100644
--- a/network/p2pNetwork.go
+++ b/network/p2pNetwork.go
@@ -45,6 +45,12 @@ import (
manet "github.com/multiformats/go-multiaddr/net"
)
+// some arbitrary number TODO: figure out a better value based on peerSelector/fetcher algorithm
+const numArchivalPeersToFind = 4
+
+// disableV22Protocol is a flag for testing in order to test v1 node can communicate with v1 + v22 node
+var disableV22Protocol = false
+
// P2PNetwork implements the GossipNode interface
type P2PNetwork struct {
service p2p.Service
@@ -84,6 +90,13 @@ type P2PNetwork struct {
httpServer *p2p.HTTPServer
identityTracker identityTracker
+
+ // supportedProtocolVersions defines versions supported by this network.
+ // Should be used instead of a global network.SupportedProtocolVersions for network/peers configuration
+ supportedProtocolVersions []string
+
+ // protocolVersion is an actual version announced as ProtocolVersionHeader
+ protocolVersion string
}
type bootstrapper struct {
@@ -255,6 +268,16 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo
net.identityTracker = noopIdentityTracker{}
}
+ // set our supported versions
+ if net.config.NetworkProtocolVersion != "" {
+ net.supportedProtocolVersions = []string{net.config.NetworkProtocolVersion}
+ } else {
+ net.supportedProtocolVersions = SupportedProtocolVersions
+ }
+
+ // set our actual version
+ net.protocolVersion = ProtocolVersion
+
err = p2p.EnableP2PLogging(log, logging.Level(cfg.BaseLoggerDebugLevel))
if err != nil {
return nil, err
@@ -266,7 +289,21 @@ func NewP2PNetwork(log logging.Logger, cfg config.Local, datadir string, phonebo
}
log.Infof("P2P host created: peer ID %s addrs %s", h.ID(), h.Addrs())
- net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, net.wsStreamHandler, pubsubMetricsTracer{})
+ // TODO: remove after consensus v41 takes effect.
+ // ordered list of supported protocol versions
+ hm := p2p.StreamHandlers{}
+ if !disableV22Protocol {
+ hm = append(hm, p2p.StreamHandlerPair{
+ ProtoID: p2p.AlgorandWsProtocolV22,
+ Handler: net.wsStreamHandlerV22,
+ })
+ }
+ hm = append(hm, p2p.StreamHandlerPair{
+ ProtoID: p2p.AlgorandWsProtocolV1,
+ Handler: net.wsStreamHandlerV1,
+ })
+ // END TODO
+ net.service, err = p2p.MakeService(net.ctx, log, cfg, h, la, hm, pubsubMetricsTracer{})
if err != nil {
return nil, err
}
@@ -434,15 +471,31 @@ func (n *P2PNetwork) meshThreadInner() int {
n.log.Warnf("Error getting relay nodes from capabilities discovery: %v", err)
}
n.log.Debugf("Discovered %d gossip peers from DHT", len(dhtPeers))
+
+ // also discover archival nodes
+ var dhtArchivalPeers []peer.AddrInfo
+ dhtArchivalPeers, err = n.capabilitiesDiscovery.PeersForCapability(p2p.Archival, numArchivalPeersToFind)
+ if err != nil {
+ n.log.Warnf("Error getting archival nodes from capabilities discovery: %v", err)
+ }
+ n.log.Debugf("Discovered %d archival peers from DHT", len(dhtArchivalPeers))
+
+ if len(dhtArchivalPeers) > 0 {
+ replace := make([]*peer.AddrInfo, len(dhtArchivalPeers))
+ for i := range dhtArchivalPeers {
+ replace[i] = &dhtArchivalPeers[i]
+ }
+ n.pstore.ReplacePeerList(replace, string(n.networkID), phonebook.ArchivalRole)
+ }
}
peers := mergeP2PAddrInfoResolvedAddresses(dnsPeers, dhtPeers)
- replace := make([]*peer.AddrInfo, 0, len(peers))
+ replace := make([]*peer.AddrInfo, len(peers))
for i := range peers {
- replace = append(replace, &peers[i])
+ replace[i] = &peers[i]
}
if len(peers) > 0 {
- n.pstore.ReplacePeerList(replace, string(n.networkID), phonebook.PhoneBookEntryRelayRole)
+ n.pstore.ReplacePeerList(replace, string(n.networkID), phonebook.RelayRole)
}
return len(peers)
}
@@ -532,7 +585,7 @@ func (n *P2PNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byt
return n.service.Publish(ctx, topic, data)
}
// Otherwise broadcast over websocket protocol stream
- return n.broadcaster.BroadcastArray(ctx, []protocol.Tag{tag}, [][]byte{data}, wait, except)
+ return n.broadcaster.broadcast(ctx, tag, data, wait, except)
}
// Relay message
@@ -640,7 +693,7 @@ func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer {
n.wsPeersLock.RUnlock()
case PeersPhonebookRelays:
const maxNodes = 100
- addrInfos := n.pstore.GetAddresses(maxNodes, phonebook.PhoneBookEntryRelayRole)
+ addrInfos := n.pstore.GetAddresses(maxNodes, phonebook.RelayRole)
for _, peerInfo := range addrInfos {
if peerInfo.ID == n.service.ID() {
continue
@@ -657,30 +710,22 @@ func (n *P2PNetwork) GetPeers(options ...PeerOption) []Peer {
n.log.Debugf("Relay node(s) from peerstore: %v", addrs)
}
case PeersPhonebookArchivalNodes:
- // query known archival nodes from DHT if enabled
- if n.config.EnableDHTProviders {
- const nodesToFind = 5
- infos, err := n.capabilitiesDiscovery.PeersForCapability(p2p.Archival, nodesToFind)
- if err != nil {
- n.log.Warnf("Error getting archival nodes from capabilities discovery: %v", err)
- return peers
+ // query known archival nodes that came from from DHT if enabled (or DNS if configured)
+ addrInfos := n.pstore.GetAddresses(numArchivalPeersToFind, phonebook.ArchivalRole)
+ for _, peerInfo := range addrInfos {
+ if peerInfo.ID == n.service.ID() {
+ continue
}
- n.log.Debugf("Got %d archival node(s) from DHT", len(infos))
- for _, addrInfo := range infos {
- if addrInfo.ID == n.service.ID() {
- continue
- }
- if peerCore, ok := addrInfoToWsPeerCore(n, &addrInfo); ok {
- peers = append(peers, &peerCore)
- }
+ if peerCore, ok := addrInfoToWsPeerCore(n, peerInfo); ok {
+ peers = append(peers, &peerCore)
}
- if n.log.GetLevel() >= logging.Debug && len(peers) > 0 {
- addrs := make([]string, 0, len(peers))
- for _, peer := range peers {
- addrs = append(addrs, peer.(*wsPeerCore).GetAddress())
- }
- n.log.Debugf("Archival node(s) from DHT: %v", addrs)
+ }
+ if n.log.GetLevel() >= logging.Debug && len(peers) > 0 {
+ addrs := make([]string, 0, len(peers))
+ for _, peer := range peers {
+ addrs = append(addrs, peer.(*wsPeerCore).GetAddress())
}
+ n.log.Debugf("Archival node(s) from peerstore: %v", addrs)
}
case PeersConnectedIn:
n.wsPeersLock.RLock()
@@ -743,11 +788,47 @@ func (n *P2PNetwork) OnNetworkAdvance() {
}
}
+// TelemetryGUID returns the telemetry GUID of this node.
+func (n *P2PNetwork) TelemetryGUID() string {
+ return n.log.GetTelemetryGUID()
+}
+
+// InstanceName returns the instance name of this node.
+func (n *P2PNetwork) InstanceName() string {
+ return n.log.GetInstanceName()
+}
+
+// GenesisID returns the genesis ID of this node.
+func (n *P2PNetwork) GenesisID() string {
+ return n.genesisID
+}
+
+// SupportedProtoVersions returns the supported protocol versions of this node.
+func (n *P2PNetwork) SupportedProtoVersions() []string {
+ return n.supportedProtocolVersions
+}
+
+// RandomID satisfies the interface but is not used in P2PNetwork.
+func (n *P2PNetwork) RandomID() string {
+ return ""
+}
+
+// PublicAddress satisfies the interface but is not used in P2PNetwork.
+func (n *P2PNetwork) PublicAddress() string {
+ return ""
+}
+
+// Config returns the configuration of this node.
+func (n *P2PNetwork) Config() config.Local {
+ return n.config
+}
+
// wsStreamHandler is a callback that the p2p package calls when a new peer connects and establishes a
// stream for the websocket protocol.
-func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) {
- if stream.Protocol() != p2p.AlgorandWsProtocol {
- n.log.Warnf("unknown protocol %s from peer%s", stream.Protocol(), p2pPeer)
+// TODO: remove after consensus v41 takes effect.
+func (n *P2PNetwork) wsStreamHandlerV1(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) {
+ if stream.Protocol() != p2p.AlgorandWsProtocolV1 {
+ n.log.Warnf("unknown protocol %s from peer %s", stream.Protocol(), p2pPeer)
return
}
@@ -755,17 +836,60 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea
var initMsg [1]byte
rn, err := stream.Read(initMsg[:])
if rn == 0 || err != nil {
- n.log.Warnf("wsStreamHandler: error reading initial message: %s, peer %s (%s)", err, p2pPeer, stream.Conn().RemoteMultiaddr().String())
+ n.log.Warnf("wsStreamHandlerV1: error reading initial message from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err)
return
}
} else {
_, err := stream.Write([]byte("1"))
if err != nil {
- n.log.Warnf("wsStreamHandler: error sending initial message: %s", err)
+ n.log.Warnf("wsStreamHandlerV1: error sending initial message: %v", err)
+ return
+ }
+ }
+
+ n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, peerMetaInfo{})
+}
+
+func (n *P2PNetwork) wsStreamHandlerV22(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool) {
+ if stream.Protocol() != p2p.AlgorandWsProtocolV22 {
+ n.log.Warnf("unknown protocol %s from peer%s", stream.Protocol(), p2pPeer)
+ return
+ }
+
+ var err error
+ var pmi peerMetaInfo
+ if incoming {
+ pmi, err = readPeerMetaHeaders(stream, p2pPeer, n.supportedProtocolVersions)
+ if err != nil {
+ n.log.Warnf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err)
+ _ = stream.Reset()
+ return
+ }
+ err = writePeerMetaHeaders(stream, p2pPeer, pmi.version, n)
+ if err != nil {
+ n.log.Warnf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err)
+ _ = stream.Reset()
+ return
+ }
+ } else {
+ err = writePeerMetaHeaders(stream, p2pPeer, n.protocolVersion, n)
+ if err != nil {
+ n.log.Warnf("wsStreamHandlerV22: error writing peer meta headers response to peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err)
+ _ = stream.Reset()
+ return
+ }
+ // read the response
+ pmi, err = readPeerMetaHeaders(stream, p2pPeer, n.supportedProtocolVersions)
+ if err != nil {
+ n.log.Warnf("wsStreamHandlerV22: error reading peer meta headers response from peer %s (%s): %v", p2pPeer, stream.Conn().RemoteMultiaddr().String(), err)
+ _ = stream.Reset()
return
}
}
+ n.baseWsStreamHandler(ctx, p2pPeer, stream, incoming, pmi)
+}
+func (n *P2PNetwork) baseWsStreamHandler(ctx context.Context, p2pPeer peer.ID, stream network.Stream, incoming bool, pmi peerMetaInfo) {
// get address for peer ID
ma := stream.Conn().RemoteMultiaddr()
addr := ma.String()
@@ -792,17 +916,15 @@ func (n *P2PNetwork) wsStreamHandler(ctx context.Context, p2pPeer peer.ID, strea
}
peerCore := makePeerCore(ctx, n, n.log, n.handler.readBuffer, addr, client, addr)
wsp := &wsPeer{
- wsPeerCore: peerCore,
- conn: &wsPeerConnP2P{stream: stream},
- outgoing: !incoming,
- identity: netIdentPeerID,
- peerType: peerTypeP2P,
- }
- protos, err := n.pstore.GetProtocols(p2pPeer)
- if err != nil {
- n.log.Warnf("Error getting protocols for peer %s: %v", p2pPeer, err)
+ wsPeerCore: peerCore,
+ conn: &wsPeerConnP2P{stream: stream},
+ outgoing: !incoming,
+ identity: netIdentPeerID,
+ peerType: peerTypeP2P,
+ TelemetryGUID: pmi.telemetryID,
+ InstanceName: pmi.instanceName,
+ features: decodePeerFeatures(pmi.version, pmi.features),
}
- wsp.TelemetryGUID, wsp.InstanceName = p2p.GetPeerTelemetryInfo(protos)
localAddr, has := n.Address()
if !has {
@@ -927,24 +1049,33 @@ func (n *P2PNetwork) txTopicHandleLoop() {
}
n.log.Debugf("Subscribed to topic %s", p2p.TXTopicName)
- for {
- // msg from sub.Next not used since all work done by txTopicValidator
- _, err := sub.Next(n.ctx)
- if err != nil {
- if err != pubsub.ErrSubscriptionCancelled && err != context.Canceled {
- n.log.Errorf("Error reading from subscription %v, peerId %s", err, n.service.ID())
+ const threads = incomingThreads / 2 // perf tests showed that 10 (half of incomingThreads) was optimal in terms of TPS (attempted 1, 5, 10, 20)
+ var wg sync.WaitGroup
+ wg.Add(threads)
+ for i := 0; i < threads; i++ {
+ go func(ctx context.Context, sub p2p.SubNextCancellable, wantTXGossip *atomic.Bool, peerID peer.ID, log logging.Logger) {
+ defer wg.Done()
+ for {
+ // msg from sub.Next not used since all work done by txTopicValidator
+ _, err := sub.Next(ctx)
+ if err != nil {
+ if err != pubsub.ErrSubscriptionCancelled && err != context.Canceled {
+ log.Errorf("Error reading from subscription %v, peerId %s", err, peerID)
+ }
+ log.Debugf("Cancelling subscription to topic %s due Subscription.Next error: %v", p2p.TXTopicName, err)
+ sub.Cancel()
+ return
+ }
+ // participation or configuration change, cancel subscription and quit
+ if !wantTXGossip.Load() {
+ log.Debugf("Cancelling subscription to topic %s due to participation change", p2p.TXTopicName)
+ sub.Cancel()
+ return
+ }
}
- n.log.Debugf("Cancelling subscription to topic %s due Subscription.Next error: %v", p2p.TXTopicName, err)
- sub.Cancel()
- return
- }
- // participation or configuration change, cancel subscription and quit
- if !n.wantTXGossip.Load() {
- n.log.Debugf("Cancelling subscription to topic %s due participation change", p2p.TXTopicName)
- sub.Cancel()
- return
- }
+ }(n.ctx, sub, &n.wantTXGossip, n.service.ID(), n.log)
}
+ wg.Wait()
}
type gsPeer struct {
diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go
index 079bbbab03..78bf6b3848 100644
--- a/network/p2pNetwork_test.go
+++ b/network/p2pNetwork_test.go
@@ -40,6 +40,8 @@ import (
"github.com/algorand/go-algorand/network/phonebook"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/algorand/go-algorand/util/uuid"
+ "github.com/algorand/go-deadlock"
pubsub "github.com/libp2p/go-libp2p-pubsub"
pb "github.com/libp2p/go-libp2p-pubsub/pb"
@@ -105,7 +107,7 @@ func TestP2PSubmitTX(t *testing.T) {
)
require.Eventually(t, func() bool {
return netA.hasPeers() && netB.hasPeers() && netC.hasPeers()
- }, 2*time.Second, 50*time.Millisecond)
+ }, 5*time.Second, 50*time.Millisecond)
// for some reason the above check is not enough in race builds on CI
time.Sleep(time.Second) // give time for peers to connect.
@@ -198,7 +200,7 @@ func TestP2PSubmitTXNoGossip(t *testing.T) {
require.Eventually(t, func() bool {
return netA.hasPeers() && netB.hasPeers() && netC.hasPeers()
- }, 2*time.Second, 50*time.Millisecond)
+ }, 5*time.Second, 50*time.Millisecond)
time.Sleep(time.Second) // give time for peers to connect.
@@ -281,7 +283,7 @@ func TestP2PSubmitWS(t *testing.T) {
require.Eventually(t, func() bool {
return netA.hasPeers() && netB.hasPeers() && netC.hasPeers()
- }, 2*time.Second, 50*time.Millisecond)
+ }, 5*time.Second, 50*time.Millisecond)
time.Sleep(time.Second) // give time for peers to connect.
@@ -374,6 +376,12 @@ func (s *mockService) GetHTTPClient(addrInfo *peer.AddrInfo, connTimeStore limit
return nil, nil
}
+func (s *mockService) NetworkNotify(notifiee network.Notifiee) {
+}
+
+func (s *mockService) NetworkStopNotify(notifiee network.Notifiee) {
+}
+
func makeMockService(id peer.ID, addrs []ma.Multiaddr) *mockService {
return &mockService{
id: id,
@@ -598,7 +606,7 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
- netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, test.nis[0], nil)
+ netA, err := NewP2PNetwork(log.With("name", "netA"), cfg, "", nil, genesisID, config.Devtestnet, test.nis[0], nil)
require.NoError(t, err)
err = netA.Start()
@@ -612,13 +620,13 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) {
multiAddrStr := addrsA[0].String()
phoneBookAddresses := []string{multiAddrStr}
- netB, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[1], nil)
+ netB, err := NewP2PNetwork(log.With("name", "netB"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[1], nil)
require.NoError(t, err)
err = netB.Start()
require.NoError(t, err)
defer netB.Stop()
- netC, err := NewP2PNetwork(log, cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[2], nil)
+ netC, err := NewP2PNetwork(log.With("name", "netC"), cfg, "", phoneBookAddresses, genesisID, config.Devtestnet, test.nis[2], nil)
require.NoError(t, err)
err = netC.Start()
require.NoError(t, err)
@@ -680,9 +688,10 @@ func TestP2PNetworkDHTCapabilities(t *testing.T) {
fmt.Sprintf("Not all expected %s cap peers were found", cap),
)
// ensure GetPeers gets PeersPhonebookArchivalNodes peers
- // it appears there are artifical peers because of listening on localhost and on a real network interface
+ // it appears there are artificial peers because of listening on localhost and on a real network interface
// so filter out and save only unique peers by their IDs
net := nets[idx]
+ net.meshThreadInner() // update peerstore with DHT peers
peers := net.GetPeers(PeersPhonebookArchivalNodes)
uniquePeerIDs := make(map[peer.ID]struct{})
for _, p := range peers {
@@ -790,7 +799,7 @@ func TestP2PHTTPHandler(t *testing.T) {
require.NoError(t, err)
pstore, err := peerstore.MakePhonebook(0, 10*time.Second)
require.NoError(t, err)
- pstore.AddPersistentPeers([]*peer.AddrInfo{&peerInfoA}, "net", phonebook.PhoneBookEntryRelayRole)
+ pstore.AddPersistentPeers([]*peer.AddrInfo{&peerInfoA}, "net", phonebook.RelayRole)
httpClient, err = netB.service.GetHTTPClient(&peerInfoA, pstore, 1*time.Second)
require.NoError(t, err)
_, err = httpClient.Get("/test")
@@ -893,7 +902,11 @@ func TestP2PRelay(t *testing.T) {
return netA.hasPeers() && netB.hasPeers()
}, 2*time.Second, 50*time.Millisecond)
- makeCounterHandler := func(numExpected int, counter *atomic.Uint32, msgs *[][]byte) ([]TaggedMessageValidatorHandler, chan struct{}) {
+ type logMessages struct {
+ msgs [][]byte
+ mu deadlock.Mutex
+ }
+ makeCounterHandler := func(numExpected int, counter *atomic.Uint32, msgSink *logMessages) ([]TaggedMessageValidatorHandler, chan struct{}) {
counterDone := make(chan struct{})
counterHandler := []TaggedMessageValidatorHandler{
{
@@ -902,8 +915,10 @@ func TestP2PRelay(t *testing.T) {
ValidateHandleFunc
}{
ValidateHandleFunc(func(msg IncomingMessage) OutgoingMessage {
- if msgs != nil {
- *msgs = append(*msgs, msg.Data)
+ if msgSink != nil {
+ msgSink.mu.Lock()
+ msgSink.msgs = append(msgSink.msgs, msg.Data)
+ msgSink.mu.Unlock()
}
if count := counter.Add(1); int(count) >= numExpected {
close(counterDone)
@@ -969,8 +984,8 @@ func TestP2PRelay(t *testing.T) {
const expectedMsgs = 10
counter.Store(0)
- var loggedMsgs [][]byte
- counterHandler, counterDone = makeCounterHandler(expectedMsgs, &counter, &loggedMsgs)
+ var msgsSink logMessages
+ counterHandler, counterDone = makeCounterHandler(expectedMsgs, &counter, &msgsSink)
netA.ClearValidatorHandlers()
netA.RegisterValidatorHandlers(counterHandler)
@@ -990,10 +1005,10 @@ func TestP2PRelay(t *testing.T) {
case <-counterDone:
case <-time.After(3 * time.Second):
if c := counter.Load(); c < expectedMsgs {
- t.Logf("Logged messages: %v", loggedMsgs)
+ t.Logf("Logged messages: %v", msgsSink.msgs)
require.Failf(t, "One or more messages failed to reach destination network", "%d > %d", expectedMsgs, c)
} else if c > expectedMsgs {
- t.Logf("Logged messages: %v", loggedMsgs)
+ t.Logf("Logged messages: %v", msgsSink.msgs)
require.Failf(t, "One or more messages that were expected to be dropped, reached destination network", "%d < %d", expectedMsgs, c)
}
}
@@ -1349,8 +1364,23 @@ func TestP2PEnableGossipService_BothDisable(t *testing.T) {
relayCfg := cfg
relayCfg.NetAddress = "127.0.0.1:0"
+ var netAConnected atomic.Bool
+ var netBConnected atomic.Bool
+ notifiee1 := &network.NotifyBundle{
+ ConnectedF: func(n network.Network, c network.Conn) {
+ netAConnected.Store(true)
+ },
+ }
+ notifiee2 := &network.NotifyBundle{
+ ConnectedF: func(n network.Network, c network.Conn) {
+ netBConnected.Store(true)
+ },
+ }
+
netA, err := NewP2PNetwork(log.With("net", "netA"), relayCfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
require.NoError(t, err)
+ netA.service.NetworkNotify(notifiee1)
+ defer netA.service.NetworkStopNotify(notifiee1)
netA.Start()
defer netA.Stop()
@@ -1366,11 +1396,13 @@ func TestP2PEnableGossipService_BothDisable(t *testing.T) {
netB, err := NewP2PNetwork(log.With("net", "netB"), nodeCfg, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
require.NoError(t, err)
+ netB.service.NetworkNotify(notifiee2)
+ defer netB.service.NetworkStopNotify(notifiee2)
netB.Start()
defer netB.Stop()
require.Eventually(t, func() bool {
- return len(netA.service.Conns()) > 0 && len(netB.service.Conns()) > 0
+ return netAConnected.Load() && netBConnected.Load()
}, 1*time.Second, 50*time.Millisecond)
require.False(t, netA.hasPeers())
@@ -1427,7 +1459,7 @@ func TestGetPeersFiltersSelf(t *testing.T) {
ID: selfID,
Addrs: []multiaddr.Multiaddr{selfAddr},
}
- net.pstore.AddPersistentPeers([]*peer.AddrInfo{selfInfo}, "test-network", phonebook.PhoneBookEntryRelayRole)
+ net.pstore.AddPersistentPeers([]*peer.AddrInfo{selfInfo}, "test-network", phonebook.RelayRole)
// Create and add another peer
otherID, err := peer.Decode("QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N")
@@ -1438,7 +1470,7 @@ func TestGetPeersFiltersSelf(t *testing.T) {
ID: otherID,
Addrs: []multiaddr.Multiaddr{addr},
}
- net.pstore.AddPersistentPeers([]*peer.AddrInfo{otherInfo}, "test-network", phonebook.PhoneBookEntryRelayRole)
+ net.pstore.AddPersistentPeers([]*peer.AddrInfo{otherInfo}, "test-network", phonebook.RelayRole)
peers := net.GetPeers(PeersPhonebookRelays)
@@ -1452,3 +1484,110 @@ func TestGetPeersFiltersSelf(t *testing.T) {
}
}
}
+
+// TestP2PMetainfoExchange checks that the metainfo exchange works correctly
+func TestP2PMetainfoExchange(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ cfg := config.GetDefaultLocal()
+ cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses
+ cfg.NetAddress = "127.0.0.1:0"
+ cfg.EnableVoteCompression = true
+ log := logging.TestingLog(t)
+ err := log.EnableTelemetryContext(context.Background(), logging.TelemetryConfig{Enable: true, SendToLog: true, GUID: uuid.New()})
+ require.NoError(t, err)
+ netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
+ require.NoError(t, err)
+ err = netA.Start()
+ require.NoError(t, err)
+ defer netA.Stop()
+
+ peerInfoA := netA.service.AddrInfo()
+ addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA)
+ require.NoError(t, err)
+ require.NotZero(t, addrsA[0])
+
+ cfg2 := cfg
+ cfg2.EnableVoteCompression = false
+ cfg.NetAddress = ""
+ multiAddrStr := addrsA[0].String()
+ phoneBookAddresses := []string{multiAddrStr}
+ netB, err := NewP2PNetwork(log, cfg2, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
+ require.NoError(t, err)
+ err = netB.Start()
+ require.NoError(t, err)
+ defer netB.Stop()
+
+ require.Eventually(t, func() bool {
+ return len(netA.service.Conns()) > 0 && len(netB.service.Conns()) > 0
+ }, 2*time.Second, 50*time.Millisecond)
+
+ peers := netA.GetPeers(PeersConnectedIn)
+ require.Len(t, peers, 1)
+ peer := peers[0].(*wsPeer)
+ require.True(t, peer.features&pfCompressedProposal != 0)
+ require.False(t, peer.vpackVoteCompressionSupported())
+
+ peers = netB.GetPeers(PeersConnectedOut)
+ require.Len(t, peers, 1)
+ peer = peers[0].(*wsPeer)
+ require.True(t, peer.features&pfCompressedProposal != 0)
+ require.True(t, peer.vpackVoteCompressionSupported())
+}
+
+// TestP2PMetainfoV1vsV22 checks v1 and v22 nodes works together.
+// It is done with setting disableV22Protocol=true for the second node,
+// and it renders EnableVoteCompression options to have no effect.
+func TestP2PMetainfoV1vsV22(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ cfg := config.GetDefaultLocal()
+ cfg.DNSBootstrapID = "" // disable DNS lookups since the test uses phonebook addresses
+ cfg.NetAddress = "127.0.0.1:0"
+ cfg.EnableVoteCompression = true
+ log := logging.TestingLog(t)
+ netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
+ require.NoError(t, err)
+ err = netA.Start()
+ require.NoError(t, err)
+ defer netA.Stop()
+
+ peerInfoA := netA.service.AddrInfo()
+ addrsA, err := peer.AddrInfoToP2pAddrs(&peerInfoA)
+ require.NoError(t, err)
+ require.NotZero(t, addrsA[0])
+
+ cfg2 := cfg
+ cfg2.EnableVoteCompression = true
+ cfg.NetAddress = ""
+ multiAddrStr := addrsA[0].String()
+ phoneBookAddresses := []string{multiAddrStr}
+ disableV22Protocol = true
+ defer func() {
+ disableV22Protocol = false
+ }()
+ netB, err := NewP2PNetwork(log, cfg2, "", phoneBookAddresses, genesisID, config.Devtestnet, &nopeNodeInfo{}, nil)
+ require.NoError(t, err)
+ err = netB.Start()
+ require.NoError(t, err)
+ defer netB.Stop()
+
+ require.Eventually(t, func() bool {
+ return len(netA.service.Conns()) > 0 && len(netB.service.Conns()) > 0
+ }, 2*time.Second, 50*time.Millisecond)
+
+ var peers []Peer
+ require.Eventually(t, func() bool {
+ peers = netA.GetPeers(PeersConnectedIn)
+ return len(peers) > 0
+ }, 2*time.Second, 50*time.Millisecond)
+ peer := peers[0].(*wsPeer)
+ require.False(t, peer.features&pfCompressedProposal != 0)
+ require.False(t, peer.vpackVoteCompressionSupported())
+
+ peers = netB.GetPeers(PeersConnectedOut)
+ require.Len(t, peers, 1)
+ peer = peers[0].(*wsPeer)
+ require.False(t, peer.features&pfCompressedProposal != 0)
+ require.False(t, peer.vpackVoteCompressionSupported())
+}
diff --git a/network/p2pPeer.go b/network/p2pPeer.go
index ecc47d1857..52fdb3432e 100644
--- a/network/p2pPeer.go
+++ b/network/p2pPeer.go
@@ -75,8 +75,14 @@ func (c *wsPeerConnP2P) CloseWithMessage([]byte, time.Time) error {
func (c *wsPeerConnP2P) SetReadLimit(int64) {}
-func (c *wsPeerConnP2P) CloseWithoutFlush() error {
- err := c.stream.Close()
+func (c *wsPeerConnP2P) CloseWithoutFlush() (err error) {
+ err = c.stream.Reset()
+ defer func() {
+ err0 := c.stream.Conn().Close()
+ if err == nil {
+ err = err0
+ }
+ }()
if err != nil && err != yamux.ErrStreamClosed && err != yamux.ErrSessionShutdown && err != yamux.ErrStreamReset {
return err
}
diff --git a/network/phonebook/phonebook.go b/network/phonebook/phonebook.go
index 8e8dacc1f0..58d3774cc1 100644
--- a/network/phonebook/phonebook.go
+++ b/network/phonebook/phonebook.go
@@ -29,23 +29,84 @@ import (
// of how many addresses the phonebook actually has. ( with the retry-after logic applied )
const getAllAddresses = math.MaxInt32
-// PhoneBookEntryRoles defines the roles that a single entry on the phonebook can take.
-// currently, we have two roles : relay role and archival role, which are mutually exclusive.
+// RoleSet defines the roles that a single entry on the phonebook can take.
+// currently, we have two roles : relay role and archival role.
//
-//msgp:ignore PhoneBookEntryRoles
-type PhoneBookEntryRoles int
+//msgp:ignore Roles
+type RoleSet struct {
+ roles Role // roles is a bitfield of the roles that this entry has
+ persistence persistence // persistence is a bitfield of the roles that are persistent
+ _ func() // func is not comparable so that Roles. This is to prevent roles misuse and direct comparison.
+}
+
+// Role is a single role that a phonebook entry can have.
+type Role uint8
+type persistence uint8
-// PhoneBookEntryRelayRole used for all the relays that are provided either via the algobootstrap SRV record
-// or via a configuration file.
-const PhoneBookEntryRelayRole = 1
+// enforceUint8 is a generic type that only compiles for types whose underlying type is uint8.
+type enforceUint8[T ~uint8] struct{}
-// PhoneBookEntryArchivalRole used for all the archival nodes that are provided via the archive SRV record.
-const PhoneBookEntryArchivalRole = 2
+// enforce the underlying type of Role and persistence to be the same uint8
+var _ enforceUint8[Role]
+var _ enforceUint8[persistence]
+
+const (
+ // RelayRole used for all the relays that are provided either via the algobootstrap SRV record
+ // or via a configuration file.
+ RelayRole Role = 1 << iota
+ // ArchivalRole used for all the archival nodes that are provided via the archive SRV record.
+ ArchivalRole
+)
+
+// MakeRoleSet creates a new RoleSet with the passed role
+func MakeRoleSet(role Role, persistent bool) RoleSet {
+ r := RoleSet{roles: role}
+ if persistent {
+ r.persistence = persistence(role)
+ }
+ return r
+}
+
+// Has checks if the role in this role set has the other role
+func (r RoleSet) Has(other Role) bool {
+ return r.roles&other != 0
+}
+
+// Is checks if this set of roles is exactly the same as the other roles
+func (r RoleSet) Is(other Role) bool {
+ return r.roles == other
+}
+
+// Add adds the other roles to this set of roles
+func (r *RoleSet) Add(other Role) {
+ r.roles |= other
+}
+
+// Remove removes the other role from this set of roles
+func (r *RoleSet) Remove(other Role) {
+ r.roles &= ^other
+}
+
+// AddPersistent adds a role and marks it as persistent
+func (r *RoleSet) AddPersistent(role Role) {
+ r.roles |= role
+ r.persistence |= persistence(role)
+}
+
+// IsPersistent checks if the given role is marked as persistent
+func (r RoleSet) IsPersistent(role Role) bool {
+ return r.persistence&persistence(role) != 0
+}
+
+// hasPersistentRoles checks if there are any persistent roles in the set
+func (r RoleSet) hasPersistentRoles() bool {
+ return r.persistence != 0
+}
// Phonebook stores or looks up addresses of nodes we might contact
type Phonebook interface {
// GetAddresses(N) returns up to N addresses, but may return fewer
- GetAddresses(n int, role PhoneBookEntryRoles) []string
+ GetAddresses(n int, role Role) []string
// UpdateRetryAfter updates the retry-after field for the entries matching the given address
UpdateRetryAfter(addr string, retryAfter time.Time)
@@ -65,12 +126,13 @@ type Phonebook interface {
// ReplacePeerList merges a set of addresses with that passed in for networkName
// new entries in dnsAddresses are being added
// existing items that aren't included in dnsAddresses are being removed
- // matching entries don't change
- ReplacePeerList(dnsAddresses []string, networkName string, role PhoneBookEntryRoles)
+ // matching entries roles gets updated as needed and persistent peers are not touched
+ ReplacePeerList(dnsAddresses []string, networkName string, role Role)
// AddPersistentPeers stores addresses of peers which are persistent.
- // i.e. they won't be replaced by ReplacePeerList calls
- AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles)
+ // i.e. they won't be replaced by ReplacePeerList calls.
+ // If a peer is already in the peerstore, its role will be updated.
+ AddPersistentPeers(dnsAddresses []string, networkName string, role Role)
}
// addressData: holds the information associated with each phonebook address.
@@ -85,20 +147,16 @@ type addressData struct {
// networkNames: lists the networks to which the given address belongs.
networkNames map[string]bool
- // role is the role that this address serves.
- role PhoneBookEntryRoles
-
- // persistent is set true for peers whose record should not be removed for the peer list
- persistent bool
+ // roles is the roles that this address serves.
+ roles RoleSet
}
// makePhonebookEntryData creates a new addressData entry for provided network name and role.
-func makePhonebookEntryData(networkName string, role PhoneBookEntryRoles, persistent bool) addressData {
+func makePhonebookEntryData(networkName string, role Role, persistent bool) addressData {
pbData := addressData{
networkNames: make(map[string]bool),
recentConnectionTimes: make([]time.Time, 0),
- role: role,
- persistent: persistent,
+ roles: MakeRoleSet(role, persistent),
}
pbData.networkNames[networkName] = true
return pbData
@@ -127,7 +185,7 @@ func MakePhonebook(connectionsRateLimitingCount uint,
func (e *phonebookImpl) deletePhonebookEntry(entryName, networkName string) {
pbEntry := e.data[entryName]
delete(pbEntry.networkNames, networkName)
- if 0 == len(pbEntry.networkNames) {
+ if len(pbEntry.networkNames) == 0 {
delete(e.data, entryName)
}
}
@@ -149,10 +207,10 @@ func (e *phonebookImpl) appendTime(addr string, t time.Time) {
e.data[addr] = entry
}
-func (e *phonebookImpl) filterRetryTime(t time.Time, role PhoneBookEntryRoles) []string {
+func (e *phonebookImpl) filterRetryTime(t time.Time, role Role) []string {
o := make([]string, 0, len(e.data))
for addr, entry := range e.data {
- if t.After(entry.retryAfter) && role == entry.role {
+ if t.After(entry.retryAfter) && entry.roles.Has(role) {
o = append(o, addr)
}
}
@@ -162,24 +220,31 @@ func (e *phonebookImpl) filterRetryTime(t time.Time, role PhoneBookEntryRoles) [
// ReplacePeerList merges a set of addresses with that passed in.
// new entries in addressesThey are being added
// existing items that aren't included in addressesThey are being removed
-// matching entries don't change
-func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName string, role PhoneBookEntryRoles) {
+// matching entries roles gets updated as needed and persistent peers are not touched
+func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName string, role Role) {
e.lock.Lock()
defer e.lock.Unlock()
// prepare a map of items we'd like to remove.
removeItems := make(map[string]bool, 0)
for k, pbd := range e.data {
- if pbd.networkNames[networkName] && pbd.role == role && !pbd.persistent {
- removeItems[k] = true
+ if pbd.networkNames[networkName] && !pbd.roles.IsPersistent(role) {
+ if pbd.roles.Is(role) {
+ removeItems[k] = true
+ } else if pbd.roles.Has(role) {
+ pbd.roles.Remove(role)
+ e.data[k] = pbd
+ }
}
}
for _, addr := range addressesThey {
if pbData, has := e.data[addr]; has {
- // we already have this.
- // Update the networkName
+ // we already have this
+ // update the networkName and role
pbData.networkNames[networkName] = true
+ pbData.roles.Add(role)
+ e.data[addr] = pbData
// do not remove this entry
delete(removeItems, addr)
@@ -195,15 +260,18 @@ func (e *phonebookImpl) ReplacePeerList(addressesThey []string, networkName stri
}
}
-func (e *phonebookImpl) AddPersistentPeers(dnsAddresses []string, networkName string, role PhoneBookEntryRoles) {
+// AddPersistentPeers stores addresses of peers which are persistent.
+// i.e. they won't be replaced by ReplacePeerList calls.
+// If a peer is already in the peerstore, its role will be updated.
+func (e *phonebookImpl) AddPersistentPeers(dnsAddresses []string, networkName string, role Role) {
e.lock.Lock()
defer e.lock.Unlock()
for _, addr := range dnsAddresses {
if pbData, has := e.data[addr]; has {
// we already have this.
- // Make sure the persistence field is set to true
- pbData.persistent = true
+ // Make sure the persistence field is set to true and overwrite the role
+ pbData.roles.AddPersistent(role)
e.data[addr] = pbData
} else {
// we don't have this item. add it.
@@ -335,7 +403,7 @@ func shuffleSelect(set []string, n int) []string {
}
// GetAddresses returns up to N shuffled address
-func (e *phonebookImpl) GetAddresses(n int, role PhoneBookEntryRoles) []string {
+func (e *phonebookImpl) GetAddresses(n int, role Role) []string {
e.lock.RLock()
defer e.lock.RUnlock()
return shuffleSelect(e.filterRetryTime(time.Now(), role), n)
diff --git a/network/phonebook/phonebook_test.go b/network/phonebook/phonebook_test.go
index 7fdf022153..b0805582d0 100644
--- a/network/phonebook/phonebook_test.go
+++ b/network/phonebook/phonebook_test.go
@@ -25,7 +25,7 @@ import (
)
func testPhonebookAll(t *testing.T, set []string, ph Phonebook) {
- actual := ph.GetAddresses(len(set), PhoneBookEntryRelayRole)
+ actual := ph.GetAddresses(len(set), RelayRole)
for _, got := range actual {
ok := false
for _, known := range set {
@@ -57,7 +57,7 @@ func testPhonebookUniform(t *testing.T, set []string, ph Phonebook, getsize int)
expected := (uniformityTestLength * getsize) / len(set)
counts := make([]int, len(set))
for i := 0; i < uniformityTestLength; i++ {
- actual := ph.GetAddresses(getsize, PhoneBookEntryRelayRole)
+ actual := ph.GetAddresses(getsize, RelayRole)
for i, known := range set {
for _, xa := range actual {
if known == xa {
@@ -88,7 +88,7 @@ func TestArrayPhonebookAll(t *testing.T) {
set := []string{"a", "b", "c", "d", "e"}
ph := MakePhonebook(1, 1).(*phonebookImpl)
for _, e := range set {
- ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ ph.data[e] = makePhonebookEntryData("", RelayRole, false)
}
testPhonebookAll(t, set, ph)
}
@@ -99,7 +99,7 @@ func TestArrayPhonebookUniform1(t *testing.T) {
set := []string{"a", "b", "c", "d", "e"}
ph := MakePhonebook(1, 1).(*phonebookImpl)
for _, e := range set {
- ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ ph.data[e] = makePhonebookEntryData("", RelayRole, false)
}
testPhonebookUniform(t, set, ph, 1)
}
@@ -110,7 +110,7 @@ func TestArrayPhonebookUniform3(t *testing.T) {
set := []string{"a", "b", "c", "d", "e"}
ph := MakePhonebook(1, 1).(*phonebookImpl)
for _, e := range set {
- ph.data[e] = makePhonebookEntryData("", PhoneBookEntryRelayRole, false)
+ ph.data[e] = makePhonebookEntryData("", RelayRole, false)
}
testPhonebookUniform(t, set, ph, 3)
}
@@ -119,17 +119,11 @@ func TestMultiPhonebook(t *testing.T) {
partitiontest.PartitionTest(t)
set := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
- pha := make([]string, 0)
- for _, e := range set[:5] {
- pha = append(pha, e)
- }
- phb := make([]string, 0)
- for _, e := range set[5:] {
- phb = append(phb, e)
- }
+ pha := append([]string{}, set[:5]...)
+ phb := append([]string{}, set[5:]...)
mp := MakePhonebook(1, 1*time.Millisecond)
- mp.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- mp.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ mp.ReplacePeerList(pha, "pha", RelayRole)
+ mp.ReplacePeerList(phb, "phb", RelayRole)
testPhonebookAll(t, set, mp)
testPhonebookUniform(t, set, mp, 1)
@@ -143,42 +137,50 @@ func TestMultiPhonebookPersistentPeers(t *testing.T) {
persistentPeers := []string{"a"}
set := []string{"b", "c", "d", "e", "f", "g", "h", "i", "j", "k"}
- pha := make([]string, 0)
- for _, e := range set[:5] {
- pha = append(pha, e)
- }
- phb := make([]string, 0)
- for _, e := range set[5:] {
- phb = append(phb, e)
- }
+ pha := append([]string{}, set[:5]...)
+ phb := append([]string{}, set[5:]...)
mp := MakePhonebook(1, 1*time.Millisecond)
- mp.AddPersistentPeers(persistentPeers, "pha", PhoneBookEntryRelayRole)
- mp.AddPersistentPeers(persistentPeers, "phb", PhoneBookEntryRelayRole)
- mp.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- mp.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ mp.AddPersistentPeers(persistentPeers, "pha", RelayRole)
+ mp.AddPersistentPeers(persistentPeers, "phb", RelayRole)
+ mp.ReplacePeerList(pha, "pha", RelayRole)
+ mp.ReplacePeerList(phb, "phb", RelayRole)
testPhonebookAll(t, append(set, persistentPeers...), mp)
- allAddresses := mp.GetAddresses(len(set)+len(persistentPeers), PhoneBookEntryRelayRole)
+ allAddresses := mp.GetAddresses(len(set)+len(persistentPeers), RelayRole)
for _, pp := range persistentPeers {
require.Contains(t, allAddresses, pp)
}
+
+ // check that role of persistent peer gets updated with AddPersistentPeers
+ mp2 := MakePhonebook(1, 1*time.Millisecond)
+ mp2.AddPersistentPeers(persistentPeers, "phc", RelayRole)
+ mp2.AddPersistentPeers(persistentPeers, "phc", ArchivalRole)
+ allAddresses = mp2.GetAddresses(len(set)+len(persistentPeers), RelayRole)
+ require.Len(t, allAddresses, 1)
+ allAddresses = mp2.GetAddresses(len(set)+len(persistentPeers), ArchivalRole)
+ require.Len(t, allAddresses, 1)
+
+ // check that role of persistent peer survives
+ mp3 := MakePhonebook(1, 1*time.Millisecond)
+ mp3.AddPersistentPeers(persistentPeers, "phc", ArchivalRole)
+ phc := []string{"a"}
+ mp3.ReplacePeerList(phc, "phc", RelayRole)
+
+ allAddresses = mp3.GetAddresses(len(set)+len(persistentPeers), RelayRole)
+ require.Len(t, allAddresses, 1)
+ allAddresses = mp3.GetAddresses(len(set)+len(persistentPeers), ArchivalRole)
+ require.Len(t, allAddresses, 1)
}
func TestMultiPhonebookDuplicateFiltering(t *testing.T) {
partitiontest.PartitionTest(t)
set := []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}
- pha := make([]string, 0)
- for _, e := range set[:7] {
- pha = append(pha, e)
- }
- phb := make([]string, 0)
- for _, e := range set[3:] {
- phb = append(phb, e)
- }
+ pha := append([]string{}, set[:7]...)
+ phb := append([]string{}, set[3:]...)
mp := MakePhonebook(1, 1*time.Millisecond)
- mp.ReplacePeerList(pha, "pha", PhoneBookEntryRelayRole)
- mp.ReplacePeerList(phb, "phb", PhoneBookEntryRelayRole)
+ mp.ReplacePeerList(pha, "pha", RelayRole)
+ mp.ReplacePeerList(phb, "phb", RelayRole)
testPhonebookAll(t, set, mp)
testPhonebookUniform(t, set, mp, 1)
@@ -204,7 +206,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
// Test the addresses are populated in the phonebook and a
// time can be added to one of them
- entries.ReplacePeerList([]string{addr1, addr2}, "default", PhoneBookEntryRelayRole)
+ entries.ReplacePeerList([]string{addr1, addr2}, "default", RelayRole)
addrInPhonebook, waitTime, provisionalTime := entries.GetConnectionWaitTime(addr1)
require.Equal(t, true, addrInPhonebook)
require.Equal(t, time.Duration(0), waitTime)
@@ -218,7 +220,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
}
// add another value to addr
- addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1)
+ _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1)
require.Equal(t, time.Duration(0), waitTime)
require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime))
phBookData = entries.data[addr1].recentConnectionTimes
@@ -232,7 +234,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
// the first time should be removed and a new one added
// there should not be any wait
- addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1)
+ _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr1)
require.Equal(t, time.Duration(0), waitTime)
require.Equal(t, true, entries.UpdateConnectionTime(addr1, provisionalTime))
phBookData2 := entries.data[addr1].recentConnectionTimes
@@ -259,7 +261,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
}
// value 2
- addrInPhonebook, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2)
+ _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2)
require.Equal(t, time.Duration(0), waitTime)
require.Equal(t, true, entries.UpdateConnectionTime(addr2, provisionalTime))
// value 3
@@ -272,7 +274,7 @@ func TestWaitAndAddConnectionTimeLongtWindow(t *testing.T) {
require.Equal(t, 3, len(phBookData))
// add another element to trigger wait
- _, waitTime, provisionalTime = entries.GetConnectionWaitTime(addr2)
+ _, waitTime, _ = entries.GetConnectionWaitTime(addr2)
require.Greater(t, int64(waitTime), int64(0))
// no element should be removed
phBookData2 = entries.data[addr2].recentConnectionTimes
@@ -306,7 +308,7 @@ func TestWaitAndAddConnectionTimeShortWindow(t *testing.T) {
addr1 := "addrABC"
// Init the data structures
- entries.ReplacePeerList([]string{addr1}, "default", PhoneBookEntryRelayRole)
+ entries.ReplacePeerList([]string{addr1}, "default", RelayRole)
// add 3 values. should not wait
// value 1
@@ -345,20 +347,20 @@ func TestPhonebookRoles(t *testing.T) {
archiverSet := []string{"archiver1", "archiver2", "archiver3"}
ph := MakePhonebook(1, 1).(*phonebookImpl)
- ph.ReplacePeerList(relaysSet, "default", PhoneBookEntryRelayRole)
- ph.ReplacePeerList(archiverSet, "default", PhoneBookEntryArchivalRole)
+ ph.ReplacePeerList(relaysSet, "default", RelayRole)
+ ph.ReplacePeerList(archiverSet, "default", ArchivalRole)
require.Equal(t, len(relaysSet)+len(archiverSet), len(ph.data))
require.Equal(t, len(relaysSet)+len(archiverSet), ph.Length())
- for _, role := range []PhoneBookEntryRoles{PhoneBookEntryRelayRole, PhoneBookEntryArchivalRole} {
+ for _, role := range []Role{RelayRole, ArchivalRole} {
for k := 0; k < 100; k++ {
for l := 0; l < 3; l++ {
entries := ph.GetAddresses(l, role)
- if role == PhoneBookEntryRelayRole {
+ if role == RelayRole {
for _, entry := range entries {
require.Contains(t, entry, "relay")
}
- } else if role == PhoneBookEntryArchivalRole {
+ } else if role == ArchivalRole {
for _, entry := range entries {
require.Contains(t, entry, "archiver")
}
@@ -367,3 +369,124 @@ func TestPhonebookRoles(t *testing.T) {
}
}
}
+
+func TestRolesOperations(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ var tests = []struct {
+ role Role
+ otherRoles Role
+ }{
+ {RelayRole, ArchivalRole},
+ {ArchivalRole, RelayRole},
+ }
+
+ for _, test := range tests {
+ combo := RoleSet{roles: test.role}
+ combo.Add(test.otherRoles)
+ require.Equal(t, test.role|test.otherRoles, combo.roles)
+ require.True(t, combo.Has(test.role))
+ require.False(t, combo.Is(test.role))
+ require.True(t, combo.Has(test.otherRoles))
+ require.False(t, combo.Is(test.otherRoles))
+
+ combo.Remove(test.otherRoles)
+ require.Equal(t, test.role, combo.roles)
+ require.True(t, combo.Has(test.role))
+ require.True(t, combo.Is(test.role))
+ require.False(t, combo.Has(test.otherRoles))
+ require.False(t, combo.Is(test.otherRoles))
+ }
+}
+
+func TestReplacePeerList(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ pb := MakePhonebook(1, 1)
+ pb.ReplacePeerList([]string{"a", "b"}, "default", RelayRole)
+ res := pb.GetAddresses(4, RelayRole)
+ require.ElementsMatch(t, []string{"a", "b"}, res)
+
+ pb.ReplacePeerList([]string{"c"}, "default", ArchivalRole)
+ res = pb.GetAddresses(4, ArchivalRole)
+ require.ElementsMatch(t, []string{"c"}, res)
+
+ // make b archival in addition to relay
+ pb.ReplacePeerList([]string{"b", "c"}, "default", ArchivalRole)
+ res = pb.GetAddresses(4, RelayRole)
+ require.ElementsMatch(t, []string{"a", "b"}, res)
+ res = pb.GetAddresses(4, ArchivalRole)
+ require.ElementsMatch(t, []string{"b", "c"}, res)
+
+ // update relays
+ pb.ReplacePeerList([]string{"a", "b"}, "default", RelayRole)
+ res = pb.GetAddresses(4, RelayRole)
+ require.ElementsMatch(t, []string{"a", "b"}, res)
+ res = pb.GetAddresses(4, ArchivalRole)
+ require.ElementsMatch(t, []string{"b", "c"}, res)
+
+ // exclude b from archival
+ pb.ReplacePeerList([]string{"c"}, "default", ArchivalRole)
+ res = pb.GetAddresses(4, RelayRole)
+ require.ElementsMatch(t, []string{"a", "b"}, res)
+ res = pb.GetAddresses(4, ArchivalRole)
+ require.ElementsMatch(t, []string{"c"}, res)
+}
+
+func TestRoleSetPersistence(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ fn func(*testing.T)
+ }{
+ {
+ name: "MakeRoleSet with persistence",
+ fn: func(t *testing.T) {
+ rs := MakeRoleSet(RelayRole, true)
+ require.True(t, rs.IsPersistent(RelayRole), "Expected RelayRole to be persistent")
+ require.True(t, rs.hasPersistentRoles(), "Expected HasPersistentRoles to return true")
+ },
+ },
+ {
+ name: "MakeRoleSet without persistence",
+ fn: func(t *testing.T) {
+ rs := MakeRoleSet(RelayRole, false)
+ require.False(t, rs.IsPersistent(RelayRole), "Expected RelayRole to not be persistent")
+ require.False(t, rs.hasPersistentRoles(), "Expected HasPersistentRoles to return false")
+ },
+ },
+ {
+ name: "AddPersistent",
+ fn: func(t *testing.T) {
+ rs := MakeRoleSet(RelayRole, false)
+ rs.AddPersistent(ArchivalRole)
+
+ require.True(t, rs.IsPersistent(ArchivalRole), "Expected ArchivalRole to be persistent")
+ require.False(t, rs.IsPersistent(RelayRole), "Expected RelayRole to not be persistent")
+ require.True(t, rs.Has(ArchivalRole), "Expected to have ArchivalRole")
+ require.True(t, rs.Has(RelayRole), "Expected to have RelayRole")
+ require.True(t, rs.hasPersistentRoles(), "Expected HasPersistentRoles to return true")
+ },
+ },
+ {
+ name: "Multiple roles with different persistence",
+ fn: func(t *testing.T) {
+ rs := MakeRoleSet(RelayRole, true)
+ rs.Add(ArchivalRole)
+
+ require.True(t, rs.IsPersistent(RelayRole), "Expected RelayRole to be persistent")
+ require.False(t, rs.IsPersistent(ArchivalRole), "Expected ArchivalRole to not be persistent")
+ require.True(t, rs.Has(RelayRole), "Expected to have RelayRole")
+ require.True(t, rs.Has(ArchivalRole), "Expected to have ArchivalRole")
+ },
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, test.fn)
+ }
+}
diff --git a/network/requestLogger_test.go b/network/requestLogger_test.go
index 4da7ea08b3..04e288bca6 100644
--- a/network/requestLogger_test.go
+++ b/network/requestLogger_test.go
@@ -53,7 +53,7 @@ func TestRequestLogger(t *testing.T) {
log: dl,
config: defaultConfig,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: "go-test-network-genesis",
+ genesisID: "go-test-network-genesis",
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: noopIdentityTracker{},
@@ -71,7 +71,7 @@ func TestRequestLogger(t *testing.T) {
require.True(t, postListen)
t.Log(addrA)
netB.phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer func() { t.Log("stopping B"); netB.Stop(); t.Log("B done") }()
diff --git a/network/requestTracker_test.go b/network/requestTracker_test.go
index d359ae15a4..8ef12ad714 100644
--- a/network/requestTracker_test.go
+++ b/network/requestTracker_test.go
@@ -90,7 +90,7 @@ func TestRateLimiting(t *testing.T) {
log: log,
config: testConfig,
phonebook: phonebook.MakePhonebook(1, 1),
- GenesisID: "go-test-network-genesis",
+ genesisID: "go-test-network-genesis",
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: noopIdentityTracker{},
@@ -124,9 +124,9 @@ func TestRateLimiting(t *testing.T) {
networks[i].config.GossipFanout = 1
phonebooks[i] = phonebook.MakePhonebook(networks[i].config.ConnectionsRateLimitingCount,
time.Duration(networks[i].config.ConnectionsRateLimitingWindowSeconds)*time.Second)
- phonebooks[i].ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ phonebooks[i].ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
networks[i].phonebook = phonebook.MakePhonebook(1, 1*time.Millisecond)
- networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ networks[i].phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
defer func(net *WebsocketNetwork, i int) {
t.Logf("stopping network %d", i)
net.Stop()
@@ -156,7 +156,7 @@ func TestRateLimiting(t *testing.T) {
case <-readyCh:
// it's closed, so this client got connected.
connectedClients++
- phonebookLen := len(phonebooks[i].GetAddresses(1, phonebook.PhoneBookEntryRelayRole))
+ phonebookLen := len(phonebooks[i].GetAddresses(1, phonebook.RelayRole))
// if this channel is ready, than we should have an address, since it didn't get blocked.
require.Equal(t, 1, phonebookLen)
default:
diff --git a/network/vpack/README.md b/network/vpack/README.md
new file mode 100644
index 0000000000..1bb6dcd2c5
--- /dev/null
+++ b/network/vpack/README.md
@@ -0,0 +1,72 @@
+# Stateless *vpack* wire format
+
+This document specifies the byte‑level (on‑wire) layout produced by `StatelessEncoder.CompressVote` and accepted by `StatelessDecoder.DecompressVote`.
+The goal is to minimize vote size while retaining a 1‑to‑1, loss‑free mapping to the canonical msgpack representation of `agreement.UnauthenticatedVote`.
+The canonical msgpack representation we rely on is provided by agreement/msgp_gen.go, generated by our [custom msgpack code generator](https://github.com/algorand/msgp)
+which ensures fields are generated in lexicographic order, omit empty key-value pairs, and use specific formats for certain types as defined in
+[our specification](https://github.com/algorandfoundation/specs/blob/c0331123148971e4705f25b9c937cb23e5ee28d1/dev/crypto.md#L22-L40).
+
+---
+
+## 1. High‑level structure
+
+```
++---------+-----------------+---------------------+--------------------------+
+| Header | VrfProof ("pf") | rawVote ("r") | OneTimeSignature ("sig") |
+| 2 bytes | 80 bytes | variable length | 256 bytes |
++---------+-----------------+---------------------+--------------------------+
+```
+
+All fields appear exactly once, and in the fixed order above. The presence of optional sub‑fields inside `rawVote` are indicated by a 1‑byte bitmask in the header.
+No field names appear, only values.
+
+---
+
+## 2. Header (2 bytes)
+
+| Offset | Description |
+| ------ | -------------------------------------------------------------- |
+| `0` | Presence flags for optional values (LSB first, see table). |
+| `1` | Reserved, currently zero. |
+
+### 2.1 Bit‑mask layout (byte 0)
+
+| Bit | Flag | Field enabled | Encoded size |
+| --- | ----------- | -------------------------------- | ------------ |
+| 0 | `bitPer` | `r.per` (varuint) | 1 - 9 bytes |
+| 1 | `bitDig` | `r.prop.dig` (digest) | 32 bytes |
+| 2 | `bitEncDig` | `r.prop.encdig` (digest) | 32 bytes |
+| 3 | `bitOper` | `r.prop.oper` (varuint) | 1 - 9 bytes |
+| 4 | `bitOprop` | `r.prop.oprop` (address) | 32 bytes |
+| 5 | `bitStep` | `r.step` (varuint) | 1 - 9 bytes |
+
+Binary fields are represented by their 32-, 64-, and 80-byte values without markers.
+Integers use msgpack's variable-length unsigned integer encoding:
+- `fixint` (≤ 127), 1 byte in length (values 0x00-0x7f)
+- `uint8` 2 bytes in length (marker byte 0xcc + 1-byte value)
+- `uint16` 3 bytes in length (marker byte 0xcd + 2-byte value)
+- `uint32` 5 bytes in length (marker byte 0xce + 4-byte value)
+- `uint64` 9 bytes in length (marker byte 0xcf + 8-byte value)
+
+---
+
+## 3. Field serialization order
+
+After the 2-byte header, the encoder emits values in the following order:
+
+| Field | Type | Encoded size | Presence flag |
+| -------------- | ------------------------------ | ------------ | ------------- |
+| `pf` | VRF credential | 80 bytes | Required |
+| `r.per` | Period | varuint | `bitPer` |
+| `r.prop.dig` | Proposal digest | 32 bytes | `bitDig` |
+| `r.prop.encdig`| Digest of encoded proposal | 32 bytes | `bitEncDig` |
+| `r.prop.oper` | Proposal's original period | varuint | `bitOper` |
+| `r.prop.oprop` | Proposal's original proposer | 32 bytes | `bitOprop` |
+| `r.rnd` | Round number | varuint | Required |
+| `r.snd` | Voter's (sender) address | 32 bytes | Required |
+| `r.step` | Step | varuint | `bitStep` |
+| `sig.p` | Ed25519 public key | 32 bytes | Required |
+| `sig.p1s` | Signature over offset ID | 64 bytes | Required |
+| `sig.p2` | Second-tier Ed25519 public key | 32 bytes | Required |
+| `sig.p2s` | Signature over batch ID | 64 bytes | Required |
+| `sig.s` | Signature over vote using `p` | 64 bytes | Required |
diff --git a/network/vpack/msgp.go b/network/vpack/msgp.go
new file mode 100644
index 0000000000..0346eae866
--- /dev/null
+++ b/network/vpack/msgp.go
@@ -0,0 +1,197 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "fmt"
+)
+
+// Minimal msgpack constants used here
+const (
+ msgpFixMapMask = 0x80
+ msgpFixMapMax = 0x8f
+ msgpFixStrMask = 0xa0
+ msgpFixStrMax = 0xbf
+ msgpBin8 = 0xc4
+ msgpBin8Len32 = "\xc4\x20" // bin8 marker with 32 items
+ msgpBin8Len64 = "\xc4\x40" // bin8 marker with 64 items
+ msgpBin8Len80 = "\xc4\x50" // bin8 marker with 80 items
+ msgpUint8 = 0xcc
+ msgpUint16 = 0xcd
+ msgpUint32 = 0xce
+ msgpUint64 = 0xcf
+
+ msgpFixstrCred = "\xa4cred"
+ msgpFixstrDig = "\xa3dig"
+ msgpFixstrEncdig = "\xa6encdig"
+ msgpFixstrOper = "\xa4oper"
+ msgpFixstrOprop = "\xa5oprop"
+ msgpFixstrP = "\xa1p"
+ msgpFixstrP1s = "\xa3p1s"
+ msgpFixstrP2 = "\xa2p2"
+ msgpFixstrP2s = "\xa3p2s"
+ msgpFixstrPer = "\xa3per"
+ msgpFixstrPf = "\xa2pf"
+ msgpFixstrProp = "\xa4prop"
+ msgpFixstrPs = "\xa2ps"
+ msgpFixstrR = "\xa1r"
+ msgpFixstrRnd = "\xa3rnd"
+ msgpFixstrS = "\xa1s"
+ msgpFixstrSig = "\xa3sig"
+ msgpFixstrSnd = "\xa3snd"
+ msgpFixstrStep = "\xa4step"
+)
+
+func isMsgpFixint(b byte) bool {
+ return b>>7 == 0
+}
+
+// msgpVoteParser provides a zero-allocation msgpVoteParser for vote messages.
+type msgpVoteParser struct {
+ data []byte
+ pos int
+}
+
+func newMsgpVoteParser(data []byte) *msgpVoteParser {
+ return &msgpVoteParser{data: data}
+}
+
+// Error if we need more bytes than available
+func (p *msgpVoteParser) ensureBytes(n int) error {
+ if p.pos+n > len(p.data) {
+ return fmt.Errorf("unexpected EOF: need %d bytes, have %d", n, len(p.data)-p.pos)
+ }
+ return nil
+}
+
+// Read a single byte
+func (p *msgpVoteParser) readByte() (byte, error) {
+ if err := p.ensureBytes(1); err != nil {
+ return 0, err
+ }
+ b := p.data[p.pos]
+ p.pos++
+ return b, nil
+}
+
+// Read a fixmap header and return the count
+func (p *msgpVoteParser) readFixMap() (uint8, error) {
+ b, err := p.readByte()
+ if err != nil {
+ return 0, err
+ }
+
+ if b < msgpFixMapMask || b > msgpFixMapMax {
+ return 0, fmt.Errorf("expected fixmap, got 0x%02x", b)
+ }
+
+ return b & 0x0f, nil
+}
+
+// Zero-allocation string reading that returns a slice of the original data
+func (p *msgpVoteParser) readString() ([]byte, error) {
+ b, err := p.readByte()
+ if err != nil {
+ return nil, err
+ }
+ if b < msgpFixStrMask || b > msgpFixStrMax {
+ return nil, fmt.Errorf("readString: expected fixstr, got 0x%02x", b)
+ }
+ length := int(b & 0x1f)
+ if err := p.ensureBytes(length); err != nil {
+ return nil, err
+ }
+ s := p.data[p.pos : p.pos+length]
+ p.pos += length
+ return s, nil
+}
+
+func (p *msgpVoteParser) readBin80() ([80]byte, error) {
+ const sz = 80
+ var data [sz]byte
+ if err := p.ensureBytes(sz + 2); err != nil {
+ return data, err
+ }
+ if p.data[p.pos] != msgpBin8 || p.data[p.pos+1] != sz {
+ return data, fmt.Errorf("expected bin8 length %d, got %d", sz, int(p.data[p.pos+1]))
+ }
+ copy(data[:], p.data[p.pos+2:p.pos+sz+2])
+ p.pos += sz + 2
+ return data, nil
+}
+
+func (p *msgpVoteParser) readBin32() ([32]byte, error) {
+ const sz = 32
+ var data [sz]byte
+ if err := p.ensureBytes(sz + 2); err != nil {
+ return data, err
+ }
+ if p.data[p.pos] != msgpBin8 || p.data[p.pos+1] != sz {
+ return data, fmt.Errorf("expected bin8 length %d, got %d", sz, int(p.data[p.pos+1]))
+ }
+ copy(data[:], p.data[p.pos+2:p.pos+sz+2])
+ p.pos += sz + 2
+ return data, nil
+}
+
+func (p *msgpVoteParser) readBin64() ([64]byte, error) {
+ const sz = 64
+ var data [sz]byte
+ if err := p.ensureBytes(sz + 2); err != nil {
+ return data, err
+ }
+ if p.data[p.pos] != msgpBin8 || p.data[p.pos+1] != sz {
+ return data, fmt.Errorf("expected bin8 length %d, got %d", sz, int(p.data[p.pos+1]))
+ }
+ copy(data[:], p.data[p.pos+2:p.pos+sz+2])
+ p.pos += sz + 2
+ return data, nil
+}
+
+// readUintBytes reads a variable-length msgpack unsigned integer from the reader.
+// It will return a zero-length/nil slice iff err != nil.
+func (p *msgpVoteParser) readUintBytes() ([]byte, error) {
+ startPos := p.pos
+ // read marker byte
+ b, err := p.readByte()
+ if err != nil {
+ return nil, err
+ }
+ // fixint is a single byte containing marker and value
+ if isMsgpFixint(b) {
+ return p.data[startPos : startPos+1], nil
+ }
+ // otherwise, we expect a tag byte followed by the value
+ var dataSize int
+ switch b {
+ case msgpUint8:
+ dataSize = 1
+ case msgpUint16:
+ dataSize = 2
+ case msgpUint32:
+ dataSize = 4
+ case msgpUint64:
+ dataSize = 8
+ default:
+ return nil, fmt.Errorf("expected uint tag, got 0x%02x", b)
+ }
+ if err := p.ensureBytes(dataSize); err != nil {
+ return nil, err
+ }
+ p.pos += dataSize
+ return p.data[startPos : startPos+dataSize+1], nil
+}
diff --git a/network/vpack/parse.go b/network/vpack/parse.go
new file mode 100644
index 0000000000..17598ee76f
--- /dev/null
+++ b/network/vpack/parse.go
@@ -0,0 +1,255 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "fmt"
+)
+
+type voteValueType uint8
+
+const (
+ credPfVoteValue voteValueType = iota
+ rPerVoteValue
+ rPropDigVoteValue
+ rPropEncdigVoteValue
+ rPropOperVoteValue
+ rPropOpropVoteValue
+ rRndVoteValue
+ rSndVoteValue
+ rStepVoteValue
+ sigP1sVoteValue
+ sigP2VoteValue
+ sigP2sVoteValue
+ sigPVoteValue
+ sigPsVoteValue
+ sigSVoteValue
+)
+
+func parseMsgpVote(msgpData []byte, c *StatelessEncoder) error {
+ p := newMsgpVoteParser(msgpData)
+
+ // Parse unauthenticatedVote
+ cnt, err := p.readFixMap()
+ if err != nil {
+ return fmt.Errorf("reading map for unauthenticatedVote: %w", err)
+ }
+ // Assert unauthenticatedVote struct has 3 fields
+ if cnt != 3 {
+ return fmt.Errorf("expected fixed map size 3 for unauthenticatedVote, got %d", cnt)
+ }
+ // Required nested map in unauthenticatedVote: cred
+ if s, rErr := p.readString(); rErr != nil || string(s) != "cred" {
+ return fmt.Errorf("expected string cred, got %s: %w", s, rErr)
+ }
+
+ // Parse UnauthenticatedCredential
+ cnt, err = p.readFixMap()
+ if err != nil {
+ return fmt.Errorf("reading map for UnauthenticatedCredential: %w", err)
+ }
+ // Assert UnauthenticatedCredential struct has 1 fields
+ if cnt != 1 {
+ return fmt.Errorf("expected fixed map size 1 for UnauthenticatedCredential, got %d", cnt)
+ }
+ // Required field name for UnauthenticatedCredential: pf
+ if s, rErr := p.readString(); rErr != nil || string(s) != "pf" {
+ return fmt.Errorf("expected string pf, got %s: %w", s, rErr)
+ }
+ val, err := p.readBin80()
+ if err != nil {
+ return fmt.Errorf("reading pf: %w", err)
+ }
+ c.writeBin80(credPfVoteValue, val)
+
+ // Required nested map in unauthenticatedVote: r
+ if s, rErr := p.readString(); rErr != nil || string(s) != "r" {
+ return fmt.Errorf("expected string r, got %s: %w", s, rErr)
+ }
+
+ // Parse rawVote fixmap
+ cnt, err = p.readFixMap()
+ if err != nil {
+ return fmt.Errorf("reading map for rawVote: %w", err)
+ }
+ if cnt < 1 || cnt > 5 {
+ return fmt.Errorf("expected fixmap size for rawVote 1 <= cnt <= 5, got %d", cnt)
+ }
+ for range cnt {
+ voteKey, err1 := p.readString()
+ if err1 != nil {
+ return fmt.Errorf("reading key for rawVote: %w", err1)
+ }
+ switch string(voteKey) {
+ case "per":
+ val, err1 := p.readUintBytes()
+ if err1 != nil {
+ return fmt.Errorf("reading per: %w", err1)
+ }
+ c.writeVaruint(rPerVoteValue, val)
+ case "prop":
+ // Parse proposalValue fixmap
+ propCnt, err1 := p.readFixMap()
+ if err1 != nil {
+ return fmt.Errorf("reading map for proposalValue: %w", err1)
+ }
+ if propCnt < 1 || propCnt > 4 {
+ return fmt.Errorf("expected fixmap size for proposalValue 1 <= cnt <= 4, got %d", propCnt)
+ }
+ for range propCnt {
+ propKey, err2 := p.readString()
+ if err2 != nil {
+ return fmt.Errorf("reading key for proposalValue: %w", err2)
+ }
+ switch string(propKey) {
+ case "dig":
+ val, err2 := p.readBin32()
+ if err2 != nil {
+ return fmt.Errorf("reading dig: %w", err2)
+ }
+ c.writeBin32(rPropDigVoteValue, val)
+
+ case "encdig":
+ val, err2 := p.readBin32()
+ if err2 != nil {
+ return fmt.Errorf("reading encdig: %w", err2)
+ }
+ c.writeBin32(rPropEncdigVoteValue, val)
+
+ case "oper":
+ val, err2 := p.readUintBytes()
+ if err2 != nil {
+ return fmt.Errorf("reading oper: %w", err2)
+ }
+ c.writeVaruint(rPropOperVoteValue, val)
+ case "oprop":
+ val, err2 := p.readBin32()
+ if err2 != nil {
+ return fmt.Errorf("reading oprop: %w", err2)
+ }
+ c.writeBin32(rPropOpropVoteValue, val)
+
+ default:
+ return fmt.Errorf("unexpected field in proposalValue: %q", propKey)
+ }
+ }
+ case "rnd":
+ val, err1 := p.readUintBytes()
+ if err1 != nil {
+ return fmt.Errorf("reading rnd: %w", err1)
+ }
+ c.writeVaruint(rRndVoteValue, val)
+ case "snd":
+ val, err1 := p.readBin32()
+ if err1 != nil {
+ return fmt.Errorf("reading snd: %w", err1)
+ }
+ c.writeBin32(rSndVoteValue, val)
+
+ case "step":
+ val, err1 := p.readUintBytes()
+ if err1 != nil {
+ return fmt.Errorf("reading step: %w", err1)
+ }
+ c.writeVaruint(rStepVoteValue, val)
+ default:
+ return fmt.Errorf("unexpected field in rawVote: %q", voteKey)
+ }
+ }
+
+ // Required nested map in unauthenticatedVote: sig
+ if s, rErr := p.readString(); rErr != nil || string(s) != "sig" {
+ return fmt.Errorf("expected string sig, got %s: %w", s, rErr)
+ }
+
+ // Parse OneTimeSignature fixmap
+ cnt, err = p.readFixMap()
+ if err != nil {
+ return fmt.Errorf("reading map for OneTimeSignature: %w", err)
+ }
+ // Assert OneTimeSignature struct has 6 fields
+ if cnt != 6 {
+ return fmt.Errorf("expected fixed map size 6 for OneTimeSignature, got %d", cnt)
+ }
+ // Required field for OneTimeSignature: p
+ if s, rErr := p.readString(); rErr != nil || string(s) != "p" {
+ return fmt.Errorf("expected string p, got %s: %w", s, rErr)
+ }
+ val32, err := p.readBin32()
+ if err != nil {
+ return fmt.Errorf("reading p: %w", err)
+ }
+ c.writeBin32(sigPVoteValue, val32)
+
+ // Required field for OneTimeSignature: p1s
+ if s, rErr := p.readString(); rErr != nil || string(s) != "p1s" {
+ return fmt.Errorf("expected string p1s, got %s: %w", s, rErr)
+ }
+ val64, err := p.readBin64()
+ if err != nil {
+ return fmt.Errorf("reading p1s: %w", err)
+ }
+ c.writeBin64(sigP1sVoteValue, val64)
+
+ // Required field for OneTimeSignature: p2
+ if s, rErr := p.readString(); rErr != nil || string(s) != "p2" {
+ return fmt.Errorf("expected string p2, got %s: %w", s, rErr)
+ }
+ val32, err = p.readBin32()
+ if err != nil {
+ return fmt.Errorf("reading p2: %w", err)
+ }
+ c.writeBin32(sigP2VoteValue, val32)
+
+ // Required field for OneTimeSignature: p2s
+ if s, rErr := p.readString(); rErr != nil || string(s) != "p2s" {
+ return fmt.Errorf("expected string p2s, got %s: %w", s, rErr)
+ }
+ val64, err = p.readBin64()
+ if err != nil {
+ return fmt.Errorf("reading p2s: %w", err)
+ }
+ c.writeBin64(sigP2sVoteValue, val64)
+
+ // Required field for OneTimeSignature: ps
+ if s, rErr := p.readString(); rErr != nil || string(s) != "ps" {
+ return fmt.Errorf("expected string ps, got %s: %w", s, rErr)
+ }
+ val64, err = p.readBin64()
+ if err != nil {
+ return fmt.Errorf("reading ps: %w", err)
+ }
+ if val64 != [64]byte{} {
+ return fmt.Errorf("expected empty array for ps, got %v", val64)
+ }
+
+ // Required field for OneTimeSignature: s
+ if s, rErr := p.readString(); rErr != nil || string(s) != "s" {
+ return fmt.Errorf("expected string s, got %s: %w", s, rErr)
+ }
+ val64, err = p.readBin64()
+ if err != nil {
+ return fmt.Errorf("reading s: %w", err)
+ }
+ c.writeBin64(sigSVoteValue, val64)
+
+ // Check for trailing bytes
+ if p.pos < len(p.data) {
+ return fmt.Errorf("unexpected trailing data: %d bytes remain unprocessed", len(p.data)-p.pos)
+ }
+ return nil
+}
diff --git a/network/vpack/parse_test.go b/network/vpack/parse_test.go
new file mode 100644
index 0000000000..0ad6230c8a
--- /dev/null
+++ b/network/vpack/parse_test.go
@@ -0,0 +1,189 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/crypto"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/assert"
+)
+
+// a string that is greater than the max 5-bit fixmap size
+const gtFixMapString = "12345678901234567890123456789012"
+
+var parseVoteTestCases = []struct {
+ obj any
+ errContains string
+}{
+ // vote
+ {map[string]string{"a": "1", "b": "2"},
+ "expected fixed map size 3 for unauthenticatedVote, got 2"},
+ {map[string]any{"a": 1, "b": 2, "c": 3},
+ "expected string cred, got a"},
+ {[]int{1, 2, 3},
+ "reading map for unauthenticatedVote"},
+ {map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "e": "5", "f": "6", "g": "7"},
+ "expected fixed map size 3 for unauthenticatedVote, got 7"},
+ {map[string]string{gtFixMapString: "1", "b": "2", "c": "3"},
+ "readString: expected fixstr, got 0xd9"},
+
+ // cred
+ {map[string]string{"cred": "1", "d": "2", "e": "3"},
+ "reading map for UnauthenticatedCredential"},
+ {map[string]any{"cred": map[string]int{"pf": 1, "q": 2}, "d": "2", "e": "3"},
+ "expected fixed map size 1 for UnauthenticatedCredential, got 2"},
+ {map[string]any{"cred": map[string]int{gtFixMapString: 1}, "d": "2", "e": "3"},
+ "readString: expected fixstr, got 0xd9"},
+ {map[string]any{"cred": map[string]string{"invalid": "1"}, "r": "2", "sig": "3"},
+ "expected string pf, got invalid"},
+ {map[string]any{"cred": map[string]any{"pf": []byte{1, 2, 3}}, "r": "2", "sig": "3"},
+ "reading pf"},
+ {map[string]any{"cred": map[string]any{"pf": [100]byte{1, 2, 3}}, "r": "2", "sig": "3"},
+ "reading pf: expected bin8 length 80, got 100"},
+
+ // rawVote
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": []int{1, 2, 3}, "sig": "3"},
+ "reading map for rawVote"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]string{}, "sig": "3"},
+ "expected fixmap size for rawVote 1 <= cnt <= 5, got 0"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]string{"a": "1", "b": "2", "c": "3", "d": "4", "e": "5", "f": "6"}, "sig": "3"},
+ "expected fixmap size for rawVote 1 <= cnt <= 5, got 6"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]string{gtFixMapString: "1"}, "sig": "3"},
+ "reading key for rawVote"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]string{"invalid": "1"}, "sig": "3"},
+ "unexpected field in rawVote"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"per": "not-a-number"}, "sig": "3"},
+ "reading per"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": "not-a-number"}, "sig": "3"},
+ "reading rnd"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"step": "not-a-number"}, "sig": "3"},
+ "reading step"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": "not-a-map"}, "sig": "3"},
+ "reading map for proposalValue"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"snd": []int{1, 2, 3}}, "sig": "3"},
+ "reading snd: unexpected EOF"},
+
+ // proposalValue
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]string{"invalid": "1"}}, "sig": "3"},
+ "unexpected field in proposalValue"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]string{gtFixMapString: "1"}}, "sig": "3"},
+ "reading key for proposalValue"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]any{"dig": []int{1, 2, 3}}}, "sig": "3"},
+ "reading dig"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]any{"encdig": []int{1, 2, 3}}}, "sig": "3"},
+ "reading encdig"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]any{"oper": "not-a-number"}}, "sig": "3"},
+ "reading oper"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]any{"oprop": []int{1, 2, 3}}}, "sig": "3"},
+ "reading oprop"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"prop": map[string]any{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}}, "sig": "3"},
+ "expected fixmap size for proposalValue 1 <= cnt <= 4, got 5"},
+
+ // sig
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": []int{1, 2, 3}},
+ "reading map for OneTimeSignature"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{}},
+ "expected fixed map size 6 for OneTimeSignature, got 0"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{"p": []int{1}}},
+ "expected fixed map size 6 for OneTimeSignature, got 1"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ gtFixMapString: "1", "a": 1, "b": 2, "c": 3, "d": 4, "e": 5}},
+ "readString: expected fixstr, got 0xd9"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "a": 1, "b": 2, "c": 3, "d": 4, "e": 5, "f": 6}},
+ "expected string p, got a"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": []int{1}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "reading p: expected bin8 length 32"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": []int{1}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "reading p1s: expected bin8 length 64"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": []int{1}, "p2s": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "reading p2: expected bin8 length 32"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": []int{1}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "reading p2s: expected bin8 length 64"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": []int{1}, "s": [64]byte{}}},
+ "reading ps: expected bin8 length 64"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{1}}, "r": map[string]any{"rnd": 1}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{}, "s": []int{1}}},
+ "reading s: unexpected EOF"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "ra": 1, "sig": map[string]any{}},
+ "expected string r"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "snd": 1},
+ "expected string sig, got snd"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1x": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "expected string p1s, got p1x"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2x": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "expected string p2s, got p2x"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p1x": [64]byte{}, "p2": [32]byte{}, "ps": [64]byte{}, "s": [64]byte{}}},
+ "expected string p2, got p1x"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "pt": [64]byte{}, "s": [64]byte{}}},
+ "expected string ps, got pt"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{1}, "s": [64]byte{}}},
+ "expected empty array for ps"},
+ {map[string]any{"cred": map[string]any{"pf": crypto.VrfProof{}}, "r": map[string]any{"rnd": uint64(1)}, "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{}, "p2s": [64]byte{}, "ps": [64]byte{}, "sa": [64]byte{}}},
+ "expected string s, got sa"},
+}
+
+// TestParseVoteErrors tests error cases of the parseMsgpVote function
+func TestParseVoteErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ for _, tc := range parseVoteTestCases {
+ t.Run(tc.errContains, func(t *testing.T) {
+ buf := protocol.EncodeReflect(tc.obj)
+ se := NewStatelessEncoder()
+ _, err := se.CompressVote(nil, buf)
+ assert.ErrorContains(t, err, tc.errContains)
+ })
+ }
+}
+
+func TestParseVoteTrailingDataErr(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // Build minimal valid vote
+ obj := map[string]any{
+ "cred": map[string]any{"pf": crypto.VrfProof{}},
+ "r": map[string]any{"rnd": uint64(1)},
+ "sig": map[string]any{
+ "p": [32]byte{},
+ "p1s": [64]byte{},
+ "p2": [32]byte{},
+ "p2s": [64]byte{},
+ "ps": [64]byte{},
+ "s": [64]byte{},
+ },
+ }
+ buf := protocol.EncodeReflect(obj)
+ buf = append(buf, 0xFF)
+ se := NewStatelessEncoder()
+ _, err := se.CompressVote(nil, buf)
+ assert.ErrorContains(t, err, "unexpected trailing data")
+}
diff --git a/network/vpack/rapid_test.go b/network/vpack/rapid_test.go
new file mode 100644
index 0000000000..eae07011e2
--- /dev/null
+++ b/network/vpack/rapid_test.go
@@ -0,0 +1,269 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "bytes"
+ "math"
+ "reflect"
+ "testing"
+ "unsafe"
+
+ "github.com/algorand/go-algorand/agreement"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+ "pgregory.net/rapid"
+)
+
+// TestCheckStatelessEncoder tests the StatelessEncoder/Decoder using randomly generated votes
+// generated by rapid.
+func TestCheckStatelessEncoder(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ rapid.Check(t, checkStatelessEncoder)
+}
+
+// FuzzCheckStatelessEncoder is the same as TestCheckStatelessEncoder, but is a fuzz test.
+func FuzzCheckStatelessEncoder(f *testing.F) {
+ partitiontest.PartitionTest(f)
+ f.Fuzz(rapid.MakeFuzz(checkStatelessEncoder))
+}
+
+func checkStatelessEncoder(t *rapid.T) {
+ // Generate a random vote
+ v0 := generateRandomVote().Draw(t, "vote")
+
+ // Convert to msgpack
+ msgpBuf := protocol.EncodeMsgp(v0)
+ require.LessOrEqual(t, len(msgpBuf), MaxMsgpackVoteSize)
+
+ // Try to compress with StatelessEncoder
+ encBM := NewStatelessEncoder()
+ encBufBM, err := encBM.CompressVote(nil, msgpBuf)
+ require.NoError(t, err)
+ require.LessOrEqual(t, len(encBufBM), MaxCompressedVoteSize)
+
+ // Verify the bitmask is at the beginning
+ require.GreaterOrEqual(t, len(encBufBM), 2, "Compressed data should have at least 2 bytes for header")
+ // Decompress with StatelessDecoder
+ decBM := NewStatelessDecoder()
+ decBufBM, err := decBM.DecompressVote(nil, encBufBM)
+ require.NoError(t, err)
+
+ // Ensure the decompressed data matches the original msgpack data
+ require.Equal(t, msgpBuf, decBufBM)
+
+ // Decode the decompressed data and verify it matches the original vote
+ var v1 agreement.UnauthenticatedVote
+ err = protocol.Decode(decBufBM, &v1)
+ require.NoError(t, err)
+ require.Equal(t, *v0, v1)
+}
+
+// generateRandomVote creates a random vote generator using rapid
+func generateRandomVote() *rapid.Generator[*agreement.UnauthenticatedVote] {
+ return rapid.Custom(func(t *rapid.T) *agreement.UnauthenticatedVote {
+ v := &agreement.UnauthenticatedVote{}
+
+ filterZeroBytes := func(b []byte) bool {
+ for _, v := range b {
+ if v != 0 {
+ return true
+ }
+ }
+ return false
+ }
+
+ // Generate random sender address (32 bytes)
+ addressBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Filter(filterZeroBytes).Draw(t, "sender")
+ copy(v.R.Sender[:], addressBytes)
+
+ // Create an equal distribution generator for different integer ranges
+ // This will test different MessagePack varuint encodings (uint8, uint16, uint32, uint64)
+ integerRangeGen := rapid.OneOf(
+ rapid.Uint64Range(0, 255), // uint8 range
+ rapid.Uint64Range(256, 65535), // uint16 range
+ rapid.Uint64Range(65536, 4294967295), // uint32 range
+ rapid.Uint64Range(4294967296, math.MaxUint64), // uint64 range
+ )
+
+ // Generate non-zero round using the range generator
+ roundNum := integerRangeGen.Filter(func(n uint64) bool {
+ return n > 0 // Ensure non-zero round
+ }).Draw(t, "round")
+ v.R.Round = basics.Round(roundNum)
+
+ // Use reflection to set the unexported period field with the range generator
+ rPeriodField := reflect.ValueOf(&v.R).Elem().FieldByName("Period")
+ rPeriodField = reflect.NewAt(rPeriodField.Type(), unsafe.Pointer(rPeriodField.UnsafeAddr())).Elem()
+ rPeriodField.SetUint(integerRangeGen.Draw(t, "period"))
+
+ // Create a biased generator for steps to emphasize early steps (0, 1, 2, 3)
+ stepGen := rapid.OneOf(
+ rapid.Just(uint64(0)), // Explicitly test step 0
+ rapid.Just(uint64(1)), // Explicitly test step 1
+ rapid.Just(uint64(2)), // Explicitly test step 2
+ rapid.Just(uint64(3)), // Explicitly test step 3
+ integerRangeGen, // Test other steps with less probability
+ )
+
+ // Use reflection to set the unexported step field
+ rStepField := reflect.ValueOf(&v.R).Elem().FieldByName("Step")
+ rStepField = reflect.NewAt(rStepField.Type(), unsafe.Pointer(rStepField.UnsafeAddr())).Elem()
+ rStepField.SetUint(stepGen.Draw(t, "step"))
+
+ // Decide whether to include a proposal or leave it empty
+ // If empty, the default zero values will be used
+ includeProposal := rapid.Bool().Draw(t, "includeProposal")
+ if includeProposal {
+ // Use reflection to set the OriginalPeriod field in the proposal
+ propVal := reflect.ValueOf(&v.R.Proposal).Elem()
+ origPeriodField := propVal.FieldByName("OriginalPeriod")
+ origPeriodField = reflect.NewAt(origPeriodField.Type(), unsafe.Pointer(origPeriodField.UnsafeAddr())).Elem()
+ origPeriodField.SetUint(integerRangeGen.Draw(t, "originalPeriod"))
+ // Generate random OpropField, BlockDigest, and EncodingDigest bytes (32 bytes each)
+ // But sometimes make them empty to test edge cases
+ makeBytesFn := func(name string) []byte {
+ generator := rapid.OneOf(
+ rapid.Just([]byte{}), // Empty case
+ rapid.SliceOfN(rapid.Byte(), 32, 32), // Full case
+ )
+ return generator.Draw(t, name)
+ }
+ opropBytes := makeBytesFn("oprop")
+ digestBytes := makeBytesFn("digest")
+ encDigestBytes := makeBytesFn("encDigest")
+
+ copy(v.R.Proposal.OriginalProposer[:], opropBytes)
+ copy(v.R.Proposal.BlockDigest[:], digestBytes)
+ copy(v.R.Proposal.EncodingDigest[:], encDigestBytes)
+
+ }
+
+ // Generate random proof bytes (80 bytes)
+ pfBytes := rapid.SliceOfN(rapid.Byte(), 80, 80).Filter(filterZeroBytes).Draw(t, "proof")
+ copy(v.Cred.Proof[:], pfBytes)
+
+ // Generate signature fields (variable sizes)
+ sigBytes := rapid.SliceOfN(rapid.Byte(), 64, 64).Filter(filterZeroBytes).Draw(t, "sig")
+ pkBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Filter(filterZeroBytes).Draw(t, "pk")
+ p2Bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Filter(filterZeroBytes).Draw(t, "pk2")
+ p1sBytes := rapid.SliceOfN(rapid.Byte(), 64, 64).Filter(filterZeroBytes).Draw(t, "pk1sig")
+ p2sBytes := rapid.SliceOfN(rapid.Byte(), 64, 64).Filter(filterZeroBytes).Draw(t, "pk2sig")
+ copy(v.Sig.Sig[:], sigBytes)
+ copy(v.Sig.PK[:], pkBytes)
+ copy(v.Sig.PK2[:], p2Bytes)
+ copy(v.Sig.PK1Sig[:], p1sBytes)
+ copy(v.Sig.PK2Sig[:], p2sBytes)
+
+ // PKSigOld is deprecated and always zero when encoded with StatelessEncoder
+ v.Sig.PKSigOld = [64]byte{}
+
+ return v
+ })
+}
+
+// FuzzStatelessEncoder is a fuzz test that generates random votes, encodes them as
+// msgpack, and uses them to seed the Go fuzzer. If they are valid, they will also be
+// decompressed.
+//
+// Since the fuzzer is generating random data that StatelessEncoder
+// expects to be valid msgpack-encoded votes, this test is only ensures that
+// StatelessEncoder and StatelessDecoder don't crash on malformed data.
+func FuzzStatelessEncoder(f *testing.F) {
+ partitiontest.PartitionTest(f)
+
+ // Seed with valid compressed votes from random vote generator
+ voteGen := generateRandomVote()
+ var msgpBuf []byte
+ for i := range 100 {
+ vote := voteGen.Example(i)
+ msgpBuf := protocol.EncodeMsgp(vote)
+ f.Add(msgpBuf) // Add seed corpus for the fuzzer
+ }
+ // Provide truncated versions of the last valid compressed vote
+ for i := 1; i < len(msgpBuf); i++ {
+ f.Add(msgpBuf[:i])
+ }
+ // Add parseVote test cases
+ for _, tc := range parseVoteTestCases {
+ f.Add(protocol.EncodeReflect(tc.obj))
+ }
+
+ // Use a separate function that properly utilizes the fuzzer input
+ f.Fuzz(func(t *testing.T, msgpBuf []byte) {
+ // Try to compress the input
+ enc := NewStatelessEncoder()
+ compressed, err := enc.CompressVote(nil, msgpBuf)
+ if err != nil {
+ // Not valid msgpack data for a vote, skip
+ return
+ }
+
+ // Then decompress it
+ dec := NewStatelessDecoder()
+ decompressed, err := dec.DecompressVote(nil, compressed)
+ if err != nil {
+ t.Fatalf("Failed to decompress valid compressed data: %v", err)
+ }
+
+ // Verify the decompressed data matches the original
+ if !bytes.Equal(msgpBuf, decompressed) {
+ t.Fatalf("Decompressed data does not match original")
+ }
+ })
+}
+
+// FuzzStatelessDecoder is a fuzz test specifically targeting the StatelessDecoder
+// with potentially malformed input.
+func FuzzStatelessDecoder(f *testing.F) {
+ partitiontest.PartitionTest(f)
+
+ // Add valid compressed votes from random vote generator
+ voteGen := generateRandomVote()
+ var msgpBuf []byte
+ for i := range 100 {
+ vote := voteGen.Example(i) // Use deterministic seeds
+ msgpBuf = protocol.EncodeMsgp(vote)
+ require.LessOrEqual(f, len(msgpBuf), MaxMsgpackVoteSize)
+ enc := NewStatelessEncoder()
+ compressedVote, err := enc.CompressVote(nil, msgpBuf)
+ if err != nil {
+ continue
+ }
+ require.LessOrEqual(f, len(compressedVote), MaxCompressedVoteSize)
+ f.Add(compressedVote)
+ }
+ // Provide truncated versions of the last valid compressed vote
+ for i := 1; i < len(msgpBuf); i++ {
+ f.Add(msgpBuf[:i])
+ }
+
+ f.Add([]byte{})
+ f.Add([]byte{0x01})
+ f.Add([]byte{0x01, 0x02})
+ f.Add([]byte{0xFF, 0xFF})
+ f.Add([]byte{0x00, 0x00})
+ f.Add([]byte{0x00, 0x00, 0xFF})
+
+ f.Fuzz(func(t *testing.T, data []byte) {
+ dec := NewStatelessDecoder()
+ decbuf, _ := dec.DecompressVote(nil, data) // Ensure it doesn't crash
+ require.LessOrEqual(t, len(decbuf), MaxMsgpackVoteSize)
+ })
+}
diff --git a/network/vpack/vpack.go b/network/vpack/vpack.go
new file mode 100644
index 0000000000..05e2c5861d
--- /dev/null
+++ b/network/vpack/vpack.go
@@ -0,0 +1,407 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "fmt"
+ "math/bits"
+)
+
+// A vote is made up of 14 values, some of which are optional.
+// The required values are: cred.pf, r.rnd, r.snd, sig.p, sig.p1s, sig.p2,
+// sig.p2s, sig.s (sig.ps is always zero).
+// The remaining 6 optional values are either present or omitted, and their
+// presence is indicated in a 1-byte bitmask in the header.
+const (
+ bitPer uint8 = 1 << iota // r.per
+ bitDig // r.prop.dig
+ bitEncDig // r.prop.encdig
+ bitOper // r.prop.oper
+ bitOprop // r.prop.oprop
+ bitStep // r.step
+
+ propFieldsMask uint8 = bitDig | bitEncDig | bitOper | bitOprop
+ totalRequiredFields = 8
+)
+
+const (
+ headerSize = 2 // 1 byte for mask, 1 byte for future use
+
+ maxMsgpVaruintSize = 9 // max size of a varuint is 8 bytes + 1 byte for the marker
+ msgpBin8Len32Size = len(msgpBin8Len32) + 32
+ msgpBin8Len64Size = len(msgpBin8Len64) + 64
+ msgpBin8Len80Size = len(msgpBin8Len80) + 80
+ msgpFixMapMarkerSize = 1
+
+ // MaxMsgpackVoteSize is the maximum size of a vote, including msgpack control characters
+ // and all required and optional data fields.
+ MaxMsgpackVoteSize = msgpFixMapMarkerSize + // top-level fixmap
+ len(msgpFixstrCred) + msgpFixMapMarkerSize + // cred: fixmap
+ len(msgpFixstrPf) + msgpBin8Len80Size + // cred.pf: bin8(80)
+ len(msgpFixstrR) + msgpFixMapMarkerSize + // r: fixmap
+ len(msgpFixstrPer) + maxMsgpVaruintSize + // r.per: varuint
+ len(msgpFixstrProp) + msgpFixMapMarkerSize + // r.prop: fixmap
+ len(msgpFixstrDig) + msgpBin8Len32Size + // r.prop.dig: bin8(32)
+ len(msgpFixstrEncdig) + msgpBin8Len32Size + // r.prop.encdig: bin8(32)
+ len(msgpFixstrOper) + maxMsgpVaruintSize + // r.prop.oper: varuint
+ len(msgpFixstrOprop) + msgpBin8Len32Size + // r.prop.oprop: bin8(32)
+ len(msgpFixstrRnd) + maxMsgpVaruintSize + // r.rnd: varuint
+ len(msgpFixstrSnd) + msgpBin8Len32Size + // r.snd: bin8(32)
+ len(msgpFixstrStep) + maxMsgpVaruintSize + // r.step: varuint
+ len(msgpFixstrSig) + msgpFixMapMarkerSize + // sig: fixmap
+ len(msgpFixstrP) + msgpBin8Len32Size + // sig.p: bin8(32)
+ len(msgpFixstrP1s) + msgpBin8Len64Size + // sig.p1s: bin8(64)
+ len(msgpFixstrP2) + msgpBin8Len32Size + // sig.p2: bin8(32)
+ len(msgpFixstrP2s) + msgpBin8Len64Size + // sig.p2s: bin8(64)
+ len(msgpFixstrPs) + msgpBin8Len64Size + // sig.ps: bin8(64)
+ len(msgpFixstrS) + msgpBin8Len64Size // sig.s: bin8(64)
+
+ // MaxCompressedVoteSize is the maximum size of a compressed vote using StatelessEncoder,
+ // including all required and optional fields.
+ MaxCompressedVoteSize = headerSize +
+ 80 + // cred.pf
+ maxMsgpVaruintSize*4 + // r.rnd, r.per, r.step, r.prop.oper
+ 32*6 + // r.prop.dig, r.prop.encdig, r.prop.oprop, r.snd, sig.p, sig.p2
+ 64*3 // sig.p1s, sig.p2s, sig.s (sig.ps is omitted)
+)
+
+// StatelessEncoder compresses a msgpack-encoded vote by stripping all msgpack
+// formatting and field names, replacing them with a bitmask indicating which
+// fields are present. It is not thread-safe.
+type StatelessEncoder struct {
+ cur []byte
+ pos int
+ mask uint8
+
+ requiredFields uint8
+}
+
+// NewStatelessEncoder returns a new StatelessEncoder.
+func NewStatelessEncoder() *StatelessEncoder {
+ return &StatelessEncoder{}
+}
+
+// ErrBufferTooSmall is returned when the destination buffer is too small
+var ErrBufferTooSmall = fmt.Errorf("destination buffer too small")
+
+// CompressVote compresses a vote in src and writes it to dst.
+// If the provided buffer dst is nil or too small, a new slice is allocated.
+// The returned slice may be the same as dst.
+// To re-use dst, run like: dst = enc.CompressVote(dst[:0], src)
+func (e *StatelessEncoder) CompressVote(dst, src []byte) ([]byte, error) {
+ bound := MaxCompressedVoteSize
+ // Reuse dst if it's big enough, otherwise allocate a new buffer
+ if cap(dst) >= bound {
+ dst = dst[0:bound] // Reuse dst buffer with its full capacity
+ } else {
+ dst = make([]byte, bound)
+ }
+
+ // Reset our position to the beginning
+ e.cur = dst
+ e.mask = 0
+ e.requiredFields = 0
+ // put empty header at beginning, to fill in later
+ e.pos = headerSize
+ err := parseMsgpVote(src, e)
+ if err != nil {
+ return nil, err
+ }
+
+ // Check if we overflowed the buffer
+ if e.pos > len(dst) {
+ return nil, ErrBufferTooSmall
+ }
+
+ if e.requiredFields != totalRequiredFields {
+ return nil, fmt.Errorf("missing required fields")
+ }
+ // fill in header's first byte with mask
+ e.cur[0] = e.mask
+
+ // Return only the portion that was used
+ return dst[:e.pos], nil
+
+}
+
+// writeBytes writes multiple bytes to the current buffer
+// This is optimized to avoid per-byte bounds checking when possible
+func (e *StatelessEncoder) writeBytes(bytes []byte) {
+ // If we have enough room in the buffer, use direct copy
+ if e.pos+len(bytes) <= len(e.cur) {
+ copy(e.cur[e.pos:], bytes)
+ }
+ // Always increment pos, so CompressVote will return ErrBufferTooSmall
+ e.pos += len(bytes)
+}
+
+func (e *StatelessEncoder) updateMask(field voteValueType) {
+ switch field {
+ case rPerVoteValue:
+ e.mask |= bitPer
+ case rPropDigVoteValue:
+ e.mask |= bitDig
+ case rPropEncdigVoteValue:
+ e.mask |= bitEncDig
+ case rPropOperVoteValue:
+ e.mask |= bitOper
+ case rPropOpropVoteValue:
+ e.mask |= bitOprop
+ case rStepVoteValue:
+ e.mask |= bitStep
+ default:
+ // all other fields are required
+ e.requiredFields++
+ }
+}
+
+func (e *StatelessEncoder) writeVaruint(field voteValueType, b []byte) {
+ e.updateMask(field)
+ e.writeBytes(b)
+}
+
+func (e *StatelessEncoder) writeBin32(field voteValueType, b [32]byte) {
+ e.updateMask(field)
+ e.writeBytes(b[:])
+}
+
+func (e *StatelessEncoder) writeBin64(field voteValueType, b [64]byte) {
+ e.updateMask(field)
+ e.writeBytes(b[:])
+}
+
+func (e *StatelessEncoder) writeBin80(field voteValueType, b [80]byte) {
+ e.updateMask(field)
+ e.writeBytes(b[:])
+}
+
+// StatelessDecoder decompresses votes that were compressed by StatelessEncoder.
+type StatelessDecoder struct {
+ dst, src []byte
+ pos int
+}
+
+// NewStatelessDecoder returns a new StatelessDecoder.
+func NewStatelessDecoder() *StatelessDecoder {
+ return &StatelessDecoder{}
+}
+
+func (d *StatelessDecoder) rawVoteMapSize(mask uint8) (cnt uint8) {
+ // Count how many of per, step are set (rnd & snd must be present)
+ cnt = 2 + uint8(bits.OnesCount8(mask&(bitPer|bitStep)))
+ // Add 1 if any prop bits are set
+ if mask&propFieldsMask != 0 {
+ cnt++
+ }
+ return
+}
+
+func (d *StatelessDecoder) proposalValueMapSize(mask uint8) uint8 {
+ // Count how many of dig, encdig, oper, oprop are set
+ return uint8(bits.OnesCount8(mask & (bitDig | bitEncDig | bitOper | bitOprop)))
+}
+
+// DecompressVote decodes a compressed vote in src and appends it to dst.
+// To re-use dst, run like: dst = dec.DecompressVote(dst[:0], src)
+func (d *StatelessDecoder) DecompressVote(dst, src []byte) ([]byte, error) {
+ if len(src) < 2 {
+ return nil, fmt.Errorf("header missing")
+ }
+ mask := uint8(src[0])
+ d.pos = 2
+ d.src = src
+ d.dst = dst
+ if d.dst == nil { // allocate a new buffer if dst is nil
+ d.dst = make([]byte, 0, MaxMsgpackVoteSize)
+ }
+
+ // top-level UnauthenticatedVote: fixmap(3) { cred, rawVote, sig }
+ d.dst = append(d.dst, msgpFixMapMask|3)
+
+ // cred: fixmap(1) { pf: bin8(80) }
+ d.dst = append(d.dst, msgpFixstrCred...)
+ d.dst = append(d.dst, msgpFixMapMask|1)
+
+ // cred.pf is always present
+ if err := d.bin80(msgpFixstrPf); err != nil {
+ return nil, err
+ }
+
+ // rawVote: fixmap { per, prop, rnd, snd, step }
+ d.dst = append(d.dst, msgpFixstrR...)
+ d.dst = append(d.dst, msgpFixMapMask|d.rawVoteMapSize(mask))
+
+ // rawVote.per
+ if (mask & bitPer) != 0 {
+ if err := d.varuint(msgpFixstrPer); err != nil {
+ return nil, err
+ }
+ }
+
+ // rawVote.prop could be zero (bottom vote is empty value)
+ if (mask & propFieldsMask) != 0 {
+ // proposalValue: fixmap { dig, encdig, oper, oprop }
+ d.dst = append(d.dst, msgpFixstrProp...)
+ d.dst = append(d.dst, msgpFixMapMask|d.proposalValueMapSize(mask))
+ // prop.dig
+ if (mask & bitDig) != 0 {
+ if err := d.bin32(msgpFixstrDig); err != nil {
+ return nil, err
+ }
+ }
+ // prop.encdig
+ if (mask & bitEncDig) != 0 {
+ if err := d.bin32(msgpFixstrEncdig); err != nil {
+ return nil, err
+ }
+ }
+ // prop.oper
+ if (mask & bitOper) != 0 {
+ if err := d.varuint(msgpFixstrOper); err != nil {
+ return nil, err
+ }
+ }
+ // prop.oprop
+ if (mask & bitOprop) != 0 {
+ if err := d.bin32(msgpFixstrOprop); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // rawVote.rnd is always present
+ if err := d.varuint(msgpFixstrRnd); err != nil {
+ return nil, err
+ }
+
+ // rawVote.snd is always present
+ if err := d.bin32(msgpFixstrSnd); err != nil {
+ return nil, err
+ }
+
+ // rawVote.step
+ if (mask & bitStep) != 0 {
+ if err := d.varuint(msgpFixstrStep); err != nil {
+ return nil, err
+ }
+ }
+
+ // crypto.OneTimeSignature does not use omitempty; all fields are required
+ // and always present.
+
+ // sig: fixmap(6) { p, p1s, p2, p2s, ps, s }
+ d.dst = append(d.dst, msgpFixstrSig...)
+ d.dst = append(d.dst, msgpFixMapMask|6)
+ // sig.p
+ if err := d.bin32(msgpFixstrP); err != nil {
+ return nil, err
+ }
+ // sig.p1s
+ if err := d.bin64(msgpFixstrP1s); err != nil {
+ return nil, err
+ }
+ // sig.p2
+ if err := d.bin32(msgpFixstrP2); err != nil {
+ return nil, err
+ }
+ // sig.p2s
+ if err := d.bin64(msgpFixstrP2s); err != nil {
+ return nil, err
+ }
+ // sig.ps is always zero
+ d.dst = append(d.dst, msgpFixstrPs...)
+ d.dst = append(d.dst, msgpBin8Len64...)
+ d.dst = append(d.dst, make([]byte, 64)...)
+ // sig.s
+ if err := d.bin64(msgpFixstrS); err != nil {
+ return nil, err
+ }
+
+ if d.pos < len(d.src) {
+ return nil, fmt.Errorf("unexpected trailing data: %d bytes remain", len(d.src)-d.pos)
+ }
+
+ return d.dst, nil
+}
+
+func (d *StatelessDecoder) bin64(fieldStr string) error {
+ if d.pos+64 > len(d.src) {
+ return fmt.Errorf("not enough data to read value for field %s", fieldStr)
+ }
+ d.dst = append(d.dst, fieldStr...)
+ d.dst = append(d.dst, msgpBin8Len64...)
+ d.dst = append(d.dst, d.src[d.pos:d.pos+64]...)
+ d.pos += 64
+ return nil
+}
+
+func (d *StatelessDecoder) bin32(fieldStr string) error {
+ if d.pos+32 > len(d.src) {
+ return fmt.Errorf("not enough data to read value for field %s", fieldStr)
+ }
+ d.dst = append(d.dst, fieldStr...)
+ d.dst = append(d.dst, msgpBin8Len32...)
+ d.dst = append(d.dst, d.src[d.pos:d.pos+32]...)
+ d.pos += 32
+ return nil
+}
+
+func (d *StatelessDecoder) bin80(fieldStr string) error {
+ if d.pos+80 > len(d.src) {
+ return fmt.Errorf("not enough data to read value for field %s, d.pos=%d, len(src)=%d", fieldStr, d.pos, len(d.src))
+ }
+ d.dst = append(d.dst, fieldStr...)
+ d.dst = append(d.dst, msgpBin8Len80...)
+ d.dst = append(d.dst, d.src[d.pos:d.pos+80]...)
+ d.pos += 80
+ return nil
+}
+
+func (d *StatelessDecoder) varuint(fieldName string) error {
+ if d.pos+1 > len(d.src) {
+ return fmt.Errorf("not enough data to read varuint marker for field %s", fieldName)
+ }
+ marker := d.src[d.pos] // read msgpack varuint marker
+ moreBytes := 0
+ switch marker {
+ case msgpUint8:
+ moreBytes = 1
+ case msgpUint16:
+ moreBytes = 2
+ case msgpUint32:
+ moreBytes = 4
+ case msgpUint64:
+ moreBytes = 8
+ default: // fixint uses a single byte for marker+value
+ if !isMsgpFixint(marker) {
+ return fmt.Errorf("not a fixint for field %s, got %d", fieldName, marker)
+ }
+ moreBytes = 0
+ }
+
+ if d.pos+1+moreBytes > len(d.src) {
+ return fmt.Errorf("not enough data for varuint (need %d bytes) for field %s", moreBytes, fieldName)
+ }
+ d.dst = append(d.dst, fieldName...)
+ d.dst = append(d.dst, marker)
+ if moreBytes > 0 {
+ d.dst = append(d.dst, d.src[d.pos+1:d.pos+moreBytes+1]...)
+ }
+ d.pos += moreBytes + 1 // account for marker byte + value bytes
+
+ return nil
+}
diff --git a/network/vpack/vpack_test.go b/network/vpack/vpack_test.go
new file mode 100644
index 0000000000..9062a61da8
--- /dev/null
+++ b/network/vpack/vpack_test.go
@@ -0,0 +1,390 @@
+// Copyright (C) 2019-2025 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 vpack
+
+import (
+ "encoding/json"
+ "fmt"
+ "reflect"
+ "slices"
+ "testing"
+ "unsafe"
+
+ "github.com/algorand/go-algorand/agreement"
+ "github.com/algorand/go-algorand/data/basics"
+ "github.com/algorand/go-algorand/protocol"
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+// checkVoteValid analyzes a vote to determine if it would cause compression errors and what kind.
+// Returns (true, expectedError) if an error is expected during compression, (false, "") otherwise.
+func checkVoteValid(vote *agreement.UnauthenticatedVote) (ok bool, expectedError string) {
+ if vote.R.MsgIsZero() || vote.Cred.MsgIsZero() || vote.Sig.MsgIsZero() {
+ return false, "expected fixed map size 3 for unauthenticatedVote"
+ }
+ if vote.Cred.Proof.MsgIsZero() {
+ return false, "expected fixed map size 1 for UnauthenticatedCredential"
+ }
+ if !vote.Sig.PKSigOld.MsgIsZero() {
+ return false, "expected empty array for ps"
+ }
+ if vote.R.Round == 0 {
+ return false, "missing required fields"
+ }
+ if vote.R.Sender.IsZero() {
+ return false, "missing required fields"
+ }
+
+ return true, ""
+}
+
+// based on RunEncodingTest from protocol/codec_tester.go
+func TestEncodingTest(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ var errorCount int
+ const iters = 20000
+ for range iters {
+ v0obj, err := protocol.RandomizeObject(&agreement.UnauthenticatedVote{},
+ protocol.RandomizeObjectWithZeroesEveryN(10),
+ protocol.RandomizeObjectWithAllUintSizes(),
+ )
+ require.NoError(t, err)
+
+ v0 := v0obj.(*agreement.UnauthenticatedVote)
+ if *v0 == (agreement.UnauthenticatedVote{}) {
+ continue // don't try to encode or compress empty votes (a single byte, 0x80)
+ }
+ // zero out ps, always empty
+ v0.Sig.PKSigOld = [64]byte{}
+
+ var expectError string
+ if ok, errorMsg := checkVoteValid(v0); !ok {
+ expectError = errorMsg
+ }
+
+ msgpBuf := protocol.EncodeMsgp(v0)
+ require.LessOrEqual(t, len(msgpBuf), MaxMsgpackVoteSize)
+ enc := NewStatelessEncoder()
+ encBuf, err := enc.CompressVote(nil, msgpBuf)
+ require.LessOrEqual(t, len(encBuf), MaxCompressedVoteSize)
+ if expectError != "" {
+ // skip expected errors
+ require.ErrorContains(t, err, expectError, "expected error: %s", expectError)
+ require.Nil(t, encBuf)
+ errorCount++
+ continue
+ }
+ require.NoError(t, err)
+
+ // decompress and compare to original
+ dec := NewStatelessDecoder()
+ decMsgpBuf, err := dec.DecompressVote(nil, encBuf)
+ jsonBuf, _ := json.MarshalIndent(v0, "", " ")
+ require.NoError(t, err, "got vote %s", jsonBuf)
+ require.Equal(t, msgpBuf, decMsgpBuf) // msgp encoding matches
+ var v1 agreement.UnauthenticatedVote
+ err = protocol.Decode(decMsgpBuf, &v1)
+ require.NoError(t, err)
+ require.Equal(t, *v0, v1) // vote objects match
+ }
+ t.Logf("TestEncodingTest: %d expected errors out of %d iterations", errorCount, iters)
+}
+
+func TestStatelessDecoderErrors(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ z := func(n int) []byte { return make([]byte, n) }
+ h := func(m uint8) []byte { return []byte{m, 0x00} }
+
+ fix1 := []byte{0x01} // msgpack fixint 1 (rnd = 1)
+ pf := z(80) // cred.pf (always present)
+ z32, z64 := z(32), z(64)
+
+ type tc struct {
+ name, want string
+ buf func() []byte
+ }
+
+ cases := []tc{
+ // ---------- cred ----------
+ {"pf-bin80", fmt.Sprintf("field %s", msgpFixstrPf),
+ func() []byte { return slices.Concat(h(0), z(79)) }},
+
+ // ---------- r.per ----------
+ {"per-varuint-marker", fmt.Sprintf("field %s", msgpFixstrPer),
+ func() []byte { return slices.Concat(h(bitPer), pf) }},
+
+ // ---------- r.prop.* ----------
+ {"dig-bin32", fmt.Sprintf("field %s", msgpFixstrDig),
+ func() []byte { return slices.Concat(h(bitDig), pf, z(10)) }},
+ {"encdig-bin32", fmt.Sprintf("field %s", msgpFixstrEncdig),
+ func() []byte { return slices.Concat(h(bitDig|bitEncDig), pf, z32, z(10)) }},
+ {"oper-varuint-marker", fmt.Sprintf("field %s", msgpFixstrOper),
+ func() []byte { return slices.Concat(h(bitOper), pf) }},
+ {"oprop-bin32", fmt.Sprintf("field %s", msgpFixstrOprop),
+ func() []byte { return slices.Concat(h(bitOprop), pf, z(5)) }},
+
+ // ---------- r.rnd ----------
+ {"rnd-varuint-marker", fmt.Sprintf("field %s", msgpFixstrRnd),
+ func() []byte { return slices.Concat(h(0), pf) }},
+ {"rnd-varuint-trunc", fmt.Sprintf("not enough data for varuint (need 4 bytes) for field %s", msgpFixstrRnd),
+ func() []byte { return slices.Concat(h(0), pf, []byte{msgpUint32, 0x00}) }},
+
+ // ---------- r.snd / r.step ----------
+ {"snd-bin32", fmt.Sprintf("field %s", msgpFixstrSnd),
+ func() []byte { return slices.Concat(h(0), pf, fix1) }},
+ {"step-varuint-marker", fmt.Sprintf("field %s", msgpFixstrStep),
+ func() []byte { return slices.Concat(h(bitStep), pf, fix1, z32) }},
+
+ // ---------- sig.* ----------
+ {"p-bin32", fmt.Sprintf("field %s", msgpFixstrP),
+ func() []byte { return slices.Concat(h(0), pf, fix1, z32) }},
+ {"p1s-bin64", fmt.Sprintf("field %s", msgpFixstrP1s),
+ func() []byte { return slices.Concat(h(0), pf, fix1, z32, z32, z(12)) }},
+ {"p2-bin32", fmt.Sprintf("field %s", msgpFixstrP2),
+ func() []byte { return slices.Concat(h(0), pf, fix1, z32, z32, z64) }},
+ {"p2s-bin64", fmt.Sprintf("field %s", msgpFixstrP2s),
+ func() []byte { return slices.Concat(h(0), pf, fix1, z32, z32, z64, z32, z(3)) }},
+ {"s-bin64", fmt.Sprintf("field %s", msgpFixstrS),
+ func() []byte { return slices.Concat(h(0), pf, fix1, z32, z32, z64, z32, z64) }},
+
+ // ---------- trailing data ----------
+ {"trailing-bytes", "unexpected trailing data",
+ func() []byte {
+ return slices.Concat(
+ h(0), pf, fix1, // header, pf, rnd
+ z32, // snd
+ z32, z64, z32, z64, // p, p1s, p2, p2s
+ z64, // s
+ []byte{0xFF}, // extra byte
+ )
+ }},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ dec := NewStatelessDecoder()
+ _, err := dec.DecompressVote(nil, tc.buf())
+ require.ErrorContains(t, err, tc.want)
+ })
+ }
+}
+
+// FuzzMsgpVote is a fuzz test for parseVote, CompressVote and DecompressVote.
+// It generates random msgp-encoded votes, then compresses & decompresses them.
+func FuzzMsgpVote(f *testing.F) {
+ partitiontest.PartitionTest(f)
+
+ addVote := func(obj any) []byte {
+ var buf []byte
+ if v, ok := obj.(*agreement.UnauthenticatedVote); ok {
+ buf = protocol.Encode(v)
+ } else {
+ buf = protocol.EncodeReflect(obj)
+ }
+ f.Add(buf)
+ f.Add(append([]byte{0x80}, buf...)) // add a prefix
+ f.Add(append([]byte{0x00}, buf...)) // add a prefix
+ f.Add(append(buf, 0x80)) // add a suffix
+ f.Add(append(buf, 0x00)) // add a suffix
+ return buf
+ }
+ // error cases (weird msgp bufs)
+ for _, tc := range parseVoteTestCases {
+ addVote(tc.obj)
+ }
+ for range 100 { // random valid votes
+ v, err := protocol.RandomizeObject(&agreement.UnauthenticatedVote{},
+ protocol.RandomizeObjectWithZeroesEveryN(10),
+ protocol.RandomizeObjectWithAllUintSizes())
+ require.NoError(f, err)
+ msgpbuf := addVote(v)
+ require.LessOrEqual(f, len(msgpbuf), MaxMsgpackVoteSize)
+ for i := range len(msgpbuf) {
+ f.Add(msgpbuf[:i])
+ }
+ }
+
+ f.Fuzz(func(t *testing.T, buf []byte) {
+ enc := NewStatelessEncoder()
+ encBuf, err := enc.CompressVote(nil, buf)
+ require.LessOrEqual(t, len(encBuf), MaxCompressedVoteSize)
+ if err != nil {
+ // invalid msgpbuf, skip
+ return
+ }
+ dec := NewStatelessDecoder()
+ decBuf, err := dec.DecompressVote(nil, encBuf)
+ require.LessOrEqual(t, len(decBuf), MaxMsgpackVoteSize)
+ require.NoError(t, err)
+ require.Equal(t, buf, decBuf)
+ })
+}
+
+func FuzzVoteFields(f *testing.F) {
+ partitiontest.PartitionTest(f)
+
+ f.Fuzz(func(t *testing.T, snd []byte, rnd, per, step uint64,
+ oper uint64, oprop, dig, encdig []byte,
+ pf []byte, s, p, ps, p2, p1s, p2s []byte) {
+ var v0 agreement.UnauthenticatedVote
+ copy(v0.R.Sender[:], snd)
+ v0.R.Round = basics.Round(rnd)
+ // Use reflection to set the unexported period field
+ rPeriodField := reflect.ValueOf(&v0.R).Elem().FieldByName("Period")
+ rPeriodField = reflect.NewAt(rPeriodField.Type(), unsafe.Pointer(rPeriodField.UnsafeAddr())).Elem()
+ rPeriodField.SetUint(per)
+ require.EqualValues(t, per, v0.R.Period)
+ // Use reflection to set the unexported step field
+ rStepField := reflect.ValueOf(&v0.R).Elem().FieldByName("Step")
+ rStepField = reflect.NewAt(rStepField.Type(), unsafe.Pointer(rStepField.UnsafeAddr())).Elem()
+ rStepField.SetUint(step)
+ require.EqualValues(t, step, v0.R.Step)
+ // Use reflection to set the OriginalPeriod field in the proposal
+ propVal := reflect.ValueOf(&v0.R.Proposal).Elem()
+ origPeriodField := propVal.FieldByName("OriginalPeriod")
+ origPeriodField = reflect.NewAt(origPeriodField.Type(), unsafe.Pointer(origPeriodField.UnsafeAddr())).Elem()
+ origPeriodField.SetUint(oper)
+ require.EqualValues(t, oper, v0.R.Proposal.OriginalPeriod)
+
+ copy(v0.R.Proposal.OriginalProposer[:], oprop)
+ copy(v0.R.Proposal.BlockDigest[:], dig)
+ copy(v0.R.Proposal.EncodingDigest[:], encdig)
+ copy(v0.Cred.Proof[:], pf)
+ copy(v0.Sig.Sig[:], s)
+ copy(v0.Sig.PK[:], p)
+ copy(v0.Sig.PKSigOld[:], ps)
+ copy(v0.Sig.PK2[:], p2)
+ copy(v0.Sig.PK1Sig[:], p1s)
+ copy(v0.Sig.PK2Sig[:], p2s)
+
+ var expectError string
+ if ok, errorMsg := checkVoteValid(&v0); !ok {
+ expectError = errorMsg
+ }
+
+ msgpBuf := protocol.Encode(&v0)
+ require.LessOrEqual(t, len(msgpBuf), MaxMsgpackVoteSize)
+ enc := NewStatelessEncoder()
+ encBuf, err := enc.CompressVote(nil, msgpBuf)
+ require.LessOrEqual(t, len(encBuf), MaxCompressedVoteSize)
+ if expectError != "" {
+ // skip expected errors
+ require.ErrorContains(t, err, expectError)
+ require.Nil(t, encBuf)
+ return
+ }
+ require.NoError(t, err)
+ dec := NewStatelessDecoder()
+ decBuf, err := dec.DecompressVote(nil, encBuf)
+ require.NoError(t, err)
+ require.Equal(t, msgpBuf, decBuf)
+ var v1 agreement.UnauthenticatedVote
+ err = protocol.Decode(decBuf, &v1)
+ require.NoError(t, err)
+ require.Equal(t, v0, v1)
+ })
+}
+
+// TestEncoderReuse specifically tests the reuse of a StatelessEncoder instance across
+// multiple compression operations. This test would have caught the bug where
+// the encoder's position wasn't being reset between calls.
+func TestEncoderReuse(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ // Create several random votes
+ const numVotes = 10
+ msgpBufs := make([][]byte, 0, numVotes)
+ voteGen := generateRandomVote()
+
+ // Generate random votes and encode them
+ for i := 0; i < numVotes; i++ {
+ msgpBufs = append(msgpBufs, protocol.EncodeMsgp(voteGen.Example(i)))
+ require.LessOrEqual(t, len(msgpBufs[i]), MaxMsgpackVoteSize)
+ }
+
+ // Test reusing the same encoder multiple times
+ enc := NewStatelessEncoder()
+ var compressedBufs [][]byte
+
+ // First case: Create a new buffer for each compression
+ for i, msgpBuf := range msgpBufs {
+ compressedBuf, err := enc.CompressVote(nil, msgpBuf)
+ require.NoError(t, err, "Vote %d failed to compress with new buffer", i)
+ require.LessOrEqual(t, len(compressedBuf), MaxCompressedVoteSize)
+ compressedBufs = append(compressedBufs, compressedBuf)
+ }
+
+ // Verify all compressed buffers can be decompressed correctly
+ dec := NewStatelessDecoder()
+ for i, compressedBuf := range compressedBufs {
+ decompressedBuf, err := dec.DecompressVote(nil, compressedBuf)
+ require.LessOrEqual(t, len(decompressedBuf), MaxMsgpackVoteSize)
+ require.NoError(t, err, "Vote %d failed to decompress", i)
+ require.Equal(t, msgpBufs[i], decompressedBuf, "Vote %d decompressed incorrectly", i)
+ }
+
+ // Second case: Reuse a single pre-allocated buffer
+ compressedBufs = compressedBufs[:0] // Clear
+ reusedBuffer := make([]byte, 0, 4096)
+
+ for i, msgpBuf := range msgpBufs {
+ // Save the compressed result and create a new copy
+ // to avoid the buffer being modified by subsequent operations
+ compressed, err := enc.CompressVote(reusedBuffer[:0], msgpBuf)
+ require.NoError(t, err, "Vote %d failed to compress with reused buffer", i)
+ require.LessOrEqual(t, len(compressed), MaxCompressedVoteSize)
+ compressedCopy := make([]byte, len(compressed))
+ copy(compressedCopy, compressed)
+ compressedBufs = append(compressedBufs, compressedCopy)
+ }
+
+ // Verify all compressed buffers with reused buffer can be decompressed correctly
+ for i, compressedBuf := range compressedBufs {
+ decompressedBuf, err := dec.DecompressVote(nil, compressedBuf)
+ require.NoError(t, err, "Vote %d failed to decompress (reused buffer)", i)
+ require.LessOrEqual(t, len(decompressedBuf), MaxMsgpackVoteSize)
+ require.Equal(t, msgpBufs[i], decompressedBuf, "Vote %d decompressed incorrectly (reused buffer)", i)
+ }
+
+ // Third case: Test with varying buffer sizes to ensure we handle capacity changes correctly
+ compressedBufs = compressedBufs[:0] // Clear
+ varyingBuffer := make([]byte, 0, 10) // Start with a small buffer
+
+ for i, msgpBuf := range msgpBufs {
+ // This will cause the buffer to be reallocated sometimes
+ compressed, err := enc.CompressVote(varyingBuffer[:0], msgpBuf)
+ require.NoError(t, err, "Vote %d failed to compress with varying buffer", i)
+ require.LessOrEqual(t, len(compressed), MaxCompressedVoteSize)
+ compressedCopy := make([]byte, len(compressed))
+ copy(compressedCopy, compressed)
+ compressedBufs = append(compressedBufs, compressedCopy)
+
+ // Update the buffer for next iteration - it might have grown
+ varyingBuffer = compressed
+ }
+
+ // Verify all compressed buffers with varying buffer can be decompressed correctly
+ for i, compressedBuf := range compressedBufs {
+ decompressedBuf, err := dec.DecompressVote(nil, compressedBuf)
+ require.NoError(t, err, "Vote %d failed to decompress (varying buffer)", i)
+ require.LessOrEqual(t, len(decompressedBuf), MaxMsgpackVoteSize)
+ require.Equal(t, msgpBufs[i], decompressedBuf, "Vote %d decompressed incorrectly (varying buffer)", i)
+ }
+}
diff --git a/network/wsNetwork.go b/network/wsNetwork.go
index b13c499667..38bdad2b8f 100644
--- a/network/wsNetwork.go
+++ b/network/wsNetwork.go
@@ -176,9 +176,9 @@ type WebsocketNetwork struct {
phonebook phonebook.Phonebook
- GenesisID string
+ genesisID string
NetworkID protocol.NetworkID
- RandomID string
+ randomID string
ready atomic.Int32
readyChan chan struct{}
@@ -283,8 +283,8 @@ const (
)
type broadcastRequest struct {
- tags []Tag
- data [][]byte
+ tag Tag
+ data []byte
except Peer
done chan struct{}
enqueueTime time.Time
@@ -302,6 +302,8 @@ type msgBroadcaster struct {
broadcastQueueBulk chan broadcastRequest
// slowWritingPeerMonitorInterval defines the interval between two consecutive tests for slow peer writing
slowWritingPeerMonitorInterval time.Duration
+ // enableVoteCompression controls whether vote compression is enabled
+ enableVoteCompression bool
}
// msgHandler contains the logic for handling incoming messages and managing a readBuffer. It provides
@@ -361,32 +363,20 @@ func (wn *WebsocketNetwork) PublicAddress() string {
// If except is not nil then we will not send it to that neighboring Peer.
// if wait is true then the call blocks until the packet has actually been sent to all neighbors.
func (wn *WebsocketNetwork) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except Peer) error {
- dataArray := make([][]byte, 1)
- dataArray[0] = data
- tagArray := make([]protocol.Tag, 1)
- tagArray[0] = tag
- return wn.broadcaster.BroadcastArray(ctx, tagArray, dataArray, wait, except)
+ return wn.broadcaster.broadcast(ctx, tag, data, wait, except)
}
-// BroadcastArray sends an array of messages.
-// If except is not nil then we will not send it to that neighboring Peer.
-// if wait is true then the call blocks until the packet has actually been sent to all neighbors.
-// TODO: add `priority` argument so that we don't have to guess it based on tag
-func (wn *msgBroadcaster) BroadcastArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error {
+func (wn *msgBroadcaster) broadcast(ctx context.Context, tag Tag, data []byte, wait bool, except Peer) error {
if wn.config.DisableNetworking {
return nil
}
- if len(tags) != len(data) {
- return errBcastInvalidArray
- }
-
- request := broadcastRequest{tags: tags, data: data, enqueueTime: time.Now(), ctx: ctx}
+ request := broadcastRequest{tag: tag, data: data, enqueueTime: time.Now(), ctx: ctx}
if except != nil {
request.except = except
}
broadcastQueue := wn.broadcastQueueBulk
- if highPriorityTag(tags) {
+ if highPriorityTag(tag) {
broadcastQueue = wn.broadcastQueueHighPrio
}
if wait {
@@ -431,14 +421,6 @@ func (wn *WebsocketNetwork) Relay(ctx context.Context, tag protocol.Tag, data []
return nil
}
-// RelayArray relays array of messages
-func (wn *WebsocketNetwork) RelayArray(ctx context.Context, tags []protocol.Tag, data [][]byte, wait bool, except Peer) error {
- if wn.relayMessages {
- return wn.broadcaster.BroadcastArray(ctx, tags, data, wait, except)
- }
- return nil
-}
-
func (wn *WebsocketNetwork) disconnectThread(badnode DisconnectablePeer, reason disconnectReason) {
defer wn.wg.Done()
wn.disconnect(badnode, reason)
@@ -530,7 +512,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer {
case PeersPhonebookRelays:
// return copy of phonebook, which probably also contains peers we're connected to, but if it doesn't maybe we shouldn't be making new connections to those peers (because they disappeared from the directory)
var addrs []string
- addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryRelayRole)
+ addrs = wn.phonebook.GetAddresses(1000, phonebook.RelayRole)
for _, addr := range addrs {
client, _ := wn.GetHTTPClient(addr)
peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, client, "" /*origin address*/)
@@ -538,7 +520,7 @@ func (wn *WebsocketNetwork) GetPeers(options ...PeerOption) []Peer {
}
case PeersPhonebookArchivalNodes:
var addrs []string
- addrs = wn.phonebook.GetAddresses(1000, phonebook.PhoneBookEntryArchivalRole)
+ addrs = wn.phonebook.GetAddresses(1000, phonebook.ArchivalRole)
for _, addr := range addrs {
client, _ := wn.GetHTTPClient(addr)
peerCore := makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, addr, client, "" /*origin address*/)
@@ -602,6 +584,7 @@ func (wn *WebsocketNetwork) setup() {
config: wn.config,
broadcastQueueHighPrio: make(chan broadcastRequest, wn.outgoingMessagesBufferSize),
broadcastQueueBulk: make(chan broadcastRequest, 100),
+ enableVoteCompression: wn.config.EnableVoteCompression,
}
if wn.broadcaster.slowWritingPeerMonitorInterval == 0 {
wn.broadcaster.slowWritingPeerMonitorInterval = slowWritingPeerMonitorInterval
@@ -628,7 +611,7 @@ func (wn *WebsocketNetwork) setup() {
var rbytes [10]byte
crypto.RandBytes(rbytes[:])
- wn.RandomID = base64.StdEncoding.EncodeToString(rbytes[:])
+ wn.randomID = base64.StdEncoding.EncodeToString(rbytes[:])
if wn.config.EnableIncomingMessageFilter {
wn.incomingMsgFilter = makeMessageFilter(wn.config.IncomingMessageFilterBucketCount, wn.config.IncomingMessageFilterBucketSize)
@@ -736,7 +719,7 @@ func (wn *WebsocketNetwork) Start() error {
go wn.postMessagesOfInterestThread()
- wn.log.Infof("serving genesisID=%s on %#v with RandomID=%s", wn.GenesisID, wn.PublicAddress(), wn.RandomID)
+ wn.log.Infof("serving genesisID=%s on %#v with RandomID=%s", wn.genesisID, wn.PublicAddress(), wn.randomID)
return nil
}
@@ -830,40 +813,99 @@ func (wn *WebsocketNetwork) RegisterValidatorHandlers(dispatch []TaggedMessageVa
func (wn *WebsocketNetwork) ClearValidatorHandlers() {
}
-func (wn *WebsocketNetwork) setHeaders(header http.Header) {
- localTelemetryGUID := wn.log.GetTelemetryGUID()
- localInstanceName := wn.log.GetInstanceName()
- header.Set(TelemetryIDHeader, localTelemetryGUID)
- header.Set(InstanceNameHeader, localInstanceName)
- header.Set(AddressHeader, wn.PublicAddress())
- header.Set(NodeRandomHeader, wn.RandomID)
+type peerMetadataProvider interface {
+ TelemetryGUID() string
+ InstanceName() string
+ GenesisID() string
+ PublicAddress() string
+ RandomID() string
+ SupportedProtoVersions() []string
+ Config() config.Local
+}
+
+// TelemetryGUID returns the telemetry GUID of this node.
+func (wn *WebsocketNetwork) TelemetryGUID() string {
+ return wn.log.GetTelemetryGUID()
+}
+
+// InstanceName returns the instance name of this node.
+func (wn *WebsocketNetwork) InstanceName() string {
+ return wn.log.GetInstanceName()
+}
+
+// GenesisID returns the genesis ID of this node.
+func (wn *WebsocketNetwork) GenesisID() string {
+ return wn.genesisID
+}
+
+// RandomID returns the random ID of this node.
+func (wn *WebsocketNetwork) RandomID() string {
+ return wn.randomID
+}
+
+// SupportedProtoVersions returns the supported protocol versions of this node.
+func (wn *WebsocketNetwork) SupportedProtoVersions() []string {
+ return wn.supportedProtocolVersions
+}
+
+// Config returns the configuration of this node.
+func (wn *WebsocketNetwork) Config() config.Local {
+ return wn.config
+}
+
+func setHeaders(header http.Header, netProtoVer string, meta peerMetadataProvider) {
+ header.Set(TelemetryIDHeader, meta.TelemetryGUID())
+ header.Set(InstanceNameHeader, meta.InstanceName())
+ if pa := meta.PublicAddress(); pa != "" {
+ header.Set(AddressHeader, pa)
+ }
+ if rid := meta.RandomID(); rid != "" {
+ header.Set(NodeRandomHeader, rid)
+ }
+ header.Set(GenesisHeader, meta.GenesisID())
+
+ // set the features header (comma-separated list)
+ header.Set(PeerFeaturesHeader, PeerFeatureProposalCompression)
+ features := []string{PeerFeatureProposalCompression}
+ if meta.Config().EnableVoteCompression {
+ features = append(features, PeerFeatureVoteVpackCompression)
+ }
+ header.Set(PeerFeaturesHeader, strings.Join(features, ","))
+
+ if netProtoVer != "" {
+ // for backward compatibility, include the ProtocolVersion header in request as well.
+ header.Set(ProtocolVersionHeader, netProtoVer)
+ }
+ for _, v := range meta.SupportedProtoVersions() {
+ header.Add(ProtocolAcceptVersionHeader, v)
+ }
}
// checkServerResponseVariables check that the version and random-id in the request headers matches the server ones.
// it returns true if it's a match, and false otherwise.
func (wn *WebsocketNetwork) checkServerResponseVariables(otherHeader http.Header, addr string) (bool, string) {
- matchingVersion, otherVersion := wn.checkProtocolVersionMatch(otherHeader)
+ matchingVersion, otherVersion := checkProtocolVersionMatch(otherHeader, wn.supportedProtocolVersions)
if matchingVersion == "" {
wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", addr, wn.supportedProtocolVersions, otherVersion, otherHeader)))
return false, ""
}
otherRandom := otherHeader.Get(NodeRandomHeader)
- if otherRandom == wn.RandomID || otherRandom == "" {
+ if otherRandom == wn.randomID || otherRandom == "" {
// This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out.
if otherRandom == "" {
// missing header.
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", addr, wn.RandomID, otherHeader)))
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", addr, wn.randomID, otherHeader)))
} else {
- wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", addr, wn.RandomID)
+ wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", addr, wn.randomID)
}
return false, ""
}
otherGenesisID := otherHeader.Get(GenesisHeader)
- if wn.GenesisID != otherGenesisID {
+ if wn.genesisID != otherGenesisID {
if otherGenesisID != "" {
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", addr, wn.GenesisID, otherGenesisID, otherHeader)))
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", addr, wn.genesisID, otherGenesisID, otherHeader)))
} else {
- wn.log.Warnf("new peer %#v did not include genesis header in response. mine=%#v headers %#v", addr, wn.GenesisID, otherHeader)
+ wn.log.Warnf("new peer %#v did not include genesis header in response. mine=%#v headers %#v", addr, wn.genesisID, otherHeader)
}
return false, ""
}
@@ -913,17 +955,17 @@ func (wn *WebsocketNetwork) checkIncomingConnectionLimits(response http.Response
}
// checkProtocolVersionMatch test ProtocolAcceptVersionHeader and ProtocolVersionHeader headers from the request/response and see if it can find a match.
-func (wn *WebsocketNetwork) checkProtocolVersionMatch(otherHeaders http.Header) (matchingVersion string, otherVersion string) {
+func checkProtocolVersionMatch(otherHeaders http.Header, ourSupportedProtocolVersions []string) (matchingVersion string, otherVersion string) {
otherAcceptedVersions := otherHeaders[textproto.CanonicalMIMEHeaderKey(ProtocolAcceptVersionHeader)]
for _, otherAcceptedVersion := range otherAcceptedVersions {
// do we have a matching version ?
- if slices.Contains(wn.supportedProtocolVersions, otherAcceptedVersion) {
+ if slices.Contains(ourSupportedProtocolVersions, otherAcceptedVersion) {
return otherAcceptedVersion, ""
}
}
otherVersion = otherHeaders.Get(ProtocolVersionHeader)
- if slices.Contains(wn.supportedProtocolVersions, otherVersion) {
+ if slices.Contains(ourSupportedProtocolVersions, otherVersion) {
return otherVersion, otherVersion
}
@@ -943,8 +985,8 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
return http.StatusNotFound
}
- if wn.GenesisID != otherGenesisID {
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", remoteAddrForLogging, wn.GenesisID, otherGenesisID, request.Header)))
+ if wn.genesisID != otherGenesisID {
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %#v genesis mismatch, mine=%#v theirs=%#v, headers %#v", remoteAddrForLogging, wn.genesisID, otherGenesisID, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching genesis-id"})
response.WriteHeader(http.StatusPreconditionFailed)
n, err := response.Write([]byte("mismatching genesis ID"))
@@ -959,7 +1001,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
// This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out.
var message string
// missing header.
- wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", remoteAddrForLogging, wn.RandomID, request.Header)))
+ wn.log.Warn(filterASCII(fmt.Sprintf("new peer %s did not include random ID header in request. mine=%s headers %#v", remoteAddrForLogging, wn.randomID, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "missing random ID header"})
message = fmt.Sprintf("Request was missing a %s header", NodeRandomHeader)
response.WriteHeader(http.StatusPreconditionFailed)
@@ -968,10 +1010,10 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo
wn.log.Warnf("ws failed to write response '%s' : n = %d err = %v", message, n, err)
}
return http.StatusPreconditionFailed
- } else if otherRandom == wn.RandomID {
+ } else if otherRandom == wn.randomID {
// This is pretty harmless and some configurations of phonebooks or DNS records make this likely. Quietly filter it out.
var message string
- wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", remoteAddrForLogging, wn.RandomID)
+ wn.log.Debugf("new peer %s has same node random id, am I talking to myself? %s", remoteAddrForLogging, wn.randomID)
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "matching random ID header"})
message = fmt.Sprintf("Request included matching %s=%s header", NodeRandomHeader, otherRandom)
response.WriteHeader(http.StatusLoopDetected)
@@ -998,7 +1040,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
return
}
- matchingVersion, otherVersion := wn.checkProtocolVersionMatch(request.Header)
+ matchingVersion, otherVersion := checkProtocolVersionMatch(request.Header, wn.supportedProtocolVersions)
if matchingVersion == "" {
wn.log.Info(filterASCII(fmt.Sprintf("new peer %s version mismatch, mine=%v theirs=%s, headers %#v", trackedRequest.remoteHost, wn.supportedProtocolVersions, otherVersion, request.Header)))
networkConnectionsDroppedTotal.Inc(map[string]string{"reason": "mismatching protocol version"})
@@ -1017,11 +1059,7 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
}
responseHeader := make(http.Header)
- wn.setHeaders(responseHeader)
- responseHeader.Set(ProtocolVersionHeader, matchingVersion)
- responseHeader.Set(GenesisHeader, wn.GenesisID)
- // set the features we support
- responseHeader.Set(PeerFeaturesHeader, PeerFeatureProposalCompression)
+ setHeaders(responseHeader, matchingVersion, wn)
var challenge string
if wn.prioScheme != nil {
challenge = wn.prioScheme.NewPrioChallenge()
@@ -1055,18 +1093,19 @@ func (wn *WebsocketNetwork) ServeHTTP(response http.ResponseWriter, request *htt
client, _ := wn.GetHTTPClient(trackedRequest.remoteAddress())
peer := &wsPeer{
- wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), client, trackedRequest.remoteHost),
- conn: wsPeerWebsocketConnImpl{conn},
- outgoing: false,
- InstanceName: trackedRequest.otherInstanceName,
- incomingMsgFilter: wn.incomingMsgFilter,
- prioChallenge: challenge,
- createTime: trackedRequest.created,
- version: matchingVersion,
- identity: peerID,
- identityChallenge: peerIDChallenge,
- identityVerified: atomic.Uint32{},
- features: decodePeerFeatures(matchingVersion, request.Header.Get(PeerFeaturesHeader)),
+ wsPeerCore: makePeerCore(wn.ctx, wn, wn.log, wn.handler.readBuffer, trackedRequest.remoteAddress(), client, trackedRequest.remoteHost),
+ conn: wsPeerWebsocketConnImpl{conn},
+ outgoing: false,
+ InstanceName: trackedRequest.otherInstanceName,
+ incomingMsgFilter: wn.incomingMsgFilter,
+ prioChallenge: challenge,
+ createTime: trackedRequest.created,
+ version: matchingVersion,
+ identity: peerID,
+ identityChallenge: peerIDChallenge,
+ identityVerified: atomic.Uint32{},
+ features: decodePeerFeatures(matchingVersion, request.Header.Get(PeerFeaturesHeader)),
+ enableVoteCompression: wn.config.EnableVoteCompression,
}
peer.TelemetryGUID = trackedRequest.otherTelemetryGUID
peer.init(wn.config, wn.outgoingMessagesBufferSize)
@@ -1351,28 +1390,34 @@ func (wn *WebsocketNetwork) getPeersChangeCounter() int32 {
// preparePeerData prepares batches of data for sending.
// It performs zstd compression for proposal massages if they this is a prio request and has proposal.
-func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool) ([][]byte, []crypto.Digest) {
- digests := make([]crypto.Digest, len(request.data))
- data := make([][]byte, len(request.data))
- for i, d := range request.data {
- tbytes := []byte(request.tags[i])
- mbytes := make([]byte, len(tbytes)+len(d))
- copy(mbytes, tbytes)
- copy(mbytes[len(tbytes):], d)
- data[i] = mbytes
- if request.tags[i] != protocol.MsgDigestSkipTag && len(d) >= messageFilterSize {
- digests[i] = crypto.Hash(mbytes)
- }
-
- if prio && request.tags[i] == protocol.ProposalPayloadTag {
- compressed, logMsg := zstdCompressMsg(tbytes, d)
- if len(logMsg) > 0 {
- wn.log.Warn(logMsg)
- }
- data[i] = compressed
+func (wn *msgBroadcaster) preparePeerData(request broadcastRequest, prio bool) ([]byte, []byte, crypto.Digest) {
+ tbytes := []byte(request.tag)
+ mbytes := make([]byte, len(tbytes)+len(request.data))
+ copy(mbytes, tbytes)
+ copy(mbytes[len(tbytes):], request.data)
+ var compressedData []byte
+
+ var digest crypto.Digest
+ if request.tag != protocol.MsgDigestSkipTag && len(request.data) >= messageFilterSize {
+ digest = crypto.Hash(mbytes)
+ }
+ // Compress proposals -- all proposals are compressed as of wsnet 2.2
+ if prio && request.tag == protocol.ProposalPayloadTag {
+ compressed, logMsg := zstdCompressMsg(tbytes, request.data)
+ if len(logMsg) > 0 {
+ wn.log.Warn(logMsg)
+ }
+ mbytes = compressed
+ }
+ // Optionally compress votes: only supporting peers will receive it.
+ if prio && request.tag == protocol.AgreementVoteTag && wn.enableVoteCompression {
+ var logMsg string
+ compressedData, logMsg = vpackCompressVote(tbytes, request.data)
+ if len(logMsg) > 0 {
+ wn.log.Warn(logMsg)
}
}
- return data, digests
+ return mbytes, compressedData, digest
}
// prio is set if the broadcast is a high-priority broadcast.
@@ -1389,7 +1434,7 @@ func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, pe
}
start := time.Now()
- data, digests := wn.preparePeerData(request, prio)
+ data, dataWithCompression, digest := wn.preparePeerData(request, prio)
// first send to all the easy outbound peers who don't block, get them started.
sentMessageCount := 0
@@ -1400,7 +1445,13 @@ func (wn *msgBroadcaster) innerBroadcast(request broadcastRequest, prio bool, pe
if Peer(peer) == request.except {
continue
}
- ok := peer.writeNonBlockMsgs(request.ctx, data, prio, digests, request.enqueueTime)
+ dataToSend := data
+ // check whether to send a compressed vote. dataWithCompression will be empty if this node
+ // has not enabled vote compression.
+ if peer.vpackVoteCompressionSupported() && len(dataWithCompression) > 0 {
+ dataToSend = dataWithCompression
+ }
+ ok := peer.writeNonBlock(request.ctx, dataToSend, prio, digest, request.enqueueTime)
if ok {
sentMessageCount++
continue
@@ -1558,12 +1609,12 @@ func (wn *WebsocketNetwork) refreshRelayArchivePhonebookAddresses() {
func (wn *WebsocketNetwork) updatePhonebookAddresses(relayAddrs []string, archiveAddrs []string) {
if len(relayAddrs) > 0 {
wn.log.Debugf("got %d relay dns addrs, %#v", len(relayAddrs), relayAddrs[:min(5, len(relayAddrs))])
- wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryRelayRole)
+ wn.phonebook.ReplacePeerList(relayAddrs, string(wn.NetworkID), phonebook.RelayRole)
} else {
wn.log.Infof("got no relay DNS addrs for network %s", wn.NetworkID)
}
if len(archiveAddrs) > 0 {
- wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), phonebook.PhoneBookEntryArchivalRole)
+ wn.phonebook.ReplacePeerList(archiveAddrs, string(wn.NetworkID), phonebook.ArchivalRole)
} else {
wn.log.Infof("got no archive DNS addrs for network %s", wn.NetworkID)
}
@@ -1582,7 +1633,7 @@ func (wn *WebsocketNetwork) checkNewConnectionsNeeded() bool {
return false
}
// get more than we need so that we can ignore duplicates
- newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, phonebook.PhoneBookEntryRelayRole)
+ newAddrs := wn.phonebook.GetAddresses(desired+numOutgoingTotal, phonebook.RelayRole)
for _, na := range newAddrs {
if na == wn.config.PublicAddress {
// filter out self-public address, so we won't try to connect to ourselves.
@@ -1855,13 +1906,13 @@ func (wn *WebsocketNetwork) getDNSAddrs(dnsBootstrap string) (relaysAddresses []
return
}
-// ProtocolVersionHeader HTTP header for protocol version.
+// ProtocolVersionHeader HTTP header for network protocol version.
const ProtocolVersionHeader = "X-Algorand-Version"
-// ProtocolAcceptVersionHeader HTTP header for accept protocol version. Client use this to advertise supported protocol versions.
+// ProtocolAcceptVersionHeader HTTP header for accept network protocol version. Client use this to advertise supported protocol versions.
const ProtocolAcceptVersionHeader = "X-Algorand-Accept-Version"
-// SupportedProtocolVersions contains the list of supported protocol versions by this node ( in order of preference ).
+// SupportedProtocolVersions contains the list of supported network protocol versions by this node ( in order of preference ).
var SupportedProtocolVersions = []string{"2.2"}
// ProtocolVersion is the current version attached to the ProtocolVersionHeader header
@@ -1906,6 +1957,10 @@ const PeerFeaturesHeader = "X-Algorand-Peer-Features"
// supports proposal payload compression with zstd
const PeerFeatureProposalCompression = "ppzstd"
+// PeerFeatureVoteVpackCompression is a value for PeerFeaturesHeader indicating peer
+// supports agreement vote message compression with vpack
+const PeerFeatureVoteVpackCompression = "avvpack"
+
var websocketsScheme = map[string]string{"http": "ws", "https": "wss"}
var errBadAddr = errors.New("bad address")
@@ -1914,8 +1969,6 @@ var errNetworkClosing = errors.New("WebsocketNetwork shutting down")
var errBcastCallerCancel = errors.New("caller cancelled broadcast")
-var errBcastInvalidArray = errors.New("invalid broadcast array")
-
var errBcastQFull = errors.New("broadcast queue full")
// tryConnectReserveAddr synchronously checks that addr is not already being connected to, returns (websocket URL or "", true if connection may proceed)
@@ -2002,9 +2055,9 @@ func (t *HTTPPAddressBoundTransport) RoundTrip(req *http.Request) (*http.Respons
// control character, new lines, deletion, etc. All the alpha numeric and punctuation characters
// are included in this range.
func filterASCII(unfilteredString string) (filteredString string) {
- for i, r := range unfilteredString {
+ for _, r := range unfilteredString {
if int(r) >= 0x20 && int(r) <= 0x7e {
- filteredString += string(unfilteredString[i])
+ filteredString += string(r)
} else {
filteredString += unprintableCharacterGlyph
}
@@ -2024,10 +2077,7 @@ func (wn *WebsocketNetwork) tryConnect(netAddr, gossipAddr string) {
defer wn.wg.Done()
requestHeader := make(http.Header)
- wn.setHeaders(requestHeader)
- for _, supportedProtocolVersion := range wn.supportedProtocolVersions {
- requestHeader.Add(ProtocolAcceptVersionHeader, supportedProtocolVersion)
- }
+ setHeaders(requestHeader, wn.protocolVersion, wn)
var idChallenge identityChallengeValue
if wn.identityScheme != nil {
@@ -2035,13 +2085,7 @@ func (wn *WebsocketNetwork) tryConnect(netAddr, gossipAddr string) {
idChallenge = wn.identityScheme.AttachChallenge(requestHeader, theirAddr)
}
- // for backward compatibility, include the ProtocolVersion header as well.
- requestHeader.Set(ProtocolVersionHeader, wn.protocolVersion)
- // set the features header (comma-separated list)
- requestHeader.Set(PeerFeaturesHeader, PeerFeatureProposalCompression)
SetUserAgentHeader(requestHeader)
- myInstanceName := wn.log.GetInstanceName()
- requestHeader.Set(InstanceNameHeader, myInstanceName)
var websocketDialer = websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: 45 * time.Second,
@@ -2143,6 +2187,7 @@ func (wn *WebsocketNetwork) tryConnect(netAddr, gossipAddr string) {
version: matchingVersion,
identity: peerID,
features: decodePeerFeatures(matchingVersion, response.Header.Get(PeerFeaturesHeader)),
+ enableVoteCompression: wn.config.EnableVoteCompression,
}
peer.TelemetryGUID, peer.InstanceName, _ = getCommonHeaders(response.Header)
@@ -2233,12 +2278,12 @@ func NewWebsocketNetwork(log logging.Logger, config config.Local, phonebookAddre
addresses = append(addresses, a)
}
}
- pb.AddPersistentPeers(addresses, string(networkID), phonebook.PhoneBookEntryRelayRole)
+ pb.AddPersistentPeers(addresses, string(networkID), phonebook.RelayRole)
wn = &WebsocketNetwork{
log: log,
config: config,
phonebook: pb,
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: networkID,
nodeInfo: nodeInfo,
resolveSRVRecords: tools_network.ReadFromSRV,
@@ -2486,4 +2531,4 @@ func (wn *WebsocketNetwork) postMessagesOfInterestThread() {
}
// GetGenesisID returns the network-specific genesisID.
-func (wn *WebsocketNetwork) GetGenesisID() string { return wn.GenesisID }
+func (wn *WebsocketNetwork) GetGenesisID() string { return wn.genesisID }
diff --git a/network/wsNetwork_test.go b/network/wsNetwork_test.go
index ad8dff913a..218b2687bd 100644
--- a/network/wsNetwork_test.go
+++ b/network/wsNetwork_test.go
@@ -131,7 +131,7 @@ func makeTestWebsocketNodeWithConfig(t testing.TB, conf config.Local, opts ...te
log: log,
config: conf,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: NewIdentityTracker(),
@@ -325,7 +325,7 @@ func setupWebsocketNetworkABwithLogger(t *testing.T, countTarget int, log loggin
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer func() {
if !success {
@@ -458,7 +458,7 @@ func TestWebsocketProposalPayloadCompression(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
messages := [][]byte{
@@ -490,6 +490,89 @@ func TestWebsocketProposalPayloadCompression(t *testing.T) {
}
}
+// Set up two nodes, send vote to test vote compression feature
+func TestWebsocketVoteCompression(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ type testDef struct {
+ netAEnableCompression, netBEnableCompression bool
+ }
+
+ var tests []testDef = []testDef{
+ {true, true}, // both nodes with compression enabled
+ {true, false}, // node A with compression, node B without
+ {false, true}, // node A without compression, node B with compression
+ {false, false}, // both nodes with compression disabled
+ }
+
+ for _, test := range tests {
+ t.Run(fmt.Sprintf("A_compression_%v+B_compression_%v", test.netAEnableCompression, test.netBEnableCompression), func(t *testing.T) {
+ cfgA := defaultConfig
+ cfgA.GossipFanout = 1
+ cfgA.EnableVoteCompression = test.netAEnableCompression
+ netA := makeTestWebsocketNodeWithConfig(t, cfgA)
+ netA.Start()
+ defer netStop(t, netA, "A")
+
+ cfgB := defaultConfig
+ cfgB.GossipFanout = 1
+ cfgB.EnableVoteCompression = test.netBEnableCompression
+ netB := makeTestWebsocketNodeWithConfig(t, cfgB)
+
+ addrA, postListen := netA.Address()
+ require.True(t, postListen)
+ t.Log(addrA)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
+ netB.Start()
+ defer netStop(t, netB, "B")
+
+ // ps is empty, so this is a valid vote
+ vote1 := map[string]any{
+ "cred": map[string]any{"pf": crypto.VrfProof{1}},
+ "r": map[string]any{"rnd": uint64(2), "snd": [32]byte{3}},
+ "sig": map[string]any{
+ "p": [32]byte{4}, "p1s": [64]byte{5}, "p2": [32]byte{6},
+ "p2s": [64]byte{7}, "ps": [64]byte{}, "s": [64]byte{9},
+ },
+ }
+ // ps is not empty: vpack compression will fail, but it will still be sent through
+ vote2 := map[string]any{
+ "cred": map[string]any{"pf": crypto.VrfProof{10}},
+ "r": map[string]any{"rnd": uint64(11), "snd": [32]byte{12}},
+ "sig": map[string]any{
+ "p": [32]byte{13}, "p1s": [64]byte{14}, "p2": [32]byte{15},
+ "p2s": [64]byte{16}, "ps": [64]byte{17}, "s": [64]byte{18},
+ },
+ }
+ // Send a totally invalid message to ensure that it goes through. Even though vpack compression
+ // and decompression will fail, the message should still go through (as an intended fallback).
+ vote3 := []byte("hello")
+ messages := [][]byte{protocol.EncodeReflect(vote1), protocol.EncodeReflect(vote2), vote3}
+ matcher := newMessageMatcher(t, messages)
+ counterDone := matcher.done
+ netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.AgreementVoteTag, MessageHandler: matcher}})
+
+ readyTimeout := time.NewTimer(2 * time.Second)
+ waitReady(t, netA, readyTimeout.C)
+ t.Log("a ready")
+ waitReady(t, netB, readyTimeout.C)
+ t.Log("b ready")
+
+ for _, msg := range messages {
+ netA.Broadcast(context.Background(), protocol.AgreementVoteTag, msg, true, nil)
+ }
+
+ select {
+ case <-counterDone:
+ case <-time.After(2 * time.Second):
+ t.Errorf("timeout, count=%d, wanted %d", len(matcher.received), len(messages))
+ }
+
+ require.True(t, matcher.Match())
+ })
+ }
+}
+
// Repeat basic, but test a unicast
func TestWebsocketNetworkUnicast(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -533,25 +616,6 @@ func TestWebsocketPeerData(t *testing.T) {
require.Equal(t, nil, netA.GetPeerData(peerB, "foo"))
}
-// Test sending array of messages
-func TestWebsocketNetworkArray(t *testing.T) {
- partitiontest.PartitionTest(t)
-
- netA, _, counter, closeFunc := setupWebsocketNetworkAB(t, 3)
- defer closeFunc()
- counterDone := counter.done
-
- tags := []protocol.Tag{protocol.TxnTag, protocol.TxnTag, protocol.TxnTag}
- data := [][]byte{[]byte("foo"), []byte("bar"), []byte("algo")}
- netA.broadcaster.BroadcastArray(context.Background(), tags, data, false, nil)
-
- select {
- case <-counterDone:
- case <-time.After(2 * time.Second):
- t.Errorf("timeout, count=%d, wanted 2", counter.count)
- }
-}
-
// Test cancelling message sends
func TestWebsocketNetworkCancel(t *testing.T) {
partitiontest.PartitionTest(t)
@@ -570,25 +634,29 @@ func TestWebsocketNetworkCancel(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
- // try calling BroadcastArray
- netA.broadcaster.BroadcastArray(ctx, tags, data, true, nil)
+ // try calling broadcast
+ for i := 0; i < 100; i++ {
+ netA.broadcaster.broadcast(ctx, tags[i], data[i], true, nil)
+ }
select {
case <-counterDone:
t.Errorf("All messages were sent, send not cancelled")
- case <-time.After(2 * time.Second):
+ case <-time.After(1 * time.Second):
}
assert.Equal(t, 0, counter.Count())
// try calling innerBroadcast
- request := broadcastRequest{tags: tags, data: data, enqueueTime: time.Now(), ctx: ctx}
peers, _ := netA.peerSnapshot([]*wsPeer{})
- netA.broadcaster.innerBroadcast(request, true, peers)
+ for i := 0; i < 100; i++ {
+ request := broadcastRequest{tag: tags[i], data: data[i], enqueueTime: time.Now(), ctx: ctx}
+ netA.broadcaster.innerBroadcast(request, true, peers)
+ }
select {
case <-counterDone:
t.Errorf("All messages were sent, send not cancelled")
- case <-time.After(2 * time.Second):
+ case <-time.After(1 * time.Second):
}
assert.Equal(t, 0, counter.Count())
@@ -600,21 +668,25 @@ func TestWebsocketNetworkCancel(t *testing.T) {
mbytes := make([]byte, len(tbytes)+len(msg))
copy(mbytes, tbytes)
copy(mbytes[len(tbytes):], msg)
- msgs = append(msgs, sendMessage{data: mbytes, enqueued: time.Now(), peerEnqueued: enqueueTime, hash: crypto.Hash(mbytes), ctx: context.Background()})
+ msgs = append(msgs, sendMessage{data: mbytes, enqueued: time.Now(), peerEnqueued: enqueueTime, ctx: context.Background()})
}
+ // cancel msg 50
msgs[50].ctx = ctx
for _, peer := range peers {
- peer.sendBufferHighPrio <- sendMessages{msgs: msgs}
+ for _, msg := range msgs {
+ peer.sendBufferHighPrio <- msg
+ }
}
select {
case <-counterDone:
t.Errorf("All messages were sent, send not cancelled")
- case <-time.After(2 * time.Second):
+ case <-time.After(1 * time.Second):
}
- assert.Equal(t, 50, counter.Count())
+ // all but msg 50 should have been sent
+ assert.Equal(t, 99, counter.Count())
}
// Set up two nodes, test that a.Broadcast is received by B, when B has no address.
@@ -637,7 +709,7 @@ func TestWebsocketNetworkNoAddress(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -702,7 +774,7 @@ func lineNetwork(t *testing.T, numNodes int) (nodes []*WebsocketNetwork, counter
if i > 0 {
addrPrev, postListen := nodes[i-1].Address()
require.True(t, postListen)
- nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", phonebook.PhoneBookEntryRelayRole)
+ nodes[i].phonebook.ReplacePeerList([]string{addrPrev}, "default", phonebook.RelayRole)
nodes[i].RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: &counters[i]}})
}
nodes[i].Start()
@@ -783,7 +855,7 @@ func TestAddrToGossipAddr(t *testing.T) {
partitiontest.PartitionTest(t)
wn := &WebsocketNetwork{}
- wn.GenesisID = "test genesisID"
+ wn.genesisID = "test genesisID"
wn.log = logging.Base()
addrtest(t, wn, "ws://r7.algodev.network.:4166/v1/test%20genesisID/gossip", "r7.algodev.network.:4166")
addrtest(t, wn, "ws://r7.algodev.network.:4166/v1/test%20genesisID/gossip", "http://r7.algodev.network.:4166")
@@ -990,8 +1062,8 @@ func TestSlowOutboundPeer(t *testing.T) {
for i := range destPeers {
destPeers[i].closing = make(chan struct{})
destPeers[i].net = node
- destPeers[i].sendBufferHighPrio = make(chan sendMessages, sendBufferLength)
- destPeers[i].sendBufferBulk = make(chan sendMessages, sendBufferLength)
+ destPeers[i].sendBufferHighPrio = make(chan sendMessage, sendBufferLength)
+ destPeers[i].sendBufferBulk = make(chan sendMessage, sendBufferLength)
destPeers[i].conn = &nopConnSingleton
destPeers[i].rootURL = fmt.Sprintf("fake %d", i)
node.addPeer(&destPeers[i])
@@ -1056,7 +1128,7 @@ func makeTestFilterWebsocketNode(t *testing.T, nodename string) *WebsocketNetwor
log: logging.TestingLog(t).With("node", nodename),
config: dc,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: logging.TestingLog(t).With("node", nodename)},
identityTracker: noopIdentityTracker{},
@@ -1080,7 +1152,7 @@ func TestDupFilter(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
counter := &messageCounterHandler{t: t, limit: 1, done: make(chan struct{})}
@@ -1093,7 +1165,7 @@ func TestDupFilter(t *testing.T) {
require.True(t, postListen)
netC := makeTestFilterWebsocketNode(t, "c")
netC.config.GossipFanout = 1
- netC.phonebook.ReplacePeerList([]string{addrB}, "default", phonebook.PhoneBookEntryRelayRole)
+ netC.phonebook.ReplacePeerList([]string{addrB}, "default", phonebook.RelayRole)
netC.Start()
defer netC.Stop()
@@ -1172,7 +1244,7 @@ func TestGetPeers(t *testing.T) {
require.True(t, postListen)
t.Log(addrA)
phbMulti := phonebook.MakePhonebook(1, 1*time.Millisecond)
- phbMulti.ReplacePeerList([]string{addrA}, "phba", phonebook.PhoneBookEntryRelayRole)
+ phbMulti.ReplacePeerList([]string{addrA}, "phba", phonebook.RelayRole)
netB.phonebook = phbMulti
netB.Start()
defer netB.Stop()
@@ -1183,10 +1255,10 @@ func TestGetPeers(t *testing.T) {
waitReady(t, netB, readyTimeout.C)
t.Log("b ready")
- phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", phonebook.PhoneBookEntryRelayRole)
+ phbMulti.ReplacePeerList([]string{"a", "b", "c"}, "ph", phonebook.RelayRole)
// A few for archival node roles
- phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", phonebook.PhoneBookEntryArchivalRole)
+ phbMulti.ReplacePeerList([]string{"d", "e", "f"}, "ph", phonebook.ArchivalRole)
//addrB, _ := netB.Address()
@@ -2182,7 +2254,7 @@ func BenchmarkWebsocketNetworkBasic(t *testing.B) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
returns := make(chan uint64, 100)
@@ -2264,7 +2336,7 @@ func TestWebsocketNetworkPrio(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -2311,7 +2383,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) {
netB.SetPrioScheme(&prioB)
netB.config.GossipFanout = 1
netB.config.NetAddress = ""
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterB}})
netB.Start()
defer netStop(t, netB, "B")
@@ -2325,7 +2397,7 @@ func TestWebsocketNetworkPrioLimit(t *testing.T) {
netC.SetPrioScheme(&prioC)
netC.config.GossipFanout = 1
netC.config.NetAddress = ""
- netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netC.RegisterHandlers([]TaggedMessageHandler{{Tag: protocol.TxnTag, MessageHandler: counterC}})
netC.Start()
defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }()
@@ -2410,7 +2482,7 @@ func TestWebsocketNetworkManyIdle(t *testing.T) {
for i := 0; i < numClients; i++ {
client := makeTestWebsocketNodeWithConfig(t, clientConf)
client.config.GossipFanout = 1
- client.phonebook.ReplacePeerList([]string{relayAddr}, "default", phonebook.PhoneBookEntryRelayRole)
+ client.phonebook.ReplacePeerList([]string{relayAddr}, "default", phonebook.RelayRole)
client.Start()
defer client.Stop()
@@ -2463,52 +2535,48 @@ func TestWebsocketNetwork_checkServerResponseVariables(t *testing.T) {
partitiontest.PartitionTest(t)
wn := makeTestWebsocketNode(t)
- wn.GenesisID = "genesis-id1"
- wn.RandomID = "random-id1"
+ wn.genesisID = "genesis-id1"
+ wn.randomID = "random-id1"
header := http.Header{}
header.Set(ProtocolVersionHeader, ProtocolVersion)
- header.Set(NodeRandomHeader, wn.RandomID+"tag")
- header.Set(GenesisHeader, wn.GenesisID)
+ header.Set(NodeRandomHeader, wn.randomID+"tag")
+ header.Set(GenesisHeader, wn.genesisID)
responseVariableOk, matchingVersion := wn.checkServerResponseVariables(header, "addressX")
require.Equal(t, true, responseVariableOk)
require.Equal(t, matchingVersion, ProtocolVersion)
noVersionHeader := http.Header{}
- noVersionHeader.Set(NodeRandomHeader, wn.RandomID+"tag")
- noVersionHeader.Set(GenesisHeader, wn.GenesisID)
+ noVersionHeader.Set(NodeRandomHeader, wn.randomID+"tag")
+ noVersionHeader.Set(GenesisHeader, wn.genesisID)
responseVariableOk, _ = wn.checkServerResponseVariables(noVersionHeader, "addressX")
require.Equal(t, false, responseVariableOk)
noRandomHeader := http.Header{}
noRandomHeader.Set(ProtocolVersionHeader, ProtocolVersion)
- noRandomHeader.Set(GenesisHeader, wn.GenesisID)
+ noRandomHeader.Set(GenesisHeader, wn.genesisID)
responseVariableOk, _ = wn.checkServerResponseVariables(noRandomHeader, "addressX")
require.Equal(t, false, responseVariableOk)
sameRandomHeader := http.Header{}
sameRandomHeader.Set(ProtocolVersionHeader, ProtocolVersion)
- sameRandomHeader.Set(NodeRandomHeader, wn.RandomID)
- sameRandomHeader.Set(GenesisHeader, wn.GenesisID)
+ sameRandomHeader.Set(NodeRandomHeader, wn.randomID)
+ sameRandomHeader.Set(GenesisHeader, wn.genesisID)
responseVariableOk, _ = wn.checkServerResponseVariables(sameRandomHeader, "addressX")
require.Equal(t, false, responseVariableOk)
differentGenesisIDHeader := http.Header{}
differentGenesisIDHeader.Set(ProtocolVersionHeader, ProtocolVersion)
- differentGenesisIDHeader.Set(NodeRandomHeader, wn.RandomID+"tag")
- differentGenesisIDHeader.Set(GenesisHeader, wn.GenesisID+"tag")
+ differentGenesisIDHeader.Set(NodeRandomHeader, wn.randomID+"tag")
+ differentGenesisIDHeader.Set(GenesisHeader, wn.genesisID+"tag")
responseVariableOk, _ = wn.checkServerResponseVariables(differentGenesisIDHeader, "addressX")
require.Equal(t, false, responseVariableOk)
}
func (wn *WebsocketNetwork) broadcastWithTimestamp(tag protocol.Tag, data []byte, when time.Time) error {
- msgArr := make([][]byte, 1)
- msgArr[0] = data
- tagArr := make([]protocol.Tag, 1)
- tagArr[0] = tag
- request := broadcastRequest{tags: tagArr, data: msgArr, enqueueTime: when, ctx: context.Background()}
+ request := broadcastRequest{tag: tag, data: data, enqueueTime: when, ctx: context.Background()}
broadcastQueue := wn.broadcaster.broadcastQueueBulk
- if highPriorityTag(tagArr) {
+ if highPriorityTag(tag) {
broadcastQueue = wn.broadcaster.broadcastQueueHighPrio
}
// no wait
@@ -2535,7 +2603,7 @@ func TestDelayedMessageDrop(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
counter := newMessageCounter(t, 5)
@@ -2568,7 +2636,7 @@ func TestSlowPeerDisconnection(t *testing.T) {
log: log,
config: defaultConfig,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: noopIdentityTracker{},
@@ -2590,7 +2658,7 @@ func TestSlowPeerDisconnection(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -2645,7 +2713,7 @@ func TestForceMessageRelaying(t *testing.T) {
log: log,
config: defaultConfig,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: noopIdentityTracker{},
@@ -2669,14 +2737,14 @@ func TestForceMessageRelaying(t *testing.T) {
noAddressConfig.NetAddress = ""
netB := makeTestWebsocketNodeWithConfig(t, noAddressConfig)
netB.config.GossipFanout = 1
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
noAddressConfig.ForceRelayMessages = true
netC := makeTestWebsocketNodeWithConfig(t, noAddressConfig)
netC.config.GossipFanout = 1
- netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netC.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netC.Start()
defer func() { t.Log("stopping C"); netC.Stop(); t.Log("C done") }()
@@ -2741,7 +2809,7 @@ func TestCheckProtocolVersionMatch(t *testing.T) {
log: log,
config: defaultConfig,
phonebook: phonebook.MakePhonebook(1, 1*time.Millisecond),
- GenesisID: genesisID,
+ genesisID: genesisID,
NetworkID: config.Devtestnet,
peerStater: peerConnectionStater{log: log},
identityTracker: noopIdentityTracker{},
@@ -2752,7 +2820,7 @@ func TestCheckProtocolVersionMatch(t *testing.T) {
header1 := make(http.Header)
header1.Add(ProtocolAcceptVersionHeader, "1")
header1.Add(ProtocolVersionHeader, "3")
- matchingVersion, otherVersion := wn.checkProtocolVersionMatch(header1)
+ matchingVersion, otherVersion := checkProtocolVersionMatch(header1, wn.supportedProtocolVersions)
require.Equal(t, "1", matchingVersion)
require.Equal(t, "", otherVersion)
@@ -2760,19 +2828,19 @@ func TestCheckProtocolVersionMatch(t *testing.T) {
header2.Add(ProtocolAcceptVersionHeader, "3")
header2.Add(ProtocolAcceptVersionHeader, "4")
header2.Add(ProtocolVersionHeader, "1")
- matchingVersion, otherVersion = wn.checkProtocolVersionMatch(header2)
+ matchingVersion, otherVersion = checkProtocolVersionMatch(header2, wn.supportedProtocolVersions)
require.Equal(t, "1", matchingVersion)
require.Equal(t, "1", otherVersion)
header3 := make(http.Header)
header3.Add(ProtocolVersionHeader, "3")
- matchingVersion, otherVersion = wn.checkProtocolVersionMatch(header3)
+ matchingVersion, otherVersion = checkProtocolVersionMatch(header3, wn.supportedProtocolVersions)
require.Equal(t, "", matchingVersion)
require.Equal(t, "3", otherVersion)
header4 := make(http.Header)
header4.Add(ProtocolVersionHeader, "5\n")
- matchingVersion, otherVersion = wn.checkProtocolVersionMatch(header4)
+ matchingVersion, otherVersion = checkProtocolVersionMatch(header4, wn.supportedProtocolVersions)
require.Equal(t, "", matchingVersion)
require.Equal(t, "5"+unprintableCharacterGlyph, otherVersion)
}
@@ -2822,7 +2890,7 @@ func TestWebsocketNetworkTopicRoundtrip(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -2922,7 +2990,7 @@ func TestWebsocketNetworkMessageOfInterest(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Logf("netA %s", addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
// have netB asking netA to send it ft2.
// Max MOI size is calculated by encoding all of the valid tags, since we are using a custom tag here we must deregister one in the default set.
@@ -3047,7 +3115,7 @@ func TestWebsocketNetworkTXMessageOfInterestRelay(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -3131,7 +3199,7 @@ func TestWebsocketNetworkTXMessageOfInterestForceTx(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -3213,7 +3281,7 @@ func TestWebsocketNetworkTXMessageOfInterestNPN(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
require.False(t, netB.relayMessages)
@@ -3319,7 +3387,7 @@ func TestWebsocketNetworkTXMessageOfInterestPN(t *testing.T) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
require.False(t, netB.relayMessages)
@@ -3441,7 +3509,7 @@ func testWebsocketDisconnection(t *testing.T, disconnectFunc func(wn *WebsocketN
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer netStop(t, netB, "B")
@@ -3574,8 +3642,8 @@ func TestMaliciousCheckServerResponseVariables(t *testing.T) {
partitiontest.PartitionTest(t)
wn := makeTestWebsocketNode(t)
- wn.GenesisID = "genesis-id1"
- wn.RandomID = "random-id1"
+ wn.genesisID = "genesis-id1"
+ wn.randomID = "random-id1"
wn.log = callbackLogger{
Logger: wn.log,
InfoCallback: func(args ...interface{}) {
@@ -3598,8 +3666,8 @@ func TestMaliciousCheckServerResponseVariables(t *testing.T) {
header1 := http.Header{}
header1.Set(ProtocolVersionHeader, ProtocolVersion+"א")
- header1.Set(NodeRandomHeader, wn.RandomID+"tag")
- header1.Set(GenesisHeader, wn.GenesisID)
+ header1.Set(NodeRandomHeader, wn.randomID+"tag")
+ header1.Set(GenesisHeader, wn.genesisID)
responseVariableOk, matchingVersion := wn.checkServerResponseVariables(header1, "addressX")
require.Equal(t, false, responseVariableOk)
require.Equal(t, "", matchingVersion)
@@ -3607,15 +3675,15 @@ func TestMaliciousCheckServerResponseVariables(t *testing.T) {
header2 := http.Header{}
header2.Set(ProtocolVersionHeader, ProtocolVersion)
header2.Set("א", "א")
- header2.Set(GenesisHeader, wn.GenesisID)
+ header2.Set(GenesisHeader, wn.genesisID)
responseVariableOk, matchingVersion = wn.checkServerResponseVariables(header2, "addressX")
require.Equal(t, false, responseVariableOk)
require.Equal(t, "", matchingVersion)
header3 := http.Header{}
header3.Set(ProtocolVersionHeader, ProtocolVersion)
- header3.Set(NodeRandomHeader, wn.RandomID+"tag")
- header3.Set(GenesisHeader, wn.GenesisID+"א")
+ header3.Set(NodeRandomHeader, wn.randomID+"tag")
+ header3.Set(GenesisHeader, wn.genesisID+"א")
responseVariableOk, matchingVersion = wn.checkServerResponseVariables(header3, "addressX")
require.Equal(t, false, responseVariableOk)
require.Equal(t, "", matchingVersion)
@@ -3636,7 +3704,7 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) {
addrA, postListen := netA.Address()
require.True(t, postListen)
t.Log(addrA)
- netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.PhoneBookEntryRelayRole)
+ netB.phonebook.ReplacePeerList([]string{addrA}, "default", phonebook.RelayRole)
netB.Start()
defer func() { netB.Stop() }()
@@ -3701,35 +3769,60 @@ func BenchmarkVariableTransactionMessageBlockSizes(t *testing.B) {
func TestPreparePeerData(t *testing.T) {
partitiontest.PartitionTest(t)
- // no compression
- req := broadcastRequest{
- tags: []protocol.Tag{protocol.AgreementVoteTag, protocol.ProposalPayloadTag},
- data: [][]byte{[]byte("test"), []byte("data")},
+ vote := map[string]any{
+ "cred": map[string]any{"pf": crypto.VrfProof{}},
+ "r": map[string]any{"rnd": uint64(1), "snd": [32]byte{}},
+ "sig": map[string]any{
+ "p": [32]byte{}, "p1s": [64]byte{}, "p2": [32]byte{},
+ "p2s": [64]byte{}, "ps": [64]byte{}, "s": [64]byte{},
+ },
+ }
+ reqs := []broadcastRequest{
+ {tag: protocol.AgreementVoteTag, data: protocol.EncodeReflect(vote)},
+ {tag: protocol.ProposalPayloadTag, data: []byte("data")},
+ {tag: protocol.TxnTag, data: []byte("txn")},
+ {tag: protocol.StateProofSigTag, data: []byte("stateproof")},
}
wn := WebsocketNetwork{}
- data, digests := wn.broadcaster.preparePeerData(req, false)
- require.NotEmpty(t, data)
- require.NotEmpty(t, digests)
- require.Equal(t, len(req.data), len(digests))
- require.Equal(t, len(data), len(digests))
+ wn.broadcaster.log = logging.TestingLog(t)
+ // Enable vote compression for the test
+ wn.broadcaster.enableVoteCompression = true
+ data := make([][]byte, len(reqs))
+ compressedData := make([][]byte, len(reqs))
+ digests := make([]crypto.Digest, len(reqs))
+
+ // Test without compression (prio = false)
+ for i, req := range reqs {
+ data[i], compressedData[i], digests[i] = wn.broadcaster.preparePeerData(req, false)
+ require.NotEmpty(t, data[i])
+ require.Empty(t, digests[i]) // small messages have no digest
+ }
for i := range data {
- require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), data[i])
+ require.Equal(t, append([]byte(reqs[i].tag), reqs[i].data...), data[i])
+ require.Empty(t, compressedData[i]) // No compression when prio = false
}
- data, digests = wn.broadcaster.preparePeerData(req, true)
- require.NotEmpty(t, data)
- require.NotEmpty(t, digests)
- require.Equal(t, len(req.data), len(digests))
- require.Equal(t, len(data), len(digests))
+ // Test with compression (prio = true)
+ for i, req := range reqs {
+ data[i], compressedData[i], digests[i] = wn.broadcaster.preparePeerData(req, true)
+ require.NotEmpty(t, data[i])
+ require.Empty(t, digests[i]) // small messages have no digest
+ }
for i := range data {
- if req.tags[i] != protocol.ProposalPayloadTag {
- require.Equal(t, append([]byte(req.tags[i]), req.data[i]...), data[i])
- require.Equal(t, data[i], data[i])
+ if reqs[i].tag == protocol.AgreementVoteTag {
+ // For votes with prio=true, the main data remains uncompressed, but compressedData is filled
+ require.Equal(t, append([]byte(reqs[i].tag), reqs[i].data...), data[i])
+ require.NotEmpty(t, compressedData[i], "Vote messages should have compressed data when prio=true")
+ } else if reqs[i].tag == protocol.ProposalPayloadTag {
+ // For proposals with prio=true, the main data is compressed with zstd
+ require.Equal(t, append([]byte(reqs[i].tag), zstdCompressionMagic[:]...), data[i][:len(reqs[i].tag)+len(zstdCompressionMagic)])
+ require.Empty(t, compressedData[i], "Proposal messages should not have separate compressed data")
} else {
- require.Equal(t, append([]byte(req.tags[i]), zstdCompressionMagic[:]...), data[i][:len(req.tags[i])+len(zstdCompressionMagic)])
+ require.Equal(t, append([]byte(reqs[i].tag), reqs[i].data...), data[i])
+ require.Empty(t, compressedData[i])
}
}
}
@@ -4010,14 +4103,13 @@ func TestDiscardUnrequestedBlockResponse(t *testing.T) {
require.Eventually(t, func() bool { return netA.NumPeers() == 1 }, 500*time.Millisecond, 25*time.Millisecond)
// send an unrequested block response
- msg := make([]sendMessage, 1)
- msg[0] = sendMessage{
+ msg := sendMessage{
data: append([]byte(protocol.TopicMsgRespTag), []byte("foo")...),
enqueued: time.Now(),
peerEnqueued: time.Now(),
ctx: context.Background(),
}
- netA.peers[0].sendBufferBulk <- sendMessages{msgs: msg}
+ netA.peers[0].sendBufferBulk <- msg
require.Eventually(t,
func() bool {
return networkConnectionsDroppedTotal.GetUint64ValueForLabels(map[string]string{"reason": "unrequestedTS"}) == 1
@@ -4080,7 +4172,7 @@ func TestDiscardUnrequestedBlockResponse(t *testing.T) {
netC.log.SetOutput(logBuffer)
// send a late TS response from A -> C
- netA.peers[0].sendBufferBulk <- sendMessages{msgs: msg}
+ netA.peers[0].sendBufferBulk <- msg
require.Eventually(
t,
func() bool { return netC.peers[0].outstandingTopicRequests.Load() == int64(0) },
@@ -4497,8 +4589,8 @@ func TestSendMessageCallbackDrain(t *testing.T) {
node := makeTestWebsocketNode(t)
destPeer := wsPeer{
closing: make(chan struct{}),
- sendBufferHighPrio: make(chan sendMessages, sendBufferLength),
- sendBufferBulk: make(chan sendMessages, sendBufferLength),
+ sendBufferHighPrio: make(chan sendMessage, sendBufferLength),
+ sendBufferBulk: make(chan sendMessage, sendBufferLength),
conn: &nopConnSingleton,
}
node.addPeer(&destPeer)
@@ -4545,7 +4637,7 @@ func TestWsNetworkPhonebookMix(t *testing.T) {
nil,
)
require.NoError(t, err)
- addrs := net.phonebook.GetAddresses(10, phonebook.PhoneBookEntryRelayRole)
+ addrs := net.phonebook.GetAddresses(10, phonebook.RelayRole)
require.Len(t, addrs, 1)
}
@@ -4657,16 +4749,16 @@ func TestPeerComparisonInBroadcast(t *testing.T) {
testPeer := &wsPeer{
wsPeerCore: makePeerCore(wn.ctx, wn, log, nil, "test-addr", nil, ""),
- sendBufferBulk: make(chan sendMessages, sendBufferLength),
+ sendBufferBulk: make(chan sendMessage, sendBufferLength),
}
exceptPeer := &wsPeer{
wsPeerCore: makePeerCore(wn.ctx, wn, log, nil, "except-addr", nil, ""),
- sendBufferBulk: make(chan sendMessages, sendBufferLength),
+ sendBufferBulk: make(chan sendMessage, sendBufferLength),
}
request := broadcastRequest{
- tags: []protocol.Tag{"test-tag"},
- data: [][]byte{[]byte("test-data")},
+ tag: protocol.Tag("test-tag"),
+ data: []byte("test-data"),
enqueueTime: time.Now(),
except: exceptPeer,
}
diff --git a/network/wsPeer.go b/network/wsPeer.go
index bc8f94c285..c102ce48f8 100644
--- a/network/wsPeer.go
+++ b/network/wsPeer.go
@@ -116,8 +116,10 @@ type sendMessage struct {
enqueued time.Time // the time at which the message was first generated
peerEnqueued time.Time // the time at which the peer was attempting to enqueue the message
msgTags map[protocol.Tag]bool // when msgTags is specified ( i.e. non-nil ), the send goroutine is to replace the message tag filter with this one. No data would be accompanied to this message.
- hash crypto.Digest
ctx context.Context
+
+ // onRelease function is called when the message is released either by being sent or discarded.
+ onRelease func()
}
// wsPeerCore also works for non-connected peers we want to do HTTP GET from
@@ -152,13 +154,6 @@ type Response struct {
Topics Topics
}
-type sendMessages struct {
- msgs []sendMessage
-
- // onRelease function is called when the message is released either by being sent or discarded.
- onRelease func()
-}
-
//msgp:ignore peerType
type peerType int
@@ -202,8 +197,8 @@ type wsPeer struct {
closing chan struct{}
- sendBufferHighPrio chan sendMessages
- sendBufferBulk chan sendMessages
+ sendBufferHighPrio chan sendMessage
+ sendBufferBulk chan sendMessage
wg sync.WaitGroup
@@ -243,6 +238,9 @@ type wsPeer struct {
// peer features derived from the peer version
features peerFeatureFlag
+ // enableCompression specifies whether this node can compress or decompress votes (and whether it has advertised this)
+ enableVoteCompression bool
+
// responseChannels used by the client to wait on the response of the request
responseChannels map[uint64]chan *Response
@@ -446,16 +444,16 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou
serializedMsg := responseTopics.MarshallTopics()
// Send serializedMsg
- msg := make([]sendMessage, 1, 1)
- msg[0] = sendMessage{
+ msg := sendMessage{
data: append([]byte(protocol.TopicMsgRespTag), serializedMsg...),
enqueued: time.Now(),
peerEnqueued: time.Now(),
ctx: context.Background(),
+ onRelease: outMsg.OnRelease,
}
select {
- case wp.sendBufferBulk <- sendMessages{msgs: msg, onRelease: outMsg.OnRelease}:
+ case wp.sendBufferBulk <- msg:
case <-wp.closing:
if outMsg.OnRelease != nil {
outMsg.OnRelease()
@@ -475,8 +473,8 @@ func (wp *wsPeer) Respond(ctx context.Context, reqMsg IncomingMessage, outMsg Ou
func (wp *wsPeer) init(config config.Local, sendBufferLength int) {
wp.log.Debugf("wsPeer init outgoing=%v %#v", wp.outgoing, wp.GetAddress())
wp.closing = make(chan struct{})
- wp.sendBufferHighPrio = make(chan sendMessages, sendBufferLength)
- wp.sendBufferBulk = make(chan sendMessages, sendBufferLength)
+ wp.sendBufferHighPrio = make(chan sendMessage, sendBufferLength)
+ wp.sendBufferBulk = make(chan sendMessage, sendBufferLength)
wp.lastPacketTime.Store(time.Now().UnixNano())
wp.responseChannels = make(map[uint64]chan *Response)
wp.sendMessageTag = defaultSendMessageTags
@@ -527,7 +525,7 @@ func (wp *wsPeer) readLoop() {
}()
wp.conn.SetReadLimit(MaxMessageLength)
slurper := MakeLimitedReaderSlurper(averageMessageLength, MaxMessageLength)
- dataConverter := makeWsPeerMsgDataConverter(wp)
+ dataConverter := makeWsPeerMsgDataDecoder(wp)
for {
msg := IncomingMessage{}
@@ -712,15 +710,13 @@ func (wp *wsPeer) handleMessageOfInterest(msg IncomingMessage) (close bool, reas
wp.log.Warnf("wsPeer handleMessageOfInterest: could not unmarshall message from: %s %v", wp.conn.RemoteAddrString(), err)
return true, disconnectBadData
}
- msgs := make([]sendMessage, 1)
- msgs[0] = sendMessage{
+ sm := sendMessage{
data: nil,
enqueued: time.Now(),
peerEnqueued: time.Now(),
msgTags: msgTagsMap,
ctx: context.Background(),
}
- sm := sendMessages{msgs: msgs}
// try to send the message to the send loop. The send loop will store the message locally and would use it.
// the rationale here is that this message is rarely sent, and we would benefit from having it being lock-free.
@@ -770,21 +766,19 @@ func (wp *wsPeer) handleFilterMessage(msg IncomingMessage) {
}
}
-func (wp *wsPeer) writeLoopSend(msgs sendMessages) disconnectReason {
- if msgs.onRelease != nil {
- defer msgs.onRelease()
+func (wp *wsPeer) writeLoopSend(msg sendMessage) disconnectReason {
+ if msg.onRelease != nil {
+ defer msg.onRelease()
+ }
+ select {
+ case <-msg.ctx.Done():
+ //logging.Base().Infof("cancelled large send, msg %v out of %v", i, len(msgs.msgs))
+ return disconnectReasonNone
+ default:
}
- for _, msg := range msgs.msgs {
- select {
- case <-msg.ctx.Done():
- //logging.Base().Infof("cancelled large send, msg %v out of %v", i, len(msgs.msgs))
- return disconnectReasonNone
- default:
- }
- if err := wp.writeLoopSendMsg(msg); err != disconnectReasonNone {
- return err
- }
+ if err := wp.writeLoopSendMsg(msg); err != disconnectReasonNone {
+ return err
}
return disconnectReasonNone
@@ -885,38 +879,16 @@ func (wp *wsPeer) writeLoopCleanup(reason disconnectReason) {
}
func (wp *wsPeer) writeNonBlock(ctx context.Context, data []byte, highPrio bool, digest crypto.Digest, msgEnqueueTime time.Time) bool {
- msgs := make([][]byte, 1)
- digests := make([]crypto.Digest, 1)
- msgs[0] = data
- digests[0] = digest
- return wp.writeNonBlockMsgs(ctx, msgs, highPrio, digests, msgEnqueueTime)
-}
-
-// return true if enqueued/sent
-func (wp *wsPeer) writeNonBlockMsgs(ctx context.Context, data [][]byte, highPrio bool, digest []crypto.Digest, msgEnqueueTime time.Time) bool {
- includeIndices := make([]int, 0, len(data))
- for i := range data {
- if wp.outgoingMsgFilter != nil && len(data[i]) > messageFilterSize && wp.outgoingMsgFilter.CheckDigest(digest[i], false, false) {
- //wp.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest)
- // peer has notified us it doesn't need this message
- outgoingNetworkMessageFilteredOutTotal.Inc(nil)
- outgoingNetworkMessageFilteredOutBytesTotal.AddUint64(uint64(len(data)), nil)
- } else {
- includeIndices = append(includeIndices, i)
- }
- }
- if len(includeIndices) == 0 {
+ if wp.outgoingMsgFilter != nil && len(data) > messageFilterSize && wp.outgoingMsgFilter.CheckDigest(digest, false, false) {
+ //wp.log.Debugf("msg drop as outbound dup %s(%d) %v", string(data[:2]), len(data)-2, digest)
+ // peer has notified us it doesn't need this message
+ outgoingNetworkMessageFilteredOutTotal.Inc(nil)
+ outgoingNetworkMessageFilteredOutBytesTotal.AddUint64(uint64(len(data)), nil)
// returning true because it is as good as sent, the peer already has it.
return true
}
- var outchan chan sendMessages
-
- msgs := make([]sendMessage, 0, len(includeIndices))
- enqueueTime := time.Now()
- for _, index := range includeIndices {
- msgs = append(msgs, sendMessage{data: data[index], enqueued: msgEnqueueTime, peerEnqueued: enqueueTime, hash: digest[index], ctx: ctx})
- }
+ var outchan chan sendMessage
if highPrio {
outchan = wp.sendBufferHighPrio
@@ -924,7 +896,7 @@ func (wp *wsPeer) writeNonBlockMsgs(ctx context.Context, data [][]byte, highPrio
outchan = wp.sendBufferBulk
}
select {
- case outchan <- sendMessages{msgs: msgs}:
+ case outchan <- sendMessage{data: data, enqueued: msgEnqueueTime, peerEnqueued: time.Now(), ctx: ctx}:
return true
default:
}
@@ -1032,14 +1004,13 @@ func (wp *wsPeer) Request(ctx context.Context, tag Tag, topics Topics) (resp *Re
defer wp.getAndRemoveResponseChannel(hash)
// Send serializedMsg
- msg := make([]sendMessage, 1)
- msg[0] = sendMessage{
+ msg := sendMessage{
data: append([]byte(tag), serializedMsg...),
enqueued: time.Now(),
peerEnqueued: time.Now(),
ctx: context.Background()}
select {
- case wp.sendBufferBulk <- sendMessages{msgs: msg}:
+ case wp.sendBufferBulk <- msg:
wp.outstandingTopicRequests.Add(1)
case <-wp.closing:
e = fmt.Errorf("peer closing %s", wp.conn.RemoteAddrString())
@@ -1118,11 +1089,16 @@ func (wp *wsPeer) OnClose(f func()) {
wp.closers = append(wp.closers, f)
}
+func (wp *wsPeer) vpackVoteCompressionSupported() bool {
+ return wp.features&pfCompressedVoteVpack != 0
+}
+
//msgp:ignore peerFeatureFlag
type peerFeatureFlag int
const (
pfCompressedProposal peerFeatureFlag = 1 << iota
+ pfCompressedVoteVpack
)
// versionPeerFeatures defines protocol version when peer features were introduced
@@ -1167,6 +1143,9 @@ func decodePeerFeatures(version string, announcedFeatures string) peerFeatureFla
if part == PeerFeatureProposalCompression {
features |= pfCompressedProposal
}
+ if part == PeerFeatureVoteVpackCompression {
+ features |= pfCompressedVoteVpack
+ }
}
return features
}
diff --git a/network/wsPeer_test.go b/network/wsPeer_test.go
index 707dc210ea..02fa324a08 100644
--- a/network/wsPeer_test.go
+++ b/network/wsPeer_test.go
@@ -166,6 +166,8 @@ func TestVersionToFeature(t *testing.T) {
{"2.2", PeerFeatureProposalCompression, pfCompressedProposal},
{"2.2", strings.Join([]string{PeerFeatureProposalCompression, "test"}, ","), pfCompressedProposal},
{"2.2", strings.Join([]string{PeerFeatureProposalCompression, "test"}, ", "), pfCompressedProposal},
+ {"2.2", strings.Join([]string{PeerFeatureProposalCompression, PeerFeatureVoteVpackCompression}, ","), pfCompressedVoteVpack | pfCompressedProposal},
+ {"2.2", PeerFeatureVoteVpackCompression, pfCompressedVoteVpack},
{"2.3", PeerFeatureProposalCompression, pfCompressedProposal},
}
for i, test := range tests {
diff --git a/node/node.go b/node/node.go
index 38dea020a1..15b2bea5e5 100644
--- a/node/node.go
+++ b/node/node.go
@@ -262,8 +262,6 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd
ExecutionPool: node.lowPriorityCryptoVerificationPool,
Ledger: node.ledger,
Net: node.net,
- GenesisID: node.genesisID,
- GenesisHash: node.genesisHash,
Config: cfg,
}
node.txHandler, err = data.MakeTxHandler(txHandlerOpts)
diff --git a/node/node_test.go b/node/node_test.go
index 77d23e0ae5..b39c114a5d 100644
--- a/node/node_test.go
+++ b/node/node_test.go
@@ -1022,15 +1022,15 @@ func TestNodeP2PRelays(t *testing.T) {
switch i {
case 0:
// node R1 connects to R2
- t.Logf("Node%d phonebook: %s", i, ni[1].p2pMultiAddr())
+ t.Logf("Node%d %s phonebook: %s", i, ni[0].p2pID, ni[1].p2pMultiAddr())
return []string{ni[1].p2pMultiAddr()}
case 1:
// node R2 connects to none one
- t.Logf("Node%d phonebook: empty", i)
+ t.Logf("Node%d %s phonebook: empty", i, ni[1].p2pID)
return []string{}
case 2:
- // node N only connects to R1
- t.Logf("Node%d phonebook: %s", i, ni[1].p2pMultiAddr())
+ // node N only connects to R2
+ t.Logf("Node%d %s phonebook: %s", i, ni[2].p2pID, ni[1].p2pMultiAddr())
return []string{ni[1].p2pMultiAddr()}
default:
t.Errorf("not expected number of nodes: %d", i)
@@ -1262,3 +1262,91 @@ func TestNodeHybridP2PGossipSend(t *testing.T) {
require.Fail(t, fmt.Sprintf("no block notification for wallet: %v.", wallets[0]))
}
}
+
+// TestNodeP2P_NetProtoVersions makes sure two p2p nodes with different network protocol versions
+// can communicate and produce blocks.
+func TestNodeP2P_NetProtoVersions(t *testing.T) {
+ partitiontest.PartitionTest(t)
+
+ const consensusTest0 = protocol.ConsensusVersion("test0")
+
+ configurableConsensus := make(config.ConsensusProtocols)
+
+ testParams0 := config.Consensus[protocol.ConsensusCurrentVersion]
+ testParams0.AgreementFilterTimeoutPeriod0 = 500 * time.Millisecond
+ configurableConsensus[consensusTest0] = testParams0
+
+ maxMoneyAtStart := 100_000_000_000
+
+ const numAccounts = 2
+ acctStake := make([]basics.MicroAlgos, numAccounts)
+ acctStake[0] = basics.MicroAlgos{Raw: uint64(maxMoneyAtStart / numAccounts)}
+ acctStake[1] = basics.MicroAlgos{Raw: uint64(maxMoneyAtStart / numAccounts)}
+
+ configHook := func(ni nodeInfo, cfg config.Local) (nodeInfo, config.Local) {
+ cfg = config.GetDefaultLocal()
+ cfg.BaseLoggerDebugLevel = uint32(logging.Debug)
+ cfg.EnableP2P = true
+ cfg.NetAddress = ""
+
+ cfg.P2PPersistPeerID = true
+ privKey, err := p2p.GetPrivKey(cfg, ni.rootDir)
+ require.NoError(t, err)
+ ni.p2pID, err = p2p.PeerIDFromPublicKey(privKey.GetPublic())
+ require.NoError(t, err)
+
+ switch ni.idx {
+ case 0:
+ cfg.NetAddress = ni.p2pNetAddr()
+ cfg.EnableVoteCompression = true
+ case 1:
+ cfg.EnableVoteCompression = false
+ default:
+ }
+ return ni, cfg
+ }
+
+ phonebookHook := func(nodes []nodeInfo, nodeIdx int) []string {
+ phonebook := make([]string, 0, len(nodes)-1)
+ for i := range nodes {
+ if i != nodeIdx {
+ phonebook = append(phonebook, nodes[i].p2pMultiAddr())
+ }
+ }
+ return phonebook
+ }
+ nodes, wallets := setupFullNodesEx(t, consensusTest0, configurableConsensus, acctStake, configHook, phonebookHook)
+ require.Len(t, nodes, numAccounts)
+ require.Len(t, wallets, numAccounts)
+ for i := 0; i < len(nodes); i++ {
+ defer os.Remove(wallets[i])
+ defer nodes[i].Stop()
+ }
+
+ startAndConnectNodes(nodes, nodelayFirstNodeStartDelay)
+
+ require.Eventually(t, func() bool {
+ connectPeers(nodes)
+ return len(nodes[0].net.GetPeers(network.PeersConnectedIn, network.PeersConnectedOut)) >= 1 &&
+ len(nodes[1].net.GetPeers(network.PeersConnectedIn, network.PeersConnectedOut)) >= 1
+ }, 60*time.Second, 1*time.Second)
+
+ const initialRound = 1
+ const maxRounds = 3
+ for tests := basics.Round(0); tests < maxRounds; tests++ {
+ blocks := make([]bookkeeping.Block, len(wallets))
+ for i := range wallets {
+ select {
+ case <-nodes[i].ledger.Wait(initialRound + tests):
+ blk, err := nodes[i].ledger.Block(initialRound + tests)
+ if err != nil {
+ panic(err)
+ }
+ blocks[i] = blk
+ case <-time.After(60 * time.Second):
+ require.Fail(t, fmt.Sprintf("no block notification for account: %v. Iteration: %v", wallets[i], tests))
+ return
+ }
+ }
+ }
+}
diff --git a/protocol/codec_tester.go b/protocol/codec_tester.go
index 1dbed5be76..7e6cb02919 100644
--- a/protocol/codec_tester.go
+++ b/protocol/codec_tester.go
@@ -49,27 +49,55 @@ func oneOf(n int) bool {
return (rand.Int() % n) == 0
}
+type randomizeObjectCfg struct {
+ // ZeroesEveryN will increase the chance of zero values being generated.
+ ZeroesEveryN int
+ // AllUintSizes will be equally likely to generate 8-bit, 16-bit, 32-bit, or 64-bit uints.
+ AllUintSizes bool
+}
+
+// RandomizeObjectOption is an option for RandomizeObject
+type RandomizeObjectOption func(*randomizeObjectCfg)
+
+// RandomizeObjectWithZeroesEveryN sets the chance of zero values being generated (one in n)
+func RandomizeObjectWithZeroesEveryN(n int) RandomizeObjectOption {
+ return func(cfg *randomizeObjectCfg) { cfg.ZeroesEveryN = n }
+}
+
+// RandomizeObjectWithAllUintSizes will be equally likely to generate 8-bit, 16-bit, 32-bit, or 64-bit uints.
+func RandomizeObjectWithAllUintSizes() RandomizeObjectOption {
+ return func(cfg *randomizeObjectCfg) { cfg.AllUintSizes = true }
+}
+
// RandomizeObject returns a random object of the same type as template
-func RandomizeObject(template interface{}) (interface{}, error) {
+func RandomizeObject(template interface{}, opts ...RandomizeObjectOption) (interface{}, error) {
+ cfg := randomizeObjectCfg{}
+ for _, opt := range opts {
+ opt(&cfg)
+ }
tt := reflect.TypeOf(template)
if tt.Kind() != reflect.Ptr {
return nil, fmt.Errorf("RandomizeObject: must be ptr")
}
v := reflect.New(tt.Elem())
changes := int(^uint(0) >> 1)
- err := randomizeValue(v.Elem(), tt.String(), "", &changes, make(map[reflect.Type]bool))
+ err := randomizeValue(v.Elem(), 0, tt.String(), "", &changes, cfg, make(map[reflect.Type]bool))
return v.Interface(), err
}
// RandomizeObjectField returns a random object of the same type as template where a single field was modified.
-func RandomizeObjectField(template interface{}) (interface{}, error) {
+func RandomizeObjectField(template interface{}, opts ...RandomizeObjectOption) (interface{}, error) {
+ cfg := randomizeObjectCfg{}
+ for _, opt := range opts {
+ opt(&cfg)
+ }
tt := reflect.TypeOf(template)
if tt.Kind() != reflect.Ptr {
return nil, fmt.Errorf("RandomizeObject: must be ptr")
}
v := reflect.New(tt.Elem())
changes := 1
- err := randomizeValue(v.Elem(), tt.String(), "", &changes, make(map[reflect.Type]bool))
+ err := randomizeValue(v.Elem(), 0, tt.String(), "", &changes, cfg, make(map[reflect.Type]bool))
return v.Interface(), err
}
@@ -211,14 +239,14 @@ func checkBoundsLimitingTag(val reflect.Value, datapath string, structTag string
return
}
-func randomizeValue(v reflect.Value, datapath string, tag string, remainingChanges *int, seenTypes map[reflect.Type]bool) error {
+func randomizeValue(v reflect.Value, depth int, datapath string, tag string, remainingChanges *int, cfg randomizeObjectCfg, seenTypes map[reflect.Type]bool) error {
if *remainingChanges == 0 {
return nil
}
- /*if oneOf(5) {
+ if depth != 0 && cfg.ZeroesEveryN > 0 && oneOf(cfg.ZeroesEveryN) {
// Leave zero value
return nil
- }*/
+ }
/* Consider cutting off recursive structures by stopping at some datapath depth.
@@ -235,7 +263,22 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
// generate value that will avoid protocol.ErrInvalidObject from HashType.Validate()
v.SetUint(rand.Uint64() % 3) // 3 is crypto.MaxHashType
} else {
- v.SetUint(rand.Uint64())
+ var num uint64
+ if cfg.AllUintSizes {
+ switch rand.Intn(4) {
+ case 0: // fewer than 8 bits
+ num = uint64(rand.Intn(1 << 8)) // 0 to 255
+ case 1: // fewer than 16 bits
+ num = uint64(rand.Intn(1 << 16)) // 0 to 65535
+ case 2: // fewer than 32 bits
+ num = uint64(rand.Uint32()) // 0 to 2^32-1
+ case 3: // fewer than 64 bits
+ num = rand.Uint64() // 0 to 2^64-1
+ }
+ } else {
+ num = rand.Uint64()
+ }
+ v.SetUint(num)
}
*remainingChanges--
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -264,7 +307,7 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
*remainingChanges--
case reflect.Ptr:
v.Set(reflect.New(v.Type().Elem()))
- err := randomizeValue(reflect.Indirect(v), datapath, tag, remainingChanges, seenTypes)
+ err := randomizeValue(reflect.Indirect(v), depth+1, datapath, tag, remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
@@ -292,7 +335,7 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
if rawMsgpType == f.Type {
return errSkipRawMsgpTesting
}
- err := randomizeValue(v.Field(fieldIdx), datapath+"/"+f.Name, string(tag), remainingChanges, seenTypes)
+ err := randomizeValue(v.Field(fieldIdx), depth+1, datapath+"/"+f.Name, string(tag), remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
@@ -304,7 +347,7 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
case reflect.Array:
indicesOrder := rand.Perm(v.Len())
for i := 0; i < v.Len(); i++ {
- err := randomizeValue(v.Index(indicesOrder[i]), fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, seenTypes)
+ err := randomizeValue(v.Index(indicesOrder[i]), depth+1, fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
@@ -325,7 +368,7 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
s := reflect.MakeSlice(v.Type(), l, l)
indicesOrder := rand.Perm(l)
for i := 0; i < l; i++ {
- err := randomizeValue(s.Index(indicesOrder[i]), fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, seenTypes)
+ err := randomizeValue(s.Index(indicesOrder[i]), depth+1, fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
@@ -349,13 +392,13 @@ func randomizeValue(v reflect.Value, datapath string, tag string, remainingChang
indicesOrder := rand.Perm(l)
for i := 0; i < l; i++ {
mk := reflect.New(mt.Key())
- err := randomizeValue(mk.Elem(), fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, seenTypes)
+ err := randomizeValue(mk.Elem(), depth+1, fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
mv := reflect.New(mt.Elem())
- err = randomizeValue(mv.Elem(), fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, seenTypes)
+ err = randomizeValue(mv.Elem(), depth+1, fmt.Sprintf("%s/%d", datapath, indicesOrder[i]), "", remainingChanges, cfg, seenTypes)
if err != nil {
return err
}
diff --git a/rpcs/txService.go b/rpcs/txService.go
index 87c4fc0a6d..38977910d1 100644
--- a/rpcs/txService.go
+++ b/rpcs/txService.go
@@ -166,9 +166,7 @@ func (txs *TxService) getFilteredTxns(bloom *bloom.Filter) (txns []transactions.
if encodedLength+txGroupLength > txs.responseSizeLimit {
break
}
- for _, tx := range txgroup {
- missingTxns = append(missingTxns, tx)
- }
+ missingTxns = append(missingTxns, txgroup...)
encodedLength += txGroupLength
}
}
diff --git a/scripts/archtype.sh b/scripts/archtype.sh
index cc241c82f7..fb4429839d 100755
--- a/scripts/archtype.sh
+++ b/scripts/archtype.sh
@@ -15,6 +15,8 @@ elif [[ "${ARCH}" = "armv7l" ]]; then
echo "arm"
elif [[ "${ARCH}" = "aarch64" ]] || [[ "${ARCH}" = "arm64" ]]; then
echo "arm64"
+elif [[ "${ARCH}" = "riscv64" ]]; then
+ echo "riscv64"
else
# Anything else needs to be specifically added...
echo "unsupported"
diff --git a/scripts/export_sdk_types.py b/scripts/export_sdk_types.py
new file mode 100755
index 0000000000..9a9efc1954
--- /dev/null
+++ b/scripts/export_sdk_types.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+
+import re
+import subprocess
+
+# Various types must match between go-algorand and go-algorand-sdk so
+# that serialized datastructures will match. This script extracts
+# those types from the go-algorand source, and patches a
+# go-algorand-sdk repo that it expects to find as a sibling of the
+# current repo.
+
+def extract_between(filename, start_pattern, stop_pattern=None):
+ """
+ Extracts and returns the portion of a file between the first occurrence of
+ start_pattern and the first subsequent occurrence of stop_pattern.
+
+ Args:
+ filename (str): Path to the input file.
+ start_pattern (str): The start delimiter.
+ stop_pattern (str): The stop delimiter.
+
+ Returns:
+ str: Extracted content between the two patterns. Empty string if not found.
+ """
+ with open(filename, 'r', encoding='utf-8') as f:
+ content = f.read()
+
+ start_idx = content.find(start_pattern)
+ if start_idx == -1:
+ return ""
+
+ start_idx += len(start_pattern)
+ stop_idx = len(content)
+ if stop_pattern:
+ stop_idx = content.find(stop_pattern, start_idx)
+ if stop_idx == -1:
+ raise ValueError("Stop pattern not found in "+filename)
+
+ return content[start_idx:stop_idx]
+
+def replace_between(filename, content, start_pattern, stop_pattern=None):
+ """
+ Replaces the content in `filename` between `start_pattern` and `stop_pattern` with `content`.
+
+ Args:
+ filename (str): Path to the file to modify.
+ content (str): New content to insert.
+ start_pattern (str): Delimiter indicating where replacement should start.
+ stop_pattern (str): Delimiter indicating where replacement should stop.
+ """
+ with open(filename, 'r', encoding='utf-8') as f:
+ original = f.read()
+
+ start_idx = original.find(start_pattern)
+ if start_idx == -1:
+ raise ValueError("Start pattern not found")
+
+ start_idx += len(start_pattern)
+ stop_idx = len(original)
+ if stop_pattern:
+ stop_idx = original.find(stop_pattern, start_idx)
+ if stop_idx == -1:
+ raise ValueError("Stop pattern not found in "+filename)
+
+ updated = original[:start_idx] + content + original[stop_idx:]
+
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.write(updated)
+
+
+SDK="../go-algorand-sdk/"
+
+def sdkize(input):
+ # allocbounds are not used by the SDK. It's confusing to leave them in.
+ input = re.sub(",allocbound=.*\"", '"', input)
+
+ # All types are in the same package in the SDK
+ input = input.replace("basics.", "")
+ input = input.replace("crypto.", "")
+ input = input.replace("protocol.", "")
+
+ # keyreg
+ input = input.replace("OneTimeSignatureVerifier", "VotePK")
+ input = input.replace("VRFVerifier", "VRFPK")
+ input = input.replace("merklesignature.Commitment", "MerkleVerifier")
+ # appl - Someone had the bright idea to change the name of this field (and type) in the SDK.
+ input = input.replace("Boxes []BoxRef", "BoxReferences []BoxReference")
+
+ # transaction - for some reason, ApplicationCallTxnFields is wrapped in this nothing-burger
+ input = input.replace("ApplicationCallTxnFields", "ApplicationFields")
+
+ return input
+
+def export(src, dst, start, stop):
+ x = extract_between(src, start, stop)
+ x = sdkize(x)
+ replace_between(SDK+dst, x, start, stop)
+ subprocess.run(["gofmt", "-w", SDK+dst])
+
+
+if __name__ == "__main__":
+ # Replace the entire file, starting with "type ConsensusParams"
+ consensus = extract_between("config/consensus.go", "type ConsensusParams")
+ replace_between(SDK+"protocol/config/consensus.go", consensus, "type ConsensusParams")
+
+ # Common tranbsaction types
+ export("data/transactions/transaction.go", "types/transaction.go",
+ "type Header ", "\n}")
+ export("data/transactions/transaction.go", "types/transaction.go",
+ "type Transaction ", "\n}")
+ export("data/transactions/signedtxn.go", "types/transaction.go",
+ "type SignedTxn ", "\n}")
+
+ # The transaction types
+ export("data/transactions/payment.go", "types/transaction.go",
+ "type PaymentTxnFields ", "\n}")
+ export("data/transactions/keyreg.go", "types/transaction.go",
+ "type KeyregTxnFields ", "\n}")
+
+ export("data/transactions/asset.go", "types/transaction.go",
+ "type AssetConfigTxnFields ", "\n}")
+ export("data/transactions/asset.go", "types/transaction.go",
+ "type AssetTransferTxnFields ", "\n}")
+ export("data/transactions/asset.go", "types/transaction.go",
+ "type AssetFreezeTxnFields ", "\n}")
+
+ export("data/transactions/application.go", "types/applications.go",
+ "type ApplicationCallTxnFields ", "\n}")
+
+ # StateDelta. Eventually need to deal with all types from ledgercore.StateDelta down
+ export("data/basics/userBalance.go", "types/statedelta.go",
+ "type AppParams ", "\n}")
diff --git a/shared/algoh/config.go b/shared/algoh/config.go
index 3be17f6e43..a11c60c548 100644
--- a/shared/algoh/config.go
+++ b/shared/algoh/config.go
@@ -27,19 +27,53 @@ const ConfigFilename = "host-config.json"
// HostConfig is algoh's configuration structure
type HostConfig struct {
+ // Send /Agreement/BlockStats messages to telemetry
SendBlockStats bool
- UploadOnError bool
+
+ // Upload log files to telemetry on error
+ UploadOnError bool
+
+ // Deadlock time in seconds
DeadManTimeSec int64
- StatusDelayMS int64
- StallDelayMS int64
+
+ // Delay between status checks, in milliseconds
+ StatusDelayMS int64
+
+ // Delay between stall checks, in milliseconds
+ StallDelayMS int64
+
+ // Directory to store archived logs
+ LogArchiveDir string
+
+ // Maximum age of archived logs
+ // This is a duration string, e.g. "24h", "1m", "1s"
+ LogArchiveMaxAge string
+
+ // Name of the log archive file
+ LogArchiveName string
+
+ // Directory to store main host.log
+ LogFileDir string
+
+ // Maximum size of the log file
+ LogSizeLimit uint64
+
+ // Logging level of messages
+ MinLogLevel uint32
}
var defaultConfig = HostConfig{
- SendBlockStats: false,
- UploadOnError: true,
- DeadManTimeSec: 120,
- StatusDelayMS: 500,
- StallDelayMS: 60 * 1000,
+ SendBlockStats: false,
+ UploadOnError: true,
+ DeadManTimeSec: 120,
+ StatusDelayMS: 500,
+ StallDelayMS: 60 * 1000,
+ LogArchiveDir: "",
+ LogArchiveMaxAge: "",
+ LogArchiveName: "host.archive.log",
+ LogFileDir: "",
+ LogSizeLimit: 1073741824,
+ MinLogLevel: 3,
}
// LoadConfigFromFile loads the configuration from the specified file, merging into the default configuration.
diff --git a/shared/pingpong/accounts.go b/shared/pingpong/accounts.go
index 953014f4a4..f011a2679d 100644
--- a/shared/pingpong/accounts.go
+++ b/shared/pingpong/accounts.go
@@ -473,28 +473,6 @@ func signAndBroadcastTransaction(senderAccount *pingPongAccount, tx transactions
return
}
-func genBigNoOpAndBigHashes(numOps uint32, numHashes uint32, hashSize string) []byte {
- var progParts []string
- progParts = append(progParts, `#pragma version 2`)
- progParts = append(progParts, `byte base64 AA==`)
-
- for i := uint32(0); i < numHashes; i++ {
- progParts = append(progParts, hashSize)
- }
- for i := uint32(0); i < numOps/2; i++ {
- progParts = append(progParts, `int 1`)
- progParts = append(progParts, `pop`)
- }
- progParts = append(progParts, `int 1`)
- progParts = append(progParts, `return`)
- progAsm := strings.Join(progParts, "\n")
- ops, err := logic.AssembleString(progAsm)
- if err != nil {
- panic(err)
- }
- return ops.Program
-}
-
func genAppProgram(numOps uint32, numHashes uint32, hashSize string, numGlobalKeys, numLocalKeys, numBoxUpdate, numBoxRead uint32) ([]byte, string) {
if numBoxUpdate != 0 || numBoxRead != 0 {
prologue := `#pragma version 8
@@ -913,7 +891,7 @@ func (pps *WorkerState) newApp(addr string, client *libgoal.Client) (tx transact
}
func (pps *WorkerState) appOptIn(addr string, appID uint64, client *libgoal.Client) (tx transactions.Transaction, err error) {
- tx, err = client.MakeUnsignedAppOptInTx(appID, nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppOptInTx(appID, nil, nil, nil, nil, nil, 0)
if err != nil {
fmt.Printf("Cannot create app txn\n")
panic(err)
diff --git a/shared/pingpong/pingpong.go b/shared/pingpong/pingpong.go
index d241edfab3..5727bc00dd 100644
--- a/shared/pingpong/pingpong.go
+++ b/shared/pingpong/pingpong.go
@@ -1217,7 +1217,7 @@ func (pps *WorkerState) constructAppTxn(from, to string, fee uint64, client *lib
}
accounts = accounts[1:]
}
- txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, accounts, nil, nil, boxRefs)
+ txn, err = client.MakeUnsignedAppNoOpTx(aidx, nil, accounts, nil, nil, boxRefs, 0)
if err != nil {
return
}
diff --git a/test/commandandcontrol/cc_agent/component/agent.go b/test/commandandcontrol/cc_agent/component/agent.go
index 66c7063fbe..203cdcaca6 100644
--- a/test/commandandcontrol/cc_agent/component/agent.go
+++ b/test/commandandcontrol/cc_agent/component/agent.go
@@ -96,7 +96,6 @@ type Command struct {
options string
time int64
status CommandStatus
- err error
}
var hostAgent = NewAgent()
diff --git a/test/commandandcontrol/cc_agent/component/pingPongComponent.go b/test/commandandcontrol/cc_agent/component/pingPongComponent.go
index 9d82c8d9b9..5c2278d35b 100644
--- a/test/commandandcontrol/cc_agent/component/pingPongComponent.go
+++ b/test/commandandcontrol/cc_agent/component/pingPongComponent.go
@@ -72,7 +72,6 @@ func (componentInstance *PingPongComponentInstance) Process(command Command) (er
log.Infof("ping pong process started")
}
}
- break
case "stop":
log.Infof("terminating Ping Pong")
err = componentInstance.Terminate()
@@ -82,7 +81,6 @@ func (componentInstance *PingPongComponentInstance) Process(command Command) (er
} else {
log.Infof("ping pong process terminated")
}
- break
default:
log.Warnf("unsupported pingpong action '%s'", command.command)
}
diff --git a/test/commandandcontrol/cc_client/main.go b/test/commandandcontrol/cc_client/main.go
index a40beabaef..7e3e30d5ee 100644
--- a/test/commandandcontrol/cc_client/main.go
+++ b/test/commandandcontrol/cc_client/main.go
@@ -18,7 +18,6 @@ package main
import (
"flag"
- "fmt"
"net/url"
"os"
"os/signal"
@@ -100,7 +99,7 @@ func main() {
ccServiceRequest := lib.CCServiceRequest{
Component: *componentName,
Command: *componentAction,
- Parameters: fmt.Sprintf("%s", options),
+ Parameters: string(options),
TargetAgentList: targetHosts,
}
@@ -119,7 +118,7 @@ func main() {
} else {
log.Infof("Response: %+v", response)
}
- if *listen == false {
+ if !*listen {
break
}
}
diff --git a/test/commandandcontrol/cc_service/main.go b/test/commandandcontrol/cc_service/main.go
index 54e84fdf27..e580eb78ef 100644
--- a/test/commandandcontrol/cc_service/main.go
+++ b/test/commandandcontrol/cc_service/main.go
@@ -105,10 +105,8 @@ func monitorAgent(ws *websocket.Conn) {
case websocket.TextMessage:
log.Infof("received text from agent: %s", message)
clientBroadcast <- message
- break
default:
log.Infof("received other from agent: %s", message)
- break
}
}
// remove the agent from the agent broadcast list
diff --git a/test/e2e-go/cli/algoh/expect/algohTimeoutTest.exp b/test/e2e-go/cli/algoh/expect/algohTimeoutTest.exp
index 081e11d8d5..dff56b029a 100644
--- a/test/e2e-go/cli/algoh/expect/algohTimeoutTest.exp
+++ b/test/e2e-go/cli/algoh/expect/algohTimeoutTest.exp
@@ -53,7 +53,7 @@ if { [catch {
# start hosted node in the background
spawn algoh -d $TEST_NODE_DIR -p $PRIMARY_ADDR
expect {
- "^Logging to: *" {puts "algoh startup successful"}
+ "^algoh logging to: *" {puts "algoh startup successful"}
timeout {::Algoh::Abort "algoh failed to start";}
eof { ::Algoh::CheckEOF "algoh failed to start" }
}
diff --git a/test/e2e-go/cli/goal/expect/createWalletTest.exp b/test/e2e-go/cli/goal/expect/createWalletTest.exp
index 5819a73220..af267f27b7 100644
--- a/test/e2e-go/cli/goal/expect/createWalletTest.exp
+++ b/test/e2e-go/cli/goal/expect/createWalletTest.exp
@@ -43,6 +43,12 @@ if { [catch {
set NEW_PRIMARY_ACCOUNT_ADDRESS [::AlgorandGoal::CreateAccountForWallet $PRIMARY_WALLET_NAME $PRIMARY_WALLET_PASSWORD $TEST_PRIMARY_NODE_DIR]
::AlgorandGoal::VerifyAccount $PRIMARY_WALLET_NAME $PRIMARY_WALLET_PASSWORD $NEW_PRIMARY_ACCOUNT_ADDRESS $TEST_PRIMARY_NODE_DIR
+ # Rename the wallet
+ set SUFFIX "_renamed"
+ set NEW_PRIMARY_WALLET_NAME $PRIMARY_WALLET_NAME$SUFFIX
+ ::AlgorandGoal::RenameWallet $PRIMARY_WALLET_NAME $PRIMARY_WALLET_PASSWORD $TEST_PRIMARY_NODE_DIR $NEW_PRIMARY_WALLET_NAME
+ set PRIMARY_WALLET_NAME $NEW_PRIMARY_WALLET_NAME
+
set WALLET_NAME_PREFIX "TestWallet_"
set WALLET_COUNT 3
set OUTPUT_FILE_NAME "$TEST_ALGO_DIR/walletlist.out"
diff --git a/test/e2e-go/cli/goal/expect/goalAccountInfoTest.exp b/test/e2e-go/cli/goal/expect/goalAccountInfoTest.exp
old mode 100644
new mode 100755
index a7e9ad1e32..3deced7315
--- a/test/e2e-go/cli/goal/expect/goalAccountInfoTest.exp
+++ b/test/e2e-go/cli/goal/expect/goalAccountInfoTest.exp
@@ -136,8 +136,8 @@ Held Assets:
\tID $CCOIN_ASSET_ID, C-Coin, balance 1000 units
\tID $DCOIN_ASSET_ID, D-Coin, balance 10.00 $DCOIN_UNIT_NAME (frozen)
Created Apps:
-\tID $GSTATE_APP_ID, $GSTATE_EXTRA_PAGES extra pages, global state used 0/0 uints, 1/$GSTATE_GLOBAL_BYTE_SLICES byte slices
-\tID $LSTATE_APP_ID, global state used 0/0 uints, 0/$LSTATE_GLOBAL_BYTE_SLICES byte slices
+\tID $GSTATE_APP_ID, $GSTATE_EXTRA_PAGES extra pages, global state used 0/0 uints, 1/$GSTATE_GLOBAL_BYTE_SLICES byte slices, version 0
+\tID $LSTATE_APP_ID, global state used 0/0 uints, 0/$LSTATE_GLOBAL_BYTE_SLICES byte slices, version 0
Opted In Apps:
\tID $LSTATE_APP_ID, local state used 0/1 uints, 1/$LSTATE_LOCAL_BYTE_SLICES byte slices
Minimum Balance:\t1578500 microAlgos"
diff --git a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp
index 058694f684..e452fbfecc 100644
--- a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp
+++ b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp
@@ -403,6 +403,22 @@ proc ::AlgorandGoal::RecoverWallet { NEW_WALLET_NAME WALLET_PASSPHRASE NEW_WALLE
return $RECOVERED_WALLET_NAME
}
+# Rename a wallet
+proc ::AlgorandGoal::RenameWallet { WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NODE_DIR NEW_WALLET_NAME } {
+ set timeout 60
+ if { [catch {
+ spawn goal wallet rename -d $TEST_PRIMARY_NODE_DIR $WALLET_NAME $NEW_WALLET_NAME
+ expect {
+ timeout { ::AlgorandGoal::Abort "Timed out seeing expected input for spawn goal wallet list" }
+ {Please type your recovery mnemonic below, and hit return when you are done:*} { send "$WALLET_PASSWORD\r" }
+ eof { ::AlgorandGoal::Abort "EOF seeing expected input for spawn goal wallet list" }
+ "*Renamed wallet*$WALLET_NAME*$NEW_WALLET_NAME" {close}
+ }
+ } EXCEPTION ] } {
+ ::AlgorandGoal::Abort "ERROR in RenameWallet: $EXCEPTION"
+ }
+}
+
# Associate a new account with a specific wallet
proc ::AlgorandGoal::CreateAccountForWallet { WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NODE_DIR } {
set timeout 60
diff --git a/test/e2e-go/cli/goal/expect/goalLogicSigTest.exp b/test/e2e-go/cli/goal/expect/goalLogicSigTest.exp
new file mode 100644
index 0000000000..9d7a3eb393
--- /dev/null
+++ b/test/e2e-go/cli/goal/expect/goalLogicSigTest.exp
@@ -0,0 +1,77 @@
+#!/usr/bin/expect -f
+set err 0
+log_user 1
+
+if { [catch {
+ source goalExpectCommon.exp
+ set TEST_ALGO_DIR [lindex $argv 0]
+ set TEST_DATA_DIR [lindex $argv 1]
+
+ puts "TEST_ALGO_DIR: $TEST_ALGO_DIR"
+ puts "TEST_DATA_DIR: $TEST_DATA_DIR"
+
+ set TIME_STAMP [clock seconds]
+
+ set TEST_ROOT_DIR $TEST_ALGO_DIR/root
+ set TEST_PRIMARY_NODE_DIR $TEST_ROOT_DIR/Primary/
+ set NETWORK_NAME test_net_expect_$TIME_STAMP
+ set NETWORK_TEMPLATE "$TEST_DATA_DIR/nettemplates/TwoNodes50Each.json"
+
+ # Create network
+ ::AlgorandGoal::CreateNetwork $NETWORK_NAME $NETWORK_TEMPLATE $TEST_ALGO_DIR $TEST_ROOT_DIR
+
+ # Start network
+ ::AlgorandGoal::StartNetwork $NETWORK_NAME $NETWORK_TEMPLATE $TEST_ROOT_DIR
+
+ set PRIMARY_NODE_ADDRESS [ ::AlgorandGoal::GetAlgodNetworkAddress $TEST_PRIMARY_NODE_DIR ]
+ puts "Primary Node Address: $PRIMARY_NODE_ADDRESS"
+
+ set PRIMARY_WALLET_NAME unencrypted-default-wallet
+
+ # Determine primary account
+ set PRIMARY_ACCOUNT_ADDRESS [::AlgorandGoal::GetHighestFundedAccountForWallet $PRIMARY_WALLET_NAME $TEST_PRIMARY_NODE_DIR]
+
+ # rekey address to logic sig
+ set TEAL_PROGS_DIR "$TEST_DATA_DIR/../scripts/e2e_subs/tealprogs"
+ set TEAL_SOURCE "$TEST_ROOT_DIR/int1.teal"
+ exec cp "$TEAL_PROGS_DIR/int1.teal" $TEAL_SOURCE
+ set CONTRACT_ADDRESS [::AlgorandGoal::TealCompile $TEAL_SOURCE]
+ spawn goal clerk send -a 0 --fee 1000 -f $PRIMARY_ACCOUNT_ADDRESS -t $PRIMARY_ACCOUNT_ADDRESS --rekey-to $CONTRACT_ADDRESS -d $TEST_PRIMARY_NODE_DIR
+ expect {
+ timeout { close; ::AlgorandGoal::Abort "goal clerk send timeout" }
+ -re {Transaction ([A-Z0-9]+) expired before it could be included in a block} {
+ break;
+ close;
+ }
+ -re {Transaction ([A-Z0-9]+) kicked out of local node pool} {
+ # this is a legit possible case, so just keep iterating if we hit this one.
+ close;
+ }
+ -re {Couldn't broadcast tx with algod: HTTP 400 Bad Request: TransactionPool.Remember: txn dead: round ([0-9]+) outside of ([0-9]+)--([0-9]+)} {
+ # this is a legit possible case, so just keep iterating if we hit this one.
+ close;
+ }
+ eof { ::AlgorandGoal::CheckEOF "Failed to send a rekey transaction" }
+ }
+
+ # create transaction with logic sig and signer
+ set TXN_WITH_SIGNER "$TEST_ROOT_DIR/txn_with_signer.txn"
+ spawn goal clerk send --from-program $TEAL_SOURCE --from $PRIMARY_ACCOUNT_ADDRESS --to $PRIMARY_ACCOUNT_ADDRESS --rekey-to $PRIMARY_ACCOUNT_ADDRESS -S $CONTRACT_ADDRESS --amount 1 -d $TEST_PRIMARY_NODE_DIR -o $TXN_WITH_SIGNER
+ expect {
+ timeout { ::AlgorandGoal::Abort "Timed out Teal transaction create" }
+ eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to create teal transaction: error code [lindex $result 3]"} }
+ }
+ set RAW_TRANSACTION_ID [::AlgorandGoal::RawSend $TXN_WITH_SIGNER $TEST_PRIMARY_NODE_DIR]
+ puts "send transaction in $RAW_TRANSACTION_ID"
+ puts "TxnWithSigner Test Successful"
+
+ # Shutdown the network
+ ::AlgorandGoal::StopNetwork $NETWORK_NAME $TEST_ROOT_DIR
+
+ puts "Goal LogicSig with Signer Test Successful"
+
+ exit 0
+
+} EXCEPTION ] } {
+ ::AlgorandGoal::Abort "ERROR in goalLogicSigTest: $EXCEPTION"
+}
diff --git a/test/e2e-go/features/accountPerf/sixMillion_test.go b/test/e2e-go/features/accountPerf/sixMillion_test.go
index 592f245948..1315fd041e 100644
--- a/test/e2e-go/features/accountPerf/sixMillion_test.go
+++ b/test/e2e-go/features/accountPerf/sixMillion_test.go
@@ -1181,7 +1181,7 @@ func makeOptInAppTransaction(
tLife uint64,
genesisHash crypto.Digest) (appTx transactions.Transaction) {
- appTx, err := client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ appTx, err := client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
require.NoError(t, err)
appTx.Header = transactions.Header{
@@ -1287,7 +1287,7 @@ func callAppTransaction(
tLife uint64,
genesisHash crypto.Digest) (appTx transactions.Transaction) {
- appTx, err := client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ appTx, err := client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
require.NoError(t, err)
appTx.Header = transactions.Header{
diff --git a/test/e2e-go/features/transactions/accountv2_test.go b/test/e2e-go/features/transactions/accountv2_test.go
index bc916f660f..b833b55951 100644
--- a/test/e2e-go/features/transactions/accountv2_test.go
+++ b/test/e2e-go/features/transactions/accountv2_test.go
@@ -215,7 +215,7 @@ int 1
checkEvalDelta(t, &client, txnRound, txnRound+1, 1, 1)
// call the app
- tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx)
a.NoError(err)
@@ -295,7 +295,7 @@ int 1
a.Equal(creator, app.Params.Creator)
// call the app
- tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx)
a.NoError(err)
@@ -520,7 +520,7 @@ int 1
checkEvalDelta(t, &client, txnRound, txnRound+1, 1, 1)
// call the app
- tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
if foreignAssets != nil {
tx.ForeignAssets = foreignAssets
@@ -610,7 +610,7 @@ int 1
a.Equal(creator, app.Params.Creator)
// call the app
- tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppNoOpTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx)
a.NoError(err)
diff --git a/test/e2e-go/features/transactions/app_pages_test.go b/test/e2e-go/features/transactions/app_pages_test.go
index c2d827649f..d9a6b79753 100644
--- a/test/e2e-go/features/transactions/app_pages_test.go
+++ b/test/e2e-go/features/transactions/app_pages_test.go
@@ -110,7 +110,7 @@ return
a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages))
// update app 1 and ensure the extra page still works
- tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, nil, bigProgram, smallProgram)
+ tx, err = client.MakeUnsignedAppUpdateTx(app1ID, nil, nil, nil, nil, nil, bigProgram, smallProgram, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
a.NoError(err)
@@ -151,7 +151,7 @@ return
a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app1ExtraPages+app2ExtraPages))
// delete app 1
- tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppDeleteTx(app1ID, nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
a.NoError(err)
@@ -170,7 +170,7 @@ return
a.Equal(*accountInfo.AppsTotalExtraPages, uint64(app2ExtraPages))
// delete app 2
- tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppDeleteTx(app2ID, nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(baseAcct, 0, 0, 0, tx)
a.NoError(err)
diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go
index ddf2a66e1e..b9530347e5 100644
--- a/test/e2e-go/features/transactions/application_test.go
+++ b/test/e2e-go/features/transactions/application_test.go
@@ -32,18 +32,6 @@ import (
"github.com/algorand/go-algorand/test/partitiontest"
)
-func checkEqual(expected []string, actual []string) bool {
- if len(expected) != len(actual) {
- return false
- }
- for i, e := range expected {
- if e != actual[i] {
- return false
- }
- }
- return true
-}
-
func TestApplication(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)
@@ -124,9 +112,7 @@ log
b, err := client.BookkeepingBlock(round)
a.NoError(err)
for _, ps := range b.Payset {
- ed := ps.ApplyData.EvalDelta
- ok = checkEqual(logs, ed.Logs)
- a.True(ok)
+ a.Equal(logs, ps.ApplyData.EvalDelta.Logs)
}
}
diff --git a/test/e2e-go/restAPI/other/appsRestAPI_test.go b/test/e2e-go/restAPI/other/appsRestAPI_test.go
index fc981cec83..781630806b 100644
--- a/test/e2e-go/restAPI/other/appsRestAPI_test.go
+++ b/test/e2e-go/restAPI/other/appsRestAPI_test.go
@@ -95,7 +95,7 @@ return
lc := basics.StateSchema{}
// create app
- appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0)
+ appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(0, nil, nil, nil, nil, nil, transactions.NoOpOC, approv, clst, gl, lc, 0, 0)
a.NoError(err)
appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
a.NoError(err)
@@ -119,7 +119,7 @@ return
a.NoError(err)
// call app, which will issue an ASA create inner txn
- appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil, nil)
+ appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(uint64(createdAppID), nil, nil, nil, nil, nil, 0)
a.NoError(err)
appCallTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn)
a.NoError(err)
@@ -233,7 +233,7 @@ end:
appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
0, nil, nil, nil,
nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
+ approval, clearState, gl, lc, 0, 0,
)
a.NoError(err)
appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
@@ -264,7 +264,7 @@ end:
var createdBoxCount uint64 = 0
// define operate box helper
- operateBoxAndSendTxn := func(operation string, boxNames []string, boxValues []string, errPrefix ...string) {
+ operateBoxAndSendTxn := func(operation string, boxNames []string, boxValues []string, errPrefix ...string) uint64 {
txns := make([]transactions.Transaction, len(boxNames))
txIDs := make(map[string]string, len(boxNames))
@@ -282,7 +282,7 @@ end:
txns[i], err = testClient.MakeUnsignedAppNoOpTx(
uint64(createdAppID), appArgs,
nil, nil, nil,
- []transactions.BoxRef{boxRef},
+ []transactions.BoxRef{boxRef}, 0,
)
a.NoError(err)
txns[i], err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, txns[i])
@@ -306,15 +306,16 @@ end:
err = testClient.BroadcastTransactionGroup(stxns)
if len(errPrefix) == 0 {
a.NoError(err)
- _, err = helper.WaitForTransaction(t, testClient, txns[0].ID().String(), 30*time.Second)
- a.NoError(err)
- } else {
- a.ErrorContains(err, errPrefix[0])
+ tx, wErr := helper.WaitForTransaction(t, testClient, txns[0].ID().String(), 30*time.Second)
+ a.NoError(wErr)
+ return *tx.ConfirmedRound
}
+ a.ErrorContains(err, errPrefix[0])
+ return 0
}
- // `assertErrorResponse` confirms the _Result limit exceeded_ error response provides expected fields and values.
- assertErrorResponse := func(err error, expectedCount, requestedMax uint64) {
+ // `assertErrorResponse` confirms the _Result limit exceeded_ when max=requestedMax
+ assertErrorResponse := func(err error, requestedMax uint64) {
a.Error(err)
e := err.(client.HTTPError)
a.Equal(400, e.StatusCode)
@@ -322,29 +323,30 @@ end:
a.Equal("Result limit exceeded", e.ErrorString)
a.EqualValues(100000, e.Data["max-api-box-per-application"])
a.EqualValues(requestedMax, e.Data["max"])
- a.EqualValues(expectedCount, e.Data["total-boxes"])
- a.Len(e.Data, 3, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", e.Data))
+ a.Len(e.Data, 2, fmt.Sprintf("error response (%v) contains unverified fields. Extend test for new fields.", e.Data))
}
// `assertBoxCount` sanity checks that the REST API respects `expectedCount` through different queries against app ID = `createdAppID`.
- assertBoxCount := func(expectedCount uint64) {
+ assertBoxCount := func(expectedCount uint64, prefix string) {
// Query without client-side limit.
- resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ resp, err := testClient.ApplicationBoxes(uint64(createdAppID), prefix, nil, 0, false)
a.NoError(err)
a.Len(resp.Boxes, int(expectedCount))
// Query with requested max < expected expectedCount.
- _, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount-1)
- assertErrorResponse(err, expectedCount, expectedCount-1)
+ if expectedCount > 1 {
+ _, err = testClient.ApplicationBoxes(uint64(createdAppID), prefix, nil, expectedCount-1, false)
+ assertErrorResponse(err, expectedCount-1)
+ }
// Query with requested max == expected expectedCount.
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount)
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), prefix, nil, expectedCount, false)
a.NoError(err)
a.Len(resp.Boxes, int(expectedCount))
// Query with requested max > expected expectedCount.
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), expectedCount+1)
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), prefix, nil, expectedCount+1, false)
a.NoError(err)
a.Len(resp.Boxes, int(expectedCount))
}
@@ -370,7 +372,7 @@ end:
a.Failf("Unknown operation %s", operation)
}
- operateBoxAndSendTxn(operation, boxNames, boxValues)
+ round := operateBoxAndSendTxn(operation, boxNames, boxValues)
if operation == "create" {
for _, box := range boxNames {
@@ -384,9 +386,13 @@ end:
createdBoxCount -= uint64(len(boxNames))
}
+ // /boxes/ endpoint only examines DB, so we wait until its response includes the round we made the boxes in.
var resp model.BoxesResponse
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
- a.NoError(err)
+ for resp.Round < round {
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "", nil, 0, false)
+ a.NoError(err)
+ time.Sleep(time.Second)
+ }
expectedCreatedBoxes := make([]string, 0, createdBoxCount)
for name, isCreate := range createdBoxName {
@@ -406,6 +412,9 @@ end:
}
testingBoxNames := []string{
+ `simple1`,
+ `simple2`,
+ `simple3`,
` `,
` `,
` ? = % ;`,
@@ -444,7 +453,10 @@ end:
}
// Happy Vanilla paths:
- resp, err := testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ resp, err := testClient.ApplicationBoxes(uint64(createdAppID), "", nil, 0, false)
+ a.NoError(err)
+ a.Empty(resp.Boxes)
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "str:xxx", nil, 0, false)
a.NoError(err)
a.Empty(resp.Boxes)
@@ -457,7 +469,7 @@ end:
nonexistantAppIndex := uint64(1337)
_, err = testClient.ApplicationInformation(nonexistantAppIndex)
a.ErrorContains(err, "application does not exist")
- resp, err = testClient.ApplicationBoxes(nonexistantAppIndex, 0)
+ resp, err = testClient.ApplicationBoxes(nonexistantAppIndex, "", nil, 0, false)
a.NoError(err)
a.Len(resp.Boxes, 0)
@@ -474,7 +486,23 @@ end:
operateAndMatchRes("create", strSliceTest)
}
- assertBoxCount(uint64(len(testingBoxNames)))
+ assertBoxCount(uint64(len(testingBoxNames)), "")
+ assertBoxCount(3, "str:simpl")
+ assertBoxCount(1, "str:simple1")
+ assertBoxCount(0, "str:simple10")
+
+ // test with a prefix
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "str:simpl", nil, 0, false)
+ a.NoError(err)
+ a.ElementsMatch([]model.Box{{Name: []byte("simple1")}, {Name: []byte("simple2")}, {Name: []byte("simple3")}},
+ resp.Boxes)
+
+ // test with prefix and a next
+ simple2 := "str:simple2"
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "str:simpl", &simple2, 0, false)
+ a.NoError(err)
+ a.ElementsMatch([]model.Box{{Name: []byte("simple2")}, {Name: []byte("simple3")}},
+ resp.Boxes)
for i := 0; i < len(testingBoxNames); i += 16 {
var strSliceTest []string
@@ -487,7 +515,7 @@ end:
operateAndMatchRes("delete", strSliceTest)
}
- resp, err = testClient.ApplicationBoxes(uint64(createdAppID), 0)
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "", nil, 0, false)
a.NoError(err)
a.Empty(resp.Boxes)
@@ -508,10 +536,11 @@ end:
{[]byte{0, 248, 255, 32}, "b64:APj/IA==", []byte("lux56")},
}
+ round := uint64(0)
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)})
+ round = operateBoxAndSendTxn("set", []string{string(boxTest.name)}, []string{string(boxTest.value)})
currentRoundBeforeBoxes, err := testClient.CurrentRound()
a.NoError(err)
@@ -522,12 +551,20 @@ end:
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)
+ a.GreaterOrEqual(*boxResponse.Round, currentRoundBeforeBoxes)
+ a.LessOrEqual(*boxResponse.Round, currentRoundAfterBoxes)
}
const numberOfBoxesRemaining = uint64(3)
- assertBoxCount(numberOfBoxesRemaining)
+
+ // wait for boxes to hit the DB
+ for resp.Round < round {
+ resp, err = testClient.ApplicationBoxes(uint64(createdAppID), "", nil, 0, false)
+ a.NoError(err)
+ time.Sleep(time.Second)
+ }
+
+ assertBoxCount(numberOfBoxesRemaining, "")
// Non-vanilla. Wasteful but correct. Can delete an app without first cleaning up its boxes.
appAccountData, err := testClient.AccountData(createdAppID.Address().String())
@@ -536,7 +573,7 @@ end:
a.Equal(uint64(30), appAccountData.TotalBoxBytes)
// delete the app
- appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(uint64(createdAppID), nil, nil, nil, nil, nil)
+ appDeleteTxn, err := testClient.MakeUnsignedAppDeleteTx(uint64(createdAppID), nil, nil, nil, nil, nil, 0)
a.NoError(err)
appDeleteTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appDeleteTxn)
a.NoError(err)
@@ -548,7 +585,8 @@ end:
_, err = testClient.ApplicationInformation(uint64(createdAppID))
a.ErrorContains(err, "application does not exist")
- assertBoxCount(numberOfBoxesRemaining)
+ assertBoxCount(numberOfBoxesRemaining, "")
+ assertBoxCount(1, "str:f")
}
func TestBlockLogs(t *testing.T) {
@@ -619,7 +657,7 @@ func TestBlockLogs(t *testing.T) {
appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
0, nil, nil, nil,
nil, nil, transactions.NoOpOC,
- outerApproval, clearState, gl, lc, 0,
+ outerApproval, clearState, gl, lc, 0, 0,
)
a.NoError(err)
appCreateTxn, err = testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCreateTxn)
@@ -649,7 +687,7 @@ func TestBlockLogs(t *testing.T) {
// call app twice
appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
uint64(createdAppID), nil, nil, nil,
- nil, nil,
+ nil, nil, 0,
)
a.NoError(err)
appCallTxn0, err := testClient.FillUnsignedTxTemplate(someAddress, 0, 0, 0, appCallTxn)
diff --git a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go
index 49ffd7897b..90e59773e8 100644
--- a/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go
+++ b/test/e2e-go/restAPI/simulate/simulateRestAPI_test.go
@@ -265,7 +265,7 @@ int 1`
txn, err := testClient.MakeUnsignedApplicationCallTx(
0, nil, nil, nil,
nil, nil, transactions.NoOpOC,
- approval, clearState, basics.StateSchema{}, basics.StateSchema{}, 0,
+ approval, clearState, basics.StateSchema{}, basics.StateSchema{}, 0, 0,
)
a.NoError(err)
txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 1, 1001, 0, txn)
@@ -473,7 +473,7 @@ int 1`
appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
0, nil, nil, nil,
nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
+ approval, clearState, gl, lc, 0, 0,
)
a.NoError(err)
appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
@@ -502,7 +502,7 @@ int 1`
// construct app call
appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
uint64(createdAppID), [][]byte{[]byte("first-arg")},
- nil, nil, nil, nil,
+ nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
@@ -601,7 +601,7 @@ int 1`
appCreateTxn, err := testClient.MakeUnsignedApplicationCallTx(
0, nil, nil, nil,
nil, nil, transactions.NoOpOC,
- approval, clearState, gl, lc, 0,
+ approval, clearState, gl, lc, 0, 0,
)
a.NoError(err)
appCreateTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCreateTxn)
@@ -629,7 +629,7 @@ int 1`
// construct app call
appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(createdAppID), nil, nil, nil, nil, nil,
+ uint64(createdAppID), nil, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, appCallTxn)
@@ -894,7 +894,7 @@ func TestMaxDepthAppWithPCandStackTrace(t *testing.T) {
// construct app calls
appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{uint64ToBytes(uint64(MaxDepth))}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{uint64ToBytes(uint64(MaxDepth))}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee*uint64(3*MaxDepth+2), appCallTxn)
@@ -1736,7 +1736,7 @@ func TestSimulateScratchSlotChange(t *testing.T) {
// construct app calls
appCallTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallTxn)
@@ -1930,18 +1930,18 @@ end:
// construct app call "global"
appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn)
a.NoError(err)
// construct app optin
- appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil)
+ appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil, 0)
a.NoError(err)
appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn)
// construct app call "global"
appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn)
@@ -2212,18 +2212,18 @@ end:
// construct app call "global"
appCallGlobalTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{[]byte("global")}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallGlobalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallGlobalTxn)
a.NoError(err)
// construct app optin
- appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil)
+ appOptInTxn, err := testClient.MakeUnsignedAppOptInTx(uint64(futureAppID), nil, nil, nil, nil, nil, 0)
a.NoError(err)
appOptInTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appOptInTxn)
// construct app call "local"
appCallLocalTxn, err := testClient.MakeUnsignedAppNoOpTx(
- uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil,
+ uint64(futureAppID), [][]byte{[]byte("local")}, nil, nil, nil, nil, 0,
)
a.NoError(err)
appCallLocalTxn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, MinFee, appCallLocalTxn)
@@ -2624,7 +2624,7 @@ int 1
// construct app call
txn, err = testClient.MakeUnsignedAppNoOpTx(
- uint64(testAppID), nil, nil, nil, nil, nil,
+ uint64(testAppID), nil, nil, nil, nil, nil, 0,
)
a.NoError(err)
txn, err = testClient.FillUnsignedTxTemplate(senderAddress, 0, 0, 0, txn)
diff --git a/test/e2e-go/upgrades/application_support_test.go b/test/e2e-go/upgrades/application_support_test.go
index c2b1a2c8fd..2570a76046 100644
--- a/test/e2e-go/upgrades/application_support_test.go
+++ b/test/e2e-go/upgrades/application_support_test.go
@@ -101,9 +101,10 @@ func TestApplicationsUpgradeOverREST(t *testing.T) {
a.NoError(err)
// Fund the manager, so it can issue transactions later on
- _, err = client.SendPaymentFromUnencryptedWallet(creator, user, fee, 10000000000, nil)
+ tx0, err := client.SendPaymentFromUnencryptedWallet(creator, user, fee, 10000000000, nil)
a.NoError(err)
- client.WaitForRound(round + 2)
+ isCommitted := fixture.WaitForTxnConfirmation(round+10, tx0.ID().String())
+ a.True(isCommitted)
// There should be no apps to start with
ad, err := client.AccountData(creator)
@@ -155,8 +156,6 @@ int 1
a.NoError(err)
signedTxn, err := client.SignTransactionWithWallet(wh, nil, tx)
a.NoError(err)
- round, err = client.CurrentRound()
- a.NoError(err)
successfullBroadcastCount := 0
_, err = client.BroadcastTransaction(signedTxn)
@@ -184,8 +183,6 @@ int 1
time.Sleep(time.Duration(smallLambdaMs) * time.Millisecond)
}
- round = curStatus.LastRound
-
// make a change to the node field to ensure we're not broadcasting the same transaction as we tried before.
tx.Note = []byte{1, 2, 3}
signedTxn, err = client.SignTransactionWithWallet(wh, nil, tx)
@@ -236,7 +233,7 @@ int 1
a.Equal(uint64(1), value.Uint)
// call the app
- tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx)
a.NoError(err)
@@ -295,7 +292,6 @@ int 1
a.NoError(err)
a.Equal(uint64(appIdx), app.Id)
a.Equal(creator, app.Params.Creator)
- return
}
// TestApplicationsUpgrade tests that we can safely upgrade from a version that doesn't support applications
@@ -488,7 +484,7 @@ int 1
a.Equal(uint64(1), value.Uint)
// call the app
- tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil)
+ tx, err = client.MakeUnsignedAppOptInTx(uint64(appIdx), nil, nil, nil, nil, nil, 0)
a.NoError(err)
tx, err = client.FillUnsignedTxTemplate(user, 0, 0, fee, tx)
a.NoError(err)
@@ -547,5 +543,4 @@ int 1
a.NoError(err)
a.Equal(uint64(appIdx), app.Id)
a.Equal(creator, app.Params.Creator)
- return
}
diff --git a/test/framework/fixtures/expectFixture.go b/test/framework/fixtures/expectFixture.go
index 2653a1b98c..2c03a704bb 100644
--- a/test/framework/fixtures/expectFixture.go
+++ b/test/framework/fixtures/expectFixture.go
@@ -191,7 +191,7 @@ func (ef *ExpectFixture) Run() {
if ferr != nil {
stderr = ferr.Error()
}
- syncTest.Logf("err running '%s': %s\nstdout: %s\nstderr: %s\n", testName, err, string(outBuf.Bytes()), stderr)
+ syncTest.Logf("err running '%s': %s\nstdout: %s\nstderr: %s\n", testName, err, outBuf, stderr)
syncTest.Fail()
} else {
// t.Logf("stdout: %s", string(outBuf.Bytes()))
diff --git a/test/framework/fixtures/kmdFixture.go b/test/framework/fixtures/kmdFixture.go
index 2570be017f..f07b0a26b4 100644
--- a/test/framework/fixtures/kmdFixture.go
+++ b/test/framework/fixtures/kmdFixture.go
@@ -34,9 +34,6 @@ import (
// defaultConfig lowers scrypt params to make tests faster
var defaultConfig = `{"drivers":{"sqlite":{"scrypt":{"scrypt_n":2},"allow_unsafe_scrypt":true}}}`
-// shutdownTimeoutSecs is time to wait for kmd to shut down before returning an error
-const shutdownTimeoutSecs = 5
-
// defaultTimeoutSecs is the number of seconds after which kmd will die if it
// receives no requests
const defaultTimeoutSecs = 60
diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go
index c6659b33be..9af69b3c8a 100644
--- a/test/framework/fixtures/libgoalFixture.go
+++ b/test/framework/fixtures/libgoalFixture.go
@@ -127,7 +127,15 @@ func (f *LibGoalFixture) setup(test TestingTB, testName string, templateFile str
importKeys := false // Don't automatically import root keys when creating folders, we'll import on-demand
file, err := os.Open(templateFile)
f.failOnError(err, "Template file could not be opened: %v")
- network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, overrides...)
+ defer file.Close()
+
+ // Override the kmd session lifetime to 5 minutes to prevent kmd wallet handles from expiring
+ kmdConfOverride := netdeploy.OverrideKmdConfig(netdeploy.TemplateKMDConfig{SessionLifetimeSecs: 300})
+ // copy overrides to prevent caller's data from being modified
+ extraOverrides := append([]netdeploy.TemplateOverride(nil), overrides...)
+ extraOverrides = append(extraOverrides, kmdConfOverride)
+
+ network, err := netdeploy.CreateNetworkFromTemplate("test", f.rootDir, file, f.binDir, importKeys, f.nodeExitWithError, f.consensus, extraOverrides...)
f.failOnError(err, "CreateNetworkFromTemplate failed: %v")
f.network = network
diff --git a/test/heapwatch/requirements.txt b/test/heapwatch/requirements.txt
index 209238ed0d..45f53b3603 100644
--- a/test/heapwatch/requirements.txt
+++ b/test/heapwatch/requirements.txt
@@ -1,6 +1,6 @@
dash==2.15.0
dash-table==5.0.0
-Jinja2==3.1.5
+Jinja2==3.1.6
matplotlib==3.7.2
plotly==5.16.0
py-algorand-sdk==2.3.0
diff --git a/test/netperf-go/puppeteer/puppeteer.go b/test/netperf-go/puppeteer/puppeteer.go
index 003027fe67..a2ebbd812c 100644
--- a/test/netperf-go/puppeteer/puppeteer.go
+++ b/test/netperf-go/puppeteer/puppeteer.go
@@ -233,8 +233,6 @@ func (p *puppet) exec(wg *sync.WaitGroup, errs chan error) {
return
}
}
-
- return
}
type stdWriter struct {
@@ -258,7 +256,7 @@ func (c *stdWriter) Write(p []byte) (n int, err error) {
if eolIdx > 0 {
line := c.prefix + c.output[:eolIdx+1]
c.output = c.output[eolIdx+1:]
- fmt.Fprintf(c.outFile, line)
+ fmt.Fprint(c.outFile, line)
} else {
break
}
diff --git a/test/partitiontest/filtering.go b/test/partitiontest/filtering.go
index 50f0b0c83f..03ae217424 100644
--- a/test/partitiontest/filtering.go
+++ b/test/partitiontest/filtering.go
@@ -25,7 +25,7 @@ import (
)
// PartitionTest checks if the current partition should run this test, and skips it if not.
-func PartitionTest(t *testing.T) {
+func PartitionTest(t testing.TB) {
pt, found := os.LookupEnv("PARTITION_TOTAL")
if !found {
return
diff --git a/test/scripts/e2e_subs/api-compression.sh b/test/scripts/e2e_subs/api-compression.sh
new file mode 100755
index 0000000000..a2ab3720f6
--- /dev/null
+++ b/test/scripts/e2e_subs/api-compression.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+filename=$(basename "$0")
+scriptname="${filename%.*}"
+date "+${scriptname} start %Y%m%d_%H%M%S"
+
+
+my_dir="$(dirname "$0")"
+source "$my_dir/rest.sh" "$@"
+
+function headers() {
+ curl -q -s -D - -o /dev/null -H "Authorization: Bearer $PUB_TOKEN" -H "Accept-Encoding: gzip, deflate, br" "$NET$1"
+}
+
+set -e
+set -x
+set -o pipefail
+export SHELLOPTS
+
+OUT=$(headers "/v2/blocks/1")
+[[ ${OUT} == *Content-Encoding:\ gzip* ]] || false
+
+date "+${scriptname} OK %Y%m%d_%H%M%S"
diff --git a/test/scripts/e2e_subs/app-version.sh b/test/scripts/e2e_subs/app-version.sh
new file mode 100755
index 0000000000..da71555048
--- /dev/null
+++ b/test/scripts/e2e_subs/app-version.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+
+filename=$(basename "$0")
+scriptname="${filename%.*}"
+date "+${scriptname} start %Y%m%d_%H%M%S"
+
+set -e
+set -x
+set -o pipefail
+export SHELLOPTS
+
+WALLET=$1
+
+TEAL=test/scripts/e2e_subs/tealprogs
+
+gcmd="goal -w ${WALLET}"
+
+ACCOUNT=$(${gcmd} account list|awk '{ print $3 }')
+
+APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog=${TEAL}/approve-all.teal --clear-prog=${TEAL}/approve-all.teal | grep Created | awk '{ print $6 }')
+
+ACCOUNTB=$(${gcmd} account new|awk '{ print $6 }')
+${gcmd} clerk send -f "$ACCOUNT" -t "$ACCOUNTB" -a 1000000
+
+# Now call from a different account, reject-version=0 allows all
+${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB" --reject-version 0
+
+# reject-version=1 allows because version is currently 0
+${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB" --reject-version 1
+
+${gcmd} app update --app-id="$APPID" --from="$ACCOUNT" --approval-prog=${TEAL}/approve-all.teal --clear-prog=${TEAL}/approve-all.teal
+
+# reject-version=0 allows all. This time do it by not specifying
+${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB"
+
+# fail with rv=1, b/c version has incremented to 1
+${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB" --reject-version 1 && exit 1
+
+# succeed with rv=2
+${gcmd} app call --app-id="$APPID" --from="$ACCOUNTB" --reject-version 2
+
+
+date "+${scriptname} OK %Y%m%d_%H%M%S"
diff --git a/test/scripts/e2e_subs/box-search.sh b/test/scripts/e2e_subs/box-search.sh
index 29ed02757b..543ce47a87 100755
--- a/test/scripts/e2e_subs/box-search.sh
+++ b/test/scripts/e2e_subs/box-search.sh
@@ -26,27 +26,18 @@ APPID=$(${gcmd} app create --creator "$ACCOUNT" --approval-prog=${TEAL}/boxes.te
APP_ACCOUNT=$(${gcmd} app info --app-id "$APPID" | grep "Application account" | awk '{print $3}')
${gcmd} clerk send --to "$APP_ACCOUNT" --from "$ACCOUNT" --amount 10000000
-# Confirm that we are informed if no application boxes exist
-BOX_LIST=$(${gcmd} app box list --app-id "$APPID" 2>&1 || true)
-EXPECTED="No boxes found for appid $APPID"
-
-[ "$BOX_LIST" = "$EXPECTED" ]
+# Confirm that "Boxes:" is the last line when there are no Boxes
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" || true)
+[[ "$BOX_LIST" = *"Boxes:" ]] || false
# Confirm that we are informed if a specific application box does not exist
BOX_INFO=$(${gcmd} app box info --app-id "$APPID" --name "str:not_found" 2>&1 || true)
-EXPECTED="No box found for appid $APPID with name str:not_found"
-
-[ "$BOX_INFO" = "$EXPECTED" ]
+[[ "$BOX_INFO" = *"No box found for appid $APPID with name str:not_found" ]] || false
# 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
+[[ "$RES" = *"invalid : tx.Boxes[0].Name too long, max len 64 bytes" ]] || false
# Create several boxes
BOX_NAMES=("str:box1" "str:with spaces" "b64:YmFzZTY0" "b64:AQIDBA==") # b64:YmFzZTY0 == str:base64, b64:AQIDBA== is not unicode
@@ -82,21 +73,44 @@ EXPECTED_APP_ACCOUNT_BOX_BYTES=121
[ "$ACTUAL_APP_ACCOUNT_NUM_BOXES" -eq "$EXPECTED_APP_ACCOUNT_NUM_BOXES" ]
[ "$ACTUAL_APP_ACCOUNT_BOX_BYTES" -eq "$EXPECTED_APP_ACCOUNT_BOX_BYTES" ]
+# goal app box list only looks at the DB, so wait a few rounds
+sleep 10
+
# Confirm that we can get a list of boxes belonging to a particular application
BOX_LIST=$(${gcmd} app box list --app-id "$APPID")
-EXPECTED="str:box1
-str:with spaces
+[[ "$BOX_LIST" = "Round: "* ]] || false
+EXPECTED="Boxes:
+b64:AQIDBA==
str:base64
-b64:AQIDBA=="
-
-# shellcheck disable=SC2059
-[ "$(printf "$BOX_LIST" | sort)" = "$(printf "$EXPECTED" | sort)" ]
+str:box1
+str:with spaces"
+[[ "$BOX_LIST" = *"$EXPECTED" ]] || false
# Confirm that we can limit the number of boxes returned
-BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --max 4)
-[ "$(echo "$BOX_LIST" | wc -l)" -eq 4 ] # only one line
-# shellcheck disable=SC2143
-[ "$(grep -w "$BOX_LIST" <<< "$EXPECTED")" ] # actual box is in the expected list
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --limit 2)
+[ "$(echo "$BOX_LIST" | wc -l)" -eq 5 ] # Round:, NextToken:, Boxes:, 2 actual responses
+[[ "$BOX_LIST" != *str:with\ spaces* ]] || false # 4th box doesn't appear
+[[ "$BOX_LIST" = *str:box1*Boxes:* ]] || false # 3rd box is this the NextToken (comes before "Boxes:")
+
+# Fetch the final two boxes
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --limit 2 --next str:box1)
+[ "$(echo "$BOX_LIST" | wc -l)" -eq 4 ] # Round:, Boxes:, 2 actual responses
+[[ "$BOX_LIST" != *NextToken* ]] || false # No NextToken
+[[ "$BOX_LIST" = *Boxes:*str:box1* ]] || false
+[[ "$BOX_LIST" = *Boxes:*str:with\ spaces* ]] || false
+
+# Confirm that we can use prefix to get only boxes that start with "bo"
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --prefix str:bo)
+[ "$(echo "$BOX_LIST" | wc -l)" -eq 3 ] # Round:, Boxes:, 1 actual response
+[[ "$BOX_LIST" != *Boxes:*str:base64* ]] || false
+[[ "$BOX_LIST" = *Boxes:*str:box1* ]] || false
+
+# Confirm that we can use prefix to get only boxes that start with "b"
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --prefix str:b)
+[ "$(echo "$BOX_LIST" | wc -l)" -eq 4 ] # Round:, Boxes:, 2 actual responses
+[[ "$BOX_LIST" = *Boxes:*str:base64* ]] || false
+[[ "$BOX_LIST" = *Boxes:*str:box1* ]] || false
+
# Create and set a box in an atomic txn group:
@@ -104,34 +118,56 @@ BOX_NAME="str:great box"
echo "Create $BOX_NAME"
${gcmd} app call --from "$ACCOUNT" --app-id "$APPID" --box "$BOX_NAME" --app-arg "str:create" --app-arg "$BOX_NAME" -o "$TEMPDIR/box_create.txn"
-echo "Set $BOX_NAME using $BOX_VALUE"
-${gcmd} app call --from "$ACCOUNT" --app-id "$APPID" --app-arg "str:set" --app-arg "$BOX_NAME" --app-arg "str:$BOX_VALUE" -o "$TEMPDIR/box_set.txn"
+echo "Set $BOX_NAME using str:GREAT"
+GREAT_VALUE=123456789012345678901234
+${gcmd} app call --from "$ACCOUNT" --app-id "$APPID" --app-arg "str:set" --app-arg "$BOX_NAME" --app-arg "str:$GREAT_VALUE" -o "$TEMPDIR/box_set.txn"
# Group them, sign and broadcast:
cat "$TEMPDIR/box_create.txn" "$TEMPDIR/box_set.txn" > "$TEMPDIR/box_create_n_set.txn"
${gcmd} clerk group -i "$TEMPDIR/box_create_n_set.txn" -o "$TEMPDIR/box_group.txn"
${gcmd} clerk sign -i "$TEMPDIR/box_group.txn" -o "$TEMPDIR/box_group.stx"
-${gcmd} clerk rawsend -f "$TEMPDIR/box_group.stx"
+COMMIT=$(${gcmd} clerk rawsend -f "$TEMPDIR/box_group.stx" | grep "committed in round" | head -1 | awk '{print $6}')
+echo "Last box made in $COMMIT"
-echo "Confirm that NAME $BOX_NAME as expected"
+echo "Confirm the NAME is $BOX_NAME"
${gcmd} app box info --app-id "$APPID" --name "$BOX_NAME"
NAME=$(${gcmd} app box info --app-id "$APPID" --name "$BOX_NAME" | grep Name | tr -s ' ' | cut -d" " -f2-)
[ "$NAME" = "$BOX_NAME" ]
-echo "Confirm that VALUE $BOX_VALUE i.e. ($B64_BOX_VALUE) as expected"
VALUE=$(${gcmd} app box info --app-id "$APPID" --name "$BOX_NAME" | grep Value | tr -s ' ' | cut -d" " -f2-)
-[ "$VALUE" = "$B64_BOX_VALUE" ]
+[ "$VALUE" = str:$GREAT_VALUE ]
+
+
+# Confirm that we can still get the list of boxes (need to keep asking
+# until the returned results are for $ROUND)
+retry=0
+while [ $retry -lt 10 ]; do
+ BOX_LIST=$(${gcmd} app box list --app-id "$APPID")
+ ROUND=$(echo "$BOX_LIST" | awk '/Round: / {print $2}')
+ if [[ "$COMMIT" == "$ROUND" ]]; then
+ break
+ fi
+ retry=$((retry + 1))
+ sleep 2
+done
-# Confirm that we can still get the list of boxes
-BOX_LIST=$(${gcmd} app box list --app-id "$APPID")
-EXPECTED="str:box1
-str:with spaces
-str:base64
+EXPECTED="Boxes:
b64:AQIDBA==
-str:great box"
-
-# shellcheck disable=SC2059
-[ "$(printf "$BOX_LIST" | sort)" = "$(printf "$EXPECTED" | sort)" ]
+str:base64
+str:box1
+str:great box
+str:with spaces"
+[[ "$BOX_LIST" = *"$EXPECTED" ]] || false
+
+# Confirm that values are available
+BOX_LIST=$(${gcmd} app box list --app-id "$APPID" --values)
+EXPECTED="Boxes:
+b64:AQIDBA== : $B64_BOX_VALUE
+str:base64 : $B64_BOX_VALUE
+str:box1 : $B64_BOX_VALUE
+str:great box : str:$GREAT_VALUE
+str:with spaces : $B64_BOX_VALUE"
+[[ "$BOX_LIST" = *"$EXPECTED" ]] || false
# Confirm that the account data representation still knows about all the boxes
APP_ACCOUNT_JSON_DUMP=$(${gcmd} account dump --address "$APP_ACCOUNT")
diff --git a/test/scripts/e2e_subs/e2e-app-extra-pages.sh b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
index b2d3cf8709..673ee0f9a2 100755
--- a/test/scripts/e2e_subs/e2e-app-extra-pages.sh
+++ b/test/scripts/e2e_subs/e2e-app-extra-pages.sh
@@ -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 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 || true)
EXP="Created app"
APPID=$(echo $RES | awk '{print $NF}')
if [[ $RES != *"${EXP}"* ]]; then
@@ -92,9 +92,10 @@ if [[ $RES != *"${EXP}"* ]]; then
false
fi
-RES=$(${gcmd} app info --app-id ${APPID} 2>&1 || true)
+RES=$(${gcmd} app info --app-id ${APPID} || true)
PROGHASH="Approval hash: 7356635AKR4FJOOKXXBWNN6HDJ5U3O2YWAOSK6NZBPMOGIQSWCL2N74VT4"
EXTRAPAGES="Extra program pages: 1"
+VERSION1="Program version: 1"
if [[ $RES != *"${PROGHASH}"* ]]; then
date '+app-extra-pages-test FAIL the application approval program hash is incorrect %Y%m%d_%H%M%S'
false
@@ -103,15 +104,19 @@ if [[ $RES != *"${EXTRAPAGES}"* ]]; then
date '+app-extra-pages-test FAIL the application extra pages value is incorrect %Y%m%d_%H%M%S'
false
fi
+if [[ $RES == *version* ]]; then
+ date '+app-extra-pages-test FAIL a new application should not show Program version %Y%m%d_%H%M%S'
+ false
+fi
-RES=$(${gcmd} app update --app-id ${APPID} --approval-prog "${APPR_PROG}" --clear-prog "${SMALL_TEAL_FILE}" --from ${ACCOUNT} 2>&1 || true)
+RES=$(${gcmd} app update --app-id ${APPID} --approval-prog "${APPR_PROG}" --clear-prog "${SMALL_TEAL_FILE}" --from ${ACCOUNT} || true)
EXP="Attempting to update app"
if [[ $RES != *"${EXP}"* ]]; then
date '+app-extra-pages-test FAIL the application update should succeed %Y%m%d_%H%M%S'
false
fi
-RES=$(${gcmd} app info --app-id ${APPID} 2>&1 || true)
+RES=$(${gcmd} app info --app-id ${APPID} || true)
if [[ $RES == *"${PROGHASH}"* ]]; then
date '+app-extra-pages-test FAIL the application approval program should have been updated %Y%m%d_%H%M%S'
false
@@ -120,3 +125,7 @@ if [[ $RES != *"${EXTRAPAGES}"* ]]; then
date '+app-extra-pages-test FAIL the application extra pages value is incorrect after update %Y%m%d_%H%M%S'
false
fi
+if [[ $RES != *"${VERSION1}"* ]]; then
+ date '+app-extra-pages-test FAIL the Program version is not 1 after update %Y%m%d_%H%M%S'
+ false
+fi
diff --git a/test/scripts/e2e_subs/tealprogs/int1.teal b/test/scripts/e2e_subs/tealprogs/int1.teal
new file mode 100644
index 0000000000..0da948ae88
--- /dev/null
+++ b/test/scripts/e2e_subs/tealprogs/int1.teal
@@ -0,0 +1,2 @@
+#pragma version 10
+pushint 1
diff --git a/test/testdata/configs/config-v36.json b/test/testdata/configs/config-v36.json
new file mode 100644
index 0000000000..fec7d7bf6d
--- /dev/null
+++ b/test/testdata/configs/config-v36.json
@@ -0,0 +1,147 @@
+{
+ "Version": 36,
+ "AccountUpdatesStatsInterval": 5000000000,
+ "AccountsRebuildSynchronousMode": 1,
+ "AgreementIncomingBundlesQueueLength": 15,
+ "AgreementIncomingProposalsQueueLength": 50,
+ "AgreementIncomingVotesQueueLength": 20000,
+ "AnnounceParticipationKey": true,
+ "Archival": false,
+ "BaseLoggerDebugLevel": 4,
+ "BlockDBDir": "",
+ "BlockServiceCustomFallbackEndpoints": "",
+ "BlockServiceMemCap": 500000000,
+ "BroadcastConnectionsLimit": -1,
+ "CadaverDirectory": "",
+ "CadaverSizeTarget": 0,
+ "CatchpointDir": "",
+ "CatchpointFileHistoryLength": 365,
+ "CatchpointInterval": 10000,
+ "CatchpointTracking": 0,
+ "CatchupBlockDownloadRetryAttempts": 1000,
+ "CatchupBlockValidateMode": 0,
+ "CatchupFailurePeerRefreshRate": 10,
+ "CatchupGossipBlockFetchTimeoutSec": 4,
+ "CatchupHTTPBlockFetchTimeoutSec": 4,
+ "CatchupLedgerDownloadRetryAttempts": 50,
+ "CatchupParallelBlocks": 16,
+ "ColdDataDir": "",
+ "ConnectionsRateLimitingCount": 60,
+ "ConnectionsRateLimitingWindowSeconds": 1,
+ "CrashDBDir": "",
+ "DNSBootstrapID": ".algorand.network?backup=.algorand.net&dedup=.algorand-.(network|net)",
+ "DNSSecurityFlags": 9,
+ "DeadlockDetection": 0,
+ "DeadlockDetectionThreshold": 30,
+ "DisableAPIAuth": false,
+ "DisableLedgerLRUCache": false,
+ "DisableLocalhostConnectionRateLimit": true,
+ "DisableNetworking": false,
+ "DisableOutgoingConnectionThrottling": false,
+ "EnableAccountUpdatesStats": false,
+ "EnableAgreementReporting": false,
+ "EnableAgreementTimeMetrics": false,
+ "EnableAssembleStats": false,
+ "EnableBlockService": false,
+ "EnableDHTProviders": false,
+ "EnableDeveloperAPI": false,
+ "EnableExperimentalAPI": false,
+ "EnableFollowMode": false,
+ "EnableGossipBlockService": true,
+ "EnableGossipService": true,
+ "EnableIncomingMessageFilter": false,
+ "EnableLedgerService": false,
+ "EnableMetricReporting": false,
+ "EnableNetDevMetrics": false,
+ "EnableOutgoingNetworkMessageFiltering": true,
+ "EnableP2P": false,
+ "EnableP2PHybridMode": false,
+ "EnablePingHandler": true,
+ "EnablePrivateNetworkAccessHeader": false,
+ "EnableProcessBlockStats": false,
+ "EnableProfiler": false,
+ "EnableRequestLogger": false,
+ "EnableRuntimeMetrics": false,
+ "EnableTopAccountsReporting": false,
+ "EnableTxBacklogAppRateLimiting": true,
+ "EnableTxBacklogRateLimiting": true,
+ "EnableTxnEvalTracer": false,
+ "EnableUsageLog": false,
+ "EnableVerbosedTransactionSyncLogging": false,
+ "EnableVoteCompression": true,
+ "EndpointAddress": "127.0.0.1:0",
+ "FallbackDNSResolverAddress": "",
+ "ForceFetchTransactions": false,
+ "ForceRelayMessages": false,
+ "GoMemLimit": 0,
+ "GossipFanout": 4,
+ "HeartbeatUpdateInterval": 600,
+ "HotDataDir": "",
+ "IncomingConnectionsLimit": 2400,
+ "IncomingMessageFilterBucketCount": 5,
+ "IncomingMessageFilterBucketSize": 512,
+ "LedgerSynchronousMode": 2,
+ "LogArchiveDir": "",
+ "LogArchiveMaxAge": "",
+ "LogArchiveName": "node.archive.log",
+ "LogFileDir": "",
+ "LogSizeLimit": 1073741824,
+ "MaxAPIBoxPerApplication": 100000,
+ "MaxAPIResourcesPerAccount": 100000,
+ "MaxAcctLookback": 4,
+ "MaxBlockHistoryLookback": 0,
+ "MaxCatchpointDownloadDuration": 43200000000000,
+ "MaxConnectionsPerIP": 8,
+ "MinCatchpointFileDownloadBytesPerSecond": 20480,
+ "NetAddress": "",
+ "NetworkMessageTraceServer": "",
+ "NetworkProtocolVersion": "",
+ "NodeExporterListenAddress": ":9100",
+ "NodeExporterPath": "./node_exporter",
+ "OptimizeAccountsDatabaseOnStartup": false,
+ "OutgoingMessageFilterBucketCount": 3,
+ "OutgoingMessageFilterBucketSize": 128,
+ "P2PHybridIncomingConnectionsLimit": 1200,
+ "P2PHybridNetAddress": "",
+ "P2PPersistPeerID": false,
+ "P2PPrivateKeyLocation": "",
+ "ParticipationKeysRefreshInterval": 60000000000,
+ "PeerConnectionsUpdateInterval": 3600,
+ "PeerPingPeriodSeconds": 0,
+ "PriorityPeers": {},
+ "ProposalAssemblyTime": 500000000,
+ "PublicAddress": "",
+ "ReconnectTime": 60000000000,
+ "ReservedFDs": 256,
+ "RestConnectionsHardLimit": 2048,
+ "RestConnectionsSoftLimit": 1024,
+ "RestReadTimeoutSeconds": 15,
+ "RestWriteTimeoutSeconds": 120,
+ "RunHosted": false,
+ "StateproofDir": "",
+ "StorageEngine": "sqlite",
+ "SuggestedFeeBlockHistory": 3,
+ "SuggestedFeeSlidingWindowSize": 50,
+ "TLSCertFile": "",
+ "TLSKeyFile": "",
+ "TelemetryToLog": true,
+ "TrackerDBDir": "",
+ "TransactionSyncDataExchangeRate": 0,
+ "TransactionSyncSignificantMessageThreshold": 0,
+ "TxBacklogAppRateLimitingCountERLDrops": false,
+ "TxBacklogAppTxPerSecondRate": 100,
+ "TxBacklogAppTxRateLimiterMaxSize": 1048576,
+ "TxBacklogRateLimitingCongestionPct": 50,
+ "TxBacklogReservedCapacityPerPeer": 20,
+ "TxBacklogServiceRateWindowSeconds": 10,
+ "TxBacklogSize": 26000,
+ "TxIncomingFilterMaxSize": 500000,
+ "TxIncomingFilteringFlags": 1,
+ "TxPoolExponentialIncreaseFactor": 2,
+ "TxPoolSize": 75000,
+ "TxSyncIntervalSeconds": 60,
+ "TxSyncServeResponseSize": 1000000,
+ "TxSyncTimeoutSeconds": 30,
+ "UseXForwardedForAddressField": "",
+ "VerifiedTranscationsCacheSize": 150000
+}
diff --git a/tools/block-generator/go.mod b/tools/block-generator/go.mod
index 343f91c7bc..a391443cbd 100644
--- a/tools/block-generator/go.mod
+++ b/tools/block-generator/go.mod
@@ -2,7 +2,7 @@ module github.com/algorand/go-algorand/tools/block-generator
replace github.com/algorand/go-algorand => ../..
-go 1.23
+go 1.23.0
toolchain go1.23.3
@@ -169,13 +169,13 @@ require (
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
- golang.org/x/crypto v0.31.0 // indirect
+ golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mod v0.22.0 // indirect
- golang.org/x/net v0.33.0 // indirect
- golang.org/x/sync v0.10.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/net v0.36.0 // indirect
+ golang.org/x/sync v0.11.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.27.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
diff --git a/tools/block-generator/go.sum b/tools/block-generator/go.sum
index 4f27558584..67095905ea 100644
--- a/tools/block-generator/go.sum
+++ b/tools/block-generator/go.sum
@@ -677,8 +677,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
@@ -727,8 +727,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
-golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
+golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -744,8 +744,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -782,8 +782,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -801,8 +801,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -901,8 +901,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
-pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
-pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
+pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
+pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
diff --git a/tools/debug/carpenter/main.go b/tools/debug/carpenter/main.go
index 87a5b79f09..e1a5647d66 100644
--- a/tools/debug/carpenter/main.go
+++ b/tools/debug/carpenter/main.go
@@ -321,8 +321,6 @@ func outputTableFormat(out string, event logspec.Event, columns []string, colPos
outputRow(bodyTabWriter, output)
}
}
-
- return
}
func outputRow(tabWriter *tabwriter.Writer, rowContent string) {
diff --git a/tools/debug/coroner/main.go b/tools/debug/coroner/main.go
index 15a6fe2657..f199a62dea 100644
--- a/tools/debug/coroner/main.go
+++ b/tools/debug/coroner/main.go
@@ -109,6 +109,4 @@ func main() {
if commitHash != version.GetCommitHash() {
log.Printf("coroner: cadaver version mismatches coroner version:\n(%s (cadaver) != %s (coroner))\n", commitHash, version.GetCommitHash())
}
-
- return
}
diff --git a/tools/network/dnssec/testHarness.go b/tools/network/dnssec/testHarness.go
index 3d3edff768..e65e61d74b 100644
--- a/tools/network/dnssec/testHarness.go
+++ b/tools/network/dnssec/testHarness.go
@@ -345,7 +345,7 @@ func (r *testResolver) updateDNSKeyRecord(zone string, key *dns.DNSKEY, sk crypt
ok := false
for _, ds := range dss {
newDS := key.ToDS(ds.DigestType)
- if strings.ToLower(newDS.Digest) == strings.ToLower(ds.Digest) {
+ if strings.EqualFold(newDS.Digest, ds.Digest) {
ok = true
break
}
diff --git a/tools/network/resolver.go b/tools/network/resolver.go
index 104aeed71d..2c159fc644 100644
--- a/tools/network/resolver.go
+++ b/tools/network/resolver.go
@@ -109,11 +109,10 @@ func (p *Resolver) effectiveResolver() ResolverIf {
// SetFallbackResolverAddress sets preferred DNS server address
func (p *Resolver) SetFallbackResolverAddress(fallbackDNSResolverAddress net.IPAddr) {
p.dnsAddress = fallbackDNSResolverAddress
- return
}
-func (p *Resolver) resolverDial(ctx context.Context, network, address string) (net.Conn, error) {
+func (p *Resolver) resolverDial(ctx context.Context, network, _address string) (net.Conn, error) {
// override the default address with our own.
- address = p.EffectiveResolverDNS() + dnsPortSuffix
+ address := p.EffectiveResolverDNS() + dnsPortSuffix
return (&net.Dialer{}).DialContext(ctx, network, address)
}
diff --git a/tools/network/telemetryURIUpdateService.go b/tools/network/telemetryURIUpdateService.go
index 6331c57044..6175adf3f4 100644
--- a/tools/network/telemetryURIUpdateService.go
+++ b/tools/network/telemetryURIUpdateService.go
@@ -61,7 +61,7 @@ func (t *telemetryURIUpdater) Start() {
updateTelemetryURI := func() {
endpointURL := t.lookupTelemetryURL()
- if endpointURL != nil && endpointURL.String() != t.log.GetTelemetryURI() && false == t.cfg.DisableNetworking {
+ if endpointURL != nil && endpointURL.String() != t.log.GetTelemetryURI() && !t.cfg.DisableNetworking {
err := t.log.UpdateTelemetryURI(endpointURL.String())
if err != nil {
t.log.Warnf("Unable to update telemetry URI to '%s' : %v", endpointURL.String(), err)
diff --git a/tools/x-repo-types/Makefile b/tools/x-repo-types/Makefile
index 900b492716..ddb2df0ede 100644
--- a/tools/x-repo-types/Makefile
+++ b/tools/x-repo-types/Makefile
@@ -1,7 +1,7 @@
all: clean goal-v-sdk goal-v-spv
clean:
- rm x-repo-types
+ rm -f x-repo-types
x-repo-types:
go build
@@ -11,44 +11,44 @@ x-repo-types:
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: x-repo-types
- x-repo-types --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-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "LedgerStateDelta"
goal-v-sdk-genesis: x-repo-types
- x-repo-types --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-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "Genesis"
goal-v-sdk-block: x-repo-types
- x-repo-types --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-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "Block"
goal-v-sdk-blockheader: x-repo-types
- x-repo-types --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-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "BlockHeader"
goal-v-sdk-consensus: x-repo-types
- x-repo-types --x-package "github.com/algorand/go-algorand/config" \
+ ./x-repo-types --x-package "github.com/algorand/go-algorand/config" \
--x-type "ConsensusParams" \
- --y-branch "develop" \
+ --y-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/protocol/config" \
--y-type "ConsensusParams"
goal-v-sdk-stateproof: x-repo-types
- x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \
+ ./x-repo-types --x-package "github.com/algorand/go-algorand/crypto/stateproof" \
--x-type "StateProof" \
- --y-branch "develop" \
+ --y-branch "main" \
--y-package "github.com/algorand/go-algorand-sdk/v2/types" \
--y-type "StateProof"
@@ -57,7 +57,7 @@ goal-v-sdk-stateproof: x-repo-types
goal-v-spv: goal-v-spv-stateproof
goal-v-spv-stateproof:
- x-repo-types --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"
@@ -65,7 +65,7 @@ goal-v-spv-stateproof:
# reset typeAnalyzer/main.go for passing checks:
reset-dummy-main:
- x-repo-types --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/go.mod b/tools/x-repo-types/go.mod
index 094c52ea8c..d6c97063c9 100644
--- a/tools/x-repo-types/go.mod
+++ b/tools/x-repo-types/go.mod
@@ -1,6 +1,6 @@
module github.com/algorand/go-algorand/tools/x-repo-types
-go 1.23
+go 1.23.0
toolchain go1.23.3
diff --git a/tools/x-repo-types/xrt.go b/tools/x-repo-types/xrt.go
index 52cdadfd41..c7ff0e43d9 100644
--- a/tools/x-repo-types/xrt.go
+++ b/tools/x-repo-types/xrt.go
@@ -20,7 +20,6 @@ import (
"bytes"
"errors"
"fmt"
- "io/ioutil"
"log"
"os"
"os/exec"
@@ -171,17 +170,17 @@ func tearDown(fileBackups map[string]string) error {
}
func backupFile(src string) (string, error) {
- content, err := ioutil.ReadFile(src)
+ content, err := os.ReadFile(src)
if err != nil {
return "", err
}
- tmpFile, err := ioutil.TempFile("", "backup-*")
+ tmpFile, err := os.CreateTemp("", "backup-*")
if err != nil {
return "", err
}
- err = ioutil.WriteFile(tmpFile.Name(), content, 0644)
+ err = os.WriteFile(tmpFile.Name(), content, 0644)
if err != nil {
return "", err
}
@@ -210,12 +209,12 @@ func restoreFile(src, dst string) error {
return err
}
- content, err := ioutil.ReadFile(src)
+ content, err := os.ReadFile(src)
if err != nil {
return err
}
- err = ioutil.WriteFile(dst, content, dstFileInfo.Mode())
+ err = os.WriteFile(dst, content, dstFileInfo.Mode())
if err != nil {
return err
}
diff --git a/tools/x-repo-types/xrt_test.go b/tools/x-repo-types/xrt_test.go
index 6681100a77..624e17d683 100644
--- a/tools/x-repo-types/xrt_test.go
+++ b/tools/x-repo-types/xrt_test.go
@@ -42,7 +42,7 @@ func TestCrossRepoTypes(t *testing.T) {
xBranch: "",
xType: "StateDelta",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
yType: "LedgerStateDelta",
},
{
@@ -50,7 +50,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/data/bookkeeping",
xType: "Genesis",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
yType: "Genesis",
skip: true,
skipReason: `LEVEL 3 of goal basics.AccountData has 12 fields missing from SDK types.Account`,
@@ -60,7 +60,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/data/bookkeeping",
xType: "Block",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
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`,
@@ -70,7 +70,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/data/transactions",
xType: "EvalDelta",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
yType: "EvalDelta",
},
{
@@ -78,7 +78,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/config",
xType: "ConsensusParams",
yPkg: "github.com/algorand/go-algorand-sdk/v2/protocol/config",
- yBranch: "develop",
+ yBranch: "main",
yType: "ConsensusParams",
},
{
@@ -86,7 +86,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/data/bookkeeping",
xType: "BlockHeader",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
yType: "BlockHeader",
},
{
@@ -94,7 +94,7 @@ func TestCrossRepoTypes(t *testing.T) {
xPkg: "github.com/algorand/go-algorand/crypto/stateproof",
xType: "StateProof",
yPkg: "github.com/algorand/go-algorand-sdk/v2/types",
- yBranch: "develop",
+ yBranch: "main",
yType: "StateProof",
},
{
diff --git a/util/codecs/json.go b/util/codecs/json.go
index 3b48c9b094..301d542e6e 100644
--- a/util/codecs/json.go
+++ b/util/codecs/json.go
@@ -105,12 +105,12 @@ func WriteNonDefaultValues(writer io.Writer, object, defaultObject interface{},
valName := extractValueName(line)
if valName == "" {
if !inContent {
- if strings.Index(line, "{") < 0 {
+ if !strings.Contains(line, "{") {
return fmt.Errorf("error processing serialized object - we don't support nested types: %s", line)
}
inContent = true
} else {
- if strings.Index(line, "}") < 0 {
+ if !strings.Contains(line, "}") {
return fmt.Errorf("error processing serialized object - we don't support nested types: %s", line)
}
inContent = false
diff --git a/util/metrics/reporter.go b/util/metrics/reporter.go
index 3e93b7ae37..db9ae5f964 100644
--- a/util/metrics/reporter.go
+++ b/util/metrics/reporter.go
@@ -248,5 +248,4 @@ func (reporter *MetricReporter) tryInvokeNodeExporter(ctx context.Context) {
// logging.Base().Debugf("Node exporter process ended : %v", status)
(*proc) = nil
}(&reporter.neProcess)
- return
}
diff --git a/util/set.go b/util/set.go
index e6ea6a6927..55fb2be90a 100644
--- a/util/set.go
+++ b/util/set.go
@@ -32,7 +32,7 @@ func (s Set[T]) Add(elems ...T) Set[T] {
// MakeSet constructs a set instance directly from elements.
func MakeSet[T comparable](elems ...T) Set[T] {
- return make(Set[T]).Add(elems...)
+ return make(Set[T], len(elems)).Add(elems...)
}
// Empty returns true if the set is empty.
diff --git a/util/timers/monotonic.go b/util/timers/monotonic.go
index 6607d1a32f..0ea4f7ec04 100644
--- a/util/timers/monotonic.go
+++ b/util/timers/monotonic.go
@@ -64,7 +64,7 @@ func (m *Monotonic[TimeoutType]) TimeoutAt(delta time.Duration, timeoutType Time
tmt = timeout{delta: delta}
target := m.zero.Add(delta)
- left := target.Sub(time.Now())
+ left := time.Until(target)
if left < 0 {
ch := make(chan time.Time)
close(ch)
diff --git a/util/util.go b/util/util.go
index 6aa4ceda8a..74c8d2e219 100644
--- a/util/util.go
+++ b/util/util.go
@@ -36,6 +36,24 @@ func GetFdLimits() (soft uint64, hard uint64, err error) {
return rLimit.Cur, rLimit.Max, nil
}
+// RaiseFdSoftLimit raises the file descriptors soft limit to the specified value,
+// or leave it unchanged if the value is less than the current.
+func RaiseFdSoftLimit(newLimit uint64) error {
+ soft, hard, err := GetFdLimits()
+ if err != nil {
+ return fmt.Errorf("RaiseFdSoftLimit() err: %w", err)
+ }
+ if newLimit <= soft {
+ // Current limit is sufficient; no need to change it.
+ return nil
+ }
+ if newLimit > hard {
+ // New limit exceeds the hard limit; set it to the hard limit.
+ newLimit = hard
+ }
+ return SetFdSoftLimit(newLimit)
+}
+
// SetFdSoftLimit sets a new file descriptors soft limit.
func SetFdSoftLimit(newLimit uint64) error {
var rLimit syscall.Rlimit
@@ -72,3 +90,8 @@ func GetCurrentProcessTimes() (utime int64, stime int64, err error) {
}
return
}
+
+// GetTotalMemory gets total system memory
+func GetTotalMemory() uint64 {
+ return getTotalMemory()
+}
diff --git a/util/util_darwin.go b/util/util_darwin.go
new file mode 100644
index 0000000000..7c08acdefe
--- /dev/null
+++ b/util/util_darwin.go
@@ -0,0 +1,34 @@
+// Copyright (C) 2019-2025 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 (
+ "syscall"
+ "unsafe"
+)
+
+func getTotalMemory() uint64 {
+ out, err := syscall.Sysctl("hw.memsize")
+ if err != nil {
+ return 0
+ }
+ b := []byte(out)
+ if len(b) < 8 {
+ b = append(b, 0)
+ }
+ return *(*uint64)(unsafe.Pointer(&b[0]))
+}
diff --git a/util/util_linux.go b/util/util_linux.go
new file mode 100644
index 0000000000..f958ebdf5c
--- /dev/null
+++ b/util/util_linux.go
@@ -0,0 +1,31 @@
+// Copyright (C) 2019-2025 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 (
+ "syscall"
+)
+
+func getTotalMemory() uint64 {
+ var si syscall.Sysinfo_t
+ err := syscall.Sysinfo(&si)
+ if err != nil {
+ return 0
+ }
+ // support 32-bit systems where Totalram is uint32
+ return uint64(si.Totalram) * uint64(si.Unit)
+}
diff --git a/util/util_test.go b/util/util_test.go
new file mode 100644
index 0000000000..f33b5c5f95
--- /dev/null
+++ b/util/util_test.go
@@ -0,0 +1,32 @@
+// Copyright (C) 2019-2025 Algorand, Inc.
+// This file is part of go-algorand
+//
+// go-algorand is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// go-algorand is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with go-algorand. If not, see .
+
+package util
+
+import (
+ "testing"
+
+ "github.com/algorand/go-algorand/test/partitiontest"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetTotalMemory(t *testing.T) {
+ partitiontest.PartitionTest(t)
+ t.Parallel()
+
+ mem := GetTotalMemory()
+ require.Greater(t, mem, uint64(0))
+}
diff --git a/util/util_windows.go b/util/util_windows.go
index 811240eb8c..392a5698e5 100644
--- a/util/util_windows.go
+++ b/util/util_windows.go
@@ -21,6 +21,7 @@ import (
"math"
"syscall"
"time"
+ "unsafe"
)
/* misc */
@@ -30,6 +31,11 @@ func GetFdLimits() (soft uint64, hard uint64, err error) {
return math.MaxUint64, math.MaxUint64, nil // syscall.RLIM_INFINITY
}
+// RaiseFdSoftLimit raises the file descriptors soft limit.
+func RaiseFdSoftLimit(_ uint64) error {
+ return nil
+}
+
// SetFdSoftLimit sets a new file descriptors soft limit.
func SetFdSoftLimit(_ uint64) error {
return nil
@@ -69,3 +75,39 @@ func filetimeToDuration(ft *syscall.Filetime) time.Duration {
n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
return time.Duration(n * 100)
}
+
+// GetTotalMemory gets total system memory on Windows
+func GetTotalMemory() uint64 {
+ var memoryStatusEx MemoryStatusEx
+ memoryStatusEx.dwLength = uint32(unsafe.Sizeof(memoryStatusEx))
+
+ if err := globalMemoryStatusEx(&memoryStatusEx); err != nil {
+ return 0
+ }
+ return memoryStatusEx.ullTotalPhys
+}
+
+type MemoryStatusEx struct {
+ dwLength uint32
+ dwMemoryLoad uint32
+ ullTotalPhys uint64
+ ullAvailPhys uint64
+ ullTotalPageFile uint64
+ ullAvailPageFile uint64
+ ullTotalVirtual uint64
+ ullAvailVirtual uint64
+ ullAvailExtendedVirtual uint64
+}
+
+var (
+ modkernel32 = syscall.NewLazyDLL("kernel32.dll")
+ procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
+)
+
+func globalMemoryStatusEx(memoryStatusEx *MemoryStatusEx) error {
+ ret, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(memoryStatusEx)))
+ if ret == 0 {
+ return syscall.GetLastError()
+ }
+ return nil
+}