From 49086e85495a0021b40029131ac4a7d91c08e899 Mon Sep 17 00:00:00 2001 From: nicholasguoalgorand <67928479+nicholasguoalgorand@users.noreply.github.com> Date: Mon, 8 Aug 2022 20:13:18 -0700 Subject: [PATCH 01/24] tests: fix TestAccountsCanSendMoney more (#4374) --- .../features/transactions/sendReceive_test.go | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index 9b2375bb1a..b21ec529f8 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -139,24 +139,25 @@ func testAccountsCanSendMoney(t *testing.T, templatePath string, numberOfSends i curStatus, _ := pongClient.Status() curRound := curStatus.LastRound - if waitForTransaction { - fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pongClient.DataDir())) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) - } + fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pongClient.DataDir())) + fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) + fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) - pingBalance, _ = fixture.GetBalanceAndRound(pingAccount) - pongBalance, _ = fixture.GetBalanceAndRound(pongAccount) + pingBalance, err = pongClient.GetBalance(pingAccount) + a.NoError(err) + pongBalance, err = pongClient.GetBalance(pongAccount) + a.NoError(err) a.True(expectedPingBalance <= pingBalance, "ping balance is different than expected.") a.True(expectedPongBalance <= pongBalance, "pong balance is different than expected.") - if waitForTransaction { - fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pingClient.DataDir())) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) - } + fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pingClient.DataDir())) + fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) + fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) - pingBalance, _ = fixture.GetBalanceAndRound(pingAccount) - pongBalance, _ = fixture.GetBalanceAndRound(pongAccount) + pingBalance, err = pingClient.GetBalance(pingAccount) + a.NoError(err) + pongBalance, err = pingClient.GetBalance(pongAccount) + a.NoError(err) a.True(expectedPingBalance <= pingBalance, "ping balance is different than expected.") a.True(expectedPongBalance <= pongBalance, "pong balance is different than expected.") } From 2bc55c06f4dacf247b93a15f47530f57b4925d28 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Tue, 9 Aug 2022 06:31:40 +0300 Subject: [PATCH 02/24] Algod: State Proofs (#4226) Enable state proofs to allow external parties to efficiently validate Algorand stake. Summary of changes: * make state proof verifier SNARK friendly * relaxing the Merkle signature scheme ephemerality * define lightBlockHeaders * define Algorand's state as a commitment on the lightBlockHeaders within a state proof interval * limit the resources (memory and network bandwidth) if state proofs chain stalls Co-authored-by: Jonathan Weiss <85506383+algonathan@users.noreply.github.com> Co-authored-by: Or Aharonee Co-authored-by: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Co-authored-by: Almog Tal <107349997+almog-t@users.noreply.github.com> --- Makefile | 2 +- agreement/msgp_gen.go | 538 +-- catchup/catchpointService.go | 19 + catchup/service_test.go | 39 +- cmd/algokey/part.go | 5 +- compactcert/builder.go | 386 -- compactcert/signer.go | 175 - compactcert/worker_test.go | 525 --- components/mocks/mockParticipationRegistry.go | 11 +- config/config.go | 6 +- config/consensus.go | 86 +- config/consensus_test.go | 8 +- crypto/compactcert/bigfloat.go | 186 - crypto/compactcert/bigfloat_test.go | 167 - crypto/compactcert/builder.go | 247 -- crypto/compactcert/common.go | 117 - crypto/compactcert/common_test.go | 131 - crypto/compactcert/const.go | 32 - crypto/compactcert/verifier.go | 115 - crypto/falconWrapper.go | 19 +- crypto/falconWrapper_test.go | 21 +- crypto/merklearray/merkle.go | 1 + crypto/merklearray/proof.go | 34 +- crypto/merklearray/proof_test.go | 62 +- .../merklesignature/committablePublicKeys.go | 20 +- .../committablePublicKeys_test.go | 2 +- crypto/merklesignature/const.go | 29 +- crypto/merklesignature/kats_test.go | 115 + crypto/merklesignature/keysBuilder_test.go | 2 + .../merklesignature/merkleSignatureScheme.go | 170 +- .../merkleSignatureScheme_test.go | 255 +- crypto/merklesignature/msgp_gen.go | 193 +- crypto/merklesignature/msgp_gen_test.go | 60 + .../persistentMerkleSignatureScheme.go | 8 +- .../persistentMerkleSignatureScheme_test.go | 15 +- crypto/merklesignature/posdivs.go | 22 +- crypto/merklesignature/posdivs_test.go | 71 +- crypto/stateproof/builder.go | 260 ++ .../builder_test.go | 459 ++- crypto/stateproof/coinGenerator.go | 125 + crypto/stateproof/coinGenerator_test.go | 186 + .../committableSignatureSlot.go | 25 +- .../committableSignatureSlot_test.go | 16 +- crypto/stateproof/const.go | 37 + .../{compactcert => stateproof}/msgp_gen.go | 825 ++-- .../msgp_gen_test.go | 102 +- crypto/{compactcert => stateproof}/structs.go | 77 +- crypto/stateproof/verifier.go | 154 + crypto/stateproof/verifier_test.go | 180 + crypto/stateproof/weights.go | 193 + crypto/stateproof/weights_test.go | 226 ++ daemon/algod/api/algod.oas2.json | 209 +- daemon/algod/api/algod.oas3.yml | 218 +- daemon/algod/api/client/restClient.go | 10 +- .../algod/api/server/lib/bundledSpecInject.go | 3326 ++++++++--------- .../algod/api/server/v1/handlers/handlers.go | 21 +- daemon/algod/api/server/v2/errors.go | 3 +- .../api/server/v2/generated/private/routes.go | 302 +- .../api/server/v2/generated/private/types.go | 29 + .../algod/api/server/v2/generated/routes.go | 474 ++- daemon/algod/api/server/v2/generated/types.go | 29 + daemon/algod/api/server/v2/handlers.go | 95 +- .../server/v2/test/handlers_resources_test.go | 21 + .../algod/api/server/v2/test/handlers_test.go | 221 +- daemon/algod/api/server/v2/test/helpers.go | 12 +- daemon/algod/api/spec/v1/model.go | 37 +- daemon/algod/api/swagger.json | 77 +- data/account/msgp_gen.go | 38 - data/account/participation.go | 15 +- data/account/participationRegistry.go | 97 +- data/account/participationRegistry_test.go | 273 +- data/account/participation_test.go | 2 +- data/account/registeryDbOps.go | 40 + data/accountManager.go | 11 +- data/accountManager_test.go | 74 + ...{ccertpart.go => stateProofParticipant.go} | 16 +- data/basics/userBalance.go | 4 +- data/basics/userBalance_test.go | 2 +- data/bookkeeping/block.go | 39 +- data/bookkeeping/block_test.go | 2 +- data/bookkeeping/lightBlockHeader.go | 61 + data/bookkeeping/lightBlockHeader_test.go | 64 + data/bookkeeping/msgp_gen.go | 865 +++-- data/bookkeeping/msgp_gen_test.go | 120 +- data/pools/transactionPool.go | 61 +- data/pools/transactionPool_test.go | 228 ++ data/stateproofmsg/message.go | 50 + data/stateproofmsg/msgp_gen.go | 257 ++ data/stateproofmsg/msgp_gen_test.go | 75 + data/transactions/keyreg.go | 2 +- data/transactions/logic/eval.go | 2 +- data/transactions/logic/evalAppTxn_test.go | 4 +- data/transactions/msgp_gen.go | 456 +-- data/transactions/msgp_gen_test.go | 120 +- .../{compactcert.go => stateproof.go} | 41 +- data/transactions/transaction.go | 40 +- data/transactions/transaction_test.go | 134 +- data/transactions/verify/txn.go | 9 +- data/transactions/verify/txn_test.go | 30 +- data/txntest/txn.go | 17 +- gen/generate.go | 2 +- go.mod | 2 +- go.sum | 4 +- ledger/accountdb.go | 30 +- ledger/accountdb_test.go | 12 +- ledger/acctonline.go | 89 +- ledger/acctonline_test.go | 218 +- ledger/acctupdates.go | 4 +- ledger/acctupdates_test.go | 12 +- ledger/apply/apply.go | 8 + ledger/apply/keyreg_test.go | 2 +- ledger/apply/stateproof.go | 71 + ledger/blockHeaderCache.go | 86 + ledger/blockHeaderCache_test.go | 95 + ledger/catchpointwriter_test.go | 4 +- ledger/evalindexer.go | 6 +- ledger/internal/appcow_test.go | 4 +- ledger/internal/compactcert.go | 179 - ledger/internal/compactcert_test.go | 176 - ledger/internal/cow.go | 24 +- ledger/internal/cow_test.go | 6 +- ledger/internal/eval.go | 180 +- ledger/internal/eval_test.go | 103 +- ledger/internal/prefetcher/prefetcher.go | 2 +- .../prefetcher/prefetcher_alignment_test.go | 12 +- ledger/internal/prefetcher/prefetcher_test.go | 2 +- ledger/ledger.go | 38 +- ledger/ledger_test.go | 433 ++- ledger/ledgercore/accountdata.go | 2 +- ledger/ledgercore/onlineacct.go | 4 +- ledger/ledgercore/statedelta.go | 10 +- ledger/ledgercore/votersForRound.go | 62 +- ledger/testing/randomAccounts.go | 2 +- ledger/voters.go | 162 +- ledger/voters_test.go | 231 ++ libgoal/libgoal.go | 15 +- libgoal/transactions.go | 6 +- logging/telemetryspec/metric.go | 44 + logging/telemetryspec/metric_test.go | 41 +- network/wsNetwork.go | 6 +- node/node.go | 26 +- protocol/hash.go | 21 +- protocol/msgp_gen.go | 108 +- protocol/{compactcerts.go => stateproof.go} | 31 +- protocol/tags.go | 2 +- protocol/txntype.go | 4 +- {compactcert => stateproof}/abstractions.go | 15 +- stateproof/builder.go | 463 +++ {compactcert => stateproof}/db.go | 24 +- {compactcert => stateproof}/db_test.go | 2 +- {compactcert => stateproof}/msgp_gen.go | 44 +- {compactcert => stateproof}/msgp_gen_test.go | 2 +- stateproof/recovery.go | 42 + stateproof/signer.go | 177 + stateproof/stateproofMessageGenerator.go | 146 + stateproof/stateproofMessageGenerator_test.go | 405 ++ stateproof/verify/stateproof.go | 179 + stateproof/verify/stateproof_test.go | 168 + {compactcert => stateproof}/worker.go | 42 +- stateproof/worker_test.go | 1290 +++++++ .../features/compactcert/compactcert_test.go | 165 - .../onlineOfflineParticipation_test.go | 4 +- .../features/stateproofs/stateproofs_test.go | 1127 ++++++ .../features/transactions/proof_test.go | 26 +- test/e2e-go/restAPI/restClient_test.go | 7 +- ...st.go => stateproof_participation_test.go} | 0 test/framework/fixtures/libgoalFixture.go | 34 + test/testdata/nettemplates/CompactCert.json | 41 - .../nettemplates/RichAccountStateProof.json | 31 + test/testdata/nettemplates/StateProof.json | 31 + .../nettemplates/StateProofMultiWallets.json | 63 + tools/debug/algodump/main.go | 2 +- 172 files changed, 14433 insertions(+), 7770 deletions(-) delete mode 100644 compactcert/builder.go delete mode 100644 compactcert/signer.go delete mode 100644 compactcert/worker_test.go delete mode 100644 crypto/compactcert/bigfloat.go delete mode 100644 crypto/compactcert/bigfloat_test.go delete mode 100644 crypto/compactcert/builder.go delete mode 100644 crypto/compactcert/common.go delete mode 100644 crypto/compactcert/common_test.go delete mode 100644 crypto/compactcert/const.go delete mode 100644 crypto/compactcert/verifier.go create mode 100644 crypto/merklesignature/kats_test.go create mode 100644 crypto/stateproof/builder.go rename crypto/{compactcert => stateproof}/builder_test.go (51%) create mode 100644 crypto/stateproof/coinGenerator.go create mode 100644 crypto/stateproof/coinGenerator_test.go rename crypto/{compactcert => stateproof}/committableSignatureSlot.go (77%) rename crypto/{compactcert => stateproof}/committableSignatureSlot_test.go (85%) create mode 100644 crypto/stateproof/const.go rename crypto/{compactcert => stateproof}/msgp_gen.go (57%) rename crypto/{compactcert => stateproof}/msgp_gen_test.go (68%) rename crypto/{compactcert => stateproof}/structs.go (52%) create mode 100644 crypto/stateproof/verifier.go create mode 100644 crypto/stateproof/verifier_test.go create mode 100644 crypto/stateproof/weights.go create mode 100644 crypto/stateproof/weights_test.go rename data/basics/{ccertpart.go => stateProofParticipant.go} (87%) create mode 100644 data/bookkeeping/lightBlockHeader.go create mode 100644 data/bookkeeping/lightBlockHeader_test.go create mode 100644 data/stateproofmsg/message.go create mode 100644 data/stateproofmsg/msgp_gen.go create mode 100644 data/stateproofmsg/msgp_gen_test.go rename data/transactions/{compactcert.go => stateproof.go} (58%) create mode 100644 ledger/apply/stateproof.go create mode 100644 ledger/blockHeaderCache.go create mode 100644 ledger/blockHeaderCache_test.go delete mode 100644 ledger/internal/compactcert.go delete mode 100644 ledger/internal/compactcert_test.go create mode 100644 ledger/voters_test.go rename protocol/{compactcerts.go => stateproof.go} (52%) rename {compactcert => stateproof}/abstractions.go (78%) create mode 100644 stateproof/builder.go rename {compactcert => stateproof}/db.go (78%) rename {compactcert => stateproof}/db_test.go (99%) rename {compactcert => stateproof}/msgp_gen.go (82%) rename {compactcert => stateproof}/msgp_gen_test.go (98%) create mode 100644 stateproof/recovery.go create mode 100644 stateproof/signer.go create mode 100644 stateproof/stateproofMessageGenerator.go create mode 100644 stateproof/stateproofMessageGenerator_test.go create mode 100644 stateproof/verify/stateproof.go create mode 100644 stateproof/verify/stateproof_test.go rename {compactcert => stateproof}/worker.go (78%) create mode 100644 stateproof/worker_test.go delete mode 100644 test/e2e-go/features/compactcert/compactcert_test.go create mode 100644 test/e2e-go/features/stateproofs/stateproofs_test.go rename test/e2e-go/upgrades/{stateproof_test.go => stateproof_participation_test.go} (100%) delete mode 100644 test/testdata/nettemplates/CompactCert.json create mode 100644 test/testdata/nettemplates/RichAccountStateProof.json create mode 100644 test/testdata/nettemplates/StateProof.json create mode 100644 test/testdata/nettemplates/StateProofMultiWallets.json diff --git a/Makefile b/Makefile index 1c671945c0..0860d71f7d 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ GOLDFLAGS := $(GOLDFLAGS_BASE) \ UNIT_TEST_SOURCES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && go list ./... | grep -v /go-algorand/test/ )) ALGOD_API_PACKAGES := $(sort $(shell GOPATH=$(GOPATH) && GO111MODULE=off && cd daemon/algod/api; go list ./... )) -MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/compactcert ./data/basics ./data/transactions ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./node ./ledger ./ledger/ledgercore ./compactcert ./data/account ./daemon/algod/api/spec/v2 +MSGP_GENERATE := ./protocol ./protocol/test ./crypto ./crypto/merklearray ./crypto/merklesignature ./crypto/stateproof ./data/basics ./data/transactions ./data/stateproofmsg ./data/committee ./data/bookkeeping ./data/hashable ./agreement ./rpcs ./node ./ledger ./ledger/ledgercore ./stateproof ./data/account ./daemon/algod/api/spec/v2 default: build diff --git a/agreement/msgp_gen.go b/agreement/msgp_gen.go index 4f2252b875..e5508cd479 100644 --- a/agreement/msgp_gen.go +++ b/agreement/msgp_gen.go @@ -1358,87 +1358,87 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0004Len := uint32(29) var zb0004Mask uint64 /* 37 bits */ - if len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert) == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x40 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { zb0004Len-- zb0004Mask |= 0x100 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { zb0004Len-- zb0004Mask |= 0x200 } - if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { + if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400 } - if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { zb0004Len-- zb0004Mask |= 0x4000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { + if (*z).unauthenticatedProposal.OriginalPeriod == 0 { zb0004Len-- zb0004Mask |= 0x8000 } - if (*z).unauthenticatedProposal.OriginalPeriod == 0 { + if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x10000 } - if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { + if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { zb0004Len-- zb0004Mask |= 0x20000 } - if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { zb0004Len-- zb0004Mask |= 0x100000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { + if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000000 } - if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { + if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- zb0004Mask |= 0x4000000 } @@ -1478,81 +1478,61 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendMapHeader(o, zb0004Len) if zb0004Len != 0 { if (zb0004Mask & 0x40) == 0 { // if not empty - // string "cc" - o = append(o, 0xa2, 0x63, 0x63) - if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert))) - } - zb0001_keys := make([]protocol.CompactCertType, 0, len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert)) - for zb0001 := range (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(protocol.SortCompactCertType(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] - _ = zb0002 - o = zb0001.MarshalMsg(o) - o = zb0002.MarshalMsg(o) - } - } - if (zb0004Mask & 0x80) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0004Mask & 0x80) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0004Mask & 0x100) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0004Mask & 0x200) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0004Mask & 0x400) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0004Mask & 0x800) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0004Mask & 0x1000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0004Mask & 0x2000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0004Mask & 0x4000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0004Mask & 0x8000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).unauthenticatedProposal.OriginalPeriod)) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0004Mask & 0x10000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0004Mask & 0x20000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -1564,46 +1544,66 @@ func (z *proposal) MarshalMsg(b []byte) (o []byte) { o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0004Mask & 0x40000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0004Mask & 0x80000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x100000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x200000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x400000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x800000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } + if (zb0004Mask & 0x4000000) == 0 { // if not empty + // string "spt" + o = append(o, 0xa3, 0x73, 0x70, 0x74) + if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking))) + } + zb0001_keys := make([]protocol.StateProofType, 0, len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking)) + for zb0001 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(protocol.SortStateProofType(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = zb0002.MarshalMsg(o) + } + } if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) @@ -1856,34 +1856,34 @@ func (z *proposal) UnmarshalMsg(bts []byte) (o []byte, err error) { var zb0007 bool zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0006 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + if zb0006 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } if zb0007 { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0006) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0006) } for zb0006 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0006-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert", zb0001) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking", zb0001) return } - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } if zb0004 > 0 { @@ -2112,39 +2112,39 @@ func (z *proposal) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TxnCounter") return } - case "cc": + case "spt": var zb0011 int var zb0012 bool zb0011, zb0012, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } - if zb0011 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "CompactCert") + if zb0011 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "StateProofTracking") return } if zb0012 { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0011) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0011) } for zb0011 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0011-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert", zb0001) + err = msgp.WrapError(err, "StateProofTracking", zb0001) return } - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": var zb0013 int @@ -2221,9 +2221,9 @@ func (_ *proposal) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *proposal) Msgsize() (s int) { - s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 3 + msgp.MapHeaderSize - if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert != nil { - for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert { + s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking != nil { + for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { _ = zb0001 _ = zb0002 s += 0 + zb0001.Msgsize() + zb0002.Msgsize() @@ -2239,7 +2239,7 @@ func (z *proposal) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *proposal) MsgIsZero() bool { - return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) + return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -3120,91 +3120,91 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0004Len := uint32(30) var zb0004Mask uint64 /* 37 bits */ - if len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert) == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x100 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { zb0004Len-- zb0004Mask |= 0x200 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { zb0004Len-- zb0004Mask |= 0x400 } - if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "" { + if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800 } - if (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x4000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { zb0004Len-- zb0004Mask |= 0x8000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { + if (*z).unauthenticatedProposal.OriginalPeriod == 0 { zb0004Len-- zb0004Mask |= 0x10000 } - if (*z).unauthenticatedProposal.OriginalPeriod == 0 { + if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x20000 } - if (*z).unauthenticatedProposal.OriginalProposer.MsgIsZero() { + if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { zb0004Len-- zb0004Mask |= 0x40000 } - if len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x100000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { + if (*z).PriorVote.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200000 } - if (*z).PriorVote.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { zb0004Len-- zb0004Mask |= 0x400000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0 { + if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { + if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x4000000 } - if (*z).unauthenticatedProposal.SeedProof.MsgIsZero() { + if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x8000000 } - if (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero() { + if len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- zb0004Mask |= 0x10000000 } @@ -3244,81 +3244,61 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendMapHeader(o, zb0004Len) if zb0004Len != 0 { if (zb0004Mask & 0x80) == 0 { // if not empty - // string "cc" - o = append(o, 0xa2, 0x63, 0x63) - if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert))) - } - zb0001_keys := make([]protocol.CompactCertType, 0, len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert)) - for zb0001 := range (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(protocol.SortCompactCertType(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] - _ = zb0002 - o = zb0001.MarshalMsg(o) - o = zb0002.MarshalMsg(o) - } - } - if (zb0004Mask & 0x100) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0004Mask & 0x100) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0004Mask & 0x200) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0004Mask & 0x400) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0004Mask & 0x800) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0004Mask & 0x1000) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0004Mask & 0x2000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0004Mask & 0x4000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0004Mask & 0x8000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0004Mask & 0x10000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).unauthenticatedProposal.OriginalPeriod)) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0004Mask & 0x20000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).unauthenticatedProposal.OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0004Mask & 0x40000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -3330,51 +3310,71 @@ func (z *transmittedPayload) MarshalMsg(b []byte) (o []byte) { o = (*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0004Mask & 0x80000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).unauthenticatedProposal.Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x100000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x200000) == 0 { // if not empty // string "pv" o = append(o, 0xa2, 0x70, 0x76) o = (*z).PriorVote.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x400000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x800000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x8000000) == 0 { // if not empty + if (zb0004Mask & 0x4000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).unauthenticatedProposal.SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x10000000) == 0 { // if not empty + if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).unauthenticatedProposal.Block.BlockHeader.Seed.MarshalMsg(o) } + if (zb0004Mask & 0x10000000) == 0 { // if not empty + // string "spt" + o = append(o, 0xa3, 0x73, 0x70, 0x74) + if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking))) + } + zb0001_keys := make([]protocol.StateProofType, 0, len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking)) + for zb0001 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(protocol.SortStateProofType(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = zb0002.MarshalMsg(o) + } + } if (zb0004Mask & 0x20000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) @@ -3627,34 +3627,34 @@ func (z *transmittedPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { var zb0007 bool zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0006 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + if zb0006 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } if zb0007 { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0006) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0006) } for zb0006 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0006-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert", zb0001) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking", zb0001) return } - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } if zb0004 > 0 { @@ -3891,39 +3891,39 @@ func (z *transmittedPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TxnCounter") return } - case "cc": + case "spt": var zb0011 int var zb0012 bool zb0011, zb0012, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } - if zb0011 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "CompactCert") + if zb0011 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "StateProofTracking") return } if zb0012 { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = nil - } else if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert == nil { - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0011) + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = nil + } else if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking == nil { + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0011) } for zb0011 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0011-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert", zb0001) + err = msgp.WrapError(err, "StateProofTracking", zb0001) return } - (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": var zb0013 int @@ -4006,9 +4006,9 @@ func (_ *transmittedPayload) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *transmittedPayload) Msgsize() (s int) { - s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 3 + msgp.MapHeaderSize - if (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert != nil { - for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.CompactCert { + s = 3 + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.Round.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Branch.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.Seed.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID) + 3 + (*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + if (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking != nil { + for zb0001, zb0002 := range (*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking { _ = zb0001 _ = zb0002 s += 0 + zb0001.Msgsize() + zb0002.Msgsize() @@ -4024,7 +4024,7 @@ func (z *transmittedPayload) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *transmittedPayload) MsgIsZero() bool { - return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.CompactCert) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) && ((*z).PriorVote.MsgIsZero()) + return ((*z).unauthenticatedProposal.Block.BlockHeader.Round.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Branch.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.Seed.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.TimeStamp == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisID == "") && ((*z).unauthenticatedProposal.Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).unauthenticatedProposal.Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).unauthenticatedProposal.Block.BlockHeader.TxnCounter == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.StateProofTracking) == 0) && (len((*z).unauthenticatedProposal.Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).unauthenticatedProposal.Block.Payset.MsgIsZero()) && ((*z).unauthenticatedProposal.SeedProof.MsgIsZero()) && ((*z).unauthenticatedProposal.OriginalPeriod == 0) && ((*z).unauthenticatedProposal.OriginalProposer.MsgIsZero()) && ((*z).PriorVote.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -4696,87 +4696,87 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0004Len := uint32(29) var zb0004Mask uint64 /* 35 bits */ - if len((*z).Block.BlockHeader.CompactCert) == 0 { + if (*z).Block.BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x40 } - if (*z).Block.BlockHeader.RewardsState.RewardsLevel == 0 { + if (*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero() { + if (*z).Block.BlockHeader.RewardsState.RewardsResidue == 0 { zb0004Len-- zb0004Mask |= 0x100 } - if (*z).Block.BlockHeader.RewardsState.RewardsResidue == 0 { + if (*z).Block.BlockHeader.GenesisID == "" { zb0004Len-- zb0004Mask |= 0x200 } - if (*z).Block.BlockHeader.GenesisID == "" { + if (*z).Block.BlockHeader.GenesisHash.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400 } - if (*z).Block.BlockHeader.GenesisHash.MsgIsZero() { + if (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800 } - if (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { + if (*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000 } - if (*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { + if (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000 } - if (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { + if (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { zb0004Len-- zb0004Mask |= 0x4000 } - if (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0 { + if (*z).OriginalPeriod == 0 { zb0004Len-- zb0004Mask |= 0x8000 } - if (*z).OriginalPeriod == 0 { + if (*z).OriginalProposer.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x10000 } - if (*z).OriginalProposer.MsgIsZero() { + if len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { zb0004Len-- zb0004Mask |= 0x20000 } - if len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { + if (*z).Block.BlockHeader.Branch.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40000 } - if (*z).Block.BlockHeader.Branch.MsgIsZero() { + if (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80000 } - if (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { + if (*z).Block.BlockHeader.RewardsState.RewardsRate == 0 { zb0004Len-- zb0004Mask |= 0x100000 } - if (*z).Block.BlockHeader.RewardsState.RewardsRate == 0 { + if (*z).Block.BlockHeader.Round.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200000 } - if (*z).Block.BlockHeader.Round.MsgIsZero() { + if (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400000 } - if (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { + if (*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800000 } - if (*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero() { + if (*z).SeedProof.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000000 } - if (*z).SeedProof.MsgIsZero() { + if (*z).Block.BlockHeader.Seed.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x2000000 } - if (*z).Block.BlockHeader.Seed.MsgIsZero() { + if len((*z).Block.BlockHeader.StateProofTracking) == 0 { zb0004Len-- zb0004Mask |= 0x4000000 } @@ -4816,81 +4816,61 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendMapHeader(o, zb0004Len) if zb0004Len != 0 { if (zb0004Mask & 0x40) == 0 { // if not empty - // string "cc" - o = append(o, 0xa2, 0x63, 0x63) - if (*z).Block.BlockHeader.CompactCert == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).Block.BlockHeader.CompactCert))) - } - zb0001_keys := make([]protocol.CompactCertType, 0, len((*z).Block.BlockHeader.CompactCert)) - for zb0001 := range (*z).Block.BlockHeader.CompactCert { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(protocol.SortCompactCertType(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).Block.BlockHeader.CompactCert[zb0001] - _ = zb0002 - o = zb0001.MarshalMsg(o) - o = zb0002.MarshalMsg(o) - } - } - if (zb0004Mask & 0x80) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0004Mask & 0x80) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).Block.BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0004Mask & 0x100) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0004Mask & 0x200) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Block.BlockHeader.GenesisID) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0004Mask & 0x400) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Block.BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0004Mask & 0x800) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0004Mask & 0x1000) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).Block.BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0004Mask & 0x2000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0004Mask & 0x4000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0004Mask & 0x8000) == 0 { // if not empty // string "oper" o = append(o, 0xa4, 0x6f, 0x70, 0x65, 0x72) o = msgp.AppendUint64(o, uint64((*z).OriginalPeriod)) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0004Mask & 0x10000) == 0 { // if not empty // string "oprop" o = append(o, 0xa5, 0x6f, 0x70, 0x72, 0x6f, 0x70) o = (*z).OriginalProposer.MarshalMsg(o) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0004Mask & 0x20000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -4902,46 +4882,66 @@ func (z *unauthenticatedProposal) MarshalMsg(b []byte) (o []byte) { o = (*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0004Mask & 0x40000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).Block.BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0004Mask & 0x80000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x100000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).Block.BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x200000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).Block.BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x800000) == 0 { // if not empty + if (zb0004Mask & 0x400000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x1000000) == 0 { // if not empty + if (zb0004Mask & 0x800000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).Block.BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x2000000) == 0 { // if not empty + if (zb0004Mask & 0x1000000) == 0 { // if not empty // string "sdpf" o = append(o, 0xa4, 0x73, 0x64, 0x70, 0x66) o = (*z).SeedProof.MarshalMsg(o) } - if (zb0004Mask & 0x4000000) == 0 { // if not empty + if (zb0004Mask & 0x2000000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).Block.BlockHeader.Seed.MarshalMsg(o) } + if (zb0004Mask & 0x4000000) == 0 { // if not empty + // string "spt" + o = append(o, 0xa3, 0x73, 0x70, 0x74) + if (*z).Block.BlockHeader.StateProofTracking == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).Block.BlockHeader.StateProofTracking))) + } + zb0001_keys := make([]protocol.StateProofType, 0, len((*z).Block.BlockHeader.StateProofTracking)) + for zb0001 := range (*z).Block.BlockHeader.StateProofTracking { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(protocol.SortStateProofType(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).Block.BlockHeader.StateProofTracking[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = zb0002.MarshalMsg(o) + } + } if (zb0004Mask & 0x8000000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) @@ -5194,34 +5194,34 @@ func (z *unauthenticatedProposal) UnmarshalMsg(bts []byte) (o []byte, err error) var zb0007 bool zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0006 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + if zb0006 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } if zb0007 { - (*z).Block.BlockHeader.CompactCert = nil - } else if (*z).Block.BlockHeader.CompactCert == nil { - (*z).Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0006) + (*z).Block.BlockHeader.StateProofTracking = nil + } else if (*z).Block.BlockHeader.StateProofTracking == nil { + (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0006) } for zb0006 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0006-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert", zb0001) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking", zb0001) return } - (*z).Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } } if zb0004 > 0 { @@ -5450,39 +5450,39 @@ func (z *unauthenticatedProposal) UnmarshalMsg(bts []byte) (o []byte, err error) err = msgp.WrapError(err, "TxnCounter") return } - case "cc": + case "spt": var zb0011 int var zb0012 bool zb0011, zb0012, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } - if zb0011 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "CompactCert") + if zb0011 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0011), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "StateProofTracking") return } if zb0012 { - (*z).Block.BlockHeader.CompactCert = nil - } else if (*z).Block.BlockHeader.CompactCert == nil { - (*z).Block.BlockHeader.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState, zb0011) + (*z).Block.BlockHeader.StateProofTracking = nil + } else if (*z).Block.BlockHeader.StateProofTracking == nil { + (*z).Block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData, zb0011) } for zb0011 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 bookkeeping.CompactCertState + var zb0001 protocol.StateProofType + var zb0002 bookkeeping.StateProofTrackingData zb0011-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert", zb0001) + err = msgp.WrapError(err, "StateProofTracking", zb0001) return } - (*z).Block.BlockHeader.CompactCert[zb0001] = zb0002 + (*z).Block.BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": var zb0013 int @@ -5559,9 +5559,9 @@ func (_ *unauthenticatedProposal) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *unauthenticatedProposal) Msgsize() (s int) { - s = 3 + 4 + (*z).Block.BlockHeader.Round.Msgsize() + 5 + (*z).Block.BlockHeader.Branch.Msgsize() + 5 + (*z).Block.BlockHeader.Seed.Msgsize() + 4 + (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).Block.BlockHeader.GenesisID) + 3 + (*z).Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 3 + msgp.MapHeaderSize - if (*z).Block.BlockHeader.CompactCert != nil { - for zb0001, zb0002 := range (*z).Block.BlockHeader.CompactCert { + s = 3 + 4 + (*z).Block.BlockHeader.Round.Msgsize() + 5 + (*z).Block.BlockHeader.Branch.Msgsize() + 5 + (*z).Block.BlockHeader.Seed.Msgsize() + 4 + (*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).Block.BlockHeader.GenesisID) + 3 + (*z).Block.BlockHeader.GenesisHash.Msgsize() + 5 + (*z).Block.BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).Block.BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).Block.BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).Block.BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).Block.BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + if (*z).Block.BlockHeader.StateProofTracking != nil { + for zb0001, zb0002 := range (*z).Block.BlockHeader.StateProofTracking { _ = zb0001 _ = zb0002 s += 0 + zb0001.Msgsize() + zb0002.Msgsize() @@ -5577,7 +5577,7 @@ func (z *unauthenticatedProposal) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *unauthenticatedProposal) MsgIsZero() bool { - return ((*z).Block.BlockHeader.Round.MsgIsZero()) && ((*z).Block.BlockHeader.Branch.MsgIsZero()) && ((*z).Block.BlockHeader.Seed.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TimeStamp == 0) && ((*z).Block.BlockHeader.GenesisID == "") && ((*z).Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).Block.BlockHeader.TxnCounter == 0) && (len((*z).Block.BlockHeader.CompactCert) == 0) && (len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Block.Payset.MsgIsZero()) && ((*z).SeedProof.MsgIsZero()) && ((*z).OriginalPeriod == 0) && ((*z).OriginalProposer.MsgIsZero()) + return ((*z).Block.BlockHeader.Round.MsgIsZero()) && ((*z).Block.BlockHeader.Branch.MsgIsZero()) && ((*z).Block.BlockHeader.Seed.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).Block.BlockHeader.TimeStamp == 0) && ((*z).Block.BlockHeader.GenesisID == "") && ((*z).Block.BlockHeader.GenesisHash.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).Block.BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRate == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).Block.BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).Block.BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).Block.BlockHeader.TxnCounter == 0) && (len((*z).Block.BlockHeader.StateProofTracking) == 0) && (len((*z).Block.BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Block.Payset.MsgIsZero()) && ((*z).SeedProof.MsgIsZero()) && ((*z).OriginalPeriod == 0) && ((*z).OriginalProposer.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 3b9aa19872..5d01fa9608 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -19,6 +19,7 @@ package catchup import ( "context" "fmt" + "github.com/algorand/go-algorand/stateproof" "sync" "time" @@ -464,6 +465,19 @@ func (cs *CatchpointCatchupService) processStageLastestBlockDownload() (err erro return nil } +// lookbackForStateproofsSupport calculates the lookback (from topblock round) needed to be downloaded +// in order to support state proofs verification. +func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { + proto := config.Consensus[topBlock.CurrentProtocol] + if proto.StateProofInterval == 0 { + return 0 + } + lowestStateProofRound := stateproof.GetOldestExpectedStateProof(&topBlock.BlockHeader) + // in order to be able to confirm lowestStateProofRound we need to have round number: (lowestStateProofRound - stateproofInterval) + lowestStateProofRound = lowestStateProofRound.SubSaturate(basics.Round(proto.StateProofInterval)) + return uint64(topBlock.Round().SubSaturate(lowestStateProofRound)) +} + // processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against it's predecessor. func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { topBlock, err := cs.ledgerAccessor.EnsureFirstBlock(cs.ctx) @@ -478,6 +492,11 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { if lookback < proto.MaxBalLookback { lookback = proto.MaxBalLookback } + + lookbackForStateProofSupport := lookbackForStateproofsSupport(&topBlock) + if lookback < lookbackForStateProofSupport { + lookback = lookbackForStateProofSupport + } // in case the effective lookback is going before our rounds count, trim it there. // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) if lookback >= uint64(topBlock.Round()) { diff --git a/catchup/service_test.go b/catchup/service_test.go index bb4418b910..972370180b 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/algorand/go-deadlock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" @@ -957,7 +958,7 @@ func TestServiceStartStop(t *testing.T) { s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, ledger, &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) s.Start() s.Stop() - _, ok := (<-s.done) + _, ok := <-s.done require.False(t, ok) } @@ -973,3 +974,39 @@ func TestSynchronizingTime(t *testing.T) { atomic.StoreInt64(&s.syncStartNS, 1000000) require.NotEqual(t, time.Duration(0), s.SynchronizingTime()) } + +func TestDownloadBlocksToSupportStateProofs(t *testing.T) { + partitiontest.PartitionTest(t) + // make sure we download enough blocks to verify state proof 512 + topBlk := bookkeeping.Block{} + topBlk.BlockHeader.Round = 1500 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + trackingData := bookkeeping.StateProofTrackingData{StateProofNextRound: 512} + topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData + + lookback := lookbackForStateproofsSupport(&topBlk) + oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) + assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval) + + // the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512. + // instead, we will download blocks to confirm only the recovery period lookback. + topBlk = bookkeeping.Block{} + topBlk.BlockHeader.Round = 8000 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + trackingData = bookkeeping.StateProofTrackingData{StateProofNextRound: 512} + topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData + + lookback = lookbackForStateproofsSupport(&topBlk) + oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) + + lowestRoundToRetain := 8000 - (8000 % 256) - (config.Consensus[protocol.ConsensusFuture].StateProofInterval * (config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 1)) + assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) + + topBlk = bookkeeping.Block{} + topBlk.BlockHeader.Round = 8000 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusV32 + lookback = lookbackForStateproofsSupport(&topBlk) + assert.Equal(t, uint64(0), lookback) +} diff --git a/cmd/algokey/part.go b/cmd/algokey/part.go index 0360bdec99..3fb7e49c21 100644 --- a/cmd/algokey/part.go +++ b/cmd/algokey/part.go @@ -168,8 +168,9 @@ func printPartkey(partkey account.Participation) { fmt.Printf("Parent address: %s\n", partkey.Parent.String()) fmt.Printf("VRF public key: %s\n", base64.StdEncoding.EncodeToString(partkey.VRF.PK[:])) fmt.Printf("Voting public key: %s\n", base64.StdEncoding.EncodeToString(partkey.Voting.OneTimeSignatureVerifier[:])) - if partkey.StateProofSecrets != nil && !partkey.StateProofSecrets.GetVerifier().IsEmpty() { - fmt.Printf("State proof key: %s\n", base64.StdEncoding.EncodeToString(partkey.StateProofSecrets.GetVerifier()[:])) + if partkey.StateProofSecrets != nil && !partkey.StateProofSecrets.GetVerifier().MsgIsZero() { + fmt.Printf("State proof key: %s\n", base64.StdEncoding.EncodeToString(partkey.StateProofSecrets.GetVerifier().Commitment[:])) + fmt.Printf("State proof key lifetime: %d\n", partkey.StateProofSecrets.GetVerifier().KeyLifetime) } fmt.Printf("First round: %d\n", partkey.FirstValid) fmt.Printf("Last round: %d\n", partkey.LastValid) diff --git a/compactcert/builder.go b/compactcert/builder.go deleted file mode 100644 index 27302ed492..0000000000 --- a/compactcert/builder.go +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "context" - "database/sql" - "encoding/binary" - "fmt" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/compactcert" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/ledger" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network" - "github.com/algorand/go-algorand/protocol" -) - -func (ccw *Worker) builderForRound(rnd basics.Round) (builder, error) { - hdr, err := ccw.ledger.BlockHdr(rnd) - if err != nil { - return builder{}, err - } - - hdrProto := config.Consensus[hdr.CurrentProtocol] - votersRnd := rnd.SubSaturate(basics.Round(hdrProto.CompactCertRounds)) - votersHdr, err := ccw.ledger.BlockHdr(votersRnd) - if err != nil { - return builder{}, err - } - - lookback := votersRnd.SubSaturate(basics.Round(hdrProto.CompactCertVotersLookback)) - voters, err := ccw.ledger.CompactCertVoters(lookback) - if err != nil { - return builder{}, err - } - - if voters == nil { - // Voters not tracked for that round. Might not be a valid - // compact cert round; compact certs might not be enabled; etc. - return builder{}, fmt.Errorf("voters not tracked for lookback round %d", lookback) - } - - p, err := ledger.CompactCertParams(votersHdr, hdr) - if err != nil { - return builder{}, err - } - - var res builder - res.votersHdr = votersHdr - res.voters = voters - res.Builder, err = compactcert.MkBuilder(p, voters.Participants, voters.Tree) - if err != nil { - return builder{}, err - } - - ccw.builders[rnd] = res - return res, nil -} - -func (ccw *Worker) initBuilders() { - ccw.mu.Lock() - defer ccw.mu.Unlock() - - var roundSigs map[basics.Round][]pendingSig - err := ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - roundSigs, err = getPendingSigs(tx) - return - }) - if err != nil { - ccw.log.Warnf("initBuilders: getPendingSigs: %v", err) - return - } - - for rnd, sigs := range roundSigs { - _, ok := ccw.builders[rnd] - if ok { - ccw.log.Warnf("initBuilders: round %d already present", rnd) - continue - } - - builder, err := ccw.builderForRound(rnd) - if err != nil { - ccw.log.Warnf("initBuilders: builderForRound(%d): %v", rnd, err) - continue - } - - for _, sig := range sigs { - pos, ok := builder.voters.AddrToPos[sig.signer] - if !ok { - ccw.log.Warnf("initBuilders: cannot find %v in round %d", sig.signer, rnd) - continue - } - - err = builder.Add(pos, sig.sig, false) - if err != nil { - ccw.log.Warnf("initBuilders: cannot add %v in round %d: %v", sig.signer, rnd, err) - continue - } - } - } -} - -func (ccw *Worker) handleSigMessage(msg network.IncomingMessage) network.OutgoingMessage { - var ssig sigFromAddr - err := protocol.Decode(msg.Data, &ssig) - if err != nil { - ccw.log.Warnf("ccw.handleSigMessage(): decode: %v", err) - return network.OutgoingMessage{Action: network.Disconnect} - } - - fwd, err := ccw.handleSig(ssig, msg.Sender) - if err != nil { - ccw.log.Warnf("ccw.handleSigMessage(): %v", err) - } - - return network.OutgoingMessage{Action: fwd} -} - -func (ccw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.ForwardingPolicy, error) { - ccw.mu.Lock() - defer ccw.mu.Unlock() - - builder, ok := ccw.builders[sfa.Round] - if !ok { - latest := ccw.ledger.Latest() - latestHdr, err := ccw.ledger.BlockHdr(latest) - if err != nil { - return network.Disconnect, err - } - - if sfa.Round < latestHdr.CompactCert[protocol.CompactCertBasic].CompactCertNextRound { - // Already have a complete compact cert in ledger. - // Ignore this sig. - return network.Ignore, nil - } - - builder, err = ccw.builderForRound(sfa.Round) - if err != nil { - return network.Disconnect, err - } - } - - pos, ok := builder.voters.AddrToPos[sfa.Signer] - if !ok { - return network.Disconnect, fmt.Errorf("handleSig: %v not in participants for %d", sfa.Signer, sfa.Round) - } - - if builder.Present(pos) { - // Signature already part of the builder, ignore. - return network.Ignore, nil - } - - err := builder.Add(pos, sfa.Sig, true) - if err != nil { - return network.Disconnect, err - } - - err = ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return addPendingSig(tx, sfa.Round, pendingSig{ - signer: sfa.Signer, - sig: sfa.Sig, - fromThisNode: sender == nil, - }) - }) - if err != nil { - return network.Ignore, err - } - - return network.Broadcast, nil -} - -func (ccw *Worker) builder(latest basics.Round) { - // We clock the building of compact certificates based on new - // blocks. This is because the acceptable compact certificate - // size grows over time, so that we aim to construct an extremely - // compact certificate upfront, but if that doesn't work out, we - // will settle for a larger certificate. New blocks also tell us - // if a compact cert has been committed, so that we can stop trying - // to build it. - for { - ccw.tryBuilding() - - nextrnd := latest + 1 - select { - case <-ccw.ctx.Done(): - ccw.wg.Done() - return - - case <-ccw.ledger.Wait(nextrnd): - // Continue on - } - - // See if any new compact certificates were formed, according to - // the new block, which would mean we can clean up some builders. - hdr, err := ccw.ledger.BlockHdr(nextrnd) - if err != nil { - ccw.log.Warnf("ccw.builder: BlockHdr(%d): %v", nextrnd, err) - continue - } else { - ccw.deleteOldSigs(hdr.CompactCert[protocol.CompactCertBasic].CompactCertNextRound) - } - - // Broadcast signatures based on the previous block(s) that - // were agreed upon. This ensures that, if we send a signature - // for block R, nodes will have already verified block R, because - // block R+1 has been formed. - proto := config.Consensus[hdr.CurrentProtocol] - newLatest := ccw.ledger.Latest() - for r := latest; r < newLatest; r++ { - // Wait for the signer to catch up; mostly relevant in tests. - ccw.waitForSignedBlock(r) - - ccw.broadcastSigs(r, proto) - } - latest = newLatest - } -} - -// broadcastSigs periodically broadcasts pending signatures for rounds -// that have not been able to form a compact certificate. -// -// Signature re-broadcasting happens in periods of proto.CompactCertRounds -// rounds. -// -// In the first half of each such period, signers of a block broadcast their -// own signatures; this is the expected common path. -// -// In the second half of each such period, any signatures seen by this node -// are broadcast. -// -// The broadcast schedule is randomized by the address of the block signer, -// for load-balancing over time. -func (ccw *Worker) broadcastSigs(brnd basics.Round, proto config.ConsensusParams) { - if proto.CompactCertRounds == 0 { - return - } - - ccw.mu.Lock() - defer ccw.mu.Unlock() - - var roundSigs map[basics.Round][]pendingSig - err := ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { - if brnd%basics.Round(proto.CompactCertRounds) < basics.Round(proto.CompactCertRounds/2) { - roundSigs, err = getPendingSigsFromThisNode(tx) - } else { - roundSigs, err = getPendingSigs(tx) - } - return - }) - if err != nil { - ccw.log.Warnf("broadcastSigs: getPendingSigs: %v", err) - return - } - - for rnd, sigs := range roundSigs { - if rnd > brnd { - // Signature is for later block than brnd. This could happen - // during catchup or testing. The caller's loop will eventually - // invoke this function with a suitably high brnd. - continue - } - - for _, sig := range sigs { - // Randomize which sigs get broadcast over time. - addr64 := binary.LittleEndian.Uint64(sig.signer[:]) - if addr64%(proto.CompactCertRounds/2) != uint64(brnd)%(proto.CompactCertRounds/2) { - continue - } - - sfa := sigFromAddr{ - Signer: sig.signer, - Round: rnd, - Sig: sig.sig, - } - err = ccw.net.Broadcast(context.Background(), protocol.CompactCertSigTag, - protocol.Encode(&sfa), false, nil) - if err != nil { - ccw.log.Warnf("broadcastSigs: Broadcast for %d: %v", rnd, err) - } - } - } -} - -func (ccw *Worker) deleteOldSigs(nextCert basics.Round) { - ccw.mu.Lock() - defer ccw.mu.Unlock() - - err := ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - return deletePendingSigsBeforeRound(tx, nextCert) - }) - if err != nil { - ccw.log.Warnf("deletePendingSigsBeforeRound(%d): %v", nextCert, err) - } - - for rnd := range ccw.builders { - if rnd < nextCert { - delete(ccw.builders, rnd) - } - } -} - -func (ccw *Worker) tryBuilding() { - ccw.mu.Lock() - defer ccw.mu.Unlock() - - for rnd, b := range ccw.builders { - firstValid := ccw.ledger.Latest() + 1 - acceptableWeight := ledger.AcceptableCompactCertWeight(b.votersHdr, firstValid, logging.Base()) - if b.SignedWeight() < acceptableWeight { - // Haven't signed enough to build the cert at this time.. - continue - } - - if !b.Ready() { - // Haven't gotten enough signatures to get past ProvenWeight - continue - } - - cert, err := b.Build() - if err != nil { - ccw.log.Warnf("ccw.tryBuilding: building compact cert for %d: %v", rnd, err) - continue - } - - var stxn transactions.SignedTxn - stxn.Txn.Type = protocol.CompactCertTx - stxn.Txn.Sender = transactions.CompactCertSender - stxn.Txn.FirstValid = firstValid - stxn.Txn.LastValid = firstValid + basics.Round(b.voters.Proto.MaxTxnLife) - stxn.Txn.GenesisHash = ccw.ledger.GenesisHash() - stxn.Txn.CertRound = rnd - stxn.Txn.Cert = *cert - err = ccw.txnSender.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{stxn}) - if err != nil { - ccw.log.Warnf("ccw.tryBuilding: broadcasting compact cert txn for %d: %v", rnd, err) - } - } -} - -func (ccw *Worker) signedBlock(r basics.Round) { - ccw.mu.Lock() - ccw.signed = r - ccw.mu.Unlock() - - select { - case ccw.signedCh <- struct{}{}: - default: - } -} - -func (ccw *Worker) lastSignedBlock() basics.Round { - ccw.mu.Lock() - defer ccw.mu.Unlock() - return ccw.signed -} - -func (ccw *Worker) waitForSignedBlock(r basics.Round) { - for { - if r <= ccw.lastSignedBlock() { - return - } - - select { - case <-ccw.ctx.Done(): - return - case <-ccw.signedCh: - } - } -} diff --git a/compactcert/signer.go b/compactcert/signer.go deleted file mode 100644 index 915ac60407..0000000000 --- a/compactcert/signer.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "context" - "database/sql" - "time" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/merklesignature" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/protocol" -) - -// sigFromAddr encapsulates a signature on a block header, which -// will eventually be used to form a compact certificate for that -// block. -type sigFromAddr struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - Signer basics.Address `codec:"signer"` - Round basics.Round `codec:"rnd"` - Sig merklesignature.Signature `codec:"sig"` -} - -func (ccw *Worker) signer(latest basics.Round) { - var nextrnd basics.Round - -restart: - for { - latestHdr, err := ccw.ledger.BlockHdr(latest) - if err != nil { - ccw.log.Warnf("ccw.signer(): BlockHdr(latest %d): %v", latest, err) - time.Sleep(1 * time.Second) - latest = ccw.ledger.Latest() - continue - } - - nextrnd = latestHdr.CompactCert[protocol.CompactCertBasic].CompactCertNextRound - if nextrnd == 0 { - // Compact certs not enabled yet. Keep monitoring new blocks. - nextrnd = latest + 1 - } - break - } - - for { - select { - case <-ccw.ledger.Wait(nextrnd): - hdr, err := ccw.ledger.BlockHdr(nextrnd) - if err != nil { - ccw.log.Warnf("ccw.signer(): BlockHdr(next %d): %v", nextrnd, err) - time.Sleep(1 * time.Second) - latest = ccw.ledger.Latest() - goto restart - } - - ccw.signBlock(hdr) - ccw.signedBlock(nextrnd) - nextrnd++ - - case <-ccw.ctx.Done(): - ccw.wg.Done() - return - } - } -} - -func (ccw *Worker) signBlock(hdr bookkeeping.BlockHeader) { - proto := config.Consensus[hdr.CurrentProtocol] - if proto.CompactCertRounds == 0 { - return - } - - // Only sign blocks that are a multiple of CompactCertRounds. - if hdr.Round%basics.Round(proto.CompactCertRounds) != 0 { - return - } - - keys := ccw.accts.StateProofKeys(hdr.Round) - if len(keys) == 0 { - // No keys, nothing to do. - return - } - - // votersRound is the round containing the merkle root commitment - // for the voters that are going to sign this block. - votersRound := hdr.Round.SubSaturate(basics.Round(proto.CompactCertRounds)) - votersHdr, err := ccw.ledger.BlockHdr(votersRound) - if err != nil { - ccw.log.Warnf("ccw.signBlock(%d): BlockHdr(%d): %v", hdr.Round, votersRound, err) - return - } - - if votersHdr.CompactCert[protocol.CompactCertBasic].CompactCertVoters.IsEmpty() { - // No voter commitment, perhaps because compact certs were - // just enabled. - return - } - - sigs := make([]sigFromAddr, 0, len(keys)) - - for _, key := range keys { - if key.FirstValid > hdr.Round || hdr.Round > key.LastValid { - continue - } - - if key.StateProofSecrets == nil { - ccw.log.Warnf("ccw.signBlock(%d): empty state proof secrets for round", hdr.Round) - continue - } - - sig, err := key.StateProofSecrets.Sign(hdr) - if err != nil { - ccw.log.Warnf("ccw.signBlock(%d): StateProofSecrets.Sign: %v", hdr.Round, err) - continue - } - - sigs = append(sigs, sigFromAddr{ - Signer: key.Account, - Round: hdr.Round, - Sig: sig, - }) - } - - for _, sfa := range sigs { - _, err = ccw.handleSig(sfa, nil) - if err != nil { - ccw.log.Warnf("ccw.signBlock(%d): handleSig: %v", hdr.Round, err) - } - } -} - -// LatestSigsFromThisNode returns information about compact cert signatures from -// this node's participation keys that are already stored durably on disk. In -// particular, we return the round nunmber of the latest block signed with each -// account's participation key. This is intended for use by the ephemeral key -// logic: since we already have these signatures stored on disk, it is safe to -// delete the corresponding ephemeral private keys. -func (ccw *Worker) LatestSigsFromThisNode() (map[basics.Address]basics.Round, error) { - res := make(map[basics.Address]basics.Round) - err := ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { - sigs, err := getPendingSigsFromThisNode(tx) - if err != nil { - return err - } - - for rnd, psigs := range sigs { - for _, psig := range psigs { - if res[psig.signer] < rnd { - res[psig.signer] = rnd - } - } - } - - return nil - }) - return res, err -} diff --git a/compactcert/worker_test.go b/compactcert/worker_test.go deleted file mode 100644 index aaf1a4e2b8..0000000000 --- a/compactcert/worker_test.go +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" - "github.com/algorand/go-algorand/crypto/merklearray" - "github.com/algorand/go-algorand/data/account" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/ledger/ledgercore" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/network" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" - "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" -) - -type testWorkerStubs struct { - t testing.TB - mu deadlock.Mutex - latest basics.Round - waiters map[basics.Round]chan struct{} - blocks map[basics.Round]bookkeeping.BlockHeader - keys []account.Participation - keysForVoters []account.Participation - sigmsg chan []byte - txmsg chan transactions.SignedTxn - totalWeight int -} - -func newWorkerStubs(t testing.TB, keys []account.Participation, totalWeight int) *testWorkerStubs { - s := &testWorkerStubs{ - waiters: make(map[basics.Round]chan struct{}), - blocks: make(map[basics.Round]bookkeeping.BlockHeader), - sigmsg: make(chan []byte, 1024), - txmsg: make(chan transactions.SignedTxn, 1024), - keys: keys, - keysForVoters: keys, - totalWeight: totalWeight, - } - s.latest-- - s.addBlock(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].CompactCertRounds)) - return s -} - -func (s *testWorkerStubs) addBlock(ccNextRound basics.Round) { - s.latest++ - - hdr := bookkeeping.BlockHeader{} - hdr.Round = s.latest - hdr.CurrentProtocol = protocol.ConsensusFuture - - var ccBasic = bookkeeping.CompactCertState{ - CompactCertVoters: make([]byte, compactcert.HashSize), - CompactCertVotersTotal: basics.MicroAlgos{}, - CompactCertNextRound: 0, - } - ccBasic.CompactCertVotersTotal.Raw = uint64(s.totalWeight) - - if hdr.Round > 0 { - // Just so it's not zero, since the signer logic checks for all-zeroes - ccBasic.CompactCertVoters[1] = 0x12 - } - - ccBasic.CompactCertNextRound = ccNextRound - hdr.CompactCert = map[protocol.CompactCertType]bookkeeping.CompactCertState{ - protocol.CompactCertBasic: ccBasic, - } - - s.blocks[s.latest] = hdr - if s.waiters[s.latest] != nil { - close(s.waiters[s.latest]) - } -} - -func (s *testWorkerStubs) StateProofKeys(rnd basics.Round) (out []account.StateProofRecordForRound) { - for _, part := range s.keys { - if part.OverlapsInterval(rnd, rnd) { - partRecord := account.ParticipationRecord{ - ParticipationID: part.ID(), - Account: part.Parent, - FirstValid: part.FirstValid, - LastValid: part.LastValid, - KeyDilution: part.KeyDilution, - LastVote: 0, - LastBlockProposal: 0, - LastStateProof: 0, - EffectiveFirst: 0, - EffectiveLast: 0, - VRF: part.VRF, - Voting: part.Voting, - } - signerInRound := part.StateProofSecrets.GetSigner(uint64(rnd)) - partRecordForRound := account.StateProofRecordForRound{ - ParticipationRecord: partRecord, - StateProofSecrets: signerInRound, - } - out = append(out, partRecordForRound) - } - } - return -} - -func (s *testWorkerStubs) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { - s.mu.Lock() - defer s.mu.Unlock() - - hdr, ok := s.blocks[r] - if !ok { - return hdr, ledgercore.ErrNoEntry{ - Round: r, - Latest: s.latest, - Committed: s.latest, - } - } - - return hdr, nil -} - -func (s *testWorkerStubs) CompactCertVoters(r basics.Round) (*ledgercore.VotersForRound, error) { - voters := &ledgercore.VotersForRound{ - Proto: config.Consensus[protocol.ConsensusFuture], - AddrToPos: make(map[basics.Address]uint64), - TotalWeight: basics.MicroAlgos{Raw: uint64(s.totalWeight)}, - } - - for i, k := range s.keysForVoters { - voters.AddrToPos[k.Parent] = uint64(i) - voters.Participants = append(voters.Participants, basics.Participant{ - PK: *k.StateProofSecrets.GetVerifier(), - Weight: 1, - }) - } - - tree, err := merklearray.BuildVectorCommitmentTree(voters.Participants, crypto.HashFactory{HashType: compactcert.HashType}) - if err != nil { - return nil, err - } - - voters.Tree = tree - return voters, nil -} - -func (s *testWorkerStubs) GenesisHash() crypto.Digest { - return crypto.Digest{0x01, 0x02, 0x03, 0x04} -} - -func (s *testWorkerStubs) Latest() basics.Round { - s.mu.Lock() - defer s.mu.Unlock() - return s.latest -} - -func (s *testWorkerStubs) Wait(r basics.Round) chan struct{} { - s.mu.Lock() - defer s.mu.Unlock() - if s.waiters[r] == nil { - s.waiters[r] = make(chan struct{}) - if r <= s.latest { - close(s.waiters[r]) - } - } - return s.waiters[r] -} - -func (s *testWorkerStubs) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except network.Peer) error { - require.Equal(s.t, tag, protocol.CompactCertSigTag) - s.sigmsg <- data - return nil -} - -func (s *testWorkerStubs) BroadcastInternalSignedTxGroup(tx []transactions.SignedTxn) error { - require.Equal(s.t, len(tx), 1) - s.txmsg <- tx[0] - return nil -} - -func (s *testWorkerStubs) RegisterHandlers([]network.TaggedMessageHandler) { -} - -func (s *testWorkerStubs) advanceLatest(delta uint64) { - s.mu.Lock() - defer s.mu.Unlock() - - for r := uint64(0); r < delta; r++ { - s.addBlock(s.blocks[s.latest].CompactCert[protocol.CompactCertBasic].CompactCertNextRound) - } -} - -func newTestWorkerDB(t testing.TB, s *testWorkerStubs, dba db.Accessor) *Worker { - return NewWorker(dba, logging.TestingLog(t), s, s, s, s) -} - -func newTestWorker(t testing.TB, s *testWorkerStubs) *Worker { - dbs, _ := dbOpenTest(t, true) - return newTestWorkerDB(t, s, dbs.Wdb) -} - -// You must call defer part.Close() after calling this function, -// since it creates a DB accessor but the caller must close it (required for mss) -func newPartKey(t testing.TB, parent basics.Address) account.PersistedParticipation { - fn := fmt.Sprintf("%s.%d", strings.ReplaceAll(t.Name(), "/", "."), crypto.RandUint64()) - partDB, err := db.MakeAccessor(fn, false, true) - require.NoError(t, err) - - part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(10*config.Consensus[protocol.ConsensusFuture].CompactCertRounds), config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution) - require.NoError(t, err) - - return part -} - -func TestWorkerAllSigs(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 10; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, len(keys)) - w := newTestWorker(t, s) - w.Start() - defer w.Shutdown() - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(proto.CompactCertRounds + proto.CompactCertRounds/2) - - // Go through several iterations, making sure that we get - // the signatures and certs broadcast at each round. - for iter := 0; iter < 5; iter++ { - s.advanceLatest(proto.CompactCertRounds) - - for i := 0; i < len(keys); i++ { - // Expect all signatures to be broadcast. - _ = <-s.sigmsg - } - - // Expect a compact cert to be formed. - for { - tx := <-s.txmsg - require.Equal(t, tx.Txn.Type, protocol.CompactCertTx) - if tx.Txn.CertRound < basics.Round(iter+2)*basics.Round(proto.CompactCertRounds) { - continue - } - - require.Equal(t, tx.Txn.CertRound, basics.Round(iter+2)*basics.Round(proto.CompactCertRounds)) - - signedHdr, err := s.BlockHdr(tx.Txn.CertRound) - require.NoError(t, err) - - provenWeight, overflowed := basics.Muldiv(uint64(s.totalWeight), uint64(proto.CompactCertWeightThreshold), 1<<32) - require.False(t, overflowed) - - ccparams := compactcert.Params{ - Msg: signedHdr, - ProvenWeight: provenWeight, - SigRound: basics.Round(signedHdr.Round), - SecKQ: proto.CompactCertSecKQ, - } - - voters, err := s.CompactCertVoters(tx.Txn.CertRound - basics.Round(proto.CompactCertRounds) - basics.Round(proto.CompactCertVotersLookback)) - require.NoError(t, err) - - verif := compactcert.MkVerifier(ccparams, voters.Tree.Root()) - err = verif.Verify(&tx.Txn.Cert) - require.NoError(t, err) - break - } - } -} - -func TestWorkerPartialSigs(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 7; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, 10) - w := newTestWorker(t, s) - w.Start() - defer w.Shutdown() - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(proto.CompactCertRounds + proto.CompactCertRounds/2) - s.advanceLatest(proto.CompactCertRounds) - - for i := 0; i < len(keys); i++ { - // Expect all signatures to be broadcast. - _ = <-s.sigmsg - } - - // No compact cert should be formed yet: not enough sigs for a cert this early. - select { - case <-s.txmsg: - t.Fatal("compact cert formed too early") - case <-time.After(time.Second): - } - - // Expect a compact cert to be formed in the next CompactCertRounds/2. - s.advanceLatest(proto.CompactCertRounds / 2) - tx := <-s.txmsg - require.Equal(t, tx.Txn.Type, protocol.CompactCertTx) - require.Equal(t, tx.Txn.CertRound, 2*basics.Round(proto.CompactCertRounds)) - - signedHdr, err := s.BlockHdr(tx.Txn.CertRound) - require.NoError(t, err) - - provenWeight, overflowed := basics.Muldiv(uint64(s.totalWeight), uint64(proto.CompactCertWeightThreshold), 1<<32) - require.False(t, overflowed) - - ccparams := compactcert.Params{ - Msg: signedHdr, - ProvenWeight: provenWeight, - SigRound: basics.Round(signedHdr.Round), - SecKQ: proto.CompactCertSecKQ, - } - - voters, err := s.CompactCertVoters(tx.Txn.CertRound - basics.Round(proto.CompactCertRounds) - basics.Round(proto.CompactCertVotersLookback)) - require.NoError(t, err) - - verif := compactcert.MkVerifier(ccparams, voters.Tree.Root()) - err = verif.Verify(&tx.Txn.Cert) - require.NoError(t, err) -} - -func TestWorkerInsufficientSigs(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 2; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, 10) - w := newTestWorker(t, s) - w.Start() - defer w.Shutdown() - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(3 * proto.CompactCertRounds) - - for i := 0; i < len(keys); i++ { - // Expect all signatures to be broadcast. - _ = <-s.sigmsg - } - - // No compact cert should be formed: not enough sigs. - select { - case <-s.txmsg: - t.Fatal("compact cert formed without enough sigs") - case <-time.After(time.Second): - } -} - -func TestLatestSigsFromThisNode(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 10; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, 10) - w := newTestWorker(t, s) - w.Start() - defer w.Shutdown() - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(3*proto.CompactCertRounds - 2) - - // Wait for a compact cert to be formed, so we know the signer thread is caught up. - _ = <-s.txmsg - - var latestSigs map[basics.Address]basics.Round - var err error - for x := 0; x < 10; x++ { - latestSigs, err = w.LatestSigsFromThisNode() - require.NoError(t, err) - if len(latestSigs) == len(keys) { - break - } - time.Sleep(256 * time.Millisecond) - } - require.Equal(t, len(keys), len(latestSigs)) - for _, k := range keys { - require.Equal(t, latestSigs[k.Parent], basics.Round(2*proto.CompactCertRounds)) - } - - // Add a block that claims the compact cert is formed. - s.mu.Lock() - s.addBlock(3 * basics.Round(proto.CompactCertRounds)) - s.mu.Unlock() - - // Wait for the builder to discard the signatures. - for x := 0; x < 10; x++ { - latestSigs, err = w.LatestSigsFromThisNode() - require.NoError(t, err) - if len(latestSigs) == 0 { - break - } - time.Sleep(256 * time.Millisecond) - } - require.Equal(t, 0, len(latestSigs)) -} - -func TestWorkerRestart(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 10; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, 10) - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(3*proto.CompactCertRounds - 1) - - dbRand := crypto.RandUint64() - - formedAt := -1 - for i := 0; formedAt < 0 && i < len(keys); i++ { - // Give one key at a time to the worker, and then shut it down, - // to make sure that it will correctly save and restore these - // signatures across restart. - s.keys = keys[i : i+1] - dbs, _ := dbOpenTestRand(t, true, dbRand) - w := newTestWorkerDB(t, s, dbs.Wdb) - w.Start() - - // Check if the cert formed - select { - case <-s.txmsg: - formedAt = i - case <-time.After(time.Second): - } - - w.Shutdown() - } - - require.True(t, formedAt > 1) - require.True(t, formedAt < 5) -} - -func TestWorkerHandleSig(t *testing.T) { - partitiontest.PartitionTest(t) - - var keys []account.Participation - for i := 0; i < 2; i++ { - var parent basics.Address - crypto.RandBytes(parent[:]) - p := newPartKey(t, parent) - defer p.Close() - keys = append(keys, p.Participation) - } - - s := newWorkerStubs(t, keys, 10) - w := newTestWorker(t, s) - w.Start() - defer w.Shutdown() - - proto := config.Consensus[protocol.ConsensusFuture] - s.advanceLatest(3 * proto.CompactCertRounds) - - for i := 0; i < len(keys); i++ { - // Expect all signatures to be broadcast. - msg := <-s.sigmsg - res := w.handleSigMessage(network.IncomingMessage{ - Data: msg, - }) - - // This should be a dup signature, so should not be broadcast - // but also not disconnected. - require.Equal(t, res.Action, network.Ignore) - } -} diff --git a/components/mocks/mockParticipationRegistry.go b/components/mocks/mockParticipationRegistry.go index d2aa53f269..d7a53c36f0 100644 --- a/components/mocks/mockParticipationRegistry.go +++ b/components/mocks/mockParticipationRegistry.go @@ -44,6 +44,11 @@ func (m *MockParticipationRegistry) Delete(id account.ParticipationID) error { return nil } +// DeleteStateProofKeys removes all stateproof keys preceding a given round (including) +func (m *MockParticipationRegistry) DeleteStateProofKeys(id account.ParticipationID, round basics.Round) error { + return nil +} + // DeleteExpired removes all records from storage which are expired on the given round. func (m *MockParticipationRegistry) DeleteExpired(latestRound basics.Round, agreementProto config.ConsensusParams) error { return nil @@ -64,9 +69,9 @@ func (m *MockParticipationRegistry) GetForRound(id account.ParticipationID, roun return account.ParticipationRecordForRound{}, nil } -// GetStateProofForRound fetches a record with stateproof secrets for a particular round. -func (m *MockParticipationRegistry) GetStateProofForRound(id account.ParticipationID, round basics.Round) (account.StateProofRecordForRound, error) { - return account.StateProofRecordForRound{}, nil +// GetStateProofSecretsForRound fetches a record with stateproof secrets for a particular round. +func (m *MockParticipationRegistry) GetStateProofSecretsForRound(id account.ParticipationID, round basics.Round) (account.StateProofSecretsForRound, error) { + return account.StateProofSecretsForRound{}, nil } // HasLiveKeys quickly tests to see if there is a valid participation key over some range of rounds diff --git a/config/config.go b/config/config.go index 4c16801302..023561f683 100644 --- a/config/config.go +++ b/config/config.go @@ -61,9 +61,9 @@ const LedgerFilenamePrefix = "ledger" // It is used to recover from node crashes. const CrashFilename = "crash.sqlite" -// CompactCertFilename is the name of the compact certificate database file. -// It is used to track in-progress compact certificates. -const CompactCertFilename = "compactcert.sqlite" +// StateProofFileName is the name of the state proof database file. +// It is used to track in-progress state proofs. +const StateProofFileName = "stateproof.sqlite" // ParticipationRegistryFilename is the name of the participation registry database file. // It is used for tracking participation key metadata. diff --git a/config/consensus.go b/config/consensus.go index 8cb7d5f0a3..5896f97d99 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -344,50 +344,53 @@ type ConsensusParams struct { // to limit the maximum size of a single balance record MaximumMinimumBalance uint64 - // CompactCertRounds defines the frequency with which compact - // certificates are generated. Every round that is a multiple - // of CompactCertRounds, the block header will include a Merkle + // StateProofInterval defines the frequency with which state + // proofs are generated. Every round that is a multiple + // of StateProofInterval, the block header will include a vector // commitment to the set of online accounts (that can vote after - // another CompactCertRounds rounds), and that block will be signed - // (forming a compact certificate) by the voters from the previous - // such Merkle tree commitment. A value of zero means no compact - // certificates. - CompactCertRounds uint64 - - // CompactCertTopVoters is a bound on how many online accounts get to - // participate in forming the compact certificate, by including the - // top CompactCertTopVoters accounts (by normalized balance) into the - // Merkle commitment. - CompactCertTopVoters uint64 - - // CompactCertVotersLookback is the number of blocks we skip before - // publishing a Merkle commitment to the online accounts. Namely, - // if block number N contains a Merkle commitment to the online - // accounts (which, incidentally, means N%CompactCertRounds=0), + // another StateProofInterval rounds), and that block will be signed + // (forming a state proof) by the voters from the previous + // such vector commitment. A value of zero means no state proof. + StateProofInterval uint64 + + // StateProofTopVoters is a bound on how many online accounts get to + // participate in forming the state proof, by including the + // top StateProofTopVoters accounts (by normalized balance) into the + // vector commitment. + StateProofTopVoters uint64 + + // StateProofVotersLookback is the number of blocks we skip before + // publishing a vector commitment to the online accounts. Namely, + // if block number N contains a vector commitment to the online + // accounts (which, incidentally, means N%StateProofInterval=0), // then the balances reflected in that commitment must come from - // block N-CompactCertVotersLookback. This gives each node some - // time (CompactCertVotersLookback blocks worth of time) to - // construct this Merkle tree, so as to avoid placing the - // construction of this Merkle tree (and obtaining the requisite + // block N-StateProofVotersLookback. This gives each node some + // time (StateProofVotersLookback blocks worth of time) to + // construct this vector commitment, so as to avoid placing the + // construction of this vector commitment (and obtaining the requisite // accounts and balances) in the critical path. - CompactCertVotersLookback uint64 + StateProofVotersLookback uint64 - // CompactCertWeightThreshold specifies the fraction of top voters weight - // that must sign the message (block header) for security. The compact - // certificate ensures this threshold holds; however, forming a valid - // compact certificate requires a somewhat higher number of signatures, - // and the more signatures are collected, the smaller the compact cert + // StateProofWeightThreshold specifies the fraction of top voters weight + // that must sign the message (block header) for security. The state + // proof ensures this threshold holds; however, forming a valid + // state proof requires a somewhat higher number of signatures, + // and the more signatures are collected, the smaller the state proof // can be. // // This threshold can be thought of as the maximum fraction of - // malicious weight that compact certificates defend against. + // malicious weight that state proofs defend against. // - // The threshold is computed as CompactCertWeightThreshold/(1<<32). - CompactCertWeightThreshold uint32 + // The threshold is computed as StateProofWeightThreshold/(1<<32). + StateProofWeightThreshold uint32 - // CompactCertSecKQ is the security parameter (k+q) for the compact - // certificate scheme. - CompactCertSecKQ uint64 + // StateProofStrengthTarget represents either k+q (for pre-quantum security) or k+2q (for post-quantum security) + StateProofStrengthTarget uint64 + + // StateProofMaxRecoveryIntervals represents the number of state proof intervals that the network will try to catch-up with. + // When the difference between the latest state proof and the current round will be greater than value, Nodes will + // release resources allocated for creating state proofs. + StateProofMaxRecoveryIntervals uint64 // EnableAssetCloseAmount adds an extra field to the ApplyData. The field contains the amount of the remaining // asset that were sent to the close-to address. @@ -1158,12 +1161,13 @@ func initConsensusProtocols() { // Require MaxTxnLife + X blocks and headers preserved by a node vFuture.DeeperBlockHeaderHistory = 1 - // Enable compact certificates. - vFuture.CompactCertRounds = 256 - vFuture.CompactCertTopVoters = 1024 * 1024 - vFuture.CompactCertVotersLookback = 16 - vFuture.CompactCertWeightThreshold = (1 << 32) * 30 / 100 - vFuture.CompactCertSecKQ = 128 + // Enable state proofs. + vFuture.StateProofInterval = 256 + vFuture.StateProofTopVoters = 1024 + vFuture.StateProofVotersLookback = 16 + vFuture.StateProofWeightThreshold = (1 << 32) * 30 / 100 + vFuture.StateProofStrengthTarget = 256 + vFuture.StateProofMaxRecoveryIntervals = 10 vFuture.LogicSigVersion = 7 // When moving this to a release, put a new higher LogicSigVersion here vFuture.MinInnerApplVersion = 4 diff --git a/config/consensus_test.go b/config/consensus_test.go index 92edf17981..dd77ed499a 100644 --- a/config/consensus_test.go +++ b/config/consensus_test.go @@ -59,13 +59,13 @@ func TestConsensusUpgradeWindow(t *testing.T) { } } -func TestConsensusCompactCertParams(t *testing.T) { +func TestConsensusStateProofParams(t *testing.T) { partitiontest.PartitionTest(t) for _, params := range Consensus { - if params.CompactCertRounds != 0 { - require.Equal(t, uint64(1<<16), (params.MaxKeyregValidPeriod+1)/params.CompactCertRounds, - "Validity period divided by CompactCertRounds should allow for no more than %d generated keys", 1<<16) + if params.StateProofInterval != 0 { + require.Equal(t, uint64(1<<16), (params.MaxKeyregValidPeriod+1)/params.StateProofInterval, + "Validity period divided by StateProofInterval should allow for no more than %d generated keys", 1<<16) } } } diff --git a/crypto/compactcert/bigfloat.go b/crypto/compactcert/bigfloat.go deleted file mode 100644 index eb4004736c..0000000000 --- a/crypto/compactcert/bigfloat.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "fmt" - "math/bits" -) - -// A bigFloat represents the number mantissa*2^exp, which must be non-zero. -// -// A canonical representation is one where the highest bit of mantissa is -// set. Every operation enforces canonicality of results. -// -// We use 32-bit values here to avoid requiring a 64bit-by-64bit-to-128bit -// multiply operation for anyone that needs to implement this (even though -// Go has this operation, as bits.Mul64). -type bigFloat struct { - mantissa uint32 - exp int32 -} - -// Each bigFloat is associated with a rounding mode (up, away from zero, or -// down, towards zero). This is reflected by these two types of bigFloat. -type bigFloatUp struct { - bigFloat -} - -type bigFloatDn struct { - bigFloat -} - -// canonicalize() ensures that the bigFloat is canonical. -func (a *bigFloat) canonicalize() { - if a.mantissa == 0 { - // Just to avoid infinite loops in some error case. - return - } - - for (a.mantissa & (1 << 31)) == 0 { - a.mantissa = a.mantissa << 1 - a.exp = a.exp - 1 - } -} - -// doRoundUp adds one to the mantissa of a canonical bigFloat -// to implement the rounding-up when there are leftover low bits. -func (a *bigFloatUp) doRoundUp() { - if a.mantissa == (1<<32)-1 { - a.mantissa = 1 << 31 - a.exp++ - } else { - a.mantissa++ - } -} - -// geRaw returns whether a>=b. The Raw suffix indicates that -// this comparison does not take rounding into account, and might -// not be true if done with arbitrary-precision numbers. -func (a *bigFloat) geRaw(b *bigFloat) bool { - if a.exp > b.exp { - return true - } - - if a.exp < b.exp { - return false - } - - return a.mantissa >= b.mantissa -} - -// ge returns whether a>=b. It requires that a was computed with -// rounding-down and b was computed with rounding-up, so that if -// ge returns true, the arbitrary-precision computation would have -// also been >=. -func (a *bigFloatDn) ge(b *bigFloatUp) bool { - return a.geRaw(&b.bigFloat) -} - -// setu64Dn sets the value to the supplied uint64 (which might get -// rounded down in the process). x must not be zero. truncated -// returns whether any non-zero bits were truncated (rounded down). -func (a *bigFloat) setu64Dn(x uint64) (truncated bool, err error) { - if x == 0 { - return false, fmt.Errorf("bigFloat cannot be zero") - } - - e := int32(0) - - for x >= (1 << 32) { - if (x & 1) != 0 { - truncated = true - } - - x = x >> 1 - e = e + 1 - } - - a.mantissa = uint32(x) - a.exp = e - a.canonicalize() - return -} - -// setu64 calls setu64Dn and implements rounding based on the type. -func (a *bigFloatUp) setu64(x uint64) error { - truncated, err := a.setu64Dn(x) - if truncated { - a.doRoundUp() - } - return err -} - -func (a *bigFloatDn) setu64(x uint64) error { - _, err := a.setu64Dn(x) - return err -} - -// setu32 sets the value to the supplied uint32. -func (a *bigFloat) setu32(x uint32) error { - if x == 0 { - return fmt.Errorf("bigFloat cannot be zero") - } - - a.mantissa = x - a.exp = 0 - a.canonicalize() - return nil -} - -// setpow2 sets the value to 2^x. -func (a *bigFloat) setpow2(x int32) { - a.mantissa = 1 - a.exp = x - a.canonicalize() -} - -// mulDn sets a to the product a*b, keeping the most significant 32 bits -// of the product's mantissa. The return value indicates if any non-zero -// bits were discarded (rounded down). -func (a *bigFloat) mulDn(b *bigFloat) bool { - hi, lo := bits.Mul32(a.mantissa, b.mantissa) - - a.mantissa = hi - a.exp = a.exp + b.exp + 32 - - if (a.mantissa & (1 << 31)) == 0 { - a.mantissa = (a.mantissa << 1) | (lo >> 31) - a.exp = a.exp - 1 - lo = lo << 1 - } - - return lo != 0 -} - -// mul calls mulDn and implements appropriate rounding. -// Types prevent multiplying two values with different rounding types. -func (a *bigFloatUp) mul(b *bigFloatUp) { - truncated := a.mulDn(&b.bigFloat) - if truncated { - a.doRoundUp() - } -} - -func (a *bigFloatDn) mul(b *bigFloatDn) { - a.mulDn(&b.bigFloat) -} - -// String returns a string representation of a. -func (a *bigFloat) String() string { - return fmt.Sprintf("%d*2^%d", a.mantissa, a.exp) -} diff --git a/crypto/compactcert/bigfloat_test.go b/crypto/compactcert/bigfloat_test.go deleted file mode 100644 index 12efcf38b2..0000000000 --- a/crypto/compactcert/bigfloat_test.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func rand32() uint32 { - return uint32(crypto.RandUint64() & 0xffffffff) -} - -func TestBigFloatRounding(t *testing.T) { - partitiontest.PartitionTest(t) - - a := &bigFloatDn{} - b := &bigFloatUp{} - - a.setu64(1 << 63) - b.setu64(1 << 63) - - require.True(t, a.geRaw(&b.bigFloat)) - require.True(t, b.geRaw(&a.bigFloat)) - - a.mul(a) - b.mul(b) - - require.True(t, a.geRaw(&b.bigFloat)) - require.True(t, b.geRaw(&a.bigFloat)) - - a.setu64((1 << 64) - 1) - b.setu64((1 << 64) - 1) - - require.False(t, a.geRaw(&b.bigFloat)) - require.True(t, b.geRaw(&a.bigFloat)) - - a.setu32((1 << 32) - 1) - b.setu32((1 << 32) - 1) - - a.mul(a) - b.mul(b) - - require.False(t, a.geRaw(&b.bigFloat)) - require.True(t, b.geRaw(&a.bigFloat)) -} - -func TestBigFloat(t *testing.T) { - partitiontest.PartitionTest(t) - - a := &bigFloatDn{} - b := &bigFloatDn{} - - a.setu64(1) - require.Equal(t, a.mantissa, uint32(1<<31)) - require.Equal(t, a.exp, int32(-31)) - - a.setu32(1) - require.Equal(t, a.mantissa, uint32(1<<31)) - require.Equal(t, a.exp, int32(-31)) - - for i := int32(-256); i < 256; i++ { - a.setpow2(i) - require.Equal(t, a.mantissa, uint32(1<<31)) - require.Equal(t, a.exp, i-31) - } - - for i := 0; i < 8192; i++ { - x := rand32() - a.setu32(x) - require.True(t, a.exp <= 0) - require.Equal(t, x, a.mantissa>>(-a.exp)) - } - - for i := 0; i < 8192; i++ { - x := uint64(rand32()) - a.setu64(x) - if a.exp <= 0 { - require.Equal(t, x, uint64(a.mantissa>>(-a.exp))) - } - if a.exp >= 0 { - require.Equal(t, x>>a.exp, uint64(a.mantissa)) - } - } - - for i := 0; i < 8192; i++ { - x := crypto.RandUint64() - a.setu64(x) - if a.exp <= 0 { - require.Equal(t, x, uint64(a.mantissa>>(-a.exp))) - } - if a.exp >= 0 { - require.Equal(t, x>>a.exp, uint64(a.mantissa)) - } - } - - for i := 0; i < 8192; i++ { - x := rand32() - y := rand32() - a.setu64(uint64(x)) - b.setu64(uint64(y)) - - require.Equal(t, x >= y, a.geRaw(&b.bigFloat)) - require.Equal(t, x < y, b.geRaw(&a.bigFloat)) - require.True(t, a.geRaw(&a.bigFloat)) - require.True(t, b.geRaw(&b.bigFloat)) - } - - xx := &big.Int{} - yy := &big.Int{} - - for i := 0; i < 8192; i++ { - x := rand32() - y := rand32() - a.setu64(uint64(x)) - b.setu64(uint64(y)) - a.mul(b) - - xx.SetUint64(uint64(x)) - yy.SetUint64(uint64(y)) - xx.Mul(xx, yy) - if a.exp > 0 { - xx.Rsh(xx, uint(a.exp)) - } - if a.exp < 0 { - xx.Lsh(xx, uint(-a.exp)) - } - require.Equal(t, a.mantissa, uint32(xx.Uint64())) - } -} - -func BenchmarkBigFloatMulUp(b *testing.B) { - a := &bigFloatUp{} - a.setu32((1 << 32) - 1) - - for i := 0; i < b.N; i++ { - a.mul(a) - } -} - -func BenchmarkBigFloatMulDn(b *testing.B) { - a := &bigFloatDn{} - a.setu32((1 << 32) - 1) - - for i := 0; i < b.N; i++ { - a.mul(a) - } -} diff --git a/crypto/compactcert/builder.go b/crypto/compactcert/builder.go deleted file mode 100644 index 5ebacba03d..0000000000 --- a/crypto/compactcert/builder.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "fmt" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/merklearray" - "github.com/algorand/go-algorand/crypto/merklesignature" - "github.com/algorand/go-algorand/data/basics" -) - -//msgp:ignore sigslot -type sigslot struct { - // Weight is the weight of the participant signing this message. - // This information is tracked here for convenience, but it does - // not appear in the commitment to the sigs array; it comes from - // the Weight field of the corresponding participant. - Weight uint64 - - // Include the parts of the sigslot that form the commitment to - // the sigs array. - sigslotCommit -} - -// Builder keeps track of signatures on a message and eventually produces -// a compact certificate for that message. -type Builder struct { - Params - - sigs []sigslot // Indexed by pos in participants - sigsHasValidL bool // The L values in sigs are consistent with weights - signedWeight uint64 // Total weight of signatures so far - participants []basics.Participant - parttree *merklearray.Tree - - // Cached cert, if Build() was called and no subsequent - // Add() calls were made. - cert *Cert -} - -// MkBuilder constructs an empty builder (with no signatures). The message -// to be signed, as well as other security parameters, are specified in -// param. The participants that will sign the message are in part and -// parttree. -func MkBuilder(param Params, part []basics.Participant, parttree *merklearray.Tree) (*Builder, error) { - npart := len(part) - - b := &Builder{ - Params: param, - sigs: make([]sigslot, npart), - sigsHasValidL: false, - signedWeight: 0, - participants: part, - parttree: parttree, - } - - return b, nil -} - -// Present checks if the builder already contains a signature at a particular -// offset. -func (b *Builder) Present(pos uint64) bool { - return b.sigs[pos].Weight != 0 -} - -// Add a signature to the set of signatures available for building a certificate. -// verifySig should be set to true in production; setting it to false is useful -// for benchmarking to avoid the cost of signature checks. -func (b *Builder) Add(pos uint64, sig merklesignature.Signature, verifySig bool) error { - if b.Present(pos) { - return fmt.Errorf("position %d already added", pos) - } - - // Check participants array - if pos >= uint64(len(b.participants)) { - return fmt.Errorf("pos %d >= len(participants) %d", pos, len(b.participants)) - } - - p := b.participants[pos] - - if p.Weight == 0 { - return fmt.Errorf("position %d has zero weight", pos) - } - - // Check signature - if verifySig { - if err := p.PK.Verify(uint64(b.SigRound), b.Msg, sig); err != nil { - return err - } - } - - // Remember the signature - b.sigs[pos].Weight = p.Weight - b.sigs[pos].Sig.Signature = sig - b.signedWeight += p.Weight - b.cert = nil - b.sigsHasValidL = false - return nil -} - -// Ready returns whether the certificate is ready to be built. -func (b *Builder) Ready() bool { - return b.signedWeight > b.Params.ProvenWeight -} - -// SignedWeight returns the total weight of signatures added so far. -func (b *Builder) SignedWeight() uint64 { - return b.signedWeight -} - -// coinIndex returns the position pos in the sigs array such that the sum -// of all signature weights before pos is less than or equal to coinWeight, -// but the sum of all signature weights up to and including pos exceeds -// coinWeight. -// -// coinIndex works by doing a binary search on the sigs array. -func (b *Builder) coinIndex(coinWeight uint64) (uint64, error) { - if !b.sigsHasValidL { - return 0, fmt.Errorf("coinIndex: need valid L values") - } - - lo := uint64(0) - hi := uint64(len(b.sigs)) - -again: - if lo >= hi { - return 0, fmt.Errorf("coinIndex: lo %d >= hi %d", lo, hi) - } - - mid := (lo + hi) / 2 - if coinWeight < b.sigs[mid].L { - hi = mid - goto again - } - - if coinWeight < b.sigs[mid].L+b.sigs[mid].Weight { - return mid, nil - } - - lo = mid + 1 - goto again -} - -// Build returns a compact certificate, if the builder has accumulated -// enough signatures to construct it. -func (b *Builder) Build() (*Cert, error) { - if b.cert != nil { - return b.cert, nil - } - - if b.signedWeight <= b.Params.ProvenWeight { - return nil, fmt.Errorf("not enough signed weight: %d <= %d", b.signedWeight, b.Params.ProvenWeight) - } - - // Commit to the sigs array - for i := 1; i < len(b.sigs); i++ { - b.sigs[i].L = b.sigs[i-1].L + b.sigs[i-1].Weight - } - b.sigsHasValidL = true - - hfactory := crypto.HashFactory{HashType: HashType} - sigtree, err := merklearray.BuildVectorCommitmentTree(committableSignatureSlotArray(b.sigs), hfactory) - if err != nil { - return nil, err - } - - // Reveal sufficient number of signatures - c := &Cert{ - SigCommit: sigtree.Root(), - SignedWeight: b.signedWeight, - Reveals: make(map[uint64]Reveal), - } - - nr, err := b.numReveals(b.signedWeight) - if err != nil { - return nil, err - } - - var proofPositions []uint64 - msgHash := crypto.GenericHashObj(hfactory.NewHash(), b.Msg) - - for j := uint64(0); j < nr; j++ { - choice := coinChoice{ - J: j, - SignedWeight: c.SignedWeight, - ProvenWeight: b.ProvenWeight, - Sigcom: c.SigCommit, - Partcom: b.parttree.Root(), - MsgHash: msgHash, - } - - coin := hashCoin(choice) - pos, err := b.coinIndex(coin) - if err != nil { - return nil, err - } - - if pos >= uint64(len(b.participants)) { - return nil, fmt.Errorf("pos %d >= len(participants) %d", pos, len(b.participants)) - } - - // If we already revealed pos, no need to do it again - _, alreadyRevealed := c.Reveals[pos] - if alreadyRevealed { - continue - } - - // Generate the reveal for pos - c.Reveals[pos] = Reveal{ - SigSlot: b.sigs[pos].sigslotCommit, - Part: b.participants[pos], - } - - proofPositions = append(proofPositions, pos) - } - - sigProofs, err := sigtree.Prove(proofPositions) - if err != nil { - return nil, err - } - - partProofs, err := b.parttree.Prove(proofPositions) - if err != nil { - return nil, err - } - - c.SigProofs = *sigProofs - c.PartProofs = *partProofs - - return c, nil -} diff --git a/crypto/compactcert/common.go b/crypto/compactcert/common.go deleted file mode 100644 index f1f3598510..0000000000 --- a/crypto/compactcert/common.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "fmt" - "math/big" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/protocol" -) - -// The coinChoice type defines the fields that go into the hash for choosing -// the index of the coin to reveal as part of the compact certificate. -type coinChoice struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - - J uint64 `codec:"j"` - SignedWeight uint64 `codec:"sigweight"` - ProvenWeight uint64 `codec:"provenweight"` - Sigcom crypto.GenericDigest `codec:"sigcom"` - Partcom crypto.GenericDigest `codec:"partcom"` - MsgHash crypto.GenericDigest `codec:"msghash"` -} - -// ToBeHashed implements the crypto.Hashable interface. -func (cc coinChoice) ToBeHashed() (protocol.HashID, []byte) { - return protocol.CompactCertCoin, protocol.Encode(&cc) -} - -// hashCoin returns a number in [0, choice.SignedWeight) with a nearly uniform -// distribution, "randomized" by all of the fields in choice. -func hashCoin(choice coinChoice) uint64 { - h := crypto.HashObj(choice) - - i := &big.Int{} - i.SetBytes(h[:]) - - w := &big.Int{} - w.SetUint64(choice.SignedWeight) - - res := &big.Int{} - res.Mod(i, w) - return res.Uint64() -} - -// numReveals computes the number of reveals necessary to achieve the desired -// security parameters. See section 8 of the ``Compact Certificates'' -// document for the analysis. -// -// numReveals is the smallest number that satisfies -// -// 2^-k >= 2^q * (provenWeight / signedWeight) ^ numReveals -// -// which is equivalent to the following: -// -// signedWeight ^ numReveals >= 2^(k+q) * provenWeight ^ numReveals -// -// To ensure that rounding errors do not reduce the security parameter, -// we compute the left-hand side with rounding-down, and compute the -// right-hand side with rounding-up. -func numReveals(signedWeight uint64, provenWeight uint64, secKQ uint64, bound uint64) (uint64, error) { - n := uint64(0) - - sw := &bigFloatDn{} - err := sw.setu64(signedWeight) - if err != nil { - return 0, err - } - - pw := &bigFloatUp{} - err = pw.setu64(provenWeight) - if err != nil { - return 0, err - } - - lhs := &bigFloatDn{} - err = lhs.setu64(1) - if err != nil { - return 0, err - } - - rhs := &bigFloatUp{} - rhs.setpow2(int32(secKQ)) - - for { - if lhs.ge(rhs) { - return n, nil - } - - if n >= bound { - return 0, fmt.Errorf("numReveals(%d, %d, %d) > %d", signedWeight, provenWeight, secKQ, bound) - } - - lhs.mul(sw) - rhs.mul(pw) - n++ - } -} - -func (p Params) numReveals(signedWeight uint64) (uint64, error) { - return numReveals(signedWeight, p.ProvenWeight, p.SecKQ, MaxReveals) -} diff --git a/crypto/compactcert/common_test.go b/crypto/compactcert/common_test.go deleted file mode 100644 index 73e71b893b..0000000000 --- a/crypto/compactcert/common_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "testing" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func TestHashCoin(t *testing.T) { - partitiontest.PartitionTest(t) - - var slots [32]uint64 - var sigcom = make(crypto.GenericDigest, HashSize) - var partcom = make(crypto.GenericDigest, HashSize) - var msgHash = make(crypto.GenericDigest, HashSize) - - crypto.RandBytes(sigcom[:]) - crypto.RandBytes(partcom[:]) - crypto.RandBytes(msgHash[:]) - - for j := uint64(0); j < 1000; j++ { - choice := coinChoice{ - J: j, - SignedWeight: uint64(len(slots)), - ProvenWeight: uint64(len(slots)), - Sigcom: sigcom, - Partcom: partcom, - MsgHash: msgHash, - } - - coin := hashCoin(choice) - if coin >= uint64(len(slots)) { - t.Errorf("hashCoin out of bounds") - } - - slots[coin]++ - } - - for i, count := range slots { - if count < 3 { - t.Errorf("slot %d too low: %d", i, count) - } - if count > 100 { - t.Errorf("slot %d too high: %d", i, count) - } - } -} - -func BenchmarkHashCoin(b *testing.B) { - var sigcom = make(crypto.GenericDigest, HashSize) - var partcom = make(crypto.GenericDigest, HashSize) - var msgHash = make(crypto.GenericDigest, HashSize) - - crypto.RandBytes(sigcom[:]) - crypto.RandBytes(partcom[:]) - crypto.RandBytes(msgHash[:]) - - for i := 0; i < b.N; i++ { - choice := coinChoice{ - J: uint64(i), - SignedWeight: 1024, - ProvenWeight: 1024, - Sigcom: sigcom, - Partcom: partcom, - MsgHash: msgHash, - } - - hashCoin(choice) - } -} - -func TestNumReveals(t *testing.T) { - partitiontest.PartitionTest(t) - - billion := uint64(1000 * 1000 * 1000) - microalgo := uint64(1000 * 1000) - provenWeight := 2 * billion * microalgo - secKQ := uint64(compactCertSecKQForTests) - bound := uint64(1000) - - for i := uint64(3); i < 10; i++ { - signedWeight := i * billion * microalgo - n, err := numReveals(signedWeight, provenWeight, secKQ, bound) - if err != nil { - t.Error(err) - } - - if n < 50 || n > 300 { - t.Errorf("numReveals(%d, %d, %d) = %d looks suspect", - signedWeight, provenWeight, secKQ, n) - } - } -} - -func BenchmarkNumReveals(b *testing.B) { - billion := uint64(1000 * 1000 * 1000) - microalgo := uint64(1000 * 1000) - provenWeight := 100 * billion * microalgo - signedWeight := 110 * billion * microalgo - secKQ := uint64(compactCertSecKQForTests) - bound := uint64(1000) - - nr, err := numReveals(signedWeight, provenWeight, secKQ, bound) - if nr < 900 { - b.Errorf("numReveals(%d, %d, %d) = %d < 900", signedWeight, provenWeight, secKQ, nr) - } - - for i := 0; i < b.N; i++ { - _, err = numReveals(signedWeight, provenWeight, secKQ, bound) - if err != nil { - b.Error(err) - } - } -} diff --git a/crypto/compactcert/const.go b/crypto/compactcert/const.go deleted file mode 100644 index 53133205b2..0000000000 --- a/crypto/compactcert/const.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "github.com/algorand/go-algorand/crypto" -) - -// HashType/ hashSize relate to the type of hash this package uses. -const ( - HashType = crypto.Sumhash - HashSize = crypto.SumhashDigestSize -) - -const ( - // MaxReveals is a bound on allocation and on numReveals to limit log computation - MaxReveals = 1024 -) diff --git a/crypto/compactcert/verifier.go b/crypto/compactcert/verifier.go deleted file mode 100644 index 5d9819a37d..0000000000 --- a/crypto/compactcert/verifier.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "fmt" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/merklearray" -) - -// Verifier is used to verify a compact certificate. -type Verifier struct { - Params - - partcom crypto.GenericDigest -} - -// MkVerifier constructs a verifier to check the compact certificate -// on the message specified in p, with partcom specifying the Merkle -// root of the participants that must sign the message. -func MkVerifier(p Params, partcom crypto.GenericDigest) *Verifier { - return &Verifier{ - Params: p, - partcom: partcom, - } -} - -// Verify checks if c is a valid compact certificate for the message -// and participants that were used to construct the Verifier. -func (v *Verifier) Verify(c *Cert) error { - if c.SignedWeight <= v.ProvenWeight { - return fmt.Errorf("cert signed weight %d <= proven weight %d", c.SignedWeight, v.ProvenWeight) - } - - sigs := make(map[uint64]crypto.Hashable) - parts := make(map[uint64]crypto.Hashable) - for pos, r := range c.Reveals { - sig, err := buildCommittableSignature(r.SigSlot) - if err != nil { - return err - } - - sigs[pos] = sig - parts[pos] = r.Part - - // verify that the msg and the signature is valid under the given participant's Pk - err = r.Part.PK.Verify( - uint64(v.SigRound), - v.Msg, - r.SigSlot.Sig.Signature) - - if err != nil { - return fmt.Errorf("signature in reveal pos %d does not verify. error is %s", pos, err) - } - } - - // verify all the reveals proofs on the signature tree. - if err := merklearray.VerifyVectorCommitment(c.SigCommit[:], sigs, &c.SigProofs); err != nil { - return err - } - - // verify all the reveals proofs on the participant tree. - if err := merklearray.VerifyVectorCommitment(v.partcom[:], parts, &c.PartProofs); err != nil { - return err - } - - // Verify that the reveals contain the right coins - nr, err := v.numReveals(c.SignedWeight) - if err != nil { - return err - } - - msgHash := crypto.GenericHashObj(c.PartProofs.HashFactory.NewHash(), v.Msg) - - for j := uint64(0); j < nr; j++ { - choice := coinChoice{ - J: j, - SignedWeight: c.SignedWeight, - ProvenWeight: v.ProvenWeight, - Sigcom: c.SigCommit, - Partcom: v.partcom, - MsgHash: msgHash, - } - - coin := hashCoin(choice) - matchingReveal := false - for _, r := range c.Reveals { - if r.SigSlot.L <= coin && coin < r.SigSlot.L+r.Part.Weight { - matchingReveal = true - break - } - } - - if !matchingReveal { - return fmt.Errorf("no reveal for coin %d at %d", j, coin) - } - } - - return nil -} diff --git a/crypto/falconWrapper.go b/crypto/falconWrapper.go index 8dea1990e4..f24bc94339 100644 --- a/crypto/falconWrapper.go +++ b/crypto/falconWrapper.go @@ -102,13 +102,6 @@ func (d *FalconVerifier) GetFixedLengthHashableRepresentation() []byte { return d.PublicKey[:] } -// GetSignatureFixedLengthHashableRepresentation returns a serialized version of the signature -func (d *FalconVerifier) GetSignatureFixedLengthHashableRepresentation(signature FalconSignature) ([]byte, error) { - compressedSignature := cfalcon.CompressedSignature(signature) - ctSignature, err := compressedSignature.ConvertToCT() - return ctSignature[:], err -} - // NewFalconSigner creates a falconSigner that is used to sign and verify falcon signatures func NewFalconSigner() (*FalconSigner, error) { var seed FalconSeed @@ -119,3 +112,15 @@ func NewFalconSigner() (*FalconSigner, error) { } return &signer, nil } + +// GetFixedLengthHashableRepresentation returns a serialized version of the signature +func (s FalconSignature) GetFixedLengthHashableRepresentation() ([]byte, error) { + compressedSignature := cfalcon.CompressedSignature(s) + ctSignature, err := compressedSignature.ConvertToCT() + return ctSignature[:], err +} + +// IsSaltVersionEqual of the signature matches the given version +func (s FalconSignature) IsSaltVersionEqual(version byte) bool { + return (*cfalcon.CompressedSignature)(&s).SaltVersion() == version +} diff --git a/crypto/falconWrapper_test.go b/crypto/falconWrapper_test.go index b659ce56eb..20b5441eef 100644 --- a/crypto/falconWrapper_test.go +++ b/crypto/falconWrapper_test.go @@ -102,9 +102,28 @@ func TestFalconsFormatConversion(t *testing.T) { falconSig := falcon.CompressedSignature(sig) ctFormat, err := falconSig.ConvertToCT() - rawFormat, err := key.GetVerifyingKey().GetSignatureFixedLengthHashableRepresentation(sig) + rawFormat, err := sig.GetFixedLengthHashableRepresentation() a.NoError(err) a.NotEqual([]byte(sig), rawFormat) a.Equal(ctFormat[:], rawFormat) } + +func TestFalconSignature_ValidateVersion(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + msg := TestingHashable{data: []byte("Neque porro quisquam est qui dolorem ipsum quia dolor sit amet")} + var seed FalconSeed + SystemRNG.RandBytes(seed[:]) + key, err := GenerateFalconSigner(seed) + a.NoError(err) + + byteSig, err := key.Sign(msg) + a.NoError(err) + + a.True(byteSig.IsSaltVersionEqual(falcon.CurrentSaltVersion)) + + byteSig[1]++ + a.False(byteSig.IsSaltVersionEqual(falcon.CurrentSaltVersion)) +} diff --git a/crypto/merklearray/merkle.go b/crypto/merklearray/merkle.go index 4855a4c825..803eb5a25d 100644 --- a/crypto/merklearray/merkle.go +++ b/crypto/merklearray/merkle.go @@ -44,6 +44,7 @@ var ( ErrNonEmptyProofForEmptyElements = errors.New("non-empty proof for empty set of elements") ErrUnexpectedTreeDepth = errors.New("unexpected tree depth") ErrPosOutOfBound = errors.New("pos out of bound") + ErrProofLengthDigestSizeMismatch = errors.New("proof length and digest size mismatched") ) // Tree is a Merkle tree, represented by layers of nodes (hashes) in the tree diff --git a/crypto/merklearray/proof.go b/crypto/merklearray/proof.go index ed05b76e66..2670f69f15 100644 --- a/crypto/merklearray/proof.go +++ b/crypto/merklearray/proof.go @@ -17,6 +17,8 @@ package merklearray import ( + "fmt" + "github.com/algorand/go-algorand/crypto" ) @@ -83,7 +85,7 @@ func (p *SingleLeafProof) ToProof() *Proof { return &p.Proof } -// GetConcatenatedProof concats the verification path to a single slice +// GetConcatenatedProof concatenates the verification path to a single slice // This function converts an empty element in the path (i.e occurs when the tree is not a full tree) // into a sequence of digest result of zero. func (p *SingleLeafProof) GetConcatenatedProof() []byte { @@ -96,3 +98,33 @@ func (p *SingleLeafProof) GetConcatenatedProof() []byte { } return proofconcat } + +// ProofDataToSingleLeafProof receives serialized proof data and uses it to construct a proof object. +func ProofDataToSingleLeafProof(hashTypeData string, treeDepth uint64, proofBytes []byte) (SingleLeafProof, error) { + hashType, err := crypto.UnmarshalHashType(hashTypeData) + if err != nil { + return SingleLeafProof{}, err + } + + var proof SingleLeafProof + + proof.HashFactory = crypto.HashFactory{HashType: hashType} + proof.TreeDepth = uint8(treeDepth) + + digestSize := proof.HashFactory.NewHash().Size() + if len(proofBytes)%digestSize != 0 { + return SingleLeafProof{}, fmt.Errorf("proof bytes length is %d, which is not a multiple of "+ + "digest size %d: %w", len(proofBytes), digestSize, ErrProofLengthDigestSizeMismatch) + } + + var proofPath []crypto.GenericDigest + for len(proofBytes) > 0 { + d := make([]byte, digestSize) + copy(d[:], proofBytes) + proofPath = append(proofPath, d[:]) + proofBytes = proofBytes[len(d):] + } + + proof.Path = proofPath + return proof, nil +} diff --git a/crypto/merklearray/proof_test.go b/crypto/merklearray/proof_test.go index 547268515a..4645dccb26 100644 --- a/crypto/merklearray/proof_test.go +++ b/crypto/merklearray/proof_test.go @@ -148,13 +148,22 @@ func TestConcatenatedProofsMissingChild(t *testing.T) { p, err := tree.ProveSingleLeaf(6) a.NoError(err) - newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}} - - computedPath := recomputePath(p) + concatenatedProof := p.GetConcatenatedProof() + computedPath := recomputePath(concatenatedProof) + // verify that the concatenated proof can be verified correctly + newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}} newP.Path = computedPath err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, newP.ToProof()) a.NoError(err) + + recomputedProof, err := ProofDataToSingleLeafProof(p.HashFactory.HashType.String(), uint64(p.TreeDepth), concatenatedProof) + a.NoError(err) + + // verify that we can reconstruct the original singleLeafProof from the concatenated proof + err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, recomputedProof.ToProof()) + a.NoError(err) + } func TestConcatenatedProofsFullTree(t *testing.T) { @@ -172,13 +181,20 @@ func TestConcatenatedProofsFullTree(t *testing.T) { p, err := tree.ProveSingleLeaf(6) a.NoError(err) - newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}} - - computedPath := recomputePath(p) + concatenatedProof := p.GetConcatenatedProof() + computedPath := recomputePath(concatenatedProof) - newP.Path = computedPath + // verify that the concatenated proof can be verified correctly + newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: computedPath, HashFactory: p.HashFactory}} err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, newP.ToProof()) a.NoError(err) + + recomputedProof, err := ProofDataToSingleLeafProof(p.HashFactory.HashType.String(), uint64(p.TreeDepth), concatenatedProof) + a.NoError(err) + + // verify that we can reconstruct the original singleLeafProof from the concatenated proof + err = Verify(tree.Root(), map[uint64]crypto.Hashable{6: array[6]}, recomputedProof.ToProof()) + a.NoError(err) } func TestConcatenatedProofsOneLeaf(t *testing.T) { @@ -194,23 +210,37 @@ func TestConcatenatedProofsOneLeaf(t *testing.T) { p, err := tree.ProveSingleLeaf(0) a.NoError(err) - newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: []crypto.GenericDigest{}, HashFactory: p.HashFactory}} - - computedPath := recomputePath(p) + concatenatedProof := p.GetConcatenatedProof() + computedPath := recomputePath(concatenatedProof) - newP.Path = computedPath + // verify that the concatenated proof can be verified correctly + newP := SingleLeafProof{Proof: Proof{TreeDepth: p.TreeDepth, Path: computedPath, HashFactory: p.HashFactory}} err = Verify(tree.Root(), map[uint64]crypto.Hashable{0: array[0]}, newP.ToProof()) a.NoError(err) + + recomputedProof, err := ProofDataToSingleLeafProof(p.HashFactory.HashType.String(), uint64(p.TreeDepth), concatenatedProof) + a.NoError(err) + + // verify that we can reconstruct the original singleLeafProof from the concatenated proof + err = Verify(tree.Root(), map[uint64]crypto.Hashable{0: array[0]}, recomputedProof.ToProof()) + a.NoError(err) +} + +func TestProofDeserializationError(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + _, err := ProofDataToSingleLeafProof(crypto.Sha256.String(), 1, []byte{1}) + a.ErrorIs(err, ErrProofLengthDigestSizeMismatch) } -func recomputePath(p *SingleLeafProof) []crypto.GenericDigest { +func recomputePath(p []byte) []crypto.GenericDigest { var computedPath []crypto.GenericDigest - proofconcat := p.GetConcatenatedProof() - for len(proofconcat) > 0 { + for len(p) > 0 { var d crypto.Digest - copy(d[:], proofconcat) + copy(d[:], p) computedPath = append(computedPath, d[:]) - proofconcat = proofconcat[len(d):] + p = p[len(d):] } return computedPath } diff --git a/crypto/merklesignature/committablePublicKeys.go b/crypto/merklesignature/committablePublicKeys.go index 1c2c44736d..7401c67efc 100644 --- a/crypto/merklesignature/committablePublicKeys.go +++ b/crypto/merklesignature/committablePublicKeys.go @@ -29,9 +29,9 @@ type ( // committablePublicKeyArray used to arrange the keys so a merkle tree could be build on them. //msgp:ignore committablePublicKeyArray committablePublicKeyArray struct { - keys []crypto.FalconSigner - firstValid uint64 - interval uint64 + keys []crypto.FalconSigner + firstValid uint64 + keyLifetime uint64 } // CommittablePublicKey is used to create a binary representation of public keys in the merkle @@ -59,7 +59,7 @@ func (k *committablePublicKeyArray) Marshal(pos uint64) (crypto.Hashable, error) ephPK := CommittablePublicKey{ VerifyingKey: *k.keys[pos].GetVerifyingKey(), - Round: indexToRound(k.firstValid, k.interval, pos), + Round: indexToRound(k.firstValid, k.keyLifetime, pos), } return &ephPK, nil @@ -72,15 +72,15 @@ func (k *committablePublicKeyArray) Marshal(pos uint64) (crypto.Hashable, error) func (e *CommittablePublicKey) ToBeHashed() (protocol.HashID, []byte) { verifyingRawKey := e.VerifyingKey.GetFixedLengthHashableRepresentation() - roundAsBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(roundAsBytes, e.Round) + var roundAsBytes [8]byte + binary.LittleEndian.PutUint64(roundAsBytes[:], e.Round) - schemeAsBytes := make([]byte, 2) - binary.LittleEndian.PutUint16(schemeAsBytes, CryptoPrimitivesID) + var schemeAsBytes [2]byte + binary.LittleEndian.PutUint16(schemeAsBytes[:], CryptoPrimitivesID) keyCommitment := make([]byte, 0, len(schemeAsBytes)+len(verifyingRawKey)+len(roundAsBytes)) - keyCommitment = append(keyCommitment, schemeAsBytes...) - keyCommitment = append(keyCommitment, roundAsBytes...) + keyCommitment = append(keyCommitment, schemeAsBytes[:]...) + keyCommitment = append(keyCommitment, roundAsBytes[:]...) keyCommitment = append(keyCommitment, verifyingRawKey...) return protocol.KeysInMSS, keyCommitment diff --git a/crypto/merklesignature/committablePublicKeys_test.go b/crypto/merklesignature/committablePublicKeys_test.go index b2718f3e14..a6884cc59d 100644 --- a/crypto/merklesignature/committablePublicKeys_test.go +++ b/crypto/merklesignature/committablePublicKeys_test.go @@ -92,5 +92,5 @@ func TestEphemeralPublicKeysCommitmentBinaryFormat(t *testing.T) { internal2 := calculateHashOnInternalNode(k1hash, k3hash) root := calculateHashOnInternalNode(internal1, internal2) - a.Equal(root, signer.GetVerifier()[:]) + a.Equal(root, signer.GetVerifier().Commitment[:]) } diff --git a/crypto/merklesignature/const.go b/crypto/merklesignature/const.go index d8457ad7b2..c98321b514 100644 --- a/crypto/merklesignature/const.go +++ b/crypto/merklesignature/const.go @@ -16,10 +16,37 @@ package merklesignature -import "github.com/algorand/go-algorand/crypto" +import ( + "fmt" + "github.com/algorand/go-algorand/crypto" +) // HashType/ hashSize relate to the type of hash this package uses. const ( MerkleSignatureSchemeHashFunction = crypto.Sumhash MerkleSignatureSchemeRootSize = crypto.SumhashDigestSize + // KeyLifetimeDefault defines the default lifetime of a key in the merkle signature scheme (in rounds). + KeyLifetimeDefault = 256 + + // SchemeSaltVersion is the current salt version of merkleSignature + SchemeSaltVersion = byte(0) + + // CryptoPrimitivesID is an identification that the Merkle Signature Scheme uses a subset sum hash function + // and a falcon signature scheme. + CryptoPrimitivesID = uint16(0) ) + +// NoKeysCommitment is a const hash value of the empty MerkleSignature Commitment. +var NoKeysCommitment = Commitment{} + +func init() { + // no keys generated, inner tree of merkle siganture scheme is empty. + o, err := New(KeyLifetimeDefault+1, KeyLifetimeDefault+2, KeyLifetimeDefault) + if err != nil { + panic(fmt.Errorf("initializing empty merkle signature scheme failed, err: %w", err)) + } + if len(o.GetAllKeys()) > 0 { + panic("mss tree has more than just root.") + } + copy(NoKeysCommitment[:], o.GetVerifier().Commitment[:]) +} diff --git a/crypto/merklesignature/kats_test.go b/crypto/merklesignature/kats_test.go new file mode 100644 index 0000000000..8a48d699bc --- /dev/null +++ b/crypto/merklesignature/kats_test.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package merklesignature + +import ( + "encoding/json" + "fmt" + "os" + "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" +) + +type mssKat struct { + PublicKey []byte + KeyLifetime uint64 + CtSignature []byte + EphemeralKey []byte + VcIndex uint64 + CorrespondingRound uint64 + ProofDepth uint8 + ProofBytes []byte + Message []byte +} + +func extractMssSignatureParts(signature Signature) ([]byte, []byte, []byte, uint8, error) { + ctSignature, err := signature.Signature.GetFixedLengthHashableRepresentation() + if err != nil { + return nil, nil, nil, 0, err + } + + pk := signature.VerifyingKey.GetFixedLengthHashableRepresentation() + proof := signature.Proof.GetFixedLengthHashableRepresentation() + proofDepth := proof[0] + proof = proof[1:] + + return ctSignature, pk, proof, proofDepth, nil +} + +func generateMssKat(startRound, atRound, numOfKeys uint64, messageToSign []byte) (mssKat, error) { + if startRound > atRound { + return mssKat{}, fmt.Errorf("error: Signature round cann't be smaller then start round") + } + + interval := config.Consensus[protocol.ConsensusFuture].StateProofInterval + stateProofSecrets, err := New(startRound, startRound+(interval*numOfKeys)-1, interval) + if err != nil { + return mssKat{}, fmt.Errorf("error: %w", err) + } + + keyForRound := stateProofSecrets.GetSigner(atRound) + if keyForRound == nil { + return mssKat{}, fmt.Errorf("error: There is no key for round %d", atRound) + } + + signature, err := keyForRound.SignBytes(messageToSign) + if err != nil { + return mssKat{}, fmt.Errorf("error while formating mss signature %w", err) + } + verifier := stateProofSecrets.GetVerifier() + ctSignature, pk, proof, proofDepth, err := extractMssSignatureParts(signature) + if err != nil { + return mssKat{}, fmt.Errorf("error while formating mss signature %w", err) + } + + return mssKat{ + PublicKey: verifier.Commitment[:], + KeyLifetime: KeyLifetimeDefault, + CtSignature: ctSignature, + EphemeralKey: pk, + VcIndex: signature.VectorCommitmentIndex, + CorrespondingRound: atRound, + ProofDepth: proofDepth, + ProofBytes: proof, + Message: messageToSign}, nil +} + +func TestGenerateKat(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + // This test produces MSS samples for the SNARK verifier. + // it will only run explicitly by: + // + // GEN_KATS=x go test -v . -run=GenerateKat -count=1 + if os.Getenv("GEN_KATS") == "" { + t.Skip("Skipping; GEN_KATS not set") + } + + kat, err := generateMssKat(256, 512, 9, []byte("test")) + a.NoError(err) + + katAsJSON, err := json.MarshalIndent(kat, "", "\t") + a.NoError(err) + + fmt.Println(string(katAsJSON)) +} diff --git a/crypto/merklesignature/keysBuilder_test.go b/crypto/merklesignature/keysBuilder_test.go index 29b96b598e..ec9e487fe3 100644 --- a/crypto/merklesignature/keysBuilder_test.go +++ b/crypto/merklesignature/keysBuilder_test.go @@ -35,6 +35,8 @@ func TestBuilderSanity(t *testing.T) { a.Equal(uint64(len(keys)), numOfKeys) s, err := keys[0].SignBytes([]byte{0}) + a.NoError(err) + v := keys[0].GetVerifyingKey() err = v.VerifyBytes([]byte{0}, s) a.NoError(err) diff --git a/crypto/merklesignature/merkleSignatureScheme.go b/crypto/merklesignature/merkleSignatureScheme.go index 983cd859d1..b2763d496d 100644 --- a/crypto/merklesignature/merkleSignatureScheme.go +++ b/crypto/merklesignature/merkleSignatureScheme.go @@ -35,10 +35,10 @@ type ( Signature struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - Signature crypto.FalconSignature `codec:"sig"` - MerkleArrayIndex uint64 `codec:"idx"` - Proof merklearray.SingleLeafProof `codec:"prf"` - VerifyingKey crypto.FalconVerifier `codec:"vkey"` + Signature crypto.FalconSignature `codec:"sig"` + VectorCommitmentIndex uint64 `codec:"idx"` + Proof merklearray.SingleLeafProof `codec:"prf"` + VerifyingKey crypto.FalconVerifier `codec:"vkey"` } // Secrets contains the private data needed by the merkle signature scheme. @@ -58,7 +58,7 @@ type ( Signer struct { SigningKey *crypto.FalconSigner - // The round for which this SigningKey is related to + // The round for which the signature would be valid Round uint64 SignerContext @@ -68,13 +68,21 @@ type ( SignerContext struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - FirstValid uint64 `codec:"fv"` - Interval uint64 `codec:"iv"` - Tree merklearray.Tree `codec:"tree"` + FirstValid uint64 `codec:"fv"` + KeyLifetime uint64 `codec:"iv"` + Tree merklearray.Tree `codec:"tree"` } + // Commitment represents the root of the vector commitment tree built upon the MSS keys. + Commitment [MerkleSignatureSchemeRootSize]byte + // Verifier is used to verify a merklesignature.Signature produced by merklesignature.Secrets. - Verifier [MerkleSignatureSchemeRootSize]byte + Verifier struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Commitment Commitment `codec:"cmt"` + KeyLifetime uint64 `codec:"lf"` + } //KeyRoundPair represents an ephemeral signing key with it's corresponding round KeyRoundPair struct { @@ -85,43 +93,38 @@ type ( } ) -// CryptoPrimitivesID is an identification that the Merkle Signature Scheme uses a subset sum hash function -// and a falcon signature scheme. -var CryptoPrimitivesID = uint16(0) - // Errors for the merkle signature scheme var ( ErrStartBiggerThanEndRound = errors.New("cannot create Merkle Signature Scheme because end round is smaller then start round") - ErrDivisorIsZero = errors.New("received zero Interval") + ErrKeyLifetimeIsZero = errors.New("received zero KeyLifetime") ErrNoStateProofKeyForRound = errors.New("no stateproof key exists for this round") ErrSignatureSchemeVerificationFailed = errors.New("merkle signature verification failed") + ErrSignatureSaltVersionMismatch = errors.New("the signature's salt version does not match") ) // New creates secrets needed for the merkle signature scheme. // This function generates one key for each round within the participation period [firstValid, lastValid] (inclusive bounds) // which holds round % interval == 0. -// In case firstValid equals zero then signer will generate all keys from (0,Z], i.e will not generate key for round zero. -func New(firstValid, lastValid, interval uint64) (*Secrets, error) { +func New(firstValid, lastValid, keyLifetime uint64) (*Secrets, error) { if firstValid > lastValid { return nil, ErrStartBiggerThanEndRound } - if interval == 0 { - return nil, ErrDivisorIsZero - } - - if firstValid == 0 { - firstValid = 1 + if keyLifetime == 0 { + return nil, ErrKeyLifetimeIsZero } // calculates the number of indices from first valid round and up to lastValid. // writing this explicit calculation to avoid overflow. - numberOfKeys := lastValid/interval - ((firstValid - 1) / interval) + numberOfKeys := lastValid/keyLifetime - ((firstValid - 1) / keyLifetime) + if firstValid == 0 { + numberOfKeys = lastValid/keyLifetime + 1 // add 1 for round zero + } keys, err := KeysBuilder(numberOfKeys) if err != nil { return nil, err } - tree, err := merklearray.BuildVectorCommitmentTree(&committablePublicKeyArray{keys, firstValid, interval}, crypto.HashFactory{HashType: MerkleSignatureSchemeHashFunction}) + tree, err := merklearray.BuildVectorCommitmentTree(&committablePublicKeyArray{keys, firstValid, keyLifetime}, crypto.HashFactory{HashType: MerkleSignatureSchemeHashFunction}) if err != nil { return nil, err } @@ -129,9 +132,9 @@ func New(firstValid, lastValid, interval uint64) (*Secrets, error) { return &Secrets{ ephemeralKeys: keys, SignerContext: SignerContext{ - FirstValid: firstValid, - Interval: interval, - Tree: *tree, + FirstValid: firstValid, + KeyLifetime: keyLifetime, + Tree: *tree, }, }, nil } @@ -144,46 +147,63 @@ func (s *Secrets) GetVerifier() *Verifier { // GetVerifier can be used to store the commitment and verifier for this signer. func (s *SignerContext) GetVerifier() *Verifier { var ver Verifier - copy(ver[:], s.Tree.Root()) + copy(ver.Commitment[:], s.Tree.Root()) + ver.KeyLifetime = s.KeyLifetime return &ver } -// Sign signs a hash of a given message. The signature is valid on a specific round -func (s *Signer) Sign(hashable crypto.Hashable) (Signature, error) { +// FirstRoundInKeyLifetime calculates the round of the valid key for a given round by lowering to the closest KeyLiftime divisor. +func (s *Signer) FirstRoundInKeyLifetime() (uint64, error) { + if s.KeyLifetime == 0 { + return 0, ErrKeyLifetimeIsZero + } + + return firstRoundInKeyLifetime(s.Round, s.KeyLifetime), nil +} + +func (s *Signer) vectorCommitmentTreeIndex() (uint64, error) { + validKeyRound, err := s.FirstRoundInKeyLifetime() + if err != nil { + return 0, err + } + return roundToIndex(s.FirstValid, validKeyRound, s.KeyLifetime), nil +} + +// SignBytes signs a given message. The signature is valid on a specific round +func (s *Signer) SignBytes(msg []byte) (Signature, error) { key := s.SigningKey // Possible since there may not be a StateProof key for this specific round if key == nil { return Signature{}, ErrNoStateProofKeyForRound } - if err := checkMerkleSignatureSchemeParams(s.FirstValid, s.Round, s.Interval); err != nil { + if err := checkMerkleSignatureSchemeParams(s.FirstValid, s.Round, s.KeyLifetime); err != nil { + return Signature{}, err + } + + vcIdx, err := s.vectorCommitmentTreeIndex() + if err != nil { return Signature{}, err } - index := s.getMerkleTreeIndex(s.Round) - proof, err := s.Tree.ProveSingleLeaf(index) + proof, err := s.Tree.ProveSingleLeaf(vcIdx) if err != nil { return Signature{}, err } - sig, err := s.SigningKey.Sign(hashable) + sig, err := key.SignBytes(msg) if err != nil { return Signature{}, err } return Signature{ - Signature: sig, - Proof: *proof, - VerifyingKey: *s.SigningKey.GetVerifyingKey(), - MerkleArrayIndex: index, + Signature: sig, + Proof: *proof, + VerifyingKey: *s.SigningKey.GetVerifyingKey(), + VectorCommitmentIndex: vcIdx, }, nil } -// expects valid rounds, i.e round that are bigger than FirstValid. -func (s *Signer) getMerkleTreeIndex(round uint64) uint64 { - return roundToIndex(s.FirstValid, round, s.Interval) -} - // GetAllKeys returns all stateproof secrets. // An empty array will be return if no stateproof secrets are found func (s *Secrets) GetAllKeys() []KeyRoundPair { @@ -191,7 +211,7 @@ func (s *Secrets) GetAllKeys() []KeyRoundPair { keys := make([]KeyRoundPair, NumOfKeys) for i := uint64(0); i < NumOfKeys; i++ { keyRound := KeyRoundPair{ - Round: indexToRound(s.SignerContext.FirstValid, s.SignerContext.Interval, i), + Round: indexToRound(s.FirstValid, s.KeyLifetime, i), Key: &s.ephemeralKeys[i], } keys[i] = keyRound @@ -202,8 +222,9 @@ func (s *Secrets) GetAllKeys() []KeyRoundPair { // GetKey retrieves key from memory // the function return nil if the key does not exists func (s *Secrets) GetKey(round uint64) *crypto.FalconSigner { - idx := roundToIndex(s.FirstValid, round, s.Interval) - if idx >= uint64(len(s.ephemeralKeys)) || (round%s.Interval) != 0 { + keyRound := firstRoundInKeyLifetime(round, s.KeyLifetime) + idx := roundToIndex(s.FirstValid, keyRound, s.KeyLifetime) + if idx >= uint64(len(s.ephemeralKeys)) || (keyRound%s.KeyLifetime) != 0 || keyRound < s.FirstValid { return nil } @@ -220,33 +241,54 @@ func (s *Secrets) GetSigner(round uint64) *Signer { } // IsEmpty returns true if the verifier contains an empty key -func (v *Verifier) IsEmpty() bool { +func (v *Commitment) IsEmpty() bool { return *v == [MerkleSignatureSchemeRootSize]byte{} } -// Verify verifies that a merklesignature sig is valid, on a specific round, under a given public key -func (v *Verifier) Verify(round uint64, msg crypto.Hashable, sig Signature) error { +// ValidateSaltVersion validates that the version of the signature is matching the expected version +func (s *Signature) ValidateSaltVersion(version byte) error { + if !s.Signature.IsSaltVersionEqual(version) { + return ErrSignatureSaltVersionMismatch + } + return nil +} + +// FirstRoundInKeyLifetime calculates the round of the valid key for a given round by lowering to the closest KeyLiftime divisor. +func (v *Verifier) FirstRoundInKeyLifetime(round uint64) (uint64, error) { + if v.KeyLifetime == 0 { + return 0, ErrKeyLifetimeIsZero + } + + return firstRoundInKeyLifetime(round, v.KeyLifetime), nil +} + +// VerifyBytes verifies that a merklesignature sig is valid, on a specific round, under a given public key +func (v *Verifier) VerifyBytes(round uint64, msg []byte, sig *Signature) error { + validKeyRound, err := v.FirstRoundInKeyLifetime(round) + if err != nil { + return err + } ephkey := CommittablePublicKey{ VerifyingKey: sig.VerifyingKey, - Round: round, + Round: validKeyRound, } // verify the merkle tree verification path using the ephemeral public key, the // verification path and the index. - err := merklearray.VerifyVectorCommitment( - v[:], - map[uint64]crypto.Hashable{sig.MerkleArrayIndex: &ephkey}, + err = merklearray.VerifyVectorCommitment( + v.Commitment[:], + map[uint64]crypto.Hashable{sig.VectorCommitmentIndex: &ephkey}, sig.Proof.ToProof(), ) if err != nil { - return fmt.Errorf("%w - %v", ErrSignatureSchemeVerificationFailed, err) + return fmt.Errorf("%w: %v", ErrSignatureSchemeVerificationFailed, err) } // verify that the signature is valid under the ephemeral public key - err = sig.VerifyingKey.Verify(msg, sig.Signature) + err = sig.VerifyingKey.VerifyBytes(msg, sig.Signature) if err != nil { - return fmt.Errorf("%w - %v", ErrSignatureSchemeVerificationFailed, err) + return fmt.Errorf("%w: %v", ErrSignatureSchemeVerificationFailed, err) } return nil } @@ -254,25 +296,25 @@ func (v *Verifier) Verify(round uint64, msg crypto.Hashable, sig Signature) erro // GetFixedLengthHashableRepresentation returns the signature as a hashable byte sequence. // the format details can be found in the Algorand's spec. func (s *Signature) GetFixedLengthHashableRepresentation() ([]byte, error) { - schemeType := make([]byte, 2) - binary.LittleEndian.PutUint16(schemeType, CryptoPrimitivesID) - sigBytes, err := s.VerifyingKey.GetSignatureFixedLengthHashableRepresentation(s.Signature) + var schemeType [2]byte + binary.LittleEndian.PutUint16(schemeType[:], CryptoPrimitivesID) + sigBytes, err := s.Signature.GetFixedLengthHashableRepresentation() if err != nil { return nil, err } verifierBytes := s.VerifyingKey.GetFixedLengthHashableRepresentation() - binaryMerkleIndex := make([]byte, 8) - binary.LittleEndian.PutUint64(binaryMerkleIndex, s.MerkleArrayIndex) + var binaryVectorCommitmentIndex [8]byte + binary.LittleEndian.PutUint64(binaryVectorCommitmentIndex[:], s.VectorCommitmentIndex) proofBytes := s.Proof.GetFixedLengthHashableRepresentation() - merkleSignatureBytes := make([]byte, 0, len(schemeType)+len(sigBytes)+len(verifierBytes)+len(binaryMerkleIndex)+len(proofBytes)) - merkleSignatureBytes = append(merkleSignatureBytes, schemeType...) + merkleSignatureBytes := make([]byte, 0, len(schemeType)+len(sigBytes)+len(verifierBytes)+len(binaryVectorCommitmentIndex)+len(proofBytes)) + merkleSignatureBytes = append(merkleSignatureBytes, schemeType[:]...) merkleSignatureBytes = append(merkleSignatureBytes, sigBytes...) merkleSignatureBytes = append(merkleSignatureBytes, verifierBytes...) - merkleSignatureBytes = append(merkleSignatureBytes, binaryMerkleIndex...) + merkleSignatureBytes = append(merkleSignatureBytes, binaryVectorCommitmentIndex[:]...) merkleSignatureBytes = append(merkleSignatureBytes, proofBytes...) return merkleSignatureBytes, nil } diff --git a/crypto/merklesignature/merkleSignatureScheme_test.go b/crypto/merklesignature/merkleSignatureScheme_test.go index ff51639bda..db4f4e6e44 100644 --- a/crypto/merklesignature/merkleSignatureScheme_test.go +++ b/crypto/merklesignature/merkleSignatureScheme_test.go @@ -30,33 +30,26 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -type TestingHashable struct { - data []byte -} - -func (s TestingHashable) ToBeHashed() (protocol.HashID, []byte) { - return protocol.TestHashable, s.data -} - func TestSignerCreation(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) var err error - h := genHashableForTest() + h := genMsgForTest() for i := uint64(1); i < 20; i++ { signer := generateTestSigner(i, i+1, 1, a) - _, err = signer.GetSigner(i).Sign(h) + _, err = signer.GetSigner(i).SignBytes(h) a.NoError(err) } - testSignerNumKeysLimits := func(firstValid uint64, lastValid uint64, interval uint64, expectedLen int) { - signer := generateTestSigner(firstValid, lastValid, interval, a) + testSignerNumKeysLimits := func(firstValid uint64, lastValid uint64, keyLifetime uint64, expectedLen int) { + signer := generateTestSigner(firstValid, lastValid, keyLifetime, a) a.Equal(expectedLen, length(signer, a)) } - testSignerNumKeysLimits(0, 0, 1, 0) - testSignerNumKeysLimits(0, 1, 1, 1) + testSignerNumKeysLimits(0, 0, 1, 1) + testSignerNumKeysLimits(0, 1, 1, 2) testSignerNumKeysLimits(2, 2, 2, 1) testSignerNumKeysLimits(8, 21, 10, 2) testSignerNumKeysLimits(8, 20, 10, 2) @@ -67,24 +60,44 @@ func TestSignerCreation(t *testing.T) { signer := generateTestSigner(2, 2, 2, a) a.Equal(1, length(signer, a)) - sig, err := signer.GetSigner(2).Sign(genHashableForTest()) + sig, err := signer.GetSigner(2).SignBytes(genMsgForTest()) a.NoError(err) - a.NoError(signer.GetVerifier().Verify(2, genHashableForTest(), sig)) + a.NoError(signer.GetVerifier().VerifyBytes(2, genMsgForTest(), &sig)) signer = generateTestSigner(2, 2, 3, a) a.Equal(0, length(signer, a)) - _, err = signer.GetSigner(2).Sign(genHashableForTest()) + _, err = signer.GetSigner(2).SignBytes(genMsgForTest()) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) signer = generateTestSigner(11, 19, 10, a) a.Equal(0, length(signer, a)) - _, err = signer.GetSigner(2).Sign(genHashableForTest()) + _, err = signer.GetSigner(2).SignBytes(genMsgForTest()) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) + + // Make sure both rounds 10 and 11 can be signed (as key for round 10 is valid for both) + signer = generateTestSigner(0, 19, 10, a) + + sig, err = signer.GetSigner(10).SignBytes(genMsgForTest()) + a.NoError(err) + a.NoError(signer.GetVerifier().VerifyBytes(10, genMsgForTest(), &sig)) + + sig, err = signer.GetSigner(11).SignBytes(genMsgForTest()) + a.NoError(err) + a.NoError(signer.GetVerifier().VerifyBytes(11, genMsgForTest(), &sig)) + + sig, err = signer.GetSigner(0).SignBytes(genMsgForTest()) + a.NoError(err) + a.NoError(signer.GetVerifier().VerifyBytes(0, genMsgForTest(), &sig)) + + sig, err = signer.GetSigner(1).SignBytes(genMsgForTest()) + a.NoError(err) + a.NoError(signer.GetVerifier().VerifyBytes(1, genMsgForTest(), &sig)) } func TestSignerCreationOutOfBounds(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) _, err := New(8, 4, 1) @@ -93,43 +106,64 @@ func TestSignerCreationOutOfBounds(t *testing.T) { _, err = New(1, 8, 0) a.Error(err) - a.ErrorIs(err, ErrDivisorIsZero) + a.ErrorIs(err, ErrKeyLifetimeIsZero) } func TestEmptyVerifier(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) signer := generateTestSigner(8, 9, 5, a) // even if there are no keys for that period, the root is not empty // (part of the vector commitment property). - a.Equal(false, signer.GetVerifier().IsEmpty()) + a.False(signer.GetVerifier().MsgIsZero()) +} + +func TestVerifierKeyLifetimeError(t *testing.T) { + t.Parallel() + partitiontest.PartitionTest(t) + a := require.New(t) + + signer := generateTestSigner(8, 12, 1, a) + verifier := signer.GetVerifier() + + verifier.KeyLifetime = 0 + a.ErrorIs(verifier.VerifyBytes(0, []byte(""), &Signature{}), ErrKeyLifetimeIsZero) + + verifier.KeyLifetime = 1 + sig, err := signer.GetSigner(10).SignBytes([]byte("hello")) + a.NoError(err) + + a.NoError(verifier.VerifyBytes(10, []byte("hello"), &sig)) } func TestEmptySigner(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) var err error - h := genHashableForTest() + h := genMsgForTest() signer := generateTestSigner(8, 9, 5, a) a.Equal(0, length(signer, a)) - _, err = signer.GetSigner(8).Sign(h) + _, err = signer.GetSigner(8).SignBytes(h) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) - _, err = signer.GetSigner(9).Sign(h) + _, err = signer.GetSigner(9).SignBytes(h) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) } func TestDisposableKeysGeneration(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) signer := generateTestSigner(0, 100, 1, a) - for i := uint64(1); i < 100; i++ { + for i := uint64(0); i < 100; i++ { k := signer.GetKey(i) a.NotNil(k) } @@ -146,19 +180,26 @@ func TestDisposableKeysGeneration(t *testing.T) { k = signer.GetKey(999) a.Nil(k) - signer = generateTestSigner(1000, 1100, 101, a) - intervalRounds := make([]uint64, 0) - for i := uint64(1000); i <= 1100; i++ { - if i%101 == 0 { - intervalRounds = append(intervalRounds, i) - continue - } - k := signer.GetKey(i) - a.Nil(k) + signer = generateTestSigner(1000, 1100, 105, a) + i := uint64(1000) + for ; i < 1050; i++ { + a.Nil(signer.GetKey(i)) + } + + k = signer.GetKey(i) // 1050 + a.NotNil(k) + pk := k.PublicKey + i++ + + for ; i <= 1100; i++ { // same key since it's under the same lifetime period + k = signer.GetKey(i) + a.NotNil(k) + a.Equal(pk, k.PublicKey) } } func TestNonEmptyDisposableKeys(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) @@ -182,13 +223,14 @@ func TestNonEmptyDisposableKeys(t *testing.T) { } func TestSignatureStructure(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) signer := generateTestSigner(50, 100, 1, a) - hashable := genHashableForTest() - sig, err := signer.GetSigner(51).Sign(hashable) + msg := genMsgForTest() + sig, err := signer.GetSigner(51).SignBytes(msg) a.NoError(err) key := signer.GetKey(51) @@ -203,58 +245,59 @@ func TestSignatureStructure(t *testing.T) { a.NotEqual(nil, sig.Signature) } -func genHashableForTest() crypto.Hashable { - hashable := TestingHashable{[]byte("test msg")} - - return hashable +func genMsgForTest() []byte { + return []byte("test msg") } func TestSigning(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) start, end := uint64(50), uint64(100) signer := generateTestSigner(start, end, 1, a) - hashable := genHashableForTest() + msg := genMsgForTest() - sig, err := signer.GetSigner(start).Sign(hashable) + sig, err := signer.GetSigner(start).SignBytes(msg) a.NoError(err) - a.NoError(signer.GetVerifier().Verify(start, hashable, sig)) + a.NoError(signer.GetVerifier().VerifyBytes(start, msg, &sig)) - _, err = signer.GetSigner(start - 1).Sign(hashable) + _, err = signer.GetSigner(start - 1).SignBytes(msg) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) - _, err = signer.GetSigner(end + 1).Sign(hashable) + _, err = signer.GetSigner(end + 1).SignBytes(msg) a.Error(err) a.ErrorIs(err, ErrNoStateProofKeyForRound) signer = generateTestSigner(start, end, 10, a) - sig, err = signer.GetSigner(start).Sign(hashable) + sig, err = signer.GetSigner(start).SignBytes(msg) a.NoError(err) - a.NoError(signer.GetVerifier().Verify(start, hashable, sig)) + a.NoError(signer.GetVerifier().VerifyBytes(start, msg, &sig)) - sig, err = signer.GetSigner(start + 5).Sign(hashable) - a.Error(err) + sig, err = signer.GetSigner(start + 5).SignBytes(msg) + a.NoError(err) - err = signer.GetVerifier().Verify(start+5, hashable, sig) - a.Error(err) - a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) + err = signer.GetVerifier().VerifyBytes(start+5, msg, &sig) + a.NoError(err) + err = signer.GetVerifier().VerifyBytes(start+7, msg, &sig) // same key used since both rounds under same lifetime of key + a.NoError(err) signer = generateTestSigner(50, 100, 12, a) a.Equal(4, length(signer, a)) - for i := uint64(50); i < 100; i++ { - if i%12 != 0 { - _, err = signer.GetSigner(i).Sign(hashable) - a.Error(err) - } else { - sig, err = signer.GetSigner(i).Sign(hashable) - a.NoError(err) - a.NoError(signer.GetVerifier().Verify(i, hashable, sig)) - } + i := uint64(50) + for ; i < 60; i++ { // no key for these rounds (key for round 48 was not generated) + _, err = signer.GetSigner(i).SignBytes(msg) + a.Error(err) + a.ErrorIs(err, ErrNoStateProofKeyForRound) + } + for ; i < 100; i++ { + sig, err = signer.GetSigner(i).SignBytes(msg) + a.NoError(err) + a.NoError(signer.GetVerifier().VerifyBytes(i, msg, &sig)) } signer = generateTestSigner(234, 4634, 256, a) @@ -262,42 +305,44 @@ func TestSigning(t *testing.T) { a.NotNil(key) key = signer.GetKey(4096) a.NotNil(key) - key = signer.GetKey(234 + 256) + key = signer.GetKey(234) // keys valid only for round > 256 a.Nil(key) } func TestBadRound(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) start, _, signer := generateTestSignerAux(a) - hashable, sig := makeSig(signer, start, a) + msg, sig := makeSig(signer, start, a) - err := signer.GetVerifier().Verify(start+1, hashable, sig) + err := signer.GetVerifier().VerifyBytes(start+1, msg, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) - hashable, sig = makeSig(signer, start+1, a) - err = signer.GetVerifier().Verify(start, hashable, sig) + msg, sig = makeSig(signer, start+1, a) + err = signer.GetVerifier().VerifyBytes(start, msg, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) - err = signer.GetVerifier().Verify(start+2, hashable, sig) + err = signer.GetVerifier().VerifyBytes(start+2, msg, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) a.True(errors.Is(err, ErrSignatureSchemeVerificationFailed)) } func TestBadMerkleProofInSignature(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) start, _, signer := generateTestSignerAux(a) - hashable, sig := makeSig(signer, start, a) + msg, sig := makeSig(signer, start, a) sig2 := copySig(sig) sig2.Proof.Path = sig2.Proof.Path[:len(sig2.Proof.Path)-1] - err := signer.GetVerifier().Verify(start, hashable, sig2) + err := signer.GetVerifier().VerifyBytes(start, msg, &sig2) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) @@ -305,7 +350,7 @@ func TestBadMerkleProofInSignature(t *testing.T) { someDigest := crypto.Digest{} rand.Read(someDigest[:]) sig3.Proof.Path[0] = someDigest[:] - err = signer.GetVerifier().Verify(start, hashable, sig3) + err = signer.GetVerifier().VerifyBytes(start, msg, &sig3) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) } @@ -322,11 +367,12 @@ func copySig(sig Signature) Signature { } func TestIncorrectByteSignature(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) start, _, signer := generateTestSignerAux(a) - hashable, sig := makeSig(signer, start, a) + msg, sig := makeSig(signer, start, a) sig2 := sig bs := make([]byte, len(sig.Signature)) @@ -334,44 +380,46 @@ func TestIncorrectByteSignature(t *testing.T) { bs[0]++ sig2.Signature = bs - err := signer.GetVerifier().Verify(start, hashable, sig2) + err := signer.GetVerifier().VerifyBytes(start, msg, &sig2) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) } func TestIncorrectMerkleIndex(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) var err error - h := genHashableForTest() + h := genMsgForTest() signer := generateTestSigner(8, 100, 5, a) a.NoError(err) - sig, err := signer.GetSigner(20).Sign(h) + sig, err := signer.GetSigner(20).SignBytes(h) a.NoError(err) - sig.MerkleArrayIndex = 0 - err = signer.GetVerifier().Verify(20, h, sig) + sig.VectorCommitmentIndex = 0 + err = signer.GetVerifier().VerifyBytes(20, h, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) - sig.MerkleArrayIndex = math.MaxUint64 - err = signer.GetVerifier().Verify(20, h, sig) + sig.VectorCommitmentIndex = math.MaxUint64 + err = signer.GetVerifier().VerifyBytes(20, h, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) - err = signer.GetVerifier().Verify(20, h, sig) + err = signer.GetVerifier().VerifyBytes(20, h, &sig) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) } func TestAttemptToUseDifferentKey(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) start, _, signer := generateTestSignerAux(a) - hashable, sig := makeSig(signer, start+1, a) + msg, sig := makeSig(signer, start+1, a) // taking signature for specific round and changing the round // taking signature and changing the key to match different round @@ -381,12 +429,13 @@ func TestAttemptToUseDifferentKey(t *testing.T) { sig2.VerifyingKey = *(key.GetVerifyingKey()) - err := signer.GetVerifier().Verify(start+1, hashable, sig2) + err := signer.GetVerifier().VerifyBytes(start+1, msg, &sig2) a.Error(err) a.ErrorIs(err, ErrSignatureSchemeVerificationFailed) } func TestMarshal(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) @@ -405,49 +454,51 @@ func TestMarshal(t *testing.T) { } func TestNumberOfGeneratedKeys(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) - interval := uint64(256) + keyLifetime := uint64(256) numberOfKeys := uint64(1 << 6) - validPeriod := numberOfKeys*interval - 1 + validPeriod := numberOfKeys*keyLifetime - 1 firstValid := uint64(1000) lastValid := validPeriod + 1000 - s, err := New(firstValid, lastValid, interval) + s, err := New(firstValid, lastValid, keyLifetime) a.NoError(err) a.Equal(numberOfKeys, uint64(length(s, a))) firstValid = uint64(0) lastValid = validPeriod - s, err = New(firstValid, lastValid, interval) + s, err = New(firstValid, lastValid, keyLifetime) a.NoError(err) - a.Equal(numberOfKeys-1, uint64(length(s, a))) + a.Equal(numberOfKeys, uint64(length(s, a))) firstValid = uint64(1000) - lastValid = validPeriod + 1000 - (interval * 50) - s, err = New(firstValid, lastValid, interval) + lastValid = validPeriod + 1000 - (keyLifetime * 50) + s, err = New(firstValid, lastValid, keyLifetime) a.NoError(err) a.Equal(numberOfKeys-50, uint64(length(s, a))) } func TestGetAllKeys(t *testing.T) { + t.Parallel() partitiontest.PartitionTest(t) a := require.New(t) - interval := uint64(256) + keyLifetime := uint64(256) numOfKeys := uint64(1 << 8) - validPeriod := numOfKeys*interval - 1 + validPeriod := numOfKeys*keyLifetime - 1 firstValid := uint64(1000) lastValid := validPeriod + 1000 - s, err := New(firstValid, lastValid, interval) + s, err := New(firstValid, lastValid, keyLifetime) a.NoError(err) a.Equal(numOfKeys, uint64(len(s.ephemeralKeys))) keys := s.GetAllKeys() for i := uint64(0); i < uint64(len(s.ephemeralKeys)); i++ { a.Equal(s.ephemeralKeys[i], *keys[i].Key) - a.Equal(indexToRound(firstValid, interval, i), keys[i].Round) + a.Equal(indexToRound(firstValid, keyLifetime, i), keys[i].Round) } s, err = New(1, 2, 100) @@ -459,13 +510,13 @@ func TestGetAllKeys(t *testing.T) { } //#region Helper Functions -func makeSig(signer *Secrets, sigRound uint64, a *require.Assertions) (crypto.Hashable, Signature) { - hashable := genHashableForTest() +func makeSig(signer *Secrets, sigRound uint64, a *require.Assertions) ([]byte, Signature) { + msg := genMsgForTest() - sig, err := signer.GetSigner(sigRound).Sign(hashable) + sig, err := signer.GetSigner(sigRound).SignBytes(msg) a.NoError(err) - a.NoError(signer.GetVerifier().Verify(sigRound, hashable, sig)) - return hashable, sig + a.NoError(signer.GetVerifier().VerifyBytes(sigRound, msg, &sig)) + return msg, sig } func generateTestSignerAux(a *require.Assertions) (uint64, uint64, *Secrets) { @@ -474,8 +525,8 @@ func generateTestSignerAux(a *require.Assertions) (uint64, uint64, *Secrets) { return start, end, signer } -func generateTestSigner(firstValid, lastValid, interval uint64, a *require.Assertions) *Secrets { - signer, err := New(firstValid, lastValid, interval) +func generateTestSigner(firstValid, lastValid, keyLifetime uint64, a *require.Assertions) *Secrets { + signer, err := New(firstValid, lastValid, keyLifetime) a.NoError(err) return signer @@ -502,16 +553,16 @@ func copyProof(proof merklearray.SingleLeafProof) merklearray.SingleLeafProof { func TestTreeRootHashLength(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - interval := uint64(256) + keyLifetime := uint64(256) numOfKeys := uint64(1 << 8) - validPeriod := numOfKeys*interval - 1 + validPeriod := numOfKeys*keyLifetime - 1 firstValid := uint64(1000) lastValid := validPeriod + 1000 - s, err := New(firstValid, lastValid, interval) + s, err := New(firstValid, lastValid, keyLifetime) a.NoError(err) a.Equal(numOfKeys, uint64(len(s.ephemeralKeys))) a.Equal(MerkleSignatureSchemeRootSize, len(s.Tree.Root())) - a.Equal(MerkleSignatureSchemeRootSize, len(Verifier{})) + a.Equal(MerkleSignatureSchemeRootSize, len(Verifier{}.Commitment)) } diff --git a/crypto/merklesignature/msgp_gen.go b/crypto/merklesignature/msgp_gen.go index a40196d4a6..ae33fa175d 100644 --- a/crypto/merklesignature/msgp_gen.go +++ b/crypto/merklesignature/msgp_gen.go @@ -9,6 +9,14 @@ import ( ) // The following msgp objects are implemented in this file: +// Commitment +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // KeyRoundPair // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -50,6 +58,45 @@ import ( // |-----> (*) MsgIsZero // +// MarshalMsg implements msgp.Marshaler +func (z *Commitment) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendBytes(o, (*z)[:]) + return +} + +func (_ *Commitment) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Commitment) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Commitment) UnmarshalMsg(bts []byte) (o []byte, err error) { + bts, err = msgp.ReadExactBytes(bts, (*z)[:]) + if err != nil { + err = msgp.WrapError(err) + return + } + o = bts + return +} + +func (_ *Commitment) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Commitment) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Commitment) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + (MerkleSignatureSchemeRootSize * (msgp.ByteSize)) + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Commitment) MsgIsZero() bool { + return (*z) == (Commitment{}) +} + // MarshalMsg implements msgp.Marshaler func (z *KeyRoundPair) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -220,7 +267,7 @@ func (z *Secrets) MarshalMsg(b []byte) (o []byte) { zb0002Len-- zb0002Mask |= 0x8 } - if (*z).SignerContext.Interval == 0 { + if (*z).SignerContext.KeyLifetime == 0 { zb0002Len-- zb0002Mask |= 0x10 } @@ -239,7 +286,7 @@ func (z *Secrets) MarshalMsg(b []byte) (o []byte) { if (zb0002Mask & 0x10) == 0 { // if not empty // string "iv" o = append(o, 0xa2, 0x69, 0x76) - o = msgp.AppendUint64(o, (*z).SignerContext.Interval) + o = msgp.AppendUint64(o, (*z).SignerContext.KeyLifetime) } if (zb0002Mask & 0x20) == 0 { // if not empty // string "tree" @@ -278,9 +325,9 @@ func (z *Secrets) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0002 > 0 { zb0002-- - (*z).SignerContext.Interval, bts, err = msgp.ReadUint64Bytes(bts) + (*z).SignerContext.KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Interval") + err = msgp.WrapError(err, "struct-from-array", "KeyLifetime") return } } @@ -322,9 +369,9 @@ func (z *Secrets) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "iv": - (*z).SignerContext.Interval, bts, err = msgp.ReadUint64Bytes(bts) + (*z).SignerContext.KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Interval") + err = msgp.WrapError(err, "KeyLifetime") return } case "tree": @@ -359,7 +406,7 @@ func (z *Secrets) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *Secrets) MsgIsZero() bool { - return ((*z).SignerContext.FirstValid == 0) && ((*z).SignerContext.Interval == 0) && ((*z).SignerContext.Tree.MsgIsZero()) + return ((*z).SignerContext.FirstValid == 0) && ((*z).SignerContext.KeyLifetime == 0) && ((*z).SignerContext.Tree.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -368,7 +415,7 @@ func (z *Signature) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0001Len := uint32(4) var zb0001Mask uint8 /* 5 bits */ - if (*z).MerkleArrayIndex == 0 { + if (*z).VectorCommitmentIndex == 0 { zb0001Len-- zb0001Mask |= 0x2 } @@ -390,7 +437,7 @@ func (z *Signature) MarshalMsg(b []byte) (o []byte) { if (zb0001Mask & 0x2) == 0 { // if not empty // string "idx" o = append(o, 0xa3, 0x69, 0x64, 0x78) - o = msgp.AppendUint64(o, (*z).MerkleArrayIndex) + o = msgp.AppendUint64(o, (*z).VectorCommitmentIndex) } if (zb0001Mask & 0x4) == 0 { // if not empty // string "prf" @@ -439,9 +486,9 @@ func (z *Signature) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0001 > 0 { zb0001-- - (*z).MerkleArrayIndex, bts, err = msgp.ReadUint64Bytes(bts) + (*z).VectorCommitmentIndex, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "MerkleArrayIndex") + err = msgp.WrapError(err, "struct-from-array", "VectorCommitmentIndex") return } } @@ -491,9 +538,9 @@ func (z *Signature) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "idx": - (*z).MerkleArrayIndex, bts, err = msgp.ReadUint64Bytes(bts) + (*z).VectorCommitmentIndex, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "MerkleArrayIndex") + err = msgp.WrapError(err, "VectorCommitmentIndex") return } case "prf": @@ -534,7 +581,7 @@ func (z *Signature) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *Signature) MsgIsZero() bool { - return ((*z).Signature.MsgIsZero()) && ((*z).MerkleArrayIndex == 0) && ((*z).Proof.MsgIsZero()) && ((*z).VerifyingKey.MsgIsZero()) + return ((*z).Signature.MsgIsZero()) && ((*z).VectorCommitmentIndex == 0) && ((*z).Proof.MsgIsZero()) && ((*z).VerifyingKey.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -547,7 +594,7 @@ func (z *SignerContext) MarshalMsg(b []byte) (o []byte) { zb0001Len-- zb0001Mask |= 0x2 } - if (*z).Interval == 0 { + if (*z).KeyLifetime == 0 { zb0001Len-- zb0001Mask |= 0x4 } @@ -566,7 +613,7 @@ func (z *SignerContext) MarshalMsg(b []byte) (o []byte) { if (zb0001Mask & 0x4) == 0 { // if not empty // string "iv" o = append(o, 0xa2, 0x69, 0x76) - o = msgp.AppendUint64(o, (*z).Interval) + o = msgp.AppendUint64(o, (*z).KeyLifetime) } if (zb0001Mask & 0x8) == 0 { // if not empty // string "tree" @@ -605,9 +652,9 @@ func (z *SignerContext) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0001 > 0 { zb0001-- - (*z).Interval, bts, err = msgp.ReadUint64Bytes(bts) + (*z).KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Interval") + err = msgp.WrapError(err, "struct-from-array", "KeyLifetime") return } } @@ -649,9 +696,9 @@ func (z *SignerContext) UnmarshalMsg(bts []byte) (o []byte, err error) { return } case "iv": - (*z).Interval, bts, err = msgp.ReadUint64Bytes(bts) + (*z).KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { - err = msgp.WrapError(err, "Interval") + err = msgp.WrapError(err, "KeyLifetime") return } case "tree": @@ -686,13 +733,37 @@ func (z *SignerContext) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *SignerContext) MsgIsZero() bool { - return ((*z).FirstValid == 0) && ((*z).Interval == 0) && ((*z).Tree.MsgIsZero()) + return ((*z).FirstValid == 0) && ((*z).KeyLifetime == 0) && ((*z).Tree.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler func (z *Verifier) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) - o = msgp.AppendBytes(o, (*z)[:]) + // omitempty: check for empty values + zb0002Len := uint32(2) + var zb0002Mask uint8 /* 3 bits */ + if (*z).Commitment == (Commitment{}) { + zb0002Len-- + zb0002Mask |= 0x2 + } + if (*z).KeyLifetime == 0 { + zb0002Len-- + zb0002Mask |= 0x4 + } + // variable map header, size zb0002Len + o = append(o, 0x80|uint8(zb0002Len)) + if zb0002Len != 0 { + if (zb0002Mask & 0x2) == 0 { // if not empty + // string "cmt" + o = append(o, 0xa3, 0x63, 0x6d, 0x74) + o = msgp.AppendBytes(o, ((*z).Commitment)[:]) + } + if (zb0002Mask & 0x4) == 0 { // if not empty + // string "lf" + o = append(o, 0xa2, 0x6c, 0x66) + o = msgp.AppendUint64(o, (*z).KeyLifetime) + } + } return } @@ -703,10 +774,76 @@ func (_ *Verifier) CanMarshalMsg(z interface{}) bool { // UnmarshalMsg implements msgp.Unmarshaler func (z *Verifier) UnmarshalMsg(bts []byte) (o []byte, err error) { - bts, err = msgp.ReadExactBytes(bts, (*z)[:]) - if err != nil { - err = msgp.WrapError(err) - return + var field []byte + _ = field + var zb0002 int + var zb0003 bool + zb0002, zb0003, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0002, zb0003, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 > 0 { + zb0002-- + bts, err = msgp.ReadExactBytes(bts, ((*z).Commitment)[:]) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Commitment") + return + } + } + if zb0002 > 0 { + zb0002-- + (*z).KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "KeyLifetime") + return + } + } + if zb0002 > 0 { + err = msgp.ErrTooManyArrayFields(zb0002) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0003 { + (*z) = Verifier{} + } + for zb0002 > 0 { + zb0002-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "cmt": + bts, err = msgp.ReadExactBytes(bts, ((*z).Commitment)[:]) + if err != nil { + err = msgp.WrapError(err, "Commitment") + return + } + case "lf": + (*z).KeyLifetime, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "KeyLifetime") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } } o = bts return @@ -719,11 +856,11 @@ func (_ *Verifier) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *Verifier) Msgsize() (s int) { - s = msgp.ArrayHeaderSize + (MerkleSignatureSchemeRootSize * (msgp.ByteSize)) + s = 1 + 4 + msgp.ArrayHeaderSize + (MerkleSignatureSchemeRootSize * (msgp.ByteSize)) + 3 + msgp.Uint64Size return } // MsgIsZero returns whether this is a zero value func (z *Verifier) MsgIsZero() bool { - return (*z) == (Verifier{}) + return ((*z).Commitment == (Commitment{})) && ((*z).KeyLifetime == 0) } diff --git a/crypto/merklesignature/msgp_gen_test.go b/crypto/merklesignature/msgp_gen_test.go index 56c8e2bdf3..7a53df31a3 100644 --- a/crypto/merklesignature/msgp_gen_test.go +++ b/crypto/merklesignature/msgp_gen_test.go @@ -14,6 +14,66 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) +func TestMarshalUnmarshalCommitment(t *testing.T) { + partitiontest.PartitionTest(t) + v := Commitment{} + 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 TestRandomizedEncodingCommitment(t *testing.T) { + protocol.RunEncodingTest(t, &Commitment{}) +} + +func BenchmarkMarshalMsgCommitment(b *testing.B) { + v := Commitment{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgCommitment(b *testing.B) { + v := Commitment{} + 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 BenchmarkUnmarshalCommitment(b *testing.B) { + v := Commitment{} + 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 TestMarshalUnmarshalKeyRoundPair(t *testing.T) { partitiontest.PartitionTest(t) v := KeyRoundPair{} diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme.go b/crypto/merklesignature/persistentMerkleSignatureScheme.go index 1ce67c8d55..2ece3380da 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme.go @@ -94,10 +94,10 @@ func (s *Secrets) Persist(store db.Accessor) error { if s.ephemeralKeys == nil { return fmt.Errorf("no keys provided (nil)") } - if s.Interval == 0 { - return fmt.Errorf("Secrets.Persist: %w", errIntervalZero) + if s.KeyLifetime == 0 { + return fmt.Errorf("Secrets.Persist: %w", ErrKeyLifetimeIsZero) } - round := indexToRound(s.FirstValid, s.Interval, 0) + round := indexToRound(s.FirstValid, s.KeyLifetime, 0) encodedKey := protocol.GetEncodingBuf() err := store.Atomic(func(ctx context.Context, tx *sql.Tx) error { err := InstallStateProofTable(tx) // assumes schema table already exists (created by partInstallDatabase) @@ -121,7 +121,7 @@ func (s *Secrets) Persist(store db.Accessor) error { if err != nil { return fmt.Errorf("failed to insert StateProof key number %v round %d. SQL Error: %w", i, round, err) } - round += s.Interval + round += s.KeyLifetime } return nil diff --git a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go index 50ddb404d2..970daf903f 100644 --- a/crypto/merklesignature/persistentMerkleSignatureScheme_test.go +++ b/crypto/merklesignature/persistentMerkleSignatureScheme_test.go @@ -68,7 +68,7 @@ func TestFetchRestoreAllSecrets(t *testing.T) { store := createTestDB(a) defer store.Close() - firstValid := uint64(1) + firstValid := uint64(0) LastValid := uint64(5000) interval := uint64(256) @@ -81,17 +81,12 @@ func TestFetchRestoreAllSecrets(t *testing.T) { err = newMss.RestoreAllSecrets(*store) a.NoError(err) - for i := uint64(1); i < LastValid; i++ { + for i := uint64(0); i < LastValid; i++ { key1 := mss.GetKey(i) key2 := newMss.GetKey(i) - if i%interval == 0 { - a.NotNil(key1) - a.NotNil(key2) - a.Equal(*key1, *key2) - continue - } - a.Nil(key1) - a.Nil(key2) + a.NotNil(key1) + a.NotNil(key2) + a.Equal(*key1, *key2) } // make sure we exercise the path of the database being upgraded, but then diff --git a/crypto/merklesignature/posdivs.go b/crypto/merklesignature/posdivs.go index 6ac430acea..9ce88d53e9 100644 --- a/crypto/merklesignature/posdivs.go +++ b/crypto/merklesignature/posdivs.go @@ -20,20 +20,11 @@ import ( "errors" ) -var errRoundMultipleOfInterval = errors.New("the round should be a multiple of the interval") var errRoundFirstValid = errors.New("the round cannot be less than firstValid") -var errIntervalZero = errors.New("the interval should not be zero") -var errRoundNotZero = errors.New("the round should not be zero") -func checkMerkleSignatureSchemeParams(firstValid, round, interval uint64) error { - if interval == 0 { - return errIntervalZero - } - if round == 0 { - return errRoundNotZero - } - if round%interval != 0 { - return errRoundMultipleOfInterval +func checkMerkleSignatureSchemeParams(firstValid, round, keyLifetime uint64) error { + if keyLifetime == 0 { + return ErrKeyLifetimeIsZero } if round < firstValid { return errRoundFirstValid @@ -53,3 +44,10 @@ func indexToRound(firstValid, interval, pos uint64) uint64 { func roundOfFirstIndex(firstValid, interval uint64) uint64 { return ((firstValid + interval - 1) / interval) * interval } + +// firstRoundInKeyLifetime calculates the round of the valid key for a given round by lowering to the closest KeyLiftime divisor. +// It is implicitly assumed that round is larger than keyLifetime, as an MSS key for round 0 is not valid. +// A key lifetime of 0 is invalid. +func firstRoundInKeyLifetime(round, keyLifetime uint64) uint64 { + return round - (round % keyLifetime) +} diff --git a/crypto/merklesignature/posdivs_test.go b/crypto/merklesignature/posdivs_test.go index 8a48d1c32c..579a1c3d5e 100644 --- a/crypto/merklesignature/posdivs_test.go +++ b/crypto/merklesignature/posdivs_test.go @@ -29,23 +29,23 @@ func TestRoundToIndex(t *testing.T) { count := uint64(200) - // firstValid <= interval + // firstValid <= keyLifetime firstValid := uint64(100) - interval := uint64(101) + keyLifetime := uint64(101) ic := uint64(1) - checkRoundToIndex(count, ic, firstValid, interval, t) + checkRoundToIndex(count, ic, firstValid, keyLifetime, t) - // firstValid > interval + // firstValid > keyLifetime firstValid = uint64(100) - interval = uint64(99) + keyLifetime = uint64(99) ic = uint64(2) - checkRoundToIndex(count, ic, firstValid, interval, t) + checkRoundToIndex(count, ic, firstValid, keyLifetime, t) - // firstValid >> interval + // firstValid >> keyLifetime firstValid = uint64(100) - interval = uint64(20) + keyLifetime = uint64(20) ic = uint64(5) - checkRoundToIndex(count, ic, firstValid, interval, t) + checkRoundToIndex(count, ic, firstValid, keyLifetime, t) } func TestIndexToRoundToIndex(t *testing.T) { @@ -53,63 +53,52 @@ func TestIndexToRoundToIndex(t *testing.T) { count := uint64(200) firstValid := uint64(100) - interval := uint64(101) - checkIndexToRoundToIndex(count, firstValid, interval, t) + keyLifetime := uint64(101) + checkIndexToRoundToIndex(count, firstValid, keyLifetime, t) firstValid = uint64(100) - interval = uint64(99) - checkIndexToRoundToIndex(count, firstValid, interval, t) + keyLifetime = uint64(99) + checkIndexToRoundToIndex(count, firstValid, keyLifetime, t) firstValid = uint64(100) - interval = uint64(20) - checkIndexToRoundToIndex(count, firstValid, interval, t) + keyLifetime = uint64(20) + checkIndexToRoundToIndex(count, firstValid, keyLifetime, t) } func TestErrors(t *testing.T) { partitiontest.PartitionTest(t) - firstValid := uint64(100) - interval := uint64(101) - round := uint64(0) - require.Equal(t, errRoundNotZero, checkMerkleSignatureSchemeParams(firstValid, round, interval)) - - round = interval - 1 - require.Equal(t, errRoundMultipleOfInterval, checkMerkleSignatureSchemeParams(firstValid, round, interval)) - - round = interval + 1 - require.Equal(t, errRoundMultipleOfInterval, checkMerkleSignatureSchemeParams(firstValid, round, interval)) - - firstValid = uint64(101) - round = firstValid - 1 - interval = round / 2 - require.Equal(t, errRoundFirstValid, checkMerkleSignatureSchemeParams(firstValid, round, interval)) + firstValid := uint64(101) + round := firstValid - 1 + keyLifetime := round / 2 + require.Equal(t, errRoundFirstValid, checkMerkleSignatureSchemeParams(firstValid, round, keyLifetime)) - interval = 0 - require.Equal(t, errIntervalZero, checkMerkleSignatureSchemeParams(firstValid, round, interval)) + keyLifetime = 0 + require.Equal(t, ErrKeyLifetimeIsZero, checkMerkleSignatureSchemeParams(firstValid, round, keyLifetime)) - interval = 107 + keyLifetime = 107 round = 107 firstValid = 107 - require.NoError(t, checkMerkleSignatureSchemeParams(firstValid, round, interval)) + require.NoError(t, checkMerkleSignatureSchemeParams(firstValid, round, keyLifetime)) } -func checkIndexToRoundToIndex(count, firstValid, interval uint64, t *testing.T) { +func checkIndexToRoundToIndex(count, firstValid, keyLifetime uint64, t *testing.T) { for pos := uint64(0); pos < count; pos++ { - round := indexToRound(firstValid, interval, uint64(pos)) - index := roundToIndex(firstValid, round, interval) + round := indexToRound(firstValid, keyLifetime, uint64(pos)) + index := roundToIndex(firstValid, round, keyLifetime) require.Equal(t, uint64(pos), index) } } -func checkRoundToIndex(count, initC, firstValid, interval uint64, t *testing.T) { +func checkRoundToIndex(count, initC, firstValid, keyLifetime uint64, t *testing.T) { expIndex := uint64(0) for c := initC; c < count; c++ { - round := interval * c - index := roundToIndex(firstValid, round, interval) + round := keyLifetime * c + index := roundToIndex(firstValid, round, keyLifetime) require.Equal(t, expIndex, index) expIndex++ - round2 := indexToRound(firstValid, interval, index) + round2 := indexToRound(firstValid, keyLifetime, index) require.Equal(t, round, round2) } diff --git a/crypto/stateproof/builder.go b/crypto/stateproof/builder.go new file mode 100644 index 0000000000..3f85656ab4 --- /dev/null +++ b/crypto/stateproof/builder.go @@ -0,0 +1,260 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/data/basics" +) + +// Errors for the StateProof builder +var ( + ErrPositionOutOfBound = errors.New("requested position is out of bounds") + ErrPositionAlreadyPresent = errors.New("requested position is already present") + ErrPositionWithZeroWeight = errors.New("position has zero weight") + ErrCoinIndexError = errors.New("could not find corresponding index for a given coin") +) + +// Builder keeps track of signatures on a message and eventually produces +// a state proof for that message. +type Builder struct { + data MessageHash + round uint64 + sigs []sigslot // Indexed by pos in participants + signedWeight uint64 // Total weight of signatures so far + participants []basics.Participant + parttree *merklearray.Tree + lnProvenWeight uint64 + provenWeight uint64 + strengthTarget uint64 + cachedProof *StateProof +} + +// MakeBuilder constructs an empty builder. After adding enough signatures and signed weight, this builder is used to create a stateproof. +func MakeBuilder(data MessageHash, round uint64, provenWeight uint64, part []basics.Participant, parttree *merklearray.Tree, strengthTarget uint64) (*Builder, error) { + npart := len(part) + lnProvenWt, err := LnIntApproximation(provenWeight) + if err != nil { + return nil, err + } + + b := &Builder{ + data: data, + round: round, + sigs: make([]sigslot, npart), + signedWeight: 0, + participants: part, + parttree: parttree, + lnProvenWeight: lnProvenWt, + provenWeight: provenWeight, + strengthTarget: strengthTarget, + cachedProof: nil, + } + + return b, nil +} + +// Present checks if the builder already contains a signature at a particular +// offset. +func (b *Builder) Present(pos uint64) (bool, error) { + if pos >= uint64(len(b.sigs)) { + return false, fmt.Errorf("%w pos %d >= len(b.sigs) %d", ErrPositionOutOfBound, pos, len(b.sigs)) + } + + return b.sigs[pos].Weight != 0, nil +} + +// IsValid verifies that the participant along with the signature can be inserted to the builder. +// verifySig can be set to false when the signature is already verified (e.g. loaded from the DB) +func (b *Builder) IsValid(pos uint64, sig *merklesignature.Signature, verifySig bool) error { + if pos >= uint64(len(b.participants)) { + return fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.participants)) + } + + p := b.participants[pos] + + if p.Weight == 0 { + return fmt.Errorf("builder.IsValid: %w: position = %d", ErrPositionWithZeroWeight, pos) + } + + // Check signature + if verifySig { + if err := sig.ValidateSaltVersion(merklesignature.SchemeSaltVersion); err != nil { + return err + } + if err := p.PK.VerifyBytes(b.round, b.data[:], sig); err != nil { + return err + } + } + return nil +} + +// Add a signature to the set of signatures available for building a proof. +func (b *Builder) Add(pos uint64, sig merklesignature.Signature) error { + isPresent, err := b.Present(pos) + if err != nil { + return err + } + if isPresent { + return ErrPositionAlreadyPresent + } + + p := b.participants[pos] + + // Remember the signature + b.sigs[pos].Weight = p.Weight + b.sigs[pos].Sig = sig + b.signedWeight += p.Weight + b.cachedProof = nil // can rebuild a more optimized state proof + return nil +} + +// Ready returns whether the state proof is ready to be built. +func (b *Builder) Ready() bool { + return b.cachedProof != nil || b.signedWeight > b.provenWeight +} + +// SignedWeight returns the total weight of signatures added so far. +func (b *Builder) SignedWeight() uint64 { + return b.signedWeight +} + +// coinIndex returns the position pos in the sigs array such that the sum +// of all signature weights before pos is less than or equal to coinWeight, +// but the sum of all signature weights up to and including pos exceeds +// coinWeight. +// +// coinIndex works by doing a binary search on the sigs array. +func (b *Builder) coinIndex(coinWeight uint64) (uint64, error) { + lo := uint64(0) + hi := uint64(len(b.sigs)) + +again: + if lo >= hi { + return 0, fmt.Errorf("%w: lo %d >= hi %d and coin %d", ErrCoinIndexError, lo, hi, coinWeight) + } + + mid := (lo + hi) / 2 + if coinWeight < b.sigs[mid].L { + hi = mid + goto again + } + + if coinWeight < b.sigs[mid].L+b.sigs[mid].Weight { + return mid, nil + } + + lo = mid + 1 + goto again +} + +// Build returns a state proof, if the builder has accumulated +// enough signatures to construct it. +func (b *Builder) Build() (*StateProof, error) { + if b.cachedProof != nil { + return b.cachedProof, nil + } + + if !b.Ready() { + return nil, fmt.Errorf("%w: %d <= %d", ErrSignedWeightLessThanProvenWeight, b.signedWeight, b.provenWeight) + } + + // Commit to the sigs array + for i := 1; i < len(b.sigs); i++ { + b.sigs[i].L = b.sigs[i-1].L + b.sigs[i-1].Weight + } + + hfactory := crypto.HashFactory{HashType: HashType} + sigtree, err := merklearray.BuildVectorCommitmentTree(committableSignatureSlotArray(b.sigs), hfactory) + if err != nil { + return nil, err + } + + // Reveal sufficient number of signatures + s := &StateProof{ + SigCommit: sigtree.Root(), + SignedWeight: b.signedWeight, + Reveals: make(map[uint64]Reveal), + MerkleSignatureSaltVersion: merklesignature.SchemeSaltVersion, + } + + nr, err := numReveals(b.signedWeight, b.lnProvenWeight, b.strengthTarget) + if err != nil { + return nil, err + } + + choice := coinChoiceSeed{ + partCommitment: b.parttree.Root(), + lnProvenWeight: b.lnProvenWeight, + sigCommitment: s.SigCommit, + signedWeight: s.SignedWeight, + data: b.data, + } + + coinHash := makeCoinGenerator(&choice) + + var proofPositions []uint64 + revealsSequence := make([]uint64, nr) + for j := uint64(0); j < nr; j++ { + coin := coinHash.getNextCoin() + pos, err := b.coinIndex(coin) + if err != nil { + return nil, err + } + + if pos >= uint64(len(b.participants)) { + return nil, fmt.Errorf("%w pos %d >= len(participants) %d", ErrPositionOutOfBound, pos, len(b.participants)) + } + + revealsSequence[j] = pos + + // If we already revealed pos, no need to do it again + _, alreadyRevealed := s.Reveals[pos] + if alreadyRevealed { + continue + } + + // Generate the reveal for pos + s.Reveals[pos] = Reveal{ + SigSlot: b.sigs[pos].sigslotCommit, + Part: b.participants[pos], + } + + proofPositions = append(proofPositions, pos) + } + + sigProofs, err := sigtree.Prove(proofPositions) + if err != nil { + return nil, err + } + + partProofs, err := b.parttree.Prove(proofPositions) + if err != nil { + return nil, err + } + + s.SigProofs = *sigProofs + s.PartProofs = *partProofs + s.PositionsToReveal = revealsSequence + b.cachedProof = s + return s, nil +} diff --git a/crypto/compactcert/builder_test.go b/crypto/stateproof/builder_test.go similarity index 51% rename from crypto/compactcert/builder_test.go rename to crypto/stateproof/builder_test.go index 8d29e71273..780262e851 100644 --- a/crypto/compactcert/builder_test.go +++ b/crypto/stateproof/builder_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "bytes" @@ -36,10 +36,26 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -type testMessage string +type testMessage []byte -const compactCertRoundsForTests = 256 -const compactCertSecKQForTests = 128 +func (m testMessage) IntoStateProofMessageHash() MessageHash { + hsh := MessageHash{} + copy(hsh[:], m) + return hsh +} + +type paramsForTest struct { + sp StateProof + provenWeight uint64 + partCommitment crypto.GenericDigest + numberOfParticipnets uint64 + data MessageHash + builder *Builder + sig merklesignature.Signature +} + +const stateProofIntervalForTests = 256 +const stateProofStrengthTargetForTests = 256 func hashBytes(hash hash.Hash, m []byte) []byte { hash.Reset() @@ -48,10 +64,6 @@ func hashBytes(hash hash.Hash, m []byte) []byte { return outhash } -func (m testMessage) ToBeHashed() (protocol.HashID, []byte) { - return protocol.Message, []byte(m) -} - func createParticipantSliceWithWeight(totalWeight, numberOfParticipant int, key *merklesignature.Verifier) []basics.Participant { parts := make([]basics.Participant, 0, numberOfParticipant) @@ -73,93 +85,105 @@ func generateTestSigner(firstValid uint64, lastValid uint64, interval uint64, a return signer } -func TestBuildVerify(t *testing.T) { - partitiontest.PartitionTest(t) - - a := require.New(t) - currentRound := basics.Round(compactCertRoundsForTests) - // Doing a full test of 1M accounts takes too much CPU time in CI. - doLargeTest := false +func generateProofForTesting(a *require.Assertions, doLargeTest bool) paramsForTest { totalWeight := 10000000 - npartHi := 10 - npartLo := 9990 + npartHi := 2 + npartLo := 100 + stateproofIntervals := uint64(4) // affects the number of keys that will be generated if doLargeTest { npartHi *= 100 npartLo *= 100 + stateproofIntervals = 20 } npart := npartHi + npartLo - param := Params{ - Msg: testMessage("hello world"), - ProvenWeight: uint64(totalWeight / 2), - SigRound: currentRound, - SecKQ: compactCertSecKQForTests, - } + data := testMessage("hello world").IntoStateProofMessageHash() + provenWt := uint64(totalWeight / 2) // Share the key; we allow the same vote key to appear in multiple accounts.. - key := generateTestSigner(0, uint64(compactCertRoundsForTests)*20+1, compactCertRoundsForTests, a) + key := generateTestSigner(0, uint64(stateProofIntervalForTests)*stateproofIntervals+1, stateProofIntervalForTests, a) var parts []basics.Participant var sigs []merklesignature.Signature parts = append(parts, createParticipantSliceWithWeight(totalWeight, npartHi, key.GetVerifier())...) parts = append(parts, createParticipantSliceWithWeight(totalWeight, npartLo, key.GetVerifier())...) - signerInRound := key.GetSigner(uint64(currentRound)) - sig, err := signerInRound.Sign(param.Msg) - require.NoError(t, err, "failed to create keys") + signerInRound := key.GetSigner(stateProofIntervalForTests) + sig, err := signerInRound.SignBytes(data[:]) + a.NoError(err, "failed to create keys") for i := 0; i < npart; i++ { sigs = append(sigs, sig) } partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) - if err != nil { - t.Error(err) - } + a.NoError(err) - b, err := MkBuilder(param, parts, partcom) - if err != nil { - t.Error(err) - } + b, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + a.NoError(err) - for i := 0; i < npart; i++ { - err = b.Add(uint64(i), sigs[i], !doLargeTest) - if err != nil { - t.Error(err) - } + for i := uint64(0); i < uint64(npart)/2+10; i++ { // leave some signature to be added later in the test (if needed) + a.False(b.Present(i)) + a.NoError(b.IsValid(i, &sigs[i], !doLargeTest)) + b.Add(i, sigs[i]) + + // sanity check that the builder add the signature + isPresent, err := b.Present(i) + a.NoError(err) + a.True(isPresent) } - cert, err := b.Build() - if err != nil { - t.Error(err) + proof, err := b.Build() + a.NoError(err) + + p := paramsForTest{ + sp: *proof, + provenWeight: provenWt, + partCommitment: partcom.Root(), + numberOfParticipnets: uint64(npart), + data: data, + builder: b, + sig: sig, } + return p +} + +func TestBuildVerify(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + + p := generateProofForTesting(a, true) + sProof := p.sp var someReveal Reveal - for _, rev := range cert.Reveals { + for _, rev := range sProof.Reveals { someReveal = rev break } - certenc := protocol.Encode(cert) - fmt.Printf("Cert size:\n") - fmt.Printf(" %6d elems sigproofs\n", len(cert.SigProofs.Path)) - fmt.Printf(" %6d bytes sigproofs\n", len(protocol.EncodeReflect(cert.SigProofs))) - fmt.Printf(" %6d bytes partproofs\n", len(protocol.EncodeReflect(cert.PartProofs))) - fmt.Printf(" %6d bytes sigproof per reveal\n", len(protocol.EncodeReflect(cert.SigProofs))/len(cert.Reveals)) - fmt.Printf(" %6d reveals:\n", len(cert.Reveals)) + proofEnc := protocol.Encode(&sProof) + fmt.Printf("StateProof size:\n") + fmt.Printf(" %6d elems sigproofs\n", len(sProof.SigProofs.Path)) + fmt.Printf(" %6d bytes sigproofs\n", len(protocol.EncodeReflect(sProof.SigProofs))) + fmt.Printf(" %6d bytes partproofs\n", len(protocol.EncodeReflect(sProof.PartProofs))) + fmt.Printf(" %6d bytes sigproof per reveal\n", len(protocol.EncodeReflect(sProof.SigProofs))/len(sProof.Reveals)) + fmt.Printf(" %6d reveals:\n", len(sProof.Reveals)) fmt.Printf(" %6d bytes reveals[*] participant\n", len(protocol.Encode(&someReveal.Part))) fmt.Printf(" %6d bytes reveals[*] sigslot\n", len(protocol.Encode(&someReveal.SigSlot))) fmt.Printf(" %6d bytes reveals[*] total\n", len(protocol.Encode(&someReveal))) - fmt.Printf(" %6d bytes total\n", len(certenc)) + fmt.Printf(" %6d bytes total\n", len(proofEnc)) + + verif, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) - verif := MkVerifier(param, partcom.Root()) - err = verif.Verify(cert) - require.NoError(t, err, "failed to verify the compact cert") + err = verif.Verify(stateProofIntervalForTests, p.data, &sProof) + a.NoError(err, "failed to verify the state proof") } -func generateRandomParticipant(a *require.Assertions, testname string) basics.Participant { +func generateRandomParticipant(a *require.Assertions) basics.Participant { key := generateTestSigner(0, 8, 1, a) p := basics.Participant{ @@ -173,11 +197,15 @@ func calculateHashOnPartLeaf(part basics.Participant) []byte { binaryWeight := make([]byte, 8) binary.LittleEndian.PutUint64(binaryWeight, part.Weight) + keyLifetimeBytes := make([]byte, 8) + binary.LittleEndian.PutUint64(keyLifetimeBytes, part.PK.KeyLifetime) + publicKeyBytes := part.PK - partCommitment := make([]byte, 0, len(protocol.CompactCertPart)+len(binaryWeight)+len(publicKeyBytes)) - partCommitment = append(partCommitment, protocol.CompactCertPart...) + partCommitment := make([]byte, 0, len(protocol.StateProofPart)+len(binaryWeight)+len(publicKeyBytes.Commitment)+len(keyLifetimeBytes)) + partCommitment = append(partCommitment, protocol.StateProofPart...) partCommitment = append(partCommitment, binaryWeight...) - partCommitment = append(partCommitment, publicKeyBytes[:]...) + partCommitment = append(partCommitment, keyLifetimeBytes...) + partCommitment = append(partCommitment, publicKeyBytes.Commitment[:]...) factory := crypto.HashFactory{HashType: HashType} hashValue := hashBytes(factory.NewHash(), partCommitment) @@ -201,10 +229,10 @@ func TestParticipationCommitmentBinaryFormat(t *testing.T) { a := require.New(t) var parts []basics.Participant - parts = append(parts, generateRandomParticipant(a, t.Name())) - parts = append(parts, generateRandomParticipant(a, t.Name())) - parts = append(parts, generateRandomParticipant(a, t.Name())) - parts = append(parts, generateRandomParticipant(a, t.Name())) + parts = append(parts, generateRandomParticipant(a)) + parts = append(parts, generateRandomParticipant(a)) + parts = append(parts, generateRandomParticipant(a)) + parts = append(parts, generateRandomParticipant(a)) partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) @@ -230,22 +258,16 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { a := require.New(t) - currentRound := basics.Round(compactCertRoundsForTests) totalWeight := 10000000 numPart := 4 - param := Params{ - Msg: testMessage("test!"), - ProvenWeight: uint64(totalWeight / (2 * numPart)), - SigRound: currentRound, - SecKQ: compactCertSecKQForTests, - } + data := testMessage("test!").IntoStateProofMessageHash() var parts []basics.Participant var sigs []merklesignature.Signature for i := 0; i < numPart; i++ { - key := generateTestSigner(0, uint64(compactCertRoundsForTests)*8, compactCertRoundsForTests, a) + key := generateTestSigner(0, uint64(stateProofIntervalForTests)*8, stateProofIntervalForTests, a) part := basics.Participant{ PK: *key.GetVerifier(), @@ -253,7 +275,7 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { } parts = append(parts, part) - sig, err := key.GetSigner(uint64(currentRound)).Sign(param.Msg) + sig, err := key.GetSigner(stateProofIntervalForTests).SignBytes(data[:]) require.NoError(t, err, "failed to create keys") sigs = append(sigs, sig) @@ -262,21 +284,22 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) a.NoError(err) - b, err := MkBuilder(param, parts, partcom) + b, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/(2*numPart)), parts, partcom, stateProofStrengthTargetForTests) a.NoError(err) for i := 0; i < numPart; i++ { - err = b.Add(uint64(i), sigs[i], false) - a.NoError(err) + a.False(b.Present(uint64(i))) + a.NoError(b.IsValid(uint64(i), &sigs[i], false)) + b.Add(uint64(i), sigs[i]) } - cert, err := b.Build() + sProof, err := b.Build() a.NoError(err) - leaf0 := calculateHashOnSigLeaf(t, sigs[0], findLInCert(a, sigs[0], cert)) - leaf1 := calculateHashOnSigLeaf(t, sigs[1], findLInCert(a, sigs[1], cert)) - leaf2 := calculateHashOnSigLeaf(t, sigs[2], findLInCert(a, sigs[2], cert)) - leaf3 := calculateHashOnSigLeaf(t, sigs[3], findLInCert(a, sigs[3], cert)) + leaf0 := calculateHashOnSigLeaf(t, sigs[0], findLInProof(a, sigs[0], sProof)) + leaf1 := calculateHashOnSigLeaf(t, sigs[1], findLInProof(a, sigs[1], sProof)) + leaf2 := calculateHashOnSigLeaf(t, sigs[2], findLInProof(a, sigs[2], sProof)) + leaf3 := calculateHashOnSigLeaf(t, sigs[3], findLInProof(a, sigs[3], sProof)) // hash internal node according to the vector commitment indices inner1 := calculateHashOnInternalNode(leaf0, leaf2) @@ -284,11 +307,11 @@ func TestSignatureCommitmentBinaryFormat(t *testing.T) { calcRoot := calculateHashOnInternalNode(inner1, inner2) - a.Equal(cert.SigCommit, crypto.GenericDigest(calcRoot)) + a.Equal(sProof.SigCommit, crypto.GenericDigest(calcRoot)) } -// The aim of this test is to simulate how a SNARK circuit will verify a signature.(part of the overall compcatcert verification) +// The aim of this test is to simulate how a SNARK circuit will verify a signature.(part of the overall stateproof verification) // it includes parsing the signature's format (according to Algorand's spec) and binds it to a specific length. // here we also expect the scheme to use Falcon signatures and nothing else. func TestSimulateSignatureVerification(t *testing.T) { @@ -297,17 +320,17 @@ func TestSimulateSignatureVerification(t *testing.T) { signer := generateTestSigner(50, 100, 1, a) sigRound := uint64(55) - hashable := testMessage("testMessage") - sig, err := signer.GetSigner(sigRound).Sign(hashable) + msg := testMessage("testMessage") + sig, err := signer.GetSigner(sigRound).SignBytes(msg) a.NoError(err) genericKey := signer.GetVerifier() sigBytes, err := sig.GetFixedLengthHashableRepresentation() a.NoError(err) - checkSignature(a, sigBytes, genericKey, sigRound, hashable, 5, 6) + checkSignature(a, sigBytes, genericKey, sigRound, msg, 5, 6) } -// The aim of this test is to simulate how a SNARK circuit will verify a signature.(part of the overall compcatcert verification) +// The aim of this test is to simulate how a SNARK circuit will verify a signature.(part of the overall stateproof verification) // it includes parsing the signature's format (according to Algorand's spec) and binds it to a specific length. // here we also expect the scheme to use Falcon signatures and nothing else. func TestSimulateSignatureVerificationOneEphemeralKey(t *testing.T) { @@ -315,20 +338,20 @@ func TestSimulateSignatureVerificationOneEphemeralKey(t *testing.T) { a := require.New(t) // we create one ephemeral key so the signature's proof should be with len 0 - signer := generateTestSigner(1, compactCertRoundsForTests, compactCertRoundsForTests, a) + signer := generateTestSigner(1, stateProofIntervalForTests, stateProofIntervalForTests, a) - sigRound := uint64(compactCertRoundsForTests) - hashable := testMessage("testMessage") - sig, err := signer.GetSigner(sigRound).Sign(hashable) + sigRound := uint64(stateProofIntervalForTests) + msg := testMessage("testMessage") + sig, err := signer.GetSigner(sigRound).SignBytes(msg) a.NoError(err) genericKey := signer.GetVerifier() sigBytes, err := sig.GetFixedLengthHashableRepresentation() a.NoError(err) - checkSignature(a, sigBytes, genericKey, sigRound, hashable, 0, 0) + checkSignature(a, sigBytes, genericKey, sigRound, msg, 0, 0) } -func checkSignature(a *require.Assertions, sigBytes []byte, verifier *merklesignature.Verifier, round uint64, message crypto.Hashable, expectedIndex uint64, expectedPathLen uint8) { +func checkSignature(a *require.Assertions, sigBytes []byte, verifier *merklesignature.Verifier, round uint64, message []byte, expectedIndex uint64, expectedPathLen uint8) { a.Equal(len(sigBytes), 4366) parsedBytes := 0 @@ -354,7 +377,7 @@ func checkSignature(a *require.Assertions, sigBytes []byte, verifier *merklesign leafHash = verifyMerklePath(idx, pathLe, sigBytes, parsedBytes, leafHash) - a.Equal(leafHash, verifier[:]) + a.Equal(leafHash, verifier.Commitment[:]) } func verifyMerklePath(idx uint64, pathLe byte, sigBytes []byte, parsedBytes int, leafHash []byte) []byte { @@ -396,7 +419,7 @@ func hashEphemeralPublicKeyLeaf(round uint64, falconPK [falcon.PublicKeySize]byt return leafHash } -func verifyFalconSignature(a *require.Assertions, sigBytes []byte, parsedBytes int, message crypto.Hashable) (int, [falcon.PublicKeySize]byte) { +func verifyFalconSignature(a *require.Assertions, sigBytes []byte, parsedBytes int, message []byte) (int, [falcon.PublicKeySize]byte) { var falconSig [falcon.CTSignatureSize]byte copy(falconSig[:], sigBytes[parsedBytes:parsedBytes+1538]) parsedBytes += 1538 @@ -407,15 +430,14 @@ func verifyFalconSignature(a *require.Assertions, sigBytes []byte, parsedBytes i parsedBytes += 1793 ephemeralPk := falcon.PublicKey(falconPK) - msgBytes := crypto.Hash(crypto.HashRep(message)) - err := ephemeralPk.VerifyCTSignature(ctSign, msgBytes[:]) + err := ephemeralPk.VerifyCTSignature(ctSign, message) a.NoError(err) return parsedBytes, falconPK } -func findLInCert(a *require.Assertions, signature merklesignature.Signature, cert *Cert) uint64 { - for _, t := range cert.Reveals { - if bytes.Compare(t.SigSlot.Sig.Signature.Signature, signature.Signature) == 0 { +func findLInProof(a *require.Assertions, signature merklesignature.Signature, proof *StateProof) uint64 { + for _, t := range proof.Reveals { + if bytes.Compare(t.SigSlot.Sig.Signature, signature.Signature) == 0 { return t.SigSlot.L } } @@ -423,32 +445,213 @@ func findLInCert(a *require.Assertions, signature merklesignature.Signature, cer return 0 } +func TestBuilder_AddRejectsInvalidSigVersion(t *testing.T) { + partitiontest.PartitionTest(t) + + // setting up a builder + a := require.New(t) + + totalWeight := 10000000 + npartHi := 1 + npartLo := 9 + + data := testMessage("hello world").IntoStateProofMessageHash() + + key := generateTestSigner(0, uint64(stateProofIntervalForTests)*20+1, stateProofIntervalForTests, a) + var parts []basics.Participant + parts = append(parts, createParticipantSliceWithWeight(totalWeight, npartHi, key.GetVerifier())...) + parts = append(parts, createParticipantSliceWithWeight(totalWeight, npartLo, key.GetVerifier())...) + + partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) + a.NoError(err) + + builder, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + a.NoError(err) + + // actual test: + signerInRound := key.GetSigner(stateProofIntervalForTests) + sig, err := signerInRound.SignBytes(data[:]) + require.NoError(t, err, "failed to create keys") + // Corrupting the version of the signature: + sig.Signature[1]++ + + a.ErrorIs(builder.IsValid(0, &sig, true), merklesignature.ErrSignatureSaltVersionMismatch) +} + +func TestBuildAndReady(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + totalWeight := 10000000 + data := testMessage("hello world").IntoStateProofMessageHash() + var parts []basics.Participant + + partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) + a.NoError(err) + + builder, err := MakeBuilder(data, stateProofIntervalForTests, uint64(totalWeight/2), parts, partcom, stateProofStrengthTargetForTests) + a.NoError(err) + + a.False(builder.Ready()) + _, err = builder.Build() + a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) + + builder.signedWeight = builder.provenWeight + a.False(builder.Ready()) + _, err = builder.Build() + a.ErrorIs(err, ErrSignedWeightLessThanProvenWeight) + + builder.signedWeight = builder.provenWeight + 1 + a.True(builder.Ready()) + _, err = builder.Build() + a.NotErrorIs(err, ErrSignedWeightLessThanProvenWeight) + +} + +func TestErrorCases(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + builder := Builder{} + _, err := builder.Present(1) + a.ErrorIs(err, ErrPositionOutOfBound) + + builder.participants = make([]basics.Participant, 1, 1) + builder.sigs = make([]sigslot, 1, 1) + err = builder.IsValid(1, &merklesignature.Signature{}, false) + a.ErrorIs(err, ErrPositionOutOfBound) + + err = builder.IsValid(0, &merklesignature.Signature{}, false) + require.ErrorIs(t, err, ErrPositionWithZeroWeight) + + builder.participants[0].Weight = 1 + err = builder.IsValid(0, &merklesignature.Signature{}, true) + a.ErrorIs(err, merklesignature.ErrKeyLifetimeIsZero) + + builder.participants[0].PK.KeyLifetime = 20 + err = builder.IsValid(0, &merklesignature.Signature{}, true) + a.ErrorIs(err, merklesignature.ErrSignatureSchemeVerificationFailed) + + builder.sigs[0].Weight = 1 + err = builder.Add(1, merklesignature.Signature{}) + a.ErrorIs(err, ErrPositionOutOfBound) + + err = builder.Add(0, merklesignature.Signature{}) + a.ErrorIs(err, ErrPositionAlreadyPresent) +} + +func checkSigsArray(n int, a *require.Assertions) { + b := &Builder{ + sigs: make([]sigslot, n), + } + for i := 0; i < n; i++ { + b.sigs[i].L = uint64(i) + b.sigs[i].Weight = 1 + } + for i := 0; i < n; i++ { + pos, err := b.coinIndex(uint64(i)) + a.NoError(err) + a.Equal(uint64(i), pos) + } +} + +func TestCoinIndex(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + n := 1000 + checkSigsArray(n, a) + + n = 1 + checkSigsArray(n, a) + + n = 2 + checkSigsArray(n, a) + + n = 3 + checkSigsArray(n, a) +} + +func TestCoinIndexBetweenWeights(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + n := 1000 + b := &Builder{ + sigs: make([]sigslot, n), + } + for i := 0; i < n; i++ { + b.sigs[i].Weight = 2 + } + + b.sigs[0].L = 0 + for i := 1; i < n; i++ { + b.sigs[i].L = b.sigs[i-1].L + b.sigs[i-1].Weight + } + for i := 0; i < 2*n; i++ { + pos, err := b.coinIndex(uint64(i)) + a.NoError(err) + a.Equal(pos, uint64(i/2)) + } + + _, err := b.coinIndex(uint64(2*n + 1)) + a.ErrorIs(err, ErrCoinIndexError) +} + +func TestBuilderWithZeroProvenWeight(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + data := testMessage("hello world").IntoStateProofMessageHash() + + _, err := MakeBuilder(data, stateProofIntervalForTests, 0, nil, nil, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrIllegalInputForLnApprox) + +} + +func TestBuilder_BuildStateProofCache(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + p := generateProofForTesting(a, true) + sp1 := &p.sp + sp2, err := p.builder.Build() + a.NoError(err) + a.Equal(sp1, sp2) // already built, no signatures added + + err = p.builder.Add(p.numberOfParticipnets-1, p.sig) + a.NoError(err) + sp3, err := p.builder.Build() + a.NoError(err) + a.NotEqual(sp1, sp3) // better StateProof with added signature should have been built + + sp4, err := p.builder.Build() + a.NoError(err) + a.Equal(sp3, sp4) + + return +} + func BenchmarkBuildVerify(b *testing.B) { totalWeight := 1000000 - npart := 10000 + npart := 1000 - currentRound := basics.Round(compactCertRoundsForTests) a := require.New(b) - param := Params{ - Msg: testMessage("hello world"), - ProvenWeight: uint64(totalWeight / 2), - SigRound: compactCertRoundsForTests, - SecKQ: compactCertSecKQForTests, - } + provenWeight := uint64(totalWeight / 2) + data := testMessage("hello world").IntoStateProofMessageHash() var parts []basics.Participant var partkeys []*merklesignature.Secrets var sigs []merklesignature.Signature for i := 0; i < npart; i++ { - signer := generateTestSigner(0, compactCertRoundsForTests, compactCertRoundsForTests+1, a) + signer := generateTestSigner(0, stateProofIntervalForTests+1, stateProofIntervalForTests, a) part := basics.Participant{ PK: *signer.GetVerifier(), Weight: uint64(totalWeight / npart), } - signerInRound := signer.GetSigner(uint64(currentRound)) - sig, err := signerInRound.Sign(param.Msg) + signerInRound := signer.GetSigner(stateProofIntervalForTests) + sig, err := signerInRound.SignBytes(data[:]) require.NoError(b, err, "failed to create keys") partkeys = append(partkeys, signer) @@ -456,7 +659,7 @@ func BenchmarkBuildVerify(b *testing.B) { parts = append(parts, part) } - var cert *Cert + var sp *StateProof partcom, err := merklearray.BuildVectorCommitmentTree(basics.ParticipantsArray(parts), crypto.HashFactory{HashType: HashType}) if err != nil { b.Error(err) @@ -464,19 +667,18 @@ func BenchmarkBuildVerify(b *testing.B) { b.Run("AddBuild", func(b *testing.B) { for i := 0; i < b.N; i++ { - builder, err := MkBuilder(param, parts, partcom) + builder, err := MakeBuilder(data, stateProofIntervalForTests, provenWeight, parts, partcom, stateProofStrengthTargetForTests) if err != nil { b.Error(err) } for i := 0; i < npart; i++ { - err = builder.Add(uint64(i), sigs[i], true) - if err != nil { - b.Error(err) - } + a.False(builder.Present(uint64(i))) + a.NoError(builder.IsValid(uint64(i), &sigs[i], true)) + builder.Add(uint64(i), sigs[i]) } - cert, err = builder.Build() + sp, err = builder.Build() if err != nil { b.Error(err) } @@ -485,31 +687,10 @@ func BenchmarkBuildVerify(b *testing.B) { b.Run("Verify", func(b *testing.B) { for i := 0; i < b.N; i++ { - verif := MkVerifier(param, partcom.Root()) - if err = verif.Verify(cert); err != nil { + verif, _ := MkVerifier(partcom.Root(), provenWeight, stateProofStrengthTargetForTests) + if err = verif.Verify(stateProofIntervalForTests, data, sp); err != nil { b.Error(err) } } }) } - -func TestCoinIndex(t *testing.T) { - partitiontest.PartitionTest(t) - - n := 1000 - b := &Builder{ - sigs: make([]sigslot, n), - sigsHasValidL: true, - } - - for i := 0; i < n; i++ { - b.sigs[i].L = uint64(i) - b.sigs[i].Weight = 1 - } - - for i := 0; i < n; i++ { - pos, err := b.coinIndex(uint64(i)) - require.NoError(t, err) - require.Equal(t, pos, uint64(i)) - } -} diff --git a/crypto/stateproof/coinGenerator.go b/crypto/stateproof/coinGenerator.go new file mode 100644 index 0000000000..320232fbaa --- /dev/null +++ b/crypto/stateproof/coinGenerator.go @@ -0,0 +1,125 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "encoding/binary" + "golang.org/x/crypto/sha3" + "math/big" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/protocol" +) + +// The coinChoiceSeed defines the randomness seed that will be given to an XOF function. This will be used for choosing +// the index of the coin to reveal as part of the state proof. +type coinChoiceSeed struct { + // the ToBeHashed function should be updated when fields are added to this structure + version byte + partCommitment crypto.GenericDigest + lnProvenWeight uint64 + sigCommitment crypto.GenericDigest + signedWeight uint64 + data MessageHash +} + +// ToBeHashed returns a binary representation of the coinChoiceSeed structure. +// Since this code is also implemented as a circuit in the stateproof SNARK prover we can't use +// msgpack encoding since it may result in a variable length byte slice. +// Alternatively, we serialize the fields in the structure in a specific format. +func (cc *coinChoiceSeed) ToBeHashed() (protocol.HashID, []byte) { + var signedWtAsBytes [8]byte + binary.LittleEndian.PutUint64(signedWtAsBytes[:], cc.signedWeight) + + var lnProvenWtAsBytes [8]byte + binary.LittleEndian.PutUint64(lnProvenWtAsBytes[:], cc.lnProvenWeight) + + coinChoiceBytes := make([]byte, 0, 1+len(cc.partCommitment)+len(lnProvenWtAsBytes)+len(cc.sigCommitment)+len(signedWtAsBytes)+len(cc.data)) + coinChoiceBytes = append(coinChoiceBytes, cc.version) + coinChoiceBytes = append(coinChoiceBytes, cc.partCommitment...) + coinChoiceBytes = append(coinChoiceBytes, lnProvenWtAsBytes[:]...) + coinChoiceBytes = append(coinChoiceBytes, cc.sigCommitment...) + coinChoiceBytes = append(coinChoiceBytes, signedWtAsBytes[:]...) + coinChoiceBytes = append(coinChoiceBytes, cc.data[:]...) + + return protocol.StateProofCoin, coinChoiceBytes +} + +// coinGenerator is used for extracting "randomized" 64 bits for coin flips +type coinGenerator struct { + shkContext sha3.ShakeHash + signedWeight uint64 + threshold *big.Int +} + +// makeCoinGenerator creates a new CoinHash context. +// it is used for squeezing 64 bits for coin flips. +// the function inits the XOF function in the following manner +// Shake(coinChoiceSeed) +// we extract 64 bits from shake for each coin flip and divide it by signedWeight +func makeCoinGenerator(choice *coinChoiceSeed) coinGenerator { + choice.version = VersionForCoinGenerator + rep := crypto.HashRep(choice) + shk := sha3.NewShake256() + shk.Write(rep) + + threshold := prepareRejectionSamplingThreshold(choice.signedWeight) + return coinGenerator{shkContext: shk, signedWeight: choice.signedWeight, threshold: threshold} + +} + +func prepareRejectionSamplingThreshold(signedWeight uint64) *big.Int { + // we use rejection sampling in order to have a uniform random coin in [0,signedWeight). + // use b bits (b=64) per attempt. + // define k = roundDown( 2^b / signedWeight ) implemented as (2^b div signedWeight) + // and threshold = k*signedWeight + threshold := &big.Int{} + threshold.SetUint64(1) + + const numberOfBitsPerAttempt = 64 + threshold.Lsh(threshold, numberOfBitsPerAttempt) + + signedWt := &big.Int{} + signedWt.SetUint64(signedWeight) + + // k = 2^b / signedWeight + threshold.Div(threshold, signedWt) + + threshold.Mul(threshold, signedWt) + return threshold +} + +// getNextCoin returns the next 64bits integer which represents a number between [0,signedWeight) +func (cg *coinGenerator) getNextCoin() uint64 { + // take b bits from the XOF and generate an integer z. + // we accept the sample if z < threshold + // else, we reject the sample and repeat the process. + var randNumFromXof uint64 + for { + var shakeDigest [8]byte + cg.shkContext.Read(shakeDigest[:]) + randNumFromXof = binary.LittleEndian.Uint64(shakeDigest[:]) + + z := &big.Int{} + z.SetUint64(randNumFromXof) + if z.Cmp(cg.threshold) == -1 { + break + } + } + + return randNumFromXof % cg.signedWeight +} diff --git a/crypto/stateproof/coinGenerator_test.go b/crypto/stateproof/coinGenerator_test.go new file mode 100644 index 0000000000..39f3d760c8 --- /dev/null +++ b/crypto/stateproof/coinGenerator_test.go @@ -0,0 +1,186 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "fmt" + "os" + "reflect" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/test/partitiontest" +) + +// make sure that ToBeHashed function returns a specific length +// If this test breaks we need to make sure to update the SNARK prover and verifier as well. +func TestCoinFixedLengthHash(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var sigcom = make(crypto.GenericDigest, HashSize) + var partcom = make(crypto.GenericDigest, HashSize) + var data MessageHash + + crypto.RandBytes(sigcom[:]) + crypto.RandBytes(partcom[:]) + crypto.RandBytes(data[:]) + + choice := coinChoiceSeed{ + partCommitment: partcom, + lnProvenWeight: 454197, + sigCommitment: sigcom, + signedWeight: 1 << 10, + data: data, + } + e := reflect.ValueOf(choice) + a.Equal(6, e.NumField()) + + rep := crypto.HashRep(&choice) + a.Equal(180, len(rep)) +} + +func TestHashCoin(t *testing.T) { + partitiontest.PartitionTest(t) + + var slots [32]uint64 + var sigcom = make(crypto.GenericDigest, HashSize) + var partcom = make(crypto.GenericDigest, HashSize) + var msgHash MessageHash + + crypto.RandBytes(sigcom[:]) + crypto.RandBytes(partcom[:]) + crypto.RandBytes(msgHash[:]) + + choice := coinChoiceSeed{ + signedWeight: uint64(len(slots)), + sigCommitment: sigcom, + partCommitment: partcom, + data: msgHash, + } + coinHash := makeCoinGenerator(&choice) + + for j := uint64(0); j < 1000; j++ { + coin := coinHash.getNextCoin() + if coin >= uint64(len(slots)) { + t.Errorf("hashCoin out of bounds") + } + + slots[coin]++ + } + + for i, count := range slots { + if count < 3 { + t.Errorf("slot %d too low: %d", i, count) + } + if count > 100 { + t.Errorf("slot %d too high: %d", i, count) + } + } +} + +func BenchmarkHashCoin(b *testing.B) { + var sigcom = make(crypto.GenericDigest, HashSize) + var partcom = make(crypto.GenericDigest, HashSize) + var msgHash MessageHash + + crypto.RandBytes(sigcom[:]) + crypto.RandBytes(partcom[:]) + crypto.RandBytes(msgHash[:]) + + choice := coinChoiceSeed{ + signedWeight: 1025, + sigCommitment: sigcom, + partCommitment: partcom, + data: msgHash, + } + coinHash := makeCoinGenerator(&choice) + + for i := 0; i < b.N; i++ { + coinHash.getNextCoin() + } +} + +func BenchmarkHashCoinGenerate(b *testing.B) { + var sigcom = make(crypto.GenericDigest, HashSize) + var partcom = make(crypto.GenericDigest, HashSize) + var msgHash MessageHash + + crypto.RandBytes(sigcom[:]) + crypto.RandBytes(partcom[:]) + crypto.RandBytes(msgHash[:]) + + choice := coinChoiceSeed{ + signedWeight: 1025, + sigCommitment: sigcom, + partCommitment: partcom, + data: msgHash, + } + + for i := 0; i < b.N; i++ { + makeCoinGenerator(&choice) + } +} + +func TestGenerateCoinHashKATs(t *testing.T) { + partitiontest.PartitionTest(t) + + // This test produces MSS samples for the SNARK verifier. + // it will only run explicitly by: + // + // GEN_KATS=x go test -v . -run=GenerateKat -count=1 + if os.Getenv("GEN_KATS") == "" { + t.Skip("Skipping; GEN_KATS not set") + } + + const numReveals = 1000 + const signedWt = 1 << 10 + var coinslots [numReveals]uint64 + var sigcom = make(crypto.GenericDigest, HashSize) + var partcom = make(crypto.GenericDigest, HashSize) + var data MessageHash + + crypto.RandBytes(sigcom[:]) + crypto.RandBytes(partcom[:]) + crypto.RandBytes(data[:]) + + choice := coinChoiceSeed{ + partCommitment: partcom, + lnProvenWeight: 454197, + sigCommitment: sigcom, + signedWeight: signedWt, + data: data, + } + + coinHash := makeCoinGenerator(&choice) + + for j := uint64(0); j < numReveals; j++ { + coinslots[j] = coinHash.getNextCoin() + + } + fmt.Printf("signedWeight: %v \n", signedWt) + fmt.Printf("number of reveals: %v \n", numReveals) + concatString := fmt.Sprint(coinslots) + toPrint := strings.Join(strings.Split(concatString, " "), ", ") + fmt.Printf("coinvalues: %v \n", toPrint) + concatString = fmt.Sprint(crypto.HashRep(&choice)) + toPrint = strings.Join(strings.Split(concatString, " "), ", ") + fmt.Printf("seed: %v \n", toPrint) +} diff --git a/crypto/compactcert/committableSignatureSlot.go b/crypto/stateproof/committableSignatureSlot.go similarity index 77% rename from crypto/compactcert/committableSignatureSlot.go rename to crypto/stateproof/committableSignatureSlot.go index 97b949cfe9..78aef90ec0 100644 --- a/crypto/compactcert/committableSignatureSlot.go +++ b/crypto/stateproof/committableSignatureSlot.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "encoding/binary" @@ -34,9 +34,9 @@ type committableSignatureSlot struct { // ErrIndexOutOfBound returned when an index is out of the array's bound var ErrIndexOutOfBound = errors.New("index is out of bound") -// committableSignatureSlotArray is used to create a merkle tree on the compact cert's +// committableSignatureSlotArray is used to create a merkle tree on the stateproof's // signature array. it serializes the MSS signatures using a specific format -// compact cert signature array. +// state proof signature array. //msgp:ignore committableSignatureSlotArray type committableSignatureSlotArray []sigslot @@ -58,9 +58,12 @@ func (sc committableSignatureSlotArray) Marshal(pos uint64) (crypto.Hashable, er } func buildCommittableSignature(sigCommit sigslotCommit) (*committableSignatureSlot, error) { - if sigCommit.Sig.Signature.Signature == nil { + if sigCommit.Sig.MsgIsZero() { // Empty merkle signature return &committableSignatureSlot{isEmptySlot: true}, nil } + if sigCommit.Sig.Signature == nil { // Merkle signature is not empty, but falcon signature is (invalid case) + return nil, fmt.Errorf("buildCommittableSignature: Falcon signature is nil") + } sigBytes, err := sigCommit.Sig.GetFixedLengthHashableRepresentation() if err != nil { return nil, err @@ -74,14 +77,14 @@ func buildCommittableSignature(sigCommit sigslotCommit) (*committableSignatureSl // be bad for creating SNARK func (cs *committableSignatureSlot) ToBeHashed() (protocol.HashID, []byte) { if cs.isEmptySlot { - return protocol.CompactCertSig, []byte{} + return protocol.StateProofSig, []byte{} } - binaryLValue := make([]byte, 8) - binary.LittleEndian.PutUint64(binaryLValue, cs.sigCommit.L) + var binaryLValue [8]byte + binary.LittleEndian.PutUint64(binaryLValue[:], cs.sigCommit.L) - sigSlotCommitment := make([]byte, 0, len(binaryLValue)+len(cs.serializedSignature)) - sigSlotCommitment = append(sigSlotCommitment, binaryLValue...) - sigSlotCommitment = append(sigSlotCommitment, cs.serializedSignature...) + sigSlotByteRepresentation := make([]byte, 0, len(binaryLValue)+len(cs.serializedSignature)) + sigSlotByteRepresentation = append(sigSlotByteRepresentation, binaryLValue[:]...) + sigSlotByteRepresentation = append(sigSlotByteRepresentation, cs.serializedSignature...) - return protocol.CompactCertSig, sigSlotCommitment + return protocol.StateProofSig, sigSlotByteRepresentation } diff --git a/crypto/compactcert/committableSignatureSlot_test.go b/crypto/stateproof/committableSignatureSlot_test.go similarity index 85% rename from crypto/compactcert/committableSignatureSlot_test.go rename to crypto/stateproof/committableSignatureSlot_test.go index e7cb311813..811c79c4df 100644 --- a/crypto/compactcert/committableSignatureSlot_test.go +++ b/crypto/stateproof/committableSignatureSlot_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "encoding/binary" @@ -34,29 +34,29 @@ func TestSignatureArrayWithEmptySlot(t *testing.T) { a := require.New(t) sigs := make([]sigslot, 2) - key := generateTestSigner(0, uint64(compactCertRoundsForTests)*20+1, compactCertRoundsForTests, a) + key := generateTestSigner(0, uint64(stateProofIntervalForTests)*20+1, stateProofIntervalForTests, a) message := testMessage("hello world") - sig, err := key.GetSigner(uint64(256)).Sign(message) + sig, err := key.GetSigner(uint64(256)).SignBytes(message) a.NoError(err) sigs[0] = sigslot{ Weight: 60, - sigslotCommit: sigslotCommit{Sig: CompactOneTimeSignature{Signature: sig}, L: 60}, + sigslotCommit: sigslotCommit{Sig: sig, L: 60}, } hfactory := crypto.HashFactory{HashType: HashType} tree, err := merklearray.BuildVectorCommitmentTree(committableSignatureSlotArray(sigs), hfactory) leftLeafHash := calculateHashOnSigLeaf(t, sig, 60) - rightLeafHash := hashBytes(hfactory.NewHash(), []byte(protocol.CompactCertSig)) + rightLeafHash := hashBytes(hfactory.NewHash(), []byte(protocol.StateProofSig)) a.Equal([]byte(tree.Root()), calculateHashOnInternalNode(leftLeafHash, rightLeafHash)) } func calculateHashOnSigLeaf(t *testing.T, sig merklesignature.Signature, lValue uint64) []byte { var sigCommitment []byte - sigCommitment = append(sigCommitment, protocol.CompactCertSig...) + sigCommitment = append(sigCommitment, protocol.StateProofSig...) binaryL := make([]byte, 8) binary.LittleEndian.PutUint64(binaryL, lValue) @@ -64,7 +64,7 @@ func calculateHashOnSigLeaf(t *testing.T, sig merklesignature.Signature, lValue sigCommitment = append(sigCommitment, binaryL...) //build the expected binary representation of the merkle signature - serializedSig, err := sig.VerifyingKey.GetSignatureFixedLengthHashableRepresentation(sig.Signature) + serializedSig, err := sig.Signature.GetFixedLengthHashableRepresentation() require.NoError(t, err) schemeType := make([]byte, 2) @@ -75,7 +75,7 @@ func calculateHashOnSigLeaf(t *testing.T, sig merklesignature.Signature, lValue sigCommitment = append(sigCommitment, sig.VerifyingKey.GetFixedLengthHashableRepresentation()...) treeIdxBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(treeIdxBytes, sig.MerkleArrayIndex) + binary.LittleEndian.PutUint64(treeIdxBytes, sig.VectorCommitmentIndex) sigCommitment = append(sigCommitment, treeIdxBytes...) //build the expected binary representation of the merkle signature proof diff --git a/crypto/stateproof/const.go b/crypto/stateproof/const.go new file mode 100644 index 0000000000..a9dab2813d --- /dev/null +++ b/crypto/stateproof/const.go @@ -0,0 +1,37 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "github.com/algorand/go-algorand/crypto" +) + +// HashType/ hashSize relate to the type of hash this package uses. +const ( + HashType = crypto.Sumhash + HashSize = crypto.SumhashDigestSize + precisionBits = uint8(16) // number of bits used for log approximation. This should not exceed 63 + ln2IntApproximation = uint64(45427) // the value of the ln(2) with 16 bits of precision (i.e ln2IntApproximation = ceil( 2^precisionBits * ln(2) )) + MaxReveals = 640 // MaxReveals is a bound on allocation and on numReveals to limit log computation + // VersionForCoinGenerator is used as part of the seed for Fiat-Shamir. We would change this + // value if the state proof verifier algorithm changes. This will allow us to make different coins for different state proof verification algorithms + VersionForCoinGenerator = byte(0) + // MaxTreeDepth defines the maximal size of a merkle tree depth the state proof allows. + MaxTreeDepth = 20 + // MessageHashType is the type of hash used to generate MessageHash + MessageHashType = crypto.Sha256 +) diff --git a/crypto/compactcert/msgp_gen.go b/crypto/stateproof/msgp_gen.go similarity index 57% rename from crypto/compactcert/msgp_gen.go rename to crypto/stateproof/msgp_gen.go index 86fe2426b8..93e383c428 100644 --- a/crypto/compactcert/msgp_gen.go +++ b/crypto/stateproof/msgp_gen.go @@ -1,4 +1,4 @@ -package compactcert +package stateproof // Code generated by github.com/algorand/msgp DO NOT EDIT. @@ -9,21 +9,13 @@ import ( ) // The following msgp objects are implemented in this file: -// Cert -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// -// CompactOneTimeSignature -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero +// MessageHash +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero // // Reveal // |-----> (*) MarshalMsg @@ -33,7 +25,7 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // -// coinChoice +// StateProof // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg // |-----> (*) UnmarshalMsg @@ -51,454 +43,42 @@ import ( // // MarshalMsg implements msgp.Marshaler -func (z *Cert) MarshalMsg(b []byte) (o []byte) { +func (z *MessageHash) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0003Len := uint32(5) - var zb0003Mask uint8 /* 6 bits */ - if (*z).PartProofs.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x1 - } - if (*z).SigProofs.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x2 - } - if (*z).SigCommit.MsgIsZero() { - zb0003Len-- - zb0003Mask |= 0x8 - } - if len((*z).Reveals) == 0 { - zb0003Len-- - zb0003Mask |= 0x10 - } - if (*z).SignedWeight == 0 { - zb0003Len-- - zb0003Mask |= 0x20 - } - // variable map header, size zb0003Len - o = append(o, 0x80|uint8(zb0003Len)) - if zb0003Len != 0 { - if (zb0003Mask & 0x1) == 0 { // if not empty - // string "P" - o = append(o, 0xa1, 0x50) - o = (*z).PartProofs.MarshalMsg(o) - } - if (zb0003Mask & 0x2) == 0 { // if not empty - // string "S" - o = append(o, 0xa1, 0x53) - o = (*z).SigProofs.MarshalMsg(o) - } - if (zb0003Mask & 0x8) == 0 { // if not empty - // string "c" - o = append(o, 0xa1, 0x63) - o = (*z).SigCommit.MarshalMsg(o) - } - if (zb0003Mask & 0x10) == 0 { // if not empty - // string "r" - o = append(o, 0xa1, 0x72) - if (*z).Reveals == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).Reveals))) - } - zb0001_keys := make([]uint64, 0, len((*z).Reveals)) - for zb0001 := range (*z).Reveals { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(SortUint64(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).Reveals[zb0001] - _ = zb0002 - o = msgp.AppendUint64(o, zb0001) - o = zb0002.MarshalMsg(o) - } - } - if (zb0003Mask & 0x20) == 0 { // if not empty - // string "w" - o = append(o, 0xa1, 0x77) - o = msgp.AppendUint64(o, (*z).SignedWeight) - } - } + o = msgp.AppendBytes(o, (*z)[:]) return } -func (_ *Cert) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*Cert) +func (_ *MessageHash) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*MessageHash) return ok } // UnmarshalMsg implements msgp.Unmarshaler -func (z *Cert) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0003 int - var zb0004 bool - zb0003, zb0004, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0003, zb0004, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0003 > 0 { - zb0003-- - bts, err = (*z).SigCommit.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "SigCommit") - return - } - } - if zb0003 > 0 { - zb0003-- - (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "SignedWeight") - return - } - } - if zb0003 > 0 { - zb0003-- - bts, err = (*z).SigProofs.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "SigProofs") - return - } - } - if zb0003 > 0 { - zb0003-- - bts, err = (*z).PartProofs.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "PartProofs") - return - } - } - if zb0003 > 0 { - zb0003-- - var zb0005 int - var zb0006 bool - zb0005, zb0006, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Reveals") - return - } - if zb0005 > MaxReveals { - err = msgp.ErrOverflow(uint64(zb0005), uint64(MaxReveals)) - err = msgp.WrapError(err, "struct-from-array", "Reveals") - return - } - if zb0006 { - (*z).Reveals = nil - } else if (*z).Reveals == nil { - (*z).Reveals = make(map[uint64]Reveal, zb0005) - } - for zb0005 > 0 { - var zb0001 uint64 - var zb0002 Reveal - zb0005-- - zb0001, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Reveals") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Reveals", zb0001) - return - } - (*z).Reveals[zb0001] = zb0002 - } - } - if zb0003 > 0 { - err = msgp.ErrTooManyArrayFields(zb0003) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0004 { - (*z) = Cert{} - } - for zb0003 > 0 { - zb0003-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "c": - bts, err = (*z).SigCommit.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "SigCommit") - return - } - case "w": - (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "SignedWeight") - return - } - case "S": - bts, err = (*z).SigProofs.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "SigProofs") - return - } - case "P": - bts, err = (*z).PartProofs.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "PartProofs") - return - } - case "r": - var zb0007 int - var zb0008 bool - zb0007, zb0008, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err, "Reveals") - return - } - if zb0007 > MaxReveals { - err = msgp.ErrOverflow(uint64(zb0007), uint64(MaxReveals)) - err = msgp.WrapError(err, "Reveals") - return - } - if zb0008 { - (*z).Reveals = nil - } else if (*z).Reveals == nil { - (*z).Reveals = make(map[uint64]Reveal, zb0007) - } - for zb0007 > 0 { - var zb0001 uint64 - var zb0002 Reveal - zb0007-- - zb0001, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "Reveals") - return - } - bts, err = zb0002.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Reveals", zb0001) - return - } - (*z).Reveals[zb0001] = zb0002 - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } +func (z *MessageHash) UnmarshalMsg(bts []byte) (o []byte, err error) { + bts, err = msgp.ReadExactBytes(bts, (*z)[:]) + if err != nil { + err = msgp.WrapError(err) + return } o = bts return } -func (_ *Cert) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*Cert) +func (_ *MessageHash) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*MessageHash) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *Cert) Msgsize() (s int) { - s = 1 + 2 + (*z).SigCommit.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).SigProofs.Msgsize() + 2 + (*z).PartProofs.Msgsize() + 2 + msgp.MapHeaderSize - if (*z).Reveals != nil { - for zb0001, zb0002 := range (*z).Reveals { - _ = zb0001 - _ = zb0002 - s += 0 + msgp.Uint64Size + zb0002.Msgsize() - } - } +func (z *MessageHash) Msgsize() (s int) { + s = msgp.ArrayHeaderSize + (32 * (msgp.ByteSize)) return } // MsgIsZero returns whether this is a zero value -func (z *Cert) MsgIsZero() bool { - return ((*z).SigCommit.MsgIsZero()) && ((*z).SignedWeight == 0) && ((*z).SigProofs.MsgIsZero()) && ((*z).PartProofs.MsgIsZero()) && (len((*z).Reveals) == 0) -} - -// MarshalMsg implements msgp.Marshaler -func (z *CompactOneTimeSignature) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(4) - var zb0001Mask uint8 /* 6 bits */ - if (*z).Signature.MerkleArrayIndex == 0 { - zb0001Len-- - zb0001Mask |= 0x4 - } - if (*z).Signature.Proof.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x8 - } - if (*z).Signature.Signature.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x10 - } - if (*z).Signature.VerifyingKey.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x20 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "idx" - o = append(o, 0xa3, 0x69, 0x64, 0x78) - o = msgp.AppendUint64(o, (*z).Signature.MerkleArrayIndex) - } - if (zb0001Mask & 0x8) == 0 { // if not empty - // string "prf" - o = append(o, 0xa3, 0x70, 0x72, 0x66) - o = (*z).Signature.Proof.MarshalMsg(o) - } - if (zb0001Mask & 0x10) == 0 { // if not empty - // string "sig" - o = append(o, 0xa3, 0x73, 0x69, 0x67) - o = (*z).Signature.Signature.MarshalMsg(o) - } - if (zb0001Mask & 0x20) == 0 { // if not empty - // string "vkey" - o = append(o, 0xa4, 0x76, 0x6b, 0x65, 0x79) - o = (*z).Signature.VerifyingKey.MarshalMsg(o) - } - } - return -} - -func (_ *CompactOneTimeSignature) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactOneTimeSignature) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *CompactOneTimeSignature) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Signature.Signature.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Signature") - return - } - } - if zb0001 > 0 { - zb0001-- - (*z).Signature.MerkleArrayIndex, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "MerkleArrayIndex") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Signature.Proof.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Proof") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Signature.VerifyingKey.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "VerifyingKey") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = CompactOneTimeSignature{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "sig": - bts, err = (*z).Signature.Signature.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Signature") - return - } - case "idx": - (*z).Signature.MerkleArrayIndex, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err, "MerkleArrayIndex") - return - } - case "prf": - bts, err = (*z).Signature.Proof.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Proof") - return - } - case "vkey": - bts, err = (*z).Signature.VerifyingKey.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "VerifyingKey") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *CompactOneTimeSignature) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactOneTimeSignature) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *CompactOneTimeSignature) Msgsize() (s int) { - s = 1 + 4 + (*z).Signature.Signature.Msgsize() + 4 + msgp.Uint64Size + 4 + (*z).Signature.Proof.Msgsize() + 5 + (*z).Signature.VerifyingKey.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *CompactOneTimeSignature) MsgIsZero() bool { - return ((*z).Signature.Signature.MsgIsZero()) && ((*z).Signature.MerkleArrayIndex == 0) && ((*z).Signature.Proof.MsgIsZero()) && ((*z).Signature.VerifyingKey.MsgIsZero()) +func (z *MessageHash) MsgIsZero() bool { + return (*z) == (MessageHash{}) } // MarshalMsg implements msgp.Marshaler @@ -781,140 +361,228 @@ func (z *Reveal) MsgIsZero() bool { } // MarshalMsg implements msgp.Marshaler -func (z *coinChoice) MarshalMsg(b []byte) (o []byte) { +func (z *StateProof) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(6) - var zb0001Mask uint8 /* 7 bits */ - if (*z).J == 0 { - zb0001Len-- - zb0001Mask |= 0x2 + zb0004Len := uint32(7) + var zb0004Mask uint8 /* 8 bits */ + if (*z).PartProofs.MsgIsZero() { + zb0004Len-- + zb0004Mask |= 0x1 } - if (*z).MsgHash.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x4 + if (*z).SigProofs.MsgIsZero() { + zb0004Len-- + zb0004Mask |= 0x2 } - if (*z).Partcom.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x8 + if (*z).SigCommit.MsgIsZero() { + zb0004Len-- + zb0004Mask |= 0x8 } - if (*z).ProvenWeight == 0 { - zb0001Len-- - zb0001Mask |= 0x10 + if len((*z).PositionsToReveal) == 0 { + zb0004Len-- + zb0004Mask |= 0x10 } - if (*z).Sigcom.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x20 + if len((*z).Reveals) == 0 { + zb0004Len-- + zb0004Mask |= 0x20 + } + if (*z).MerkleSignatureSaltVersion == 0 { + zb0004Len-- + zb0004Mask |= 0x40 } if (*z).SignedWeight == 0 { - zb0001Len-- - zb0001Mask |= 0x40 + zb0004Len-- + zb0004Mask |= 0x80 } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "j" - o = append(o, 0xa1, 0x6a) - o = msgp.AppendUint64(o, (*z).J) + // variable map header, size zb0004Len + o = append(o, 0x80|uint8(zb0004Len)) + if zb0004Len != 0 { + if (zb0004Mask & 0x1) == 0 { // if not empty + // string "P" + o = append(o, 0xa1, 0x50) + o = (*z).PartProofs.MarshalMsg(o) } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "msghash" - o = append(o, 0xa7, 0x6d, 0x73, 0x67, 0x68, 0x61, 0x73, 0x68) - o = (*z).MsgHash.MarshalMsg(o) + if (zb0004Mask & 0x2) == 0 { // if not empty + // string "S" + o = append(o, 0xa1, 0x53) + o = (*z).SigProofs.MarshalMsg(o) } - if (zb0001Mask & 0x8) == 0 { // if not empty - // string "partcom" - o = append(o, 0xa7, 0x70, 0x61, 0x72, 0x74, 0x63, 0x6f, 0x6d) - o = (*z).Partcom.MarshalMsg(o) + if (zb0004Mask & 0x8) == 0 { // if not empty + // string "c" + o = append(o, 0xa1, 0x63) + o = (*z).SigCommit.MarshalMsg(o) } - if (zb0001Mask & 0x10) == 0 { // if not empty - // string "provenweight" - o = append(o, 0xac, 0x70, 0x72, 0x6f, 0x76, 0x65, 0x6e, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74) - o = msgp.AppendUint64(o, (*z).ProvenWeight) + if (zb0004Mask & 0x10) == 0 { // if not empty + // string "pr" + o = append(o, 0xa2, 0x70, 0x72) + if (*z).PositionsToReveal == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendArrayHeader(o, uint32(len((*z).PositionsToReveal))) + } + for zb0003 := range (*z).PositionsToReveal { + o = msgp.AppendUint64(o, (*z).PositionsToReveal[zb0003]) + } + } + if (zb0004Mask & 0x20) == 0 { // if not empty + // string "r" + o = append(o, 0xa1, 0x72) + if (*z).Reveals == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).Reveals))) + } + zb0001_keys := make([]uint64, 0, len((*z).Reveals)) + for zb0001 := range (*z).Reveals { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(SortUint64(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).Reveals[zb0001] + _ = zb0002 + o = msgp.AppendUint64(o, zb0001) + o = zb0002.MarshalMsg(o) + } } - if (zb0001Mask & 0x20) == 0 { // if not empty - // string "sigcom" - o = append(o, 0xa6, 0x73, 0x69, 0x67, 0x63, 0x6f, 0x6d) - o = (*z).Sigcom.MarshalMsg(o) + if (zb0004Mask & 0x40) == 0 { // if not empty + // string "v" + o = append(o, 0xa1, 0x76) + o = msgp.AppendByte(o, (*z).MerkleSignatureSaltVersion) } - if (zb0001Mask & 0x40) == 0 { // if not empty - // string "sigweight" - o = append(o, 0xa9, 0x73, 0x69, 0x67, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74) + if (zb0004Mask & 0x80) == 0 { // if not empty + // string "w" + o = append(o, 0xa1, 0x77) o = msgp.AppendUint64(o, (*z).SignedWeight) } } return } -func (_ *coinChoice) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*coinChoice) +func (_ *StateProof) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProof) return ok } // UnmarshalMsg implements msgp.Unmarshaler -func (z *coinChoice) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *StateProof) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + var zb0004 int + var zb0005 bool + zb0004, zb0005, bts, err = msgp.ReadMapHeaderBytes(bts) if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + zb0004, zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { err = msgp.WrapError(err) return } - if zb0001 > 0 { - zb0001-- - (*z).J, bts, err = msgp.ReadUint64Bytes(bts) + if zb0004 > 0 { + zb0004-- + bts, err = (*z).SigCommit.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "J") + err = msgp.WrapError(err, "struct-from-array", "SigCommit") return } } - if zb0001 > 0 { - zb0001-- + if zb0004 > 0 { + zb0004-- (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "struct-from-array", "SignedWeight") return } } - if zb0001 > 0 { - zb0001-- - (*z).ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if zb0004 > 0 { + zb0004-- + bts, err = (*z).SigProofs.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "ProvenWeight") + err = msgp.WrapError(err, "struct-from-array", "SigProofs") return } } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Sigcom.UnmarshalMsg(bts) + if zb0004 > 0 { + zb0004-- + bts, err = (*z).PartProofs.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Sigcom") + err = msgp.WrapError(err, "struct-from-array", "PartProofs") return } } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Partcom.UnmarshalMsg(bts) + if zb0004 > 0 { + zb0004-- + (*z).MerkleSignatureSaltVersion, bts, err = msgp.ReadByteBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Partcom") + err = msgp.WrapError(err, "struct-from-array", "MerkleSignatureSaltVersion") return } } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).MsgHash.UnmarshalMsg(bts) + if zb0004 > 0 { + zb0004-- + var zb0006 int + var zb0007 bool + zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "MsgHash") + err = msgp.WrapError(err, "struct-from-array", "Reveals") + return + } + if zb0006 > MaxReveals { + err = msgp.ErrOverflow(uint64(zb0006), uint64(MaxReveals)) + err = msgp.WrapError(err, "struct-from-array", "Reveals") return } + if zb0007 { + (*z).Reveals = nil + } else if (*z).Reveals == nil { + (*z).Reveals = make(map[uint64]Reveal, zb0006) + } + for zb0006 > 0 { + var zb0001 uint64 + var zb0002 Reveal + zb0006-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Reveals") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Reveals", zb0001) + return + } + (*z).Reveals[zb0001] = zb0002 + } } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) + if zb0004 > 0 { + zb0004-- + var zb0008 int + var zb0009 bool + zb0008, zb0009, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PositionsToReveal") + return + } + if zb0008 > MaxReveals { + err = msgp.ErrOverflow(uint64(zb0008), uint64(MaxReveals)) + err = msgp.WrapError(err, "struct-from-array", "PositionsToReveal") + return + } + if zb0009 { + (*z).PositionsToReveal = nil + } else if (*z).PositionsToReveal != nil && cap((*z).PositionsToReveal) >= zb0008 { + (*z).PositionsToReveal = ((*z).PositionsToReveal)[:zb0008] + } else { + (*z).PositionsToReveal = make([]uint64, zb0008) + } + for zb0003 := range (*z).PositionsToReveal { + (*z).PositionsToReveal[zb0003], bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "PositionsToReveal", zb0003) + return + } + } + } + if zb0004 > 0 { + err = msgp.ErrTooManyArrayFields(zb0004) if err != nil { err = msgp.WrapError(err, "struct-from-array") return @@ -925,53 +593,108 @@ func (z *coinChoice) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err) return } - if zb0002 { - (*z) = coinChoice{} + if zb0005 { + (*z) = StateProof{} } - for zb0001 > 0 { - zb0001-- + for zb0004 > 0 { + zb0004-- field, bts, err = msgp.ReadMapKeyZC(bts) if err != nil { err = msgp.WrapError(err) return } switch string(field) { - case "j": - (*z).J, bts, err = msgp.ReadUint64Bytes(bts) + case "c": + bts, err = (*z).SigCommit.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "J") + err = msgp.WrapError(err, "SigCommit") return } - case "sigweight": + case "w": (*z).SignedWeight, bts, err = msgp.ReadUint64Bytes(bts) if err != nil { err = msgp.WrapError(err, "SignedWeight") return } - case "provenweight": - (*z).ProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + case "S": + bts, err = (*z).SigProofs.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "ProvenWeight") + err = msgp.WrapError(err, "SigProofs") return } - case "sigcom": - bts, err = (*z).Sigcom.UnmarshalMsg(bts) + case "P": + bts, err = (*z).PartProofs.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "Sigcom") + err = msgp.WrapError(err, "PartProofs") return } - case "partcom": - bts, err = (*z).Partcom.UnmarshalMsg(bts) + case "v": + (*z).MerkleSignatureSaltVersion, bts, err = msgp.ReadByteBytes(bts) if err != nil { - err = msgp.WrapError(err, "Partcom") + err = msgp.WrapError(err, "MerkleSignatureSaltVersion") return } - case "msghash": - bts, err = (*z).MsgHash.UnmarshalMsg(bts) + case "r": + var zb0010 int + var zb0011 bool + zb0010, zb0011, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "MsgHash") + err = msgp.WrapError(err, "Reveals") return } + if zb0010 > MaxReveals { + err = msgp.ErrOverflow(uint64(zb0010), uint64(MaxReveals)) + err = msgp.WrapError(err, "Reveals") + return + } + if zb0011 { + (*z).Reveals = nil + } else if (*z).Reveals == nil { + (*z).Reveals = make(map[uint64]Reveal, zb0010) + } + for zb0010 > 0 { + var zb0001 uint64 + var zb0002 Reveal + zb0010-- + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "Reveals") + return + } + bts, err = zb0002.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Reveals", zb0001) + return + } + (*z).Reveals[zb0001] = zb0002 + } + case "pr": + var zb0012 int + var zb0013 bool + zb0012, zb0013, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err, "PositionsToReveal") + return + } + if zb0012 > MaxReveals { + err = msgp.ErrOverflow(uint64(zb0012), uint64(MaxReveals)) + err = msgp.WrapError(err, "PositionsToReveal") + return + } + if zb0013 { + (*z).PositionsToReveal = nil + } else if (*z).PositionsToReveal != nil && cap((*z).PositionsToReveal) >= zb0012 { + (*z).PositionsToReveal = ((*z).PositionsToReveal)[:zb0012] + } else { + (*z).PositionsToReveal = make([]uint64, zb0012) + } + for zb0003 := range (*z).PositionsToReveal { + (*z).PositionsToReveal[zb0003], bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "PositionsToReveal", zb0003) + return + } + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -985,20 +708,28 @@ func (z *coinChoice) UnmarshalMsg(bts []byte) (o []byte, err error) { return } -func (_ *coinChoice) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*coinChoice) +func (_ *StateProof) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProof) return ok } // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *coinChoice) Msgsize() (s int) { - s = 1 + 2 + msgp.Uint64Size + 10 + msgp.Uint64Size + 13 + msgp.Uint64Size + 7 + (*z).Sigcom.Msgsize() + 8 + (*z).Partcom.Msgsize() + 8 + (*z).MsgHash.Msgsize() +func (z *StateProof) Msgsize() (s int) { + s = 1 + 2 + (*z).SigCommit.Msgsize() + 2 + msgp.Uint64Size + 2 + (*z).SigProofs.Msgsize() + 2 + (*z).PartProofs.Msgsize() + 2 + msgp.ByteSize + 2 + msgp.MapHeaderSize + if (*z).Reveals != nil { + for zb0001, zb0002 := range (*z).Reveals { + _ = zb0001 + _ = zb0002 + s += 0 + msgp.Uint64Size + zb0002.Msgsize() + } + } + s += 3 + msgp.ArrayHeaderSize + (len((*z).PositionsToReveal) * (msgp.Uint64Size)) return } // MsgIsZero returns whether this is a zero value -func (z *coinChoice) MsgIsZero() bool { - return ((*z).J == 0) && ((*z).SignedWeight == 0) && ((*z).ProvenWeight == 0) && ((*z).Sigcom.MsgIsZero()) && ((*z).Partcom.MsgIsZero()) && ((*z).MsgHash.MsgIsZero()) +func (z *StateProof) MsgIsZero() bool { + return ((*z).SigCommit.MsgIsZero()) && ((*z).SignedWeight == 0) && ((*z).SigProofs.MsgIsZero()) && ((*z).PartProofs.MsgIsZero()) && ((*z).MerkleSignatureSaltVersion == 0) && (len((*z).Reveals) == 0) && (len((*z).PositionsToReveal) == 0) } // MarshalMsg implements msgp.Marshaler diff --git a/crypto/compactcert/msgp_gen_test.go b/crypto/stateproof/msgp_gen_test.go similarity index 68% rename from crypto/compactcert/msgp_gen_test.go rename to crypto/stateproof/msgp_gen_test.go index 1e682b46e5..30dd0b1d50 100644 --- a/crypto/compactcert/msgp_gen_test.go +++ b/crypto/stateproof/msgp_gen_test.go @@ -1,7 +1,7 @@ //go:build !skip_msgp_testing // +build !skip_msgp_testing -package compactcert +package stateproof // Code generated by github.com/algorand/msgp DO NOT EDIT. @@ -14,9 +14,9 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -func TestMarshalUnmarshalCert(t *testing.T) { +func TestMarshalUnmarshalMessageHash(t *testing.T) { partitiontest.PartitionTest(t) - v := Cert{} + v := MessageHash{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -35,12 +35,12 @@ func TestMarshalUnmarshalCert(t *testing.T) { } } -func TestRandomizedEncodingCert(t *testing.T) { - protocol.RunEncodingTest(t, &Cert{}) +func TestRandomizedEncodingMessageHash(t *testing.T) { + protocol.RunEncodingTest(t, &MessageHash{}) } -func BenchmarkMarshalMsgCert(b *testing.B) { - v := Cert{} +func BenchmarkMarshalMsgMessageHash(b *testing.B) { + v := MessageHash{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -48,8 +48,8 @@ func BenchmarkMarshalMsgCert(b *testing.B) { } } -func BenchmarkAppendMsgCert(b *testing.B) { - v := Cert{} +func BenchmarkAppendMsgMessageHash(b *testing.B) { + v := MessageHash{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -60,68 +60,8 @@ func BenchmarkAppendMsgCert(b *testing.B) { } } -func BenchmarkUnmarshalCert(b *testing.B) { - v := Cert{} - 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 TestMarshalUnmarshalCompactOneTimeSignature(t *testing.T) { - partitiontest.PartitionTest(t) - v := CompactOneTimeSignature{} - 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 TestRandomizedEncodingCompactOneTimeSignature(t *testing.T) { - protocol.RunEncodingTest(t, &CompactOneTimeSignature{}) -} - -func BenchmarkMarshalMsgCompactOneTimeSignature(b *testing.B) { - v := CompactOneTimeSignature{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgCompactOneTimeSignature(b *testing.B) { - v := CompactOneTimeSignature{} - 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 BenchmarkUnmarshalCompactOneTimeSignature(b *testing.B) { - v := CompactOneTimeSignature{} +func BenchmarkUnmarshalMessageHash(b *testing.B) { + v := MessageHash{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -194,9 +134,9 @@ func BenchmarkUnmarshalReveal(b *testing.B) { } } -func TestMarshalUnmarshalcoinChoice(t *testing.T) { +func TestMarshalUnmarshalStateProof(t *testing.T) { partitiontest.PartitionTest(t) - v := coinChoice{} + v := StateProof{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -215,12 +155,12 @@ func TestMarshalUnmarshalcoinChoice(t *testing.T) { } } -func TestRandomizedEncodingcoinChoice(t *testing.T) { - protocol.RunEncodingTest(t, &coinChoice{}) +func TestRandomizedEncodingStateProof(t *testing.T) { + protocol.RunEncodingTest(t, &StateProof{}) } -func BenchmarkMarshalMsgcoinChoice(b *testing.B) { - v := coinChoice{} +func BenchmarkMarshalMsgStateProof(b *testing.B) { + v := StateProof{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -228,8 +168,8 @@ func BenchmarkMarshalMsgcoinChoice(b *testing.B) { } } -func BenchmarkAppendMsgcoinChoice(b *testing.B) { - v := coinChoice{} +func BenchmarkAppendMsgStateProof(b *testing.B) { + v := StateProof{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -240,8 +180,8 @@ func BenchmarkAppendMsgcoinChoice(b *testing.B) { } } -func BenchmarkUnmarshalcoinChoice(b *testing.B) { - v := coinChoice{} +func BenchmarkUnmarshalStateProof(b *testing.B) { + v := StateProof{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) diff --git a/crypto/compactcert/structs.go b/crypto/stateproof/structs.go similarity index 52% rename from crypto/compactcert/structs.go rename to crypto/stateproof/structs.go index 9d3aaca168..d8e0b6883d 100644 --- a/crypto/compactcert/structs.go +++ b/crypto/stateproof/structs.go @@ -14,35 +14,41 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( + "fmt" + "strconv" + "strings" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/data/basics" ) -// Params defines common parameters for the verifier and builder. -type Params struct { - Msg crypto.Hashable // Message to be certified - ProvenWeight uint64 // Weight threshold proven by the certificate - SigRound basics.Round // The round for which the ephemeral key is committed to - SecKQ uint64 // Security parameter (k+q) from analysis document -} +// MessageHash represents the message that a state proof will attest to. +type MessageHash [32]byte -// CompactOneTimeSignature is crypto.OneTimeSignature with omitempty -type CompactOneTimeSignature struct { - _struct struct{} `codec:",omitempty,omitemptyarray"` - merklesignature.Signature +//msgp:ignore sigslot +type sigslot struct { + // Weight is the weight of the participant signing this message. + // This information is tracked here for convenience, but it does + // not appear in the commitment to the sigs array; it comes from + // the Weight field of the corresponding participant. + Weight uint64 + + // Include the parts of the sigslot that form the commitment to + // the sigs array. + sigslotCommit } -// A sigslotCommit is a single slot in the sigs array that forms the certificate. +// A sigslotCommit is a single slot in the sigs array that forms the state proof. type sigslotCommit struct { _struct struct{} `codec:",omitempty,omitemptyarray"` // Sig is a signature by the participant on the expected message. - Sig CompactOneTimeSignature `codec:"s"` + Sig merklesignature.Signature `codec:"s"` // L is the total weight of signatures in lower-numbered slots. // This is initialized once the builder has collected a sufficient @@ -50,8 +56,8 @@ type sigslotCommit struct { L uint64 `codec:"l"` } -// Reveal is a single array position revealed as part of a compact -// certificate. It reveals an element of the signature array and +// Reveal is a single array position revealed as part of a state +// proof. It reveals an element of the signature array and // the corresponding element of the participants array. type Reveal struct { _struct struct{} `codec:",omitempty,omitemptyarray"` @@ -60,19 +66,42 @@ type Reveal struct { Part basics.Participant `codec:"p"` } -// Cert represents a compact certificate. -type Cert struct { +// StateProof represents a proof on Algorand's state. +type StateProof struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - SigCommit crypto.GenericDigest `codec:"c"` - SignedWeight uint64 `codec:"w"` - SigProofs merklearray.Proof `codec:"S"` - PartProofs merklearray.Proof `codec:"P"` - + SigCommit crypto.GenericDigest `codec:"c"` + SignedWeight uint64 `codec:"w"` + SigProofs merklearray.Proof `codec:"S"` + PartProofs merklearray.Proof `codec:"P"` + MerkleSignatureSaltVersion byte `codec:"v"` // Reveals is a sparse map from the position being revealed // to the corresponding elements from the sigs and participants // arrays. - Reveals map[uint64]Reveal `codec:"r,allocbound=MaxReveals"` + Reveals map[uint64]Reveal `codec:"r,allocbound=MaxReveals"` + PositionsToReveal []uint64 `codec:"pr,allocbound=MaxReveals"` +} + +func (s StateProof) stringBuild() (b strings.Builder) { + b.WriteString("StateProof: {") + defer b.WriteRune('}') + + if s.MsgIsZero() { + return + } + + b.WriteString(fmt.Sprintf("%v", s.SigCommit)) + b.WriteString(", ") + b.WriteString(strconv.FormatUint(s.SignedWeight, 10)) + b.WriteString(", ") + b.WriteString(strconv.Itoa(len(s.PositionsToReveal))) + + return +} + +func (s StateProof) String() string { + b := s.stringBuild() + return b.String() } // SortUint64 implements sorting by uint64 keys for diff --git a/crypto/stateproof/verifier.go b/crypto/stateproof/verifier.go new file mode 100644 index 0000000000..892c9d4770 --- /dev/null +++ b/crypto/stateproof/verifier.go @@ -0,0 +1,154 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" +) + +// Errors for the StateProof verifier +var ( + ErrCoinNotInRange = errors.New("coin is not within slot weight range") + ErrNoRevealInPos = errors.New("no reveal for position") + ErrTreeDepthTooLarge = errors.New("tree depth is too large") +) + +// Verifier is used to verify a state proof. those fields represent all the verifier's trusted data +type Verifier struct { + strengthTarget uint64 + lnProvenWeight uint64 // ln(provenWeight) as integer with 16 bits of precision + participantsCommitment crypto.GenericDigest +} + +// MkVerifier constructs a verifier to check the state proof. the arguments for this function +// represent all the verifier's trusted data +func MkVerifier(partcom crypto.GenericDigest, provenWeight uint64, strengthTarget uint64) (*Verifier, error) { + lnProvenWt, err := LnIntApproximation(provenWeight) + if err != nil { + return nil, err + } + + return &Verifier{ + strengthTarget: strengthTarget, + lnProvenWeight: lnProvenWt, + participantsCommitment: partcom, + }, nil +} + +// MkVerifierWithLnProvenWeight constructs a verifier to check the state proof. the arguments for this function +// represent all the verifier's trusted data. This function uses the Ln(provenWeight) approximation value +func MkVerifierWithLnProvenWeight(partcom crypto.GenericDigest, lnProvenWt uint64, strengthTarget uint64) *Verifier { + return &Verifier{ + strengthTarget: strengthTarget, + lnProvenWeight: lnProvenWt, + participantsCommitment: partcom, + } +} + +// Verify checks if s is a valid state proof for the data on a round. +// it uses the trusted data from the Verifier struct +func (v *Verifier) Verify(round uint64, data MessageHash, s *StateProof) error { + if err := verifyStateProofTreesDepth(s); err != nil { + return err + } + + nr := uint64(len(s.PositionsToReveal)) + if err := verifyWeights(s.SignedWeight, v.lnProvenWeight, nr, v.strengthTarget); err != nil { + return err + } + + version := s.MerkleSignatureSaltVersion + for _, reveal := range s.Reveals { + if err := reveal.SigSlot.Sig.ValidateSaltVersion(version); err != nil { + return err + } + } + + sigs := make(map[uint64]crypto.Hashable) + parts := make(map[uint64]crypto.Hashable) + + for pos, r := range s.Reveals { + sig, err := buildCommittableSignature(r.SigSlot) + if err != nil { + return err + } + + sigs[pos] = sig + parts[pos] = r.Part + + // verify that the msg and the signature is valid under the given participant's Pk + err = r.Part.PK.VerifyBytes( + round, + data[:], + &r.SigSlot.Sig, + ) + + if err != nil { + return fmt.Errorf("signature in reveal pos %d does not verify. error is %w", pos, err) + } + } + + // verify all the reveals proofs on the signature commitment. + if err := merklearray.VerifyVectorCommitment(s.SigCommit[:], sigs, &s.SigProofs); err != nil { + return err + } + + // verify all the reveals proofs on the participant commitment. + if err := merklearray.VerifyVectorCommitment(v.participantsCommitment[:], parts, &s.PartProofs); err != nil { + return err + } + + choice := coinChoiceSeed{ + partCommitment: v.participantsCommitment, + lnProvenWeight: v.lnProvenWeight, + sigCommitment: s.SigCommit, + signedWeight: s.SignedWeight, + data: data, + } + + coinHash := makeCoinGenerator(&choice) + for j := uint64(0); j < nr; j++ { + pos := s.PositionsToReveal[j] + reveal, exists := s.Reveals[pos] + if !exists { + return fmt.Errorf("%w: %d", ErrNoRevealInPos, pos) + } + + coin := coinHash.getNextCoin() + if !(reveal.SigSlot.L <= coin && coin < reveal.SigSlot.L+reveal.Part.Weight) { + return fmt.Errorf("%w: for reveal pos %d and coin %d, ", ErrCoinNotInRange, pos, coin) + } + } + + return nil +} + +func verifyStateProofTreesDepth(s *StateProof) error { + if s.SigProofs.TreeDepth > MaxTreeDepth { + return fmt.Errorf("%w. sigTree depth is %d", ErrTreeDepthTooLarge, s.SigProofs.TreeDepth) + } + + if s.PartProofs.TreeDepth > MaxTreeDepth { + return fmt.Errorf("%w. partTree depth is %d", ErrTreeDepthTooLarge, s.PartProofs.TreeDepth) + } + + return nil +} diff --git a/crypto/stateproof/verifier_test.go b/crypto/stateproof/verifier_test.go new file mode 100644 index 0000000000..91ee123119 --- /dev/null +++ b/crypto/stateproof/verifier_test.go @@ -0,0 +1,180 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +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/test/partitiontest" +) + +func TestVerifyRevelForEachPosition(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + p := generateProofForTesting(a, false) + sProof := p.sp + + verifier, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.NoError(err) + + for i := uint64(0); i < p.numberOfParticipnets; i++ { + _, ok := sProof.Reveals[i] + if !ok { + sProof.PositionsToReveal[0] = i + break + } + } + + verifier, err = MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.ErrorIs(err, ErrNoRevealInPos) + +} + +// TestVerifyWrongCoinSlot this test makes sure that the verifier uses PositionsToReveal array, and opens reveals in a specific order +// In order to and trick the verifier we need to swap two positions in the PositionsToReveal so the coins will not match. +func TestVerifyWrongCoinSlot(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + p := generateProofForTesting(a, false) + sProof := p.sp + verifier, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.NoError(err) + + // we need to find a reveal that will not match the first coin. + // In order to accomplish that we will extract the first coin and find a reveals ( > 1, since index 0 will satisfy the verifier) + // that doesn't match + coinAt0 := sProof.PositionsToReveal[0] + choice := coinChoiceSeed{ + partCommitment: verifier.participantsCommitment, + lnProvenWeight: verifier.lnProvenWeight, + sigCommitment: sProof.SigCommit, + signedWeight: sProof.SignedWeight, + data: p.data, + } + coinHash := makeCoinGenerator(&choice) + coin := coinHash.getNextCoin() + j := 1 + for ; j < len(sProof.PositionsToReveal); j++ { + reveal := sProof.Reveals[sProof.PositionsToReveal[j]] + if !(reveal.SigSlot.L <= coin && coin < reveal.SigSlot.L+reveal.Part.Weight) { + break + } + } + + sProof.PositionsToReveal[0] = sProof.PositionsToReveal[j] + sProof.PositionsToReveal[j] = coinAt0 + + verifier, err = MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.ErrorIs(err, ErrCoinNotInRange) + +} + +func TestVerifyBadSignature(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + p := generateProofForTesting(a, false) + sProof := p.sp + + verifier, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.NoError(err) + + key := generateTestSigner(0, uint64(stateProofIntervalForTests)*20+1, stateProofIntervalForTests, a) + signerInRound := key.GetSigner(stateProofIntervalForTests) + newSig, err := signerInRound.SignBytes([]byte{0x1, 0x2}) + a.NoError(err) + + rev := sProof.Reveals[0] + rev.SigSlot.Sig = newSig + sProof.Reveals[0] = rev + + verifier, err = MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.ErrorIs(err, merklesignature.ErrSignatureSchemeVerificationFailed) + +} + +func TestVerifyZeroProvenWeight(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + partcommit := crypto.GenericDigest{} + _, err := MkVerifier(partcommit, 0, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrIllegalInputForLnApprox) +} + +func TestEqualVerifiers(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + p := generateProofForTesting(a, false) + sProof := p.sp + + verifier, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + err = verifier.Verify(stateProofIntervalForTests, p.data, &sProof) + a.NoError(err) + + lnProvenWeight, err := LnIntApproximation(p.provenWeight) + verifierLnP := MkVerifierWithLnProvenWeight(p.partCommitment, lnProvenWeight, stateProofStrengthTargetForTests) + + a.Equal(verifierLnP, verifier) +} + +func TestTreeDepth(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + p := generateProofForTesting(a, false) + sProof := p.sp + + verifier, err := MkVerifier(p.partCommitment, p.provenWeight, stateProofStrengthTargetForTests) + a.NoError(err) + + tmp := sProof.PartProofs.TreeDepth + sProof.PartProofs.TreeDepth = MaxTreeDepth + 1 + a.ErrorIs(verifier.Verify(stateProofIntervalForTests, p.data, &sProof), ErrTreeDepthTooLarge) + sProof.PartProofs.TreeDepth = tmp + + tmp = sProof.SigProofs.TreeDepth + sProof.SigProofs.TreeDepth = MaxTreeDepth + 1 + a.ErrorIs(verifier.Verify(stateProofIntervalForTests, p.data, &sProof), ErrTreeDepthTooLarge) + sProof.SigProofs.TreeDepth = tmp + + a.NoError(verifier.Verify(stateProofIntervalForTests, p.data, &sProof)) +} diff --git a/crypto/stateproof/weights.go b/crypto/stateproof/weights.go new file mode 100644 index 0000000000..8d0bdd13dc --- /dev/null +++ b/crypto/stateproof/weights.go @@ -0,0 +1,193 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "errors" + "math" + "math/big" + "math/bits" +) + +// errors for the weights verification +var ( + ErrSignedWeightLessThanProvenWeight = errors.New("signed weight is less than or equal to proven weight") + ErrTooManyReveals = errors.New("too many reveals in state proof") + ErrZeroSignedWeight = errors.New("signed weight cannot be zero") + ErrIllegalInputForLnApprox = errors.New("cannot calculate a ln integer value for 0") + ErrInsufficientSignedWeight = errors.New("the number of reveals is not large enough to prove that the desired weight signed, with the desired security level") + ErrNegativeNumOfRevealsEquation = errors.New("state proof creation failed: weights will not be able to satisfy the verification equation") +) + +func bigInt(num uint64) *big.Int { + return (&big.Int{}).SetUint64(num) +} + +// LnIntApproximation returns a uint64 approximation +func LnIntApproximation(x uint64) (uint64, error) { + if x == 0 { + return 0, ErrIllegalInputForLnApprox + } + result := math.Log(float64(x)) + precision := uint64(1 << precisionBits) + expandWithPrecision := result * float64(precision) + return uint64(math.Ceil(expandWithPrecision)), nil + +} + +// verifyWeights makes sure that the number of reveals in the state proof is correct with respect +// to the signedWeight and a provenWeight upper bound. +// This function checks that the following inequality is satisfied +// +// numReveals * (3 * 2^b * (signedWeight^2 - 2^2d) + d * (T-1) * Y) >= ((strengthTarget) * T + numReveals * P) * Y +// +// where signedWeight/(2^d) >=1 for some integer d>=0, p = P/(2^b) >= ln(provenWeight), t = T/(2^b) >= ln(2) >= (T-1)/(2^b) +// for some integers P,T >= 0 and b=16. +// +// T and b are defined in the code as the constants ln2IntApproximation and precisionBits respectively. +// P is set to lnProvenWeight argument +// more details can be found on the Algorand's spec +func verifyWeights(signedWeight uint64, lnProvenWeight uint64, numOfReveals uint64, strengthTarget uint64) error { + if numOfReveals > MaxReveals { + return ErrTooManyReveals + } + + if signedWeight == 0 { + return ErrZeroSignedWeight + } + + // in order to make the code more readable and reusable we will define the following expressions: + // y = signedWeight^2 + 2^(d + 2) * signedWeight + 2^2d + // x = 3 * 2^b * (signedWeight^2 - 2^2d) + // w = d * (T - 1) + // + // numReveals * (3 * 2^b * (signedWeight^2 - 2^2d) + d * (T-1) * Y) >= ((strengthTarget) * T + numReveals * P) * Y + // /\ + // || + // \/ + // numReveals * (x + w * y) >= ((strengthTarget) * T + numReveals * P) * y + y, x, w := getSubExpressions(signedWeight) + lhs := &big.Int{} + lhs.Set(w). + Mul(lhs, y). + Add(x, lhs). + Mul(bigInt(numOfReveals), lhs) + + revealsTimesP := &big.Int{} + revealsTimesP.Set(bigInt(numOfReveals)).Mul(revealsTimesP, bigInt(lnProvenWeight)) + + rhs := &big.Int{} + rhs.Set(bigInt(strengthTarget)) + rhs.Mul(rhs, bigInt(ln2IntApproximation)). + Add(rhs, revealsTimesP). + Mul(rhs, y) + + if lhs.Cmp(rhs) < 0 { + return ErrInsufficientSignedWeight + } + + return nil +} + +// numReveals computes the number of reveals necessary to achieve the desired +// security target. We search for small integer that will satisfy the verification +// inequality checked by the verifyWeights function. +// In order to make sure the number will satisfy the verifier we will use the following inequality +// +// numReveals >= ((strengthTarget) * T * Y / (3 * 2^b * (signedWeight^2 - 2^2d) + (d * (T - 1) - P) * Y)) +// where signedWeight/(2^d) >=1 for some integer d>=0, p = P/(2^b) >= ln(provenWeight), t = T/(2^b) >= ln(2) >= (T-1)/(2^b) +// for some integers P,T >= 0 and b=16. +// +// T and b are defined in the code as the constants ln2IntApproximation and precisionBits respectively, +// and P is set to lnProvenWeight argument. +// +// +// more details can be found on the Algorand's spec +func numReveals(signedWeight uint64, lnProvenWeight uint64, strengthTarget uint64) (uint64, error) { + // in order to make the code more readable and reusable we will define the following expressions: + // y = signedWeight^2 + 2^(d + 2) * signedWeight + 2^2d + // x = 3 * 2^b * (signedWeight^2 - 2^2d) + // w = d * (T - 1) + // + // numReveals >= ((strengthTarget) * T * Y / (3 * 2^b * (signedWeight^2 - 2^2d) + (d * (T - 1) - P) * Y)) + // /\ + // || + // \/ + // numReveals >= ((strengthTarget) * T * y / (x + (w - P) * y)) + y, x, w := getSubExpressions(signedWeight) + + // numerator = strengthTarget * ln2IntApproximation * y + numerator := bigInt(strengthTarget) + numerator.Mul(numerator, bigInt(ln2IntApproximation)). + Mul(numerator, y) + + // denom = x + (w - lnProvenWeight) * y + denom := w + denom.Sub(denom, bigInt(lnProvenWeight)). + Mul(denom, y). + Add(x, denom) + + if denom.Sign() <= 0 { + return 0, ErrNegativeNumOfRevealsEquation + } + + // numberReveals = (numerator / denom) + 1 + // by adding 1 we guarantee that the return value satisfy the inequality and therefore + // will satisfy the verifier. + // + 1 to account for the decimal point value loss due to integer division + res := numerator.Div(numerator, denom).Uint64() + 1 + if res > MaxReveals { + return 0, ErrTooManyReveals + } + return res, nil +} + +// getSubExpressions calculate the following expression to make the code more readable and reusable +// y = signedWeight^2 + 2^(d + 2) * signedWeight + 2^2d +// x = 3 * 2^b * (signedWeight^2 - 2^2d) +// w = d * (T - 1) +func getSubExpressions(signedWeight uint64) (y *big.Int, x *big.Int, w *big.Int) { + // find d s.t 2^(d+1) >= signedWeight >= 2^(d) + d := uint(bits.Len64(signedWeight)) - 1 + + signedWtPower2 := bigInt(signedWeight) + signedWtPower2.Mul(signedWtPower2, signedWtPower2) + + //tmp = 2^(d+2)*signedWt + tmp := bigInt(1) + tmp.Lsh(tmp, d+2). + Mul(tmp, bigInt(signedWeight)) + + // Y = signedWeight^2 + 2^(d+2)*signedWeight +2^2d == signedWeight^2 + tmp +2^2d + y = bigInt(1) + y.Lsh(y, 2*d). + Add(y, tmp). + Add(y, signedWtPower2) + + // x = 3*2^b*(signedWeight^2-2^2d) + x = bigInt(1) + x.Lsh(x, 2*d). + Sub(signedWtPower2, x). + Mul(x, bigInt(3)). + Mul(x, bigInt(1<. + +package stateproof + +import ( + "fmt" + "math" + "testing" + + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/stretchr/testify/require" +) + +func TestMaxNumberOfRevealsInVerify(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + signedWeight := uint64(10) + provenWeight := uint64(10) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + err = verifyWeights(signedWeight, lnProvenWt, MaxReveals+1, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrTooManyReveals) +} + +func TestMaxNumberOfReveals(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + signedWeight := uint64(1<<10 + 1) + provenWeight := uint64(1 << 10) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + _, err = numReveals(signedWeight, lnProvenWt, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrTooManyReveals) +} + +func TestVerifyProvenWeight(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + signedWeight := uint64(1 << 11) + provenWeight := uint64(1 << 10) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + numOfReveals, err := numReveals(signedWeight, lnProvenWt, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifyWeights(signedWeight, lnProvenWt, numOfReveals, stateProofStrengthTargetForTests) + a.NoError(err) + + err = verifyWeights(signedWeight, lnProvenWt, numOfReveals-1, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrInsufficientSignedWeight) +} + +func TestVerifyZeroNumberOfRevealsEquation(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + signedWeight := uint64(1<<15 + 1) + provenWeight := uint64(1 << 15) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + _, err = numReveals(signedWeight, lnProvenWt, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrNegativeNumOfRevealsEquation) +} + +func TestLnWithPrecision(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + val, err := LnIntApproximation(2) + a.NoError(err) + + // check that precisionBits will not overflow + exp := 1 << precisionBits + a.Less(precisionBits, uint8(64)) + + a.GreaterOrEqual(float64(val)/float64(exp), math.Log(2)) + a.Greater(math.Log(2), float64(val-1)/float64(exp)) + + ln2, err := LnIntApproximation(2) + a.NoError(err) + a.Equal(ln2IntApproximation, ln2) +} + +func TestVerifyLimits(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + signedWeight := uint64(0) + provenWeight := uint64(1<<10 - 1) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + err = verifyWeights(signedWeight, lnProvenWt, MaxReveals-1, stateProofStrengthTargetForTests) + a.ErrorIs(err, ErrZeroSignedWeight) +} + +func TestNumRevealsApproxBound(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + // In order to create a valid state proof we need to be bound to a MaxNumberOfReveals. + // according to SNARK-friendly weight-verification formula there would be a ratio signedWt/provenWt > 1 + // that we would not be able to generate proof since the MaxReveals would be too high. + // This test points out on the minimal ratio signedWt/provenWt we would ever prdouce. + + for j := 0; j < 10; j++ { + sigWt := uint64(1<<(40-j) - 1) + // we check the ratios = signedWt/provenWt {3, 2.99, 2.98...1} + // ratio = 1.33 (i==167) would give 625 would be the lower bound we can expect + for i := 0; i < 168; i++ { + a.NoError(checkRatio(i, sigWt, stateProofStrengthTargetForTests)) + } + a.ErrorIs(checkRatio(168, sigWt, stateProofStrengthTargetForTests), ErrTooManyReveals) + } +} + +func checkRatio(i int, sigWt uint64, secParam uint64) error { + provenWtRatio := 3 - (float64(i) / 100) + provenWt := uint64(float64(sigWt) / (provenWtRatio)) + lnProvenWt, err := LnIntApproximation(provenWt) + if err != nil { + return err + } + + numOfReveals, err := numReveals(sigWt, lnProvenWt, secParam) + if err != nil { + return fmt.Errorf("failed on sigWt %v provenWt %d ratio is %v i %v err: %w", sigWt, provenWt, provenWtRatio, i, err) + } + + log2Sig := math.Log(float64(sigWt)) / math.Log(2) + log2Prov := math.Log(float64(provenWt)) / math.Log(2) + nr := float64(secParam) / (log2Sig - log2Prov) + if 1.01 < float64(numOfReveals)/nr { + return fmt.Errorf("approximated number of reveals exceeds limit "+ + "limit %v, signedWeight: %v provenWeight %v, "+ + "appox numberOfReveals: %v, real numberOfReveals %v ratio is %v", 1.01, sigWt, provenWt, numOfReveals, nr, provenWtRatio) + } + return nil +} + +func TestNumReveals(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + billion := uint64(1000 * 1000 * 1000) + microalgo := uint64(1000 * 1000) + provenWeight := 2 * billion * microalgo + strengthTarget := uint64(stateProofStrengthTargetForTests) + lnProvenWt, err := LnIntApproximation(provenWeight) + a.NoError(err) + + for i := uint64(3); i < 10; i++ { + signedWeight := i * billion * microalgo + n, err := numReveals(signedWeight, lnProvenWt, strengthTarget) + a.NoError(err) + if n < 50 || n > 500 { + t.Errorf("numReveals(%d, %d, %d) = %d looks suspect", + signedWeight, provenWeight, strengthTarget, n) + } + + err = verifyWeights(signedWeight, lnProvenWt, n, stateProofStrengthTargetForTests) + a.NoError(err) + } +} + +func BenchmarkVerifyWeights(b *testing.B) { + billion := uint64(1000 * 1000 * 1000) + microalgo := uint64(1000 * 1000) + provenWeight := 100 * billion * microalgo + signedWeight := 110 * billion * microalgo + strengthTarget := uint64(stateProofStrengthTargetForTests) + + lnProvenWt, err := LnIntApproximation(provenWeight) + require.NoError(b, err) + + nr, err := numReveals(signedWeight, lnProvenWt, strengthTarget) + if nr < 900 { + b.Errorf("numReveals(%d, %d, %d) = %d < 900", signedWeight, provenWeight, strengthTarget, nr) + } + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + verifyWeights(signedWeight, lnProvenWt, nr, strengthTarget) + } +} + +func BenchmarkNumReveals(b *testing.B) { + billion := uint64(1000 * 1000 * 1000) + microalgo := uint64(1000 * 1000) + provenWeight := 100 * billion * microalgo + signedWeight := 110 * billion * microalgo + strengthTarget := uint64(stateProofStrengthTargetForTests) + lnProvenWt, err := LnIntApproximation(provenWeight) + require.NoError(b, err) + + nr, err := numReveals(signedWeight, lnProvenWt, strengthTarget) + if nr < 900 { + b.Errorf("numReveals(%d, %d, %d) = %d < 900", signedWeight, provenWeight, strengthTarget, nr) + } + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + numReveals(signedWeight, lnProvenWt, strengthTarget) + } +} diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 8e2c69e54c..47aa60bb41 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -1246,6 +1246,130 @@ } ] }, + "/v2/stateproofs/{round}": { + "get": { + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Get a state proof that covers a given round", + "operationId": "GetStateProof", + "parameters": [ + { + "type": "integer", + "description": "The round for which a state proof is desired.", + "name": "round", + "in": "path", + "required": true, + "minimum": 0 + } + ], + "responses": { + "200": { + "$ref": "#/responses/StateProofResponse" + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Could not find a state proof that covers a given round", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "503": { + "description": "Service Temporarily Unavailable", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + }, + "parameters": [ + { + "type": "integer", + "name": "round", + "in": "path", + "required": true + } + ] + }, + "/v2/blocks/{round}/lightheader/proof": { + "get": { + "produces": [ + "application/json" + ], + "schemes": [ + "http" + ], + "summary": "Gets a proof for a given light block header inside a state proof commitment", + "operationId": "GetProofForLightBlockHeader", + "parameters": [ + { + "type": "integer", + "description": "The round to which the light block header belongs.", + "name": "round", + "in": "path", + "required": true, + "minimum": 0 + } + ], + "responses": { + "200": { + "$ref": "#/responses/LightBlockHeaderProofResponse" + }, + "401": { + "description": "Invalid API Token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Could not create proof since some data is missing", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal Error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "503": { + "description": "Service Temporarily Unavailable", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "default": { + "description": "Unknown Error" + } + } + }, + "parameters": [ + { + "type": "integer", + "name": "round", + "in": "path", + "required": true + } + ] + }, "/v2/applications/{application-id}": { "get": { "description": "Given a application ID, it returns application information including creator, approval and clear programs, global and local schemas, and global state.", @@ -1306,7 +1430,7 @@ "name": "application-id", "in": "path", "required": true - } + } ] }, "/v2/assets/{asset-id}": { @@ -1369,7 +1493,7 @@ "name": "asset-id", "in": "path", "required": true - } + } ] }, "/v2/teal/compile": { @@ -1396,7 +1520,7 @@ "type": "string", "format": "binary" } - }, + }, { "name": "sourcemap", "description": "When set to `true`, returns the source map of the program as a JSON. Defaults to `false`.", @@ -1851,8 +1975,8 @@ "properties": { "amount": { "description": "\\[a\\] number of units held.", - "type": "integer", - "x-algorand-format": "uint64" + "type": "integer", + "x-algorand-format": "uint64" }, "asset-id": { "description": "Asset ID of the holding.", @@ -2349,7 +2473,7 @@ "accounts": { "type": "array", "items": { - "$ref": "#/definitions/Account" + "$ref": "#/definitions/Account" } }, "apps": { @@ -2484,7 +2608,7 @@ "asset-index": { "description": "The asset index if the transaction was found and it created an asset.", "type": "integer" - }, + }, "application-index": { "description": "The application index if the transaction was found and it created an application.", "type": "integer" @@ -2545,10 +2669,54 @@ }, "txn": { "description": "The raw signed transaction.", - "type": "object", + "type": "object", "x-algorand-format": "SignedTransaction" } } + }, + "StateProof": { + "description": "Represents a state proof and its corresponding message", + "type": "object", + "required": [ + "Message", + "StateProof" + ], + "properties": { + "Message": { + "description": "The encoded message.", + "type": "string", + "format": "byte" + }, + "StateProof": { + "description": "The encoded StateProof for the message.", + "type": "string", + "format": "byte" + } + } + }, + "LightBlockHeaderProof": { + "description": "Proof of membership and position of a light block header.", + "type": "object", + "required": [ + "index", + "treedepth", + "proof" + ], + "properties": { + "index": { + "description": "The index of the light block header in the vector commitment tree", + "type": "integer" + }, + "treedepth": { + "description": "Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root.", + "type": "integer" + }, + "proof": { + "description": "The encoded proof.", + "type": "string", + "format": "byte" + } + } } }, "parameters": { @@ -2714,7 +2882,8 @@ "acfg", "axfer", "afrz", - "appl" + "appl", + "stpf" ], "type": "string", "name": "tx-type", @@ -2722,6 +2891,18 @@ } }, "responses": { + "LightBlockHeaderProofResponse": { + "description": "Proof of a light block header.", + "schema": { + "$ref": "#/definitions/LightBlockHeaderProof" + } + }, + "StateProofResponse": { + "description": "StateProofResponse wraps the StateProof type in a response.", + "schema": { + "$ref": "#/definitions/StateProof" + } + }, "AccountResponse": { "description": "AccountResponse wraps the Account type in a response.", "schema": { @@ -2762,7 +2943,7 @@ "round": { "description": "The round for which this information is relevant.", "type": "integer" - }, + }, "app-local-state": { "description": "\\[appl\\] the application local data stored in this account.\n\nThe raw account uses `AppLocalState` for this type.", "$ref": "#/definitions/ApplicationLocalState" @@ -2854,7 +3035,7 @@ } } }, - "CatchpointAbortResponse":{ + "CatchpointAbortResponse": { "tags": [ "private" ], @@ -2990,12 +3171,12 @@ "$ref": "#/definitions/ParticipationKey" } }, - "PostParticipationResponse" : { + "PostParticipationResponse": { "description": "Participation ID of the submission", "schema": { "type": "object", "required": [ - "partId" + "partId" ], "properties": { "partId": { @@ -3004,9 +3185,7 @@ } } } - }, - "PostTransactionsResponse": { "description": "Transaction ID of the submission.", "schema": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 0fa09dd559..969134bcb3 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -216,7 +216,8 @@ "acfg", "axfer", "afrz", - "appl" + "appl", + "stpf" ], "type": "string" } @@ -446,6 +447,16 @@ }, "description": "DryrunResponse contains per-txn debug information from a dryrun." }, + "LightBlockHeaderProofResponse": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightBlockHeaderProof" + } + } + }, + "description": "Proof of a light block header." + }, "NodeStatusResponse": { "content": { "application/json": { @@ -666,6 +677,16 @@ }, "description": "Proof of transaction in a block." }, + "StateProofResponse": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StateProof" + } + } + }, + "description": "StateProofResponse wraps the StateProof type in a response." + }, "SupplyResponse": { "content": { "application/json": { @@ -1443,6 +1464,31 @@ ], "type": "object" }, + "LightBlockHeaderProof": { + "description": "Proof of membership and position of a light block header.", + "properties": { + "index": { + "description": "The index of the light block header in the vector commitment tree", + "type": "integer" + }, + "proof": { + "description": "The encoded proof.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "treedepth": { + "description": "Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root.", + "type": "integer" + } + }, + "required": [ + "index", + "proof", + "treedepth" + ], + "type": "object" + }, "ParticipationKey": { "description": "Represents a participation key used by the node.", "properties": { @@ -1573,6 +1619,28 @@ }, "type": "array" }, + "StateProof": { + "description": "Represents a state proof and its corresponding message", + "properties": { + "Message": { + "description": "The encoded message.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "StateProof": { + "description": "The encoded StateProof for the message.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + } + }, + "required": [ + "Message", + "StateProof" + ], + "type": "object" + }, "TealKeyValue": { "description": "Represents a key-value pair in an application store.", "properties": { @@ -2596,6 +2664,80 @@ "summary": "Get the block for the given round." } }, + "/v2/blocks/{round}/lightheader/proof": { + "get": { + "operationId": "GetProofForLightBlockHeader", + "parameters": [ + { + "description": "The round to which the light block header belongs.", + "in": "path", + "name": "round", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightBlockHeaderProof" + } + } + }, + "description": "Proof of a light block header." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Could not create proof since some data is missing" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Service Temporarily Unavailable" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Gets a proof for a given light block header inside a state proof commitment" + } + }, "/v2/blocks/{round}/transactions/{txid}/proof": { "get": { "operationId": "GetProof", @@ -3394,6 +3536,80 @@ ] } }, + "/v2/stateproofs/{round}": { + "get": { + "operationId": "GetStateProof", + "parameters": [ + { + "description": "The round for which a state proof is desired.", + "in": "path", + "name": "round", + "required": true, + "schema": { + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StateProof" + } + } + }, + "description": "StateProofResponse wraps the StateProof type in a response." + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Invalid API Token" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Could not find a state proof that covers a given round" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Internal Error" + }, + "503": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Service Temporarily Unavailable" + }, + "default": { + "content": {}, + "description": "Unknown Error" + } + }, + "summary": "Get a state proof that covers a given round" + } + }, "/v2/status": { "get": { "operationId": "GetStatus", diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index ecaafc6360..ac4a78ff32 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -654,8 +654,14 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } -// Proof gets a Merkle proof for a transaction in a block. -func (client RestClient) Proof(txid string, round uint64, hashType crypto.HashType) (response generatedV2.ProofResponse, err error) { +// LightBlockHeaderProof gets a Merkle proof for the light block header of a given round. +func (client RestClient) LightBlockHeaderProof(round uint64) (response generatedV2.LightBlockHeaderProofResponse, err error) { + err = client.get(&response, fmt.Sprintf("/v2/blocks/%d/lightheader/proof", round), nil) + return +} + +// TransactionProof gets a Merkle proof for a transaction in a block. +func (client RestClient) TransactionProof(txid string, round uint64, hashType crypto.HashType) (response generatedV2.ProofResponse, err error) { txid = stripTransaction(txid) err = client.get(&response, fmt.Sprintf("/v2/blocks/%d/transactions/%s/proof", round, txid), proofParams{HashType: hashType.String()}) return diff --git a/daemon/algod/api/server/lib/bundledSpecInject.go b/daemon/algod/api/server/lib/bundledSpecInject.go index fefff94163..50fc14c2ee 100644 --- a/daemon/algod/api/server/lib/bundledSpecInject.go +++ b/daemon/algod/api/server/lib/bundledSpecInject.go @@ -2384,740 +2384,386 @@ func init() { 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x6E, 0x52, 0x6F, 0x6F, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, 0x6D, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, - 0x65, 0x72, 0x73, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x4E, - 0x65, 0x78, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, - 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x4E, 0x65, 0x78, - 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x4E, 0x65, 0x78, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, - 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x20, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x20, - 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x69, 0x73, 0x5C, 0x6E, - 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, - 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, - 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, - 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x4E, 0x65, 0x78, 0x74, 0x52, 0x6F, - 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, - 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, - 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x72, 0x6F, 0x6F, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x65, - 0x72, 0x6B, 0x6C, 0x65, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x6F, 0x66, 0x20, 0x76, 0x6F, 0x74, - 0x65, 0x72, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x20, - 0x63, 0x65, 0x72, 0x74, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, - 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, - 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, - 0x73, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, - 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x20, 0x69, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, - 0x6F, 0x66, 0x20, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x61, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x68, 0x65, - 0x6C, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x6F, 0x74, 0x65, 0x72, 0x73, - 0x20, 0x69, 0x6E, 0x5C, 0x6E, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, - 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x20, 0x6D, 0x65, 0x72, 0x6B, 0x6C, - 0x65, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, - 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, - 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, - 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x56, 0x6F, 0x74, 0x65, 0x72, 0x73, 0x54, 0x6F, - 0x74, 0x61, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, - 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x50, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, - 0x6E, 0x67, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, - 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x70, - 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, - 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x75, - 0x72, 0x72, 0x65, 0x6E, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x66, 0x72, 0x61, 0x63, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, - 0x20, 0x6F, 0x66, 0x20, 0x6C, 0x65, 0x66, 0x74, 0x6F, 0x76, 0x65, 0x72, 0x20, 0x4D, 0x69, 0x63, - 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6F, - 0x66, 0x20, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x61, 0x74, 0x65, 0x2F, 0x72, 0x65, - 0x77, 0x61, 0x72, 0x64, 0x55, 0x6E, 0x69, 0x74, 0x73, 0x5C, 0x6E, 0x4D, 0x69, 0x63, 0x72, 0x6F, - 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x20, 0x75, 0x6E, 0x69, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x2E, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x69, 0x64, - 0x75, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x48, 0x61, 0x73, 0x68, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x62, - 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, - 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, - 0x6C, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, - 0x61, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x64, 0x20, - 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, - 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, - 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x41, 0x70, 0x70, - 0x72, 0x6F, 0x76, 0x61, 0x6C, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, - 0x6C, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x61, 0x6C, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x62, 0x6C, 0x6F, 0x63, - 0x6B, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x65, - 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x75, - 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, - 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, - 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, - 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x61, 0x6C, - 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, - 0x63, 0x6F, 0x6C, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x4F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x4F, 0x6E, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x6F, 0x6E, 0x20, 0x77, - 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, - 0x6C, 0x20, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x74, - 0x61, 0x6B, 0x65, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, - 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x53, 0x77, 0x69, 0x74, - 0x63, 0x68, 0x4F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x56, 0x6F, 0x74, 0x65, 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, - 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x56, 0x6F, 0x74, 0x65, 0x42, - 0x65, 0x66, 0x6F, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x61, - 0x64, 0x6C, 0x69, 0x6E, 0x65, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x75, 0x70, - 0x67, 0x72, 0x61, 0x64, 0x65, 0x20, 0x28, 0x4E, 0x6F, 0x20, 0x76, 0x6F, 0x74, 0x65, 0x73, 0x20, - 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x69, 0x64, 0x65, 0x72, - 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x72, 0x6F, 0x75, 0x6E, - 0x64, 0x29, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, - 0x6F, 0x63, 0x6F, 0x6C, 0x56, 0x6F, 0x74, 0x65, 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x20, 0x6F, 0x6E, - 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, - 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x50, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x72, 0x65, 0x76, 0x69, 0x6F, 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x48, 0x61, 0x73, - 0x68, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x50, 0x72, 0x65, 0x76, 0x69, 0x6F, 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x48, 0x61, 0x73, - 0x68, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6F, 0x75, - 0x73, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6F, 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, - 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, - 0x65, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x74, 0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, 0x6D, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, + 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, + 0x6F, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, - 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x72, 0x61, 0x74, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, - 0x72, 0x20, 0x6F, 0x66, 0x20, 0x6E, 0x65, 0x77, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, - 0x67, 0x6F, 0x73, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, - 0x74, 0x61, 0x6B, 0x65, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x73, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x72, 0x6F, - 0x75, 0x6E, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, - 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, - 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, - 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, - 0x73, 0x52, 0x61, 0x74, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4C, 0x65, 0x76, 0x65, 0x6C, 0x20, 0x73, 0x70, 0x65, - 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x68, 0x6F, 0x77, 0x20, 0x6D, 0x61, 0x6E, 0x79, 0x20, - 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x2C, 0x20, 0x69, 0x6E, 0x20, 0x4D, 0x69, 0x63, 0x72, - 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x2C, 0x5C, 0x6E, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, 0x65, - 0x65, 0x6E, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x20, 0x74, - 0x6F, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x67, 0x2E, 0x50, 0x72, - 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x55, 0x6E, 0x69, - 0x74, 0x5C, 0x6E, 0x6F, 0x66, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, - 0x20, 0x73, 0x69, 0x6E, 0x63, 0x65, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x2E, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4C, 0x65, 0x76, 0x65, - 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, - 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x6F, 0x6E, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x70, - 0x70, 0x65, 0x6E, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, - 0x61, 0x69, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, - 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x65, 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x73, 0x6F, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x65, 0x65, - 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x22, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, + 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x65, 0x65, 0x64, 0x22, 0x0A, 0x20, 0x20, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x50, + 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x61, + 0x63, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x54, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x6C, 0x65, + 0x66, 0x74, 0x6F, 0x76, 0x65, 0x72, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, + 0x73, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6F, 0x66, 0x20, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x52, 0x61, 0x74, 0x65, 0x2F, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x55, 0x6E, + 0x69, 0x74, 0x73, 0x5C, 0x6E, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, + 0x66, 0x6F, 0x72, 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x20, 0x75, 0x6E, 0x69, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, + 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, + 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x69, 0x64, 0x75, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, 0x6D, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, - 0x61, 0x6D, 0x70, 0x20, 0x69, 0x6E, 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x20, 0x73, - 0x69, 0x6E, 0x63, 0x65, 0x20, 0x65, 0x70, 0x6F, 0x63, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, - 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, - 0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, 0x6D, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, - 0x6E, 0x52, 0x6F, 0x6F, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, - 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, - 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, - 0x72, 0x69, 0x6E, 0x67, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, - 0x6B, 0x2E, 0x5C, 0x6E, 0x4D, 0x6F, 0x72, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x6C, 0x6C, 0x79, 0x2C, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x72, 0x6F, 0x6F, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x6D, 0x65, 0x72, 0x6B, 0x6C, 0x65, - 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x77, 0x68, 0x6F, 0x73, 0x65, 0x20, 0x6C, 0x65, 0x61, 0x76, - 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, - 0x27, 0x73, 0x20, 0x54, 0x78, 0x69, 0x64, 0x73, 0x2C, 0x20, 0x69, 0x6E, 0x20, 0x6C, 0x65, 0x78, - 0x69, 0x63, 0x6F, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x20, 0x6F, 0x72, 0x64, 0x65, 0x72, - 0x2E, 0x5C, 0x6E, 0x46, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6D, 0x70, 0x74, 0x79, - 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x2C, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x30, 0x2E, 0x5C, - 0x6E, 0x4E, 0x6F, 0x74, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, - 0x78, 0x6E, 0x43, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x64, 0x6F, - 0x65, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x20, 0x6F, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2C, 0x20, 0x6F, 0x6E, 0x6C, 0x79, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x6D, 0x73, 0x65, 0x6C, 0x76, 0x65, 0x73, 0x2E, 0x5C, 0x6E, 0x54, 0x77, 0x6F, 0x20, 0x62, - 0x6C, 0x6F, 0x63, 0x6B, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, - 0x61, 0x6D, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, - 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, - 0x65, 0x6E, 0x74, 0x20, 0x6F, 0x72, 0x64, 0x65, 0x72, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, - 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x68, 0x61, 0x76, - 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6D, 0x65, 0x20, 0x54, 0x78, 0x6E, 0x43, 0x6F, - 0x6D, 0x6D, 0x69, 0x74, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x6F, 0x6F, - 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, - 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, - 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, 0x70, - 0x72, 0x6F, 0x76, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, - 0x65, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x79, 0x65, - 0x73, 0x20, 0x76, 0x6F, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, - 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x61, 0x6C, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, - 0x70, 0x72, 0x6F, 0x76, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x70, 0x67, 0x72, 0x61, - 0x64, 0x65, 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, - 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x20, 0x61, 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x64, 0x20, 0x75, 0x70, 0x67, 0x72, - 0x61, 0x64, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x48, 0x61, 0x73, 0x68, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x68, + 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, - 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, - 0x65, 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, - 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, - 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, - 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6E, 0x74, 0x20, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x20, 0x62, 0x75, 0x69, 0x6C, 0x64, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, 0x69, 0x6E, 0x6F, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x5F, 0x6E, 0x75, 0x6D, - 0x62, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, - 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x5F, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, - 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, 0x0A, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x5F, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x4E, 0x75, 0x6D, 0x62, - 0x65, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, - 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x5F, 0x68, 0x61, 0x73, 0x68, + 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, + 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x20, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x72, 0x65, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, + 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, + 0x6F, 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4D, 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x6D, 0x69, 0x6E, 0x6F, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, - 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, - 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4D, 0x69, 0x6E, 0x6F, - 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, - 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, - 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, - 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, - 0x65, 0x63, 0x2F, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, - 0x72, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x70, - 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x66, - 0x69, 0x65, 0x6C, 0x64, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x63, 0x6F, 0x6D, 0x70, - 0x61, 0x63, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, - 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6E, 0x64, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x65, 0x72, 0x74, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x65, 0x72, 0x74, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x65, 0x72, 0x74, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6B, 0x20, - 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, 0x2E, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, - 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x43, 0x65, 0x72, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6E, 0x64, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x65, 0x72, 0x74, - 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x75, - 0x6E, 0x64, 0x20, 0x77, 0x68, 0x6F, 0x73, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x20, 0x63, 0x65, 0x72, 0x74, - 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x73, 0x20, 0x74, 0x6F, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, - 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x43, 0x65, 0x72, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, - 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, - 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4B, 0x65, 0x79, 0x72, - 0x65, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4B, 0x65, 0x79, 0x72, - 0x65, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, - 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, - 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x73, - 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x6B, 0x65, 0x79, 0x72, 0x65, 0x67, 0x20, 0x54, 0x72, - 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, - 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, - 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x73, 0x65, 0x6C, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, - 0x6E, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x56, 0x52, 0x46, 0x20, 0x70, - 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, - 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6F, 0x6E, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x53, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x66, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x46, 0x69, 0x72, - 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, - 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, - 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x69, 0x73, 0x20, - 0x76, 0x61, 0x6C, 0x69, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, - 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x46, - 0x69, 0x72, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6B, 0x64, + 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, + 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, + 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x61, 0x6C, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, - 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x69, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, - 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x32, 0x2D, 0x6C, 0x65, 0x76, 0x65, 0x6C, 0x20, 0x70, - 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6B, 0x65, 0x79, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, + 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x41, 0x70, 0x70, 0x72, 0x6F, + 0x76, 0x61, 0x6C, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, + 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x73, 0x20, 0x77, 0x68, 0x69, + 0x63, 0x68, 0x20, 0x61, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x69, 0x6C, - 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6B, - 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x56, 0x6F, 0x74, 0x65, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, - 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x75, 0x62, - 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, - 0x6B, 0x65, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, - 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x56, 0x6F, 0x74, 0x65, 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, - 0x6C, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, - 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, - 0x6B, 0x65, 0x79, 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, - 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, - 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, - 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4E, 0x6F, 0x64, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x4E, 0x6F, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x74, - 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x20, 0x61, 0x20, 0x6E, 0x6F, 0x64, - 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, - 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, - 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, - 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, - 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x75, - 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, - 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x63, 0x61, 0x74, 0x63, 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, 0x65, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x61, 0x73, 0x53, 0x79, 0x6E, 0x63, 0x65, - 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, - 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6F, 0x75, - 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x74, 0x63, - 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x61, 0x74, 0x63, 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, - 0x65, 0x20, 0x69, 0x6E, 0x20, 0x6E, 0x61, 0x6E, 0x6F, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x61, 0x74, 0x63, 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, 0x65, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x61, 0x73, 0x53, 0x79, 0x6E, 0x63, 0x65, 0x64, 0x53, - 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x48, 0x61, 0x73, 0x53, 0x79, 0x6E, - 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x20, - 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x61, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x68, 0x61, 0x73, 0x20, 0x63, 0x6F, - 0x6D, 0x70, 0x6C, 0x65, 0x74, 0x65, 0x64, 0x20, 0x73, 0x69, 0x6E, 0x63, 0x65, 0x20, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, - 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x48, 0x61, 0x73, - 0x53, 0x79, 0x6E, 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x75, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x43, 0x6F, 0x6E, 0x73, - 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, - 0x73, 0x75, 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x75, 0x70, 0x70, - 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, - 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, - 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, - 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, - 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x73, 0x65, 0x65, 0x6E, 0x22, 0x2C, 0x0A, 0x20, + 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, + 0x6F, 0x6C, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x61, 0x6C, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x53, 0x77, 0x69, + 0x74, 0x63, 0x68, 0x4F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, + 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, + 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x4F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x6F, 0x6E, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x75, 0x70, 0x67, 0x72, + 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x74, 0x61, 0x6B, 0x65, 0x20, 0x65, 0x66, + 0x66, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, + 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x4F, 0x6E, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, + 0x56, 0x6F, 0x74, 0x65, 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, + 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x56, 0x6F, 0x74, 0x65, 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x61, 0x64, 0x6C, 0x69, 0x6E, 0x65, 0x20, + 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, + 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x20, + 0x28, 0x4E, 0x6F, 0x20, 0x76, 0x6F, 0x74, 0x65, 0x73, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x62, + 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x69, 0x64, 0x65, 0x72, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x29, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, - 0x6F, 0x66, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x70, 0x72, 0x6F, - 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x74, 0x6F, 0x20, 0x75, 0x73, 0x65, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, - 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x73, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x61, 0x74, 0x20, 0x77, 0x68, 0x69, - 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x63, 0x6F, 0x6E, 0x73, - 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x77, 0x69, - 0x6C, 0x6C, 0x20, 0x61, 0x70, 0x70, 0x6C, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x56, 0x6F, + 0x74, 0x65, 0x42, 0x65, 0x66, 0x6F, 0x72, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x65, + 0x72, 0x69, 0x6F, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x50, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x65, 0x72, 0x69, 0x6F, 0x64, 0x20, 0x6F, 0x6E, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, + 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, - 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x65, + 0x72, 0x69, 0x6F, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6F, + 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x48, 0x61, 0x73, 0x68, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6F, + 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x48, 0x61, 0x73, 0x68, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6F, 0x75, 0x73, 0x20, 0x62, 0x6C, 0x6F, 0x63, + 0x6B, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x72, 0x65, + 0x76, 0x69, 0x6F, 0x75, 0x73, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, - 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, - 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x75, 0x70, 0x70, 0x6F, - 0x72, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x77, - 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, - 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, - 0x6E, 0x20, 0x69, 0x73, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x20, 0x62, - 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x75, - 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, 0x6F, - 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, - 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, - 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, - 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x20, 0x64, 0x6F, 0x65, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, - 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x77, 0x20, - 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x73, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x68, 0x61, 0x73, 0x20, 0x73, - 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, 0x20, 0x6D, 0x61, 0x6B, 0x69, 0x6E, 0x67, 0x20, 0x70, 0x72, - 0x6F, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x6F, 0x6F, 0x6C, - 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, - 0x6F, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, - 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6D, - 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, - 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, - 0x20, 0x69, 0x6E, 0x20, 0x6E, 0x61, 0x6E, 0x6F, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, - 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, - 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, - 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x44, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, - 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x70, 0x61, 0x72, 0x74, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x72, 0x66, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x66, 0x73, - 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, - 0x65, 0x6C, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x76, 0x6F, 0x74, 0x65, 0x6B, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, - 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x61, 0x72, 0x74, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, - 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, - 0x6F, 0x6F, 0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, - 0x6E, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x28, 0x69, 0x66, - 0x20, 0x61, 0x6E, 0x79, 0x29, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, - 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x72, 0x6F, 0x70, 0x6F, + 0x73, 0x65, 0x72, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, + 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, - 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x61, 0x72, 0x74, - 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x61, + 0x74, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x54, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x6E, + 0x65, 0x77, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x61, 0x64, + 0x64, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x74, 0x61, 0x6B, 0x65, 0x20, 0x66, + 0x72, 0x6F, 0x6D, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x61, 0x74, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x2E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, + 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x52, 0x61, 0x74, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x73, 0x4C, 0x65, 0x76, 0x65, 0x6C, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, + 0x20, 0x68, 0x6F, 0x77, 0x20, 0x6D, 0x61, 0x6E, 0x79, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, + 0x73, 0x2C, 0x20, 0x69, 0x6E, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, + 0x2C, 0x5C, 0x6E, 0x68, 0x61, 0x76, 0x65, 0x20, 0x62, 0x65, 0x65, 0x6E, 0x20, 0x64, 0x69, 0x73, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x65, 0x61, 0x63, 0x68, + 0x20, 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x67, 0x2E, 0x50, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, + 0x2E, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x55, 0x6E, 0x69, 0x74, 0x5C, 0x6E, 0x6F, 0x66, 0x20, + 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x73, 0x69, 0x6E, 0x63, 0x65, + 0x20, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, + 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, + 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, + 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x4C, 0x65, 0x76, 0x65, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x76, 0x6F, 0x74, 0x65, 0x66, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x72, 0x6F, - 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x22, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, + 0x6F, 0x6E, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x62, 0x6C, + 0x6F, 0x63, 0x6B, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x70, 0x70, 0x65, 0x6E, 0x64, 0x65, 0x64, + 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x69, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, + 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, + 0x65, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x53, 0x65, 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x6F, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x65, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x53, 0x65, 0x65, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6D, 0x65, + 0x73, 0x74, 0x61, 0x6D, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, + 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x20, 0x69, 0x6E, + 0x20, 0x73, 0x65, 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x20, 0x73, 0x69, 0x6E, 0x63, 0x65, 0x20, 0x65, + 0x70, 0x6F, 0x63, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, + 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x73, 0x74, 0x61, + 0x6D, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x6E, 0x52, 0x6F, 0x6F, 0x74, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x61, + 0x75, 0x74, 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x73, 0x65, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x69, + 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x2E, 0x5C, 0x6E, 0x4D, 0x6F, + 0x72, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x6C, 0x6C, 0x79, 0x2C, + 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x6F, 0x74, 0x20, 0x6F, + 0x66, 0x20, 0x61, 0x20, 0x6D, 0x65, 0x72, 0x6B, 0x6C, 0x65, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, + 0x77, 0x68, 0x6F, 0x73, 0x65, 0x20, 0x6C, 0x65, 0x61, 0x76, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x27, 0x73, 0x20, 0x54, 0x78, 0x69, + 0x64, 0x73, 0x2C, 0x20, 0x69, 0x6E, 0x20, 0x6C, 0x65, 0x78, 0x69, 0x63, 0x6F, 0x67, 0x72, 0x61, + 0x70, 0x68, 0x69, 0x63, 0x20, 0x6F, 0x72, 0x64, 0x65, 0x72, 0x2E, 0x5C, 0x6E, 0x46, 0x6F, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6D, 0x70, 0x74, 0x79, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, + 0x2C, 0x20, 0x69, 0x74, 0x27, 0x73, 0x20, 0x30, 0x2E, 0x5C, 0x6E, 0x4E, 0x6F, 0x74, 0x65, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x78, 0x6E, 0x43, 0x6F, 0x6D, 0x6D, + 0x69, 0x74, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x64, 0x6F, 0x65, 0x73, 0x20, 0x6E, 0x6F, 0x74, + 0x20, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6E, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x20, 0x6F, 0x6E, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2C, 0x20, 0x6F, 0x6E, 0x6C, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6D, 0x73, 0x65, 0x6C, 0x76, + 0x65, 0x73, 0x2E, 0x5C, 0x6E, 0x54, 0x77, 0x6F, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x73, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6D, 0x65, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, + 0x6E, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6F, 0x72, + 0x64, 0x65, 0x72, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x64, 0x69, 0x66, + 0x66, 0x65, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x73, 0x61, 0x6D, 0x65, 0x20, 0x54, 0x78, 0x6E, 0x43, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x6D, 0x65, + 0x6E, 0x74, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x6F, 0x6F, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x74, 0x78, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, + 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, + 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x20, 0x69, 0x6E, 0x64, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x79, 0x65, 0x73, 0x20, 0x76, 0x6F, 0x74, 0x65, + 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, + 0x20, 0x70, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x61, 0x6C, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, + 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x41, 0x70, 0x70, 0x72, 0x6F, 0x76, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6F, 0x70, + 0x6F, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6F, 0x70, 0x6F, 0x73, 0x65, + 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6F, + 0x70, 0x6F, 0x73, 0x65, 0x64, 0x20, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x50, 0x72, 0x6F, 0x70, 0x6F, + 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, + 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, + 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, + 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x75, 0x69, + 0x6C, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, + 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x61, + 0x6C, 0x67, 0x6F, 0x64, 0x20, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, + 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6D, 0x69, 0x6E, 0x6F, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x5F, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x5F, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x62, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x63, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, + 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x62, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x42, 0x72, 0x61, 0x6E, 0x63, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, + 0x69, 0x6C, 0x64, 0x5F, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x4E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x63, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x43, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, + 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x5F, 0x68, 0x61, 0x73, 0x68, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6D, 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, + 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4D, + 0x61, 0x6A, 0x6F, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, 0x69, 0x6E, 0x6F, 0x72, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, + 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, + 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4D, 0x69, 0x6E, 0x6F, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, + 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, + 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, + 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, + 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x63, 0x6F, 0x6D, + 0x6D, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x4B, 0x65, 0x79, 0x72, 0x65, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x4B, 0x65, 0x79, 0x72, 0x65, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x66, + 0x69, 0x65, 0x6C, 0x64, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x6B, 0x65, 0x79, 0x72, + 0x65, 0x67, 0x20, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x65, 0x6C, 0x6B, 0x65, 0x79, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x65, 0x6C, + 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x56, 0x52, 0x46, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x75, + 0x73, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, + 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, + 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x66, 0x73, 0x74, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, + 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, + 0x69, 0x72, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6B, 0x65, + 0x79, 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, @@ -3128,11 +2774,10 @@ func init() { 0x6F, 0x74, 0x65, 0x6B, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x69, 0x6C, 0x75, 0x74, - 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, - 0x72, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x75, 0x62, 0x6B, 0x65, 0x79, 0x73, 0x20, 0x69, 0x6E, 0x20, - 0x66, 0x6F, 0x72, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x61, 0x74, 0x63, 0x68, 0x20, 0x6F, - 0x66, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, - 0x6B, 0x65, 0x79, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x69, 0x6C, 0x75, 0x74, + 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x32, 0x2D, 0x6C, 0x65, + 0x76, 0x65, 0x6C, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, @@ -3140,696 +2785,699 @@ func init() { 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x69, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x76, 0x6F, 0x74, 0x65, 0x6C, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x76, 0x6F, 0x74, 0x65, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, - 0x20, 0x66, 0x6F, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, - 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, - 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, - 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, - 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, - 0x65, 0x4C, 0x61, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x72, 0x66, 0x70, 0x6B, - 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, - 0x20, 0x22, 0x56, 0x52, 0x46, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, - 0x65, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, - 0x6B, 0x65, 0x79, 0x20, 0x28, 0x69, 0x66, 0x20, 0x61, 0x6E, 0x79, 0x29, 0x20, 0x63, 0x75, 0x72, - 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, - 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x56, 0x52, 0x46, 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, - 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, - 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x61, 0x79, 0x6D, 0x65, 0x6E, - 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x61, 0x79, 0x6D, 0x65, - 0x6E, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, - 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, - 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x73, - 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x70, 0x61, 0x79, 0x6D, 0x65, 0x6E, 0x74, 0x20, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, - 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x6F, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, - 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x4D, 0x69, 0x63, - 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x65, 0x64, - 0x20, 0x74, 0x6F, 0x20, 0x62, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x65, 0x72, 0x72, - 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, - 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x54, 0x6F, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6E, 0x64, - 0x65, 0x72, 0x20, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, 0x61, 0x69, 0x6E, 0x64, 0x65, - 0x72, 0x54, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x61, 0x6D, - 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x41, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x73, 0x65, 0x6E, - 0x74, 0x20, 0x74, 0x6F, 0x20, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, 0x61, 0x69, 0x6E, - 0x64, 0x65, 0x72, 0x54, 0x6F, 0x2C, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x69, - 0x74, 0x74, 0x65, 0x64, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x41, 0x6D, 0x6F, 0x75, 0x6E, - 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x72, 0x65, 0x77, 0x61, - 0x72, 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, - 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, - 0x70, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, - 0x61, 0x70, 0x70, 0x6C, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, - 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x54, 0x6F, 0x5C, - 0x6E, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x61, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, - 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x69, 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, + 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6C, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, + 0x64, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, + 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, + 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, + 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, + 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, + 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, + 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x4E, 0x6F, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6E, + 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x20, + 0x61, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, + 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x43, 0x6F, + 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, + 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, + 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, + 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, + 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6F, 0x6E, 0x53, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, + 0x65, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x61, 0x74, 0x63, 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, + 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x61, 0x73, + 0x53, 0x79, 0x6E, 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x75, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x74, + 0x6F, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, + 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x63, 0x61, 0x74, 0x63, 0x68, 0x75, 0x70, 0x54, 0x69, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x61, 0x74, 0x63, 0x68, + 0x75, 0x70, 0x54, 0x69, 0x6D, 0x65, 0x20, 0x69, 0x6E, 0x20, 0x6E, 0x61, 0x6E, 0x6F, 0x73, 0x65, + 0x63, 0x6F, 0x6E, 0x64, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, - 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, - 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, - 0x27, 0x73, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x61, 0x74, 0x63, 0x68, 0x75, + 0x70, 0x54, 0x69, 0x6D, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x68, 0x61, 0x73, 0x53, 0x79, + 0x6E, 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x48, + 0x61, 0x73, 0x53, 0x79, 0x6E, 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x75, 0x70, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x77, + 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x61, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x68, + 0x61, 0x73, 0x20, 0x63, 0x6F, 0x6D, 0x70, 0x6C, 0x65, 0x74, 0x65, 0x64, 0x20, 0x73, 0x69, 0x6E, + 0x63, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x48, 0x61, 0x73, 0x53, 0x79, 0x6E, 0x63, 0x65, 0x64, 0x53, 0x69, 0x6E, 0x63, 0x65, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, + 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x4C, 0x61, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x64, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x63, + 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, + 0x20, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x54, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, + 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x73, 0x65, 0x65, + 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, + 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, + 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, + 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x6F, 0x66, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, + 0x73, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x74, 0x6F, 0x20, 0x75, 0x73, + 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, + 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, + 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x54, 0x6F, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x70, 0x65, 0x6E, 0x64, - 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x61, 0x70, 0x70, 0x6C, - 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x6F, 0x20, 0x61, 0x63, - 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x5C, 0x6E, 0x61, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6F, - 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, - 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x52, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, - 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, - 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, - 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, - 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, - 0x20, 0x61, 0x20, 0x70, 0x6F, 0x74, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x6C, 0x79, 0x20, 0x74, - 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, - 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x63, 0x75, - 0x72, 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, - 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x20, 0x70, 0x6F, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, - 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, - 0x6C, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x2C, 0x0A, + 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, 0x75, 0x6E, + 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x61, + 0x74, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x65, 0x78, 0x74, + 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6F, 0x6E, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x61, 0x70, 0x70, 0x6C, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, + 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x52, 0x6F, + 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x65, 0x78, 0x74, 0x43, 0x6F, 0x6E, + 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x53, 0x75, 0x70, + 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, + 0x53, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x73, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6E, 0x65, 0x78, 0x74, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, + 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x64, 0x65, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x65, 0x78, 0x74, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6F, 0x6E, 0x53, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x54, 0x78, 0x6E, 0x73, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, - 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, - 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, - 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x77, 0x61, 0x67, - 0x67, 0x65, 0x72, 0x3A, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x20, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x20, 0x72, 0x65, 0x70, - 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x61, 0x20, 0x4C, 0x6F, 0x63, 0x61, 0x6C, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x20, 0x6F, 0x72, 0x20, 0x47, 0x6C, - 0x6F, 0x62, 0x61, 0x6C, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x2E, - 0x20, 0x54, 0x68, 0x65, 0x73, 0x65, 0x5C, 0x6E, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x73, 0x20, - 0x64, 0x65, 0x74, 0x65, 0x72, 0x6D, 0x69, 0x6E, 0x65, 0x20, 0x68, 0x6F, 0x77, 0x20, 0x6D, 0x75, - 0x63, 0x68, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, - 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x61, 0x20, 0x4C, 0x6F, 0x63, 0x61, - 0x6C, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6F, 0x72, 0x5C, 0x6E, 0x47, 0x6C, 0x6F, 0x62, 0x61, - 0x6C, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x70, - 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6D, - 0x6F, 0x72, 0x65, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x2C, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x72, 0x20, 0x6D, 0x69, 0x6E, 0x69, 0x6D, - 0x75, 0x6D, 0x5C, 0x6E, 0x62, 0x61, 0x6C, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x6D, 0x75, 0x73, 0x74, - 0x20, 0x62, 0x65, 0x20, 0x6D, 0x61, 0x69, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x65, 0x64, 0x20, 0x69, - 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x68, 0x6F, - 0x6C, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2E, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, - 0x69, 0x6E, 0x74, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x6C, 0x69, 0x63, 0x65, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, - 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x73, 0x6C, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, - 0x42, 0x79, 0x74, 0x65, 0x53, 0x6C, 0x69, 0x63, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x6D, 0x61, 0x78, 0x69, 0x6D, 0x75, 0x6D, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, - 0x6F, 0x66, 0x20, 0x54, 0x45, 0x41, 0x4C, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x6C, 0x69, - 0x63, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, 0x65, 0x5C, - 0x6E, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6B, - 0x65, 0x79, 0x2F, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x42, 0x79, 0x74, 0x65, 0x53, 0x6C, 0x69, 0x63, 0x65, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x22, 0x73, 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, + 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x55, 0x69, 0x6E, - 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x61, 0x78, 0x69, 0x6D, 0x75, 0x6D, - 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x54, 0x45, 0x41, 0x4C, 0x20, - 0x75, 0x69, 0x6E, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, - 0x65, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x5C, 0x6E, 0x74, 0x68, 0x65, - 0x20, 0x6B, 0x65, 0x79, 0x2F, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x65, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x55, 0x69, 0x6E, 0x74, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, - 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, - 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, - 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, - 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, - 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, - 0x75, 0x70, 0x70, 0x6C, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x53, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x75, - 0x70, 0x70, 0x6C, 0x79, 0x20, 0x6F, 0x66, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, - 0x6F, 0x73, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, - 0x6E, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6F, - 0x6E, 0x6C, 0x69, 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, - 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x6F, 0x6E, 0x6C, 0x69, 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4F, 0x6E, - 0x6C, 0x69, 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, - 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, - 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4F, - 0x6E, 0x6C, 0x69, 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, - 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, - 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, - 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x4D, - 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x6F, 0x70, 0x70, 0x65, + 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6F, + 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x20, 0x64, 0x6F, 0x65, 0x73, + 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6E, 0x65, 0x77, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x73, 0x20, 0x61, 0x6E, 0x64, 0x20, + 0x68, 0x61, 0x73, 0x20, 0x73, 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, 0x20, 0x6D, 0x61, 0x6B, 0x69, + 0x6E, 0x67, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x62, 0x6F, 0x6F, 0x6C, 0x65, 0x61, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x53, 0x74, 0x6F, 0x70, 0x70, 0x65, 0x64, 0x41, 0x74, 0x55, 0x6E, 0x73, 0x75, + 0x70, 0x70, 0x6F, 0x72, 0x74, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x52, + 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, + 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x6E, 0x61, 0x6E, 0x6F, 0x73, 0x65, 0x63, + 0x6F, 0x6E, 0x64, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, + 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x69, 0x6E, + 0x63, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, + 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, + 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, + 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, + 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x72, 0x74, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x72, 0x66, 0x70, 0x6B, + 0x62, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, + 0x6F, 0x74, 0x65, 0x66, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6C, 0x73, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6B, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, + 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x72, 0x74, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x72, 0x6F, 0x6F, 0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, + 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, + 0x79, 0x20, 0x28, 0x69, 0x66, 0x20, 0x61, 0x6E, 0x79, 0x29, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, + 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, - 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, + 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x4B, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x66, 0x73, 0x74, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x77, 0x68, 0x69, + 0x63, 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, + 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2E, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, + 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x46, 0x69, 0x72, 0x73, 0x74, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6B, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, + 0x44, 0x69, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x73, 0x75, 0x62, 0x6B, 0x65, 0x79, + 0x73, 0x20, 0x69, 0x6E, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x61, + 0x74, 0x63, 0x68, 0x20, 0x6F, 0x66, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x6B, 0x65, 0x79, 0x73, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, + 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x56, 0x6F, 0x74, 0x65, 0x4B, 0x65, 0x79, 0x44, 0x69, 0x6C, 0x75, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x6F, 0x74, 0x65, 0x6C, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, + 0x61, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, + 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x56, 0x6F, 0x74, 0x65, 0x4C, 0x61, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x76, 0x72, 0x66, 0x70, 0x6B, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x52, 0x46, 0x50, 0x4B, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x75, + 0x62, 0x6C, 0x69, 0x63, 0x20, 0x6B, 0x65, 0x79, 0x20, 0x28, 0x69, 0x66, 0x20, 0x61, 0x6E, 0x79, + 0x29, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, + 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x52, 0x46, 0x50, 0x4B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, - 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x6C, 0x6C, 0x20, 0x66, 0x69, - 0x65, 0x6C, 0x64, 0x73, 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x20, 0x61, - 0x6C, 0x6C, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, - 0x61, 0x6E, 0x64, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6E, - 0x20, 0x65, 0x6E, 0x76, 0x65, 0x6C, 0x6F, 0x70, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x61, 0x6C, 0x6C, - 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x5C, 0x6E, 0x74, - 0x79, 0x70, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, - 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2D, 0x72, 0x6F, - 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, - 0x61, 0x73, 0x74, 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x63, 0x65, 0x72, 0x74, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, - 0x69, 0x73, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, 0x68, 0x62, 0x36, 0x34, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, - 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, - 0x2F, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x61, 0x6C, 0x6C, - 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x63, 0x65, 0x72, 0x74, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x63, 0x74, 0x43, 0x65, 0x72, - 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x63, 0x66, 0x67, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, - 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x6F, 0x6E, 0x66, 0x69, 0x67, 0x54, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x63, 0x75, 0x72, 0x66, 0x72, 0x7A, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, - 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, - 0x73, 0x65, 0x74, 0x46, 0x72, 0x65, 0x65, 0x7A, 0x65, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, - 0x72, 0x78, 0x66, 0x65, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, - 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, - 0x54, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x65, 0x72, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, - 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, - 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, - 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, - 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2D, 0x72, 0x6F, - 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, - 0x20, 0x22, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, - 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, - 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, - 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, - 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, - 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, + 0x61, 0x79, 0x6D, 0x65, 0x6E, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x50, 0x61, 0x79, 0x6D, 0x65, 0x6E, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x66, + 0x69, 0x65, 0x6C, 0x64, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x70, 0x61, 0x79, 0x6D, + 0x65, 0x6E, 0x74, 0x20, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, + 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x72, 0x6F, 0x6D, 0x20, 0x69, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6E, 0x64, 0x65, 0x72, 0x27, 0x73, 0x20, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, - 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x72, 0x6F, - 0x6D, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x72, 0x65, 0x77, 0x61, 0x72, - 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x46, 0x72, 0x6F, 0x6D, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x69, 0x73, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x70, 0x65, - 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x61, 0x70, - 0x70, 0x6C, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, 0x46, 0x72, 0x6F, - 0x6D, 0x5C, 0x6E, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x61, 0x73, 0x20, 0x70, 0x61, - 0x72, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x6D, 0x6F, 0x75, 0x6E, 0x74, + 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, + 0x66, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x69, 0x6E, 0x74, + 0x65, 0x6E, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x62, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, + 0x73, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x72, 0x6F, - 0x6D, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, - 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x20, 0x49, 0x44, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, - 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, - 0x68, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x6D, 0x6F, + 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, + 0x73, 0x65, 0x52, 0x65, 0x6D, 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x54, 0x6F, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x73, 0x65, 0x6E, 0x64, 0x65, 0x72, 0x20, 0x63, 0x6C, 0x6F, 0x73, 0x65, 0x64, 0x20, 0x74, + 0x6F, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, + 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x54, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, + 0x6F, 0x73, 0x65, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x41, 0x6D, 0x6F, + 0x75, 0x6E, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, + 0x74, 0x20, 0x73, 0x65, 0x6E, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, + 0x65, 0x6D, 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x54, 0x6F, 0x2C, 0x20, 0x66, 0x6F, 0x72, 0x20, + 0x63, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, + 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, + 0x41, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6C, 0x6F, 0x73, + 0x65, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, + 0x74, 0x20, 0x6F, 0x66, 0x20, 0x70, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, + 0x61, 0x72, 0x64, 0x73, 0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x6D, 0x61, 0x69, 0x6E, 0x64, + 0x65, 0x72, 0x54, 0x6F, 0x5C, 0x6E, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x61, 0x73, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, + 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x43, 0x6C, 0x6F, 0x73, 0x65, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x6F, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, + 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x72, 0x27, 0x73, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x67, 0x72, 0x6F, 0x75, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x72, 0x6F, 0x75, 0x70, 0x22, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, - 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, - 0x72, 0x6F, 0x75, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6B, 0x65, 0x79, 0x72, 0x65, 0x67, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x4B, 0x65, 0x79, 0x72, 0x65, 0x67, 0x54, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x6C, 0x61, 0x73, 0x74, 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, - 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x72, 0x6F, 0x75, - 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, - 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, - 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, - 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x65, 0x61, 0x73, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x4C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x65, 0x6E, 0x66, 0x6F, 0x72, 0x63, 0x65, 0x73, 0x20, 0x6D, - 0x75, 0x74, 0x75, 0x61, 0x6C, 0x20, 0x65, 0x78, 0x63, 0x6C, 0x75, 0x73, 0x69, 0x6F, 0x6E, 0x20, - 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2E, - 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x20, - 0x69, 0x73, 0x5C, 0x6E, 0x6E, 0x6F, 0x6E, 0x7A, 0x65, 0x72, 0x6F, 0x2C, 0x20, 0x74, 0x68, 0x65, - 0x6E, 0x20, 0x6F, 0x6E, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, - 0x6D, 0x65, 0x64, 0x2C, 0x20, 0x69, 0x74, 0x20, 0x61, 0x63, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6E, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x28, 0x53, - 0x65, 0x6E, 0x64, 0x65, 0x72, 0x2C, 0x20, 0x4C, 0x65, 0x61, 0x73, 0x65, 0x29, 0x20, 0x70, 0x61, - 0x69, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x75, 0x6E, 0x74, 0x69, 0x6C, 0x5C, 0x6E, 0x74, 0x68, 0x65, - 0x20, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, - 0x20, 0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x2E, 0x20, 0x20, 0x57, 0x68, 0x69, 0x6C, 0x65, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x70, 0x6F, 0x73, 0x73, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, - 0x6C, 0x65, 0x61, 0x73, 0x65, 0x2C, 0x20, 0x6E, 0x6F, 0x20, 0x6F, 0x74, 0x68, 0x65, 0x72, 0x20, - 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, 0x70, 0x65, 0x63, - 0x69, 0x66, 0x79, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6C, 0x65, 0x61, 0x73, - 0x65, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, - 0x65, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x65, 0x61, 0x73, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x6E, 0x6F, 0x74, 0x65, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, 0x74, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, - 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x20, 0x64, 0x61, 0x74, 0x61, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x4E, 0x6F, 0x74, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x61, 0x79, 0x6D, - 0x65, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, - 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x61, 0x79, 0x6D, 0x65, 0x6E, 0x74, - 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x6F, 0x6F, 0x6C, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x6F, 0x6F, - 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, - 0x20, 0x77, 0x61, 0x73, 0x20, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6F, - 0x6D, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, 0x20, 0x74, 0x72, - 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5C, 0x6E, 0x70, 0x6F, 0x6F, 0x6C, 0x20, - 0x28, 0x69, 0x66, 0x20, 0x6E, 0x6F, 0x6E, 0x2D, 0x65, 0x6D, 0x70, 0x74, 0x79, 0x29, 0x2E, 0x20, - 0x20, 0x41, 0x20, 0x6E, 0x6F, 0x6E, 0x2D, 0x65, 0x6D, 0x70, 0x74, 0x79, 0x20, 0x50, 0x6F, 0x6F, - 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x20, 0x64, 0x6F, 0x65, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, - 0x67, 0x75, 0x61, 0x72, 0x61, 0x6E, 0x74, 0x65, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, - 0x68, 0x65, 0x5C, 0x6E, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, - 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x6E, 0x65, 0x76, 0x65, 0x72, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6F, - 0x6D, 0x6D, 0x69, 0x74, 0x74, 0x65, 0x64, 0x3B, 0x20, 0x6F, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6E, - 0x6F, 0x64, 0x65, 0x73, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x68, 0x61, 0x76, - 0x65, 0x20, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x74, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x6E, 0x64, 0x20, 0x6D, - 0x61, 0x79, 0x20, 0x61, 0x74, 0x74, 0x65, 0x6D, 0x70, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6F, - 0x6D, 0x6D, 0x69, 0x74, 0x20, 0x69, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, - 0x75, 0x74, 0x75, 0x72, 0x65, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, - 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x6F, 0x6F, - 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6F, 0x75, 0x6E, - 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, - 0x43, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, - 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6C, 0x6F, - 0x63, 0x6B, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, - 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, - 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, - 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6E, 0x66, - 0x69, 0x72, 0x6D, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x78, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, - 0x22, 0x54, 0x78, 0x49, 0x44, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x54, 0x78, 0x49, 0x44, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x72, 0x65, - 0x73, 0x75, 0x6C, 0x74, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, - 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, + 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x79, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, - 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, - 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, - 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, - 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, - 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x3A, 0x20, - 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x66, 0x65, - 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, - 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x5C, 0x6E, - 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6E, 0x20, 0x75, 0x6E, 0x69, 0x74, 0x73, 0x20, - 0x6F, 0x66, 0x20, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x2D, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x70, - 0x65, 0x72, 0x20, 0x62, 0x79, 0x74, 0x65, 0x2E, 0x5C, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x6D, 0x61, - 0x79, 0x20, 0x66, 0x61, 0x6C, 0x6C, 0x20, 0x74, 0x6F, 0x20, 0x7A, 0x65, 0x72, 0x6F, 0x20, 0x62, - 0x75, 0x74, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, - 0x6D, 0x75, 0x73, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6C, 0x6C, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, - 0x61, 0x20, 0x66, 0x65, 0x65, 0x20, 0x6F, 0x66, 0x5C, 0x6E, 0x61, 0x74, 0x20, 0x6C, 0x65, 0x61, - 0x73, 0x74, 0x20, 0x4D, 0x69, 0x6E, 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x66, 0x6F, 0x72, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6E, 0x65, 0x74, - 0x77, 0x6F, 0x72, 0x6B, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, - 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, + 0x20, 0x70, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, + 0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x54, 0x6F, 0x20, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x5C, 0x6E, 0x61, 0x73, 0x20, 0x70, + 0x61, 0x72, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, + 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, + 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, - 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, - 0x73, 0x74, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x6C, 0x69, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, + 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, + 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6F, 0x74, 0x65, 0x6E, 0x74, 0x69, 0x61, + 0x6C, 0x6C, 0x79, 0x20, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, - 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, - 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, - 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, - 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x6E, 0x73, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x69, 0x6E, 0x20, + 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x6F, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, + 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x54, 0x78, + 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, + 0x74, 0x61, 0x6C, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, + 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, + 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, + 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x54, 0x78, 0x6E, + 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, + 0x54, 0x78, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, - 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, - 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, - 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, - 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, - 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, 0x65, 0x6C, 0x70, 0x20, 0x61, 0x20, 0x63, 0x6C, 0x69, 0x65, - 0x6E, 0x74, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x5C, 0x6E, 0x61, 0x20, - 0x6E, 0x65, 0x77, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, + 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, + 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, + 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x54, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, + 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x20, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x20, 0x70, 0x72, 0x6F, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, + 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x70, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x70, 0x6D, 0x73, 0x67, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x70, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x73, 0x67, + 0x70, 0x61, 0x63, 0x6B, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x70, 0x72, 0x6F, 0x6F, 0x66, + 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, + 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x73, 0x70, 0x6D, 0x73, 0x67, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6F, + 0x6F, 0x66, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6D, 0x73, 0x67, 0x70, 0x61, 0x63, 0x6B, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, + 0x67, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x70, + 0x72, 0x6F, 0x6F, 0x66, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, + 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, + 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, + 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, + 0x3A, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x20, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, + 0x65, 0x6D, 0x61, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6E, 0x74, 0x73, 0x20, 0x61, 0x20, 0x4C, 0x6F, 0x63, 0x61, 0x6C, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x20, 0x6F, 0x72, 0x20, 0x47, 0x6C, 0x6F, 0x62, 0x61, + 0x6C, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x2E, 0x20, 0x54, 0x68, + 0x65, 0x73, 0x65, 0x5C, 0x6E, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x73, 0x20, 0x64, 0x65, 0x74, + 0x65, 0x72, 0x6D, 0x69, 0x6E, 0x65, 0x20, 0x68, 0x6F, 0x77, 0x20, 0x6D, 0x75, 0x63, 0x68, 0x20, + 0x73, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x75, + 0x73, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x61, 0x20, 0x4C, 0x6F, 0x63, 0x61, 0x6C, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x20, 0x6F, 0x72, 0x5C, 0x6E, 0x47, 0x6C, 0x6F, 0x62, 0x61, 0x6C, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6D, 0x6F, 0x72, 0x65, + 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x2C, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x72, 0x20, 0x6D, 0x69, 0x6E, 0x69, 0x6D, 0x75, 0x6D, 0x5C, + 0x6E, 0x62, 0x61, 0x6C, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, + 0x20, 0x6D, 0x61, 0x69, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x68, 0x6F, 0x6C, 0x64, 0x69, + 0x6E, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2E, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, + 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, + 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x6C, 0x69, 0x63, 0x65, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x62, 0x79, 0x74, 0x65, 0x73, 0x6C, 0x69, 0x63, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x42, 0x79, 0x74, + 0x65, 0x53, 0x6C, 0x69, 0x63, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x61, + 0x78, 0x69, 0x6D, 0x75, 0x6D, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, + 0x54, 0x45, 0x41, 0x4C, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x6C, 0x69, 0x63, 0x65, 0x73, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, 0x65, 0x5C, 0x6E, 0x73, 0x74, + 0x6F, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6B, 0x65, 0x79, 0x2F, + 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, + 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x4E, 0x75, 0x6D, 0x42, 0x79, 0x74, 0x65, 0x53, 0x6C, 0x69, 0x63, 0x65, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x55, 0x69, 0x6E, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6D, 0x61, 0x78, 0x69, 0x6D, 0x75, 0x6D, 0x20, 0x6E, 0x75, + 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x54, 0x45, 0x41, 0x4C, 0x20, 0x75, 0x69, 0x6E, + 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x73, + 0x74, 0x6F, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x5C, 0x6E, 0x74, 0x68, 0x65, 0x20, 0x6B, 0x65, + 0x79, 0x2F, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, + 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x4E, 0x75, 0x6D, 0x55, 0x69, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, + 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, + 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, + 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, + 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x75, 0x70, 0x70, + 0x6C, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x75, 0x70, + 0x70, 0x6C, 0x79, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6C, + 0x79, 0x20, 0x6F, 0x66, 0x20, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, + 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, + 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, 0x79, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6F, 0x6E, 0x6C, 0x69, + 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, + 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x6F, 0x6E, 0x6C, 0x69, 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4F, 0x6E, 0x6C, 0x69, 0x6E, + 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, + 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, + 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, + 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4F, 0x6E, 0x6C, 0x69, + 0x6E, 0x65, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x6F, 0x75, + 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, + 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, + 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, + 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x52, 0x6F, 0x75, 0x6E, + 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, + 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, + 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x54, 0x6F, 0x74, 0x61, 0x6C, 0x4D, 0x6F, 0x6E, 0x65, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, + 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, + 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, + 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, + 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x63, 0x6F, + 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x6C, 0x6C, 0x20, 0x66, 0x69, 0x65, 0x6C, 0x64, + 0x73, 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x20, 0x61, 0x6C, 0x6C, 0x20, + 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x61, 0x6E, 0x64, + 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6E, 0x20, 0x65, 0x6E, + 0x76, 0x65, 0x6C, 0x6F, 0x70, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x61, 0x6C, 0x6C, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x5C, 0x6E, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, - 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, 0x68, 0x62, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6E, 0x73, 0x65, - 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, - 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, + 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x61, 0x70, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x73, 0x70, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, + 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, 0x68, 0x62, + 0x36, 0x34, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x70, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, + 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, + 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x70, 0x70, 0x6C, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, + 0x43, 0x61, 0x6C, 0x6C, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, + 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x63, 0x66, 0x67, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, + 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, + 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, 0x43, 0x6F, 0x6E, 0x66, 0x69, 0x67, + 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x75, 0x72, 0x66, 0x72, 0x7A, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, 0x46, 0x72, 0x65, 0x65, 0x7A, 0x65, 0x54, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x63, 0x75, 0x72, 0x78, 0x66, 0x65, 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, + 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, + 0x73, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x66, 0x65, 0x72, 0x54, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, + 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, + 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, + 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, + 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x69, 0x72, 0x73, 0x74, 0x52, + 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x72, 0x6F, + 0x6D, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6E, 0x64, 0x65, 0x72, 0x27, + 0x73, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x46, 0x72, 0x6F, 0x6D, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x72, 0x6F, 0x6D, 0x72, 0x65, + 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x70, 0x72, 0x6F, - 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x5C, 0x6E, 0x61, - 0x73, 0x20, 0x6F, 0x66, 0x20, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x2E, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, - 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, - 0x65, 0x64, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, - 0x65, 0x65, 0x5C, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6E, 0x20, 0x75, 0x6E, - 0x69, 0x74, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x2D, 0x41, 0x6C, 0x67, - 0x6F, 0x73, 0x20, 0x70, 0x65, 0x72, 0x20, 0x62, 0x79, 0x74, 0x65, 0x2E, 0x5C, 0x6E, 0x46, 0x65, - 0x65, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x66, 0x61, 0x6C, 0x6C, 0x20, 0x74, 0x6F, 0x20, 0x7A, 0x65, - 0x72, 0x6F, 0x20, 0x62, 0x75, 0x74, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x73, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6C, 0x6C, 0x20, 0x68, - 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x66, 0x65, 0x65, 0x20, 0x6F, 0x66, 0x5C, 0x6E, 0x61, 0x74, - 0x20, 0x6C, 0x65, 0x61, 0x73, 0x74, 0x20, 0x4D, 0x69, 0x6E, 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, - 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, - 0x20, 0x6E, 0x65, 0x74, 0x77, 0x6F, 0x72, 0x6B, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, - 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x22, 0x3A, 0x20, 0x22, 0x46, 0x72, 0x6F, 0x6D, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x6D, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x6F, 0x66, + 0x20, 0x70, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, + 0x20, 0x61, 0x70, 0x70, 0x6C, 0x69, 0x65, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x46, 0x72, 0x6F, 0x6D, 0x5C, 0x6E, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x61, 0x73, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, + 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x46, 0x72, 0x6F, 0x6D, 0x52, 0x65, 0x77, 0x61, 0x72, 0x64, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, @@ -3850,118 +3498,295 @@ func init() { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, - 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, - 0x20, 0x73, 0x65, 0x65, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, - 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, - 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, - 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6D, 0x69, 0x6E, 0x46, 0x65, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, - 0x68, 0x65, 0x20, 0x6D, 0x69, 0x6E, 0x69, 0x6D, 0x75, 0x6D, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x20, 0x28, 0x6E, 0x6F, 0x74, 0x20, - 0x70, 0x65, 0x72, 0x20, 0x62, 0x79, 0x74, 0x65, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x74, 0x78, 0x6E, 0x20, - 0x74, 0x6F, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x61, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6E, 0x65, 0x74, 0x77, - 0x6F, 0x72, 0x6B, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, - 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x4D, 0x69, 0x6E, 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, - 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, - 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, - 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, - 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, - 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, - 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x20, 0x63, 0x6F, - 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, - 0x6F, 0x6E, 0x20, 0x61, 0x62, 0x6F, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x64, - 0x65, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x74, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x72, 0x6F, 0x75, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x72, 0x6F, 0x75, 0x70, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, + 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x47, 0x72, 0x6F, 0x75, 0x70, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6B, 0x65, 0x79, + 0x72, 0x65, 0x67, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, + 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x4B, 0x65, 0x79, 0x72, 0x65, 0x67, 0x54, + 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6C, 0x61, 0x73, 0x74, 0x2D, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, + 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x61, 0x73, 0x74, 0x20, 0x76, 0x61, 0x6C, 0x69, 0x64, 0x20, + 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, + 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6C, + 0x65, 0x61, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x4C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x65, 0x6E, 0x66, 0x6F, 0x72, 0x63, 0x65, + 0x73, 0x20, 0x6D, 0x75, 0x74, 0x75, 0x61, 0x6C, 0x20, 0x65, 0x78, 0x63, 0x6C, 0x75, 0x73, 0x69, + 0x6F, 0x6E, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x2E, 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, + 0x6C, 0x64, 0x20, 0x69, 0x73, 0x5C, 0x6E, 0x6E, 0x6F, 0x6E, 0x7A, 0x65, 0x72, 0x6F, 0x2C, 0x20, + 0x74, 0x68, 0x65, 0x6E, 0x20, 0x6F, 0x6E, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6F, 0x6E, + 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x2C, 0x20, 0x69, 0x74, 0x20, 0x61, 0x63, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, + 0x64, 0x65, 0x6E, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x28, 0x53, 0x65, 0x6E, 0x64, 0x65, 0x72, 0x2C, 0x20, 0x4C, 0x65, 0x61, 0x73, 0x65, 0x29, + 0x20, 0x70, 0x61, 0x69, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x75, 0x6E, 0x74, 0x69, 0x6C, 0x5C, 0x6E, + 0x74, 0x68, 0x65, 0x20, 0x4C, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6C, 0x69, 0x64, 0x20, 0x72, 0x6F, + 0x75, 0x6E, 0x64, 0x20, 0x70, 0x61, 0x73, 0x73, 0x65, 0x73, 0x2E, 0x20, 0x20, 0x57, 0x68, 0x69, + 0x6C, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x20, 0x70, 0x6F, 0x73, 0x73, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x5C, 0x6E, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x2C, 0x20, 0x6E, 0x6F, 0x20, 0x6F, 0x74, 0x68, + 0x65, 0x72, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x73, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x79, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6C, + 0x65, 0x61, 0x73, 0x65, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x66, + 0x69, 0x72, 0x6D, 0x65, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, + 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x65, 0x61, 0x73, 0x65, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6E, 0x6F, 0x74, 0x65, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, 0x74, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x61, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x6D, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, + 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, 0x74, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, + 0x61, 0x79, 0x6D, 0x65, 0x6E, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, + 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x61, 0x79, 0x6D, + 0x65, 0x6E, 0x74, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x54, 0x79, + 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x6F, 0x6F, 0x6C, 0x65, 0x72, 0x72, 0x6F, + 0x72, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x50, 0x6F, 0x6F, 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x20, 0x77, 0x61, 0x73, 0x20, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x20, + 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, + 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5C, 0x6E, 0x70, 0x6F, + 0x6F, 0x6C, 0x20, 0x28, 0x69, 0x66, 0x20, 0x6E, 0x6F, 0x6E, 0x2D, 0x65, 0x6D, 0x70, 0x74, 0x79, + 0x29, 0x2E, 0x20, 0x20, 0x41, 0x20, 0x6E, 0x6F, 0x6E, 0x2D, 0x65, 0x6D, 0x70, 0x74, 0x79, 0x20, + 0x50, 0x6F, 0x6F, 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x20, 0x64, 0x6F, 0x65, 0x73, 0x20, 0x6E, + 0x6F, 0x74, 0x20, 0x67, 0x75, 0x61, 0x72, 0x61, 0x6E, 0x74, 0x65, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x6E, 0x65, 0x76, 0x65, 0x72, 0x20, 0x62, 0x65, + 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x74, 0x65, 0x64, 0x3B, 0x20, 0x6F, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x73, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x6E, 0x6F, 0x74, 0x20, + 0x68, 0x61, 0x76, 0x65, 0x20, 0x65, 0x76, 0x69, 0x63, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, + 0x5C, 0x6E, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x6E, + 0x64, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x61, 0x74, 0x74, 0x65, 0x6D, 0x70, 0x74, 0x20, 0x74, 0x6F, + 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x69, 0x74, 0x20, 0x69, 0x74, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x50, 0x6F, 0x6F, 0x6C, 0x45, 0x72, 0x72, 0x6F, 0x72, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, + 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x43, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, + 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x70, + 0x70, 0x65, 0x61, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, + 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, + 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, + 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x65, 0x64, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x73, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, + 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6F, 0x6F, 0x66, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, 0x44, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x20, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, + 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, + 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, 0x44, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x72, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, + 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, + 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x54, 0x79, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x54, 0x79, 0x70, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, + 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, + 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, + 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, + 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x75, + 0x67, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, + 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, + 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x5C, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x69, 0x6E, 0x20, 0x75, 0x6E, 0x69, 0x74, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x6D, 0x69, 0x63, + 0x72, 0x6F, 0x2D, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x70, 0x65, 0x72, 0x20, 0x62, 0x79, 0x74, + 0x65, 0x2E, 0x5C, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x6D, 0x61, 0x79, 0x20, 0x66, 0x61, 0x6C, 0x6C, + 0x20, 0x74, 0x6F, 0x20, 0x7A, 0x65, 0x72, 0x6F, 0x20, 0x62, 0x75, 0x74, 0x20, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x6D, 0x75, 0x73, 0x74, 0x20, 0x73, + 0x74, 0x69, 0x6C, 0x6C, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x66, 0x65, 0x65, 0x20, + 0x6F, 0x66, 0x5C, 0x6E, 0x61, 0x74, 0x20, 0x6C, 0x65, 0x61, 0x73, 0x74, 0x20, 0x4D, 0x69, 0x6E, + 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6E, 0x65, 0x74, 0x77, 0x6F, 0x72, 0x6B, 0x20, 0x70, + 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, + 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, + 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, + 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, + 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, + 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, + 0x73, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x20, 0x63, 0x6F, 0x6E, + 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, + 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, + 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x4C, 0x69, 0x73, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, + 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, + 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, + 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, + 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, + 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, + 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, + 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, + 0x61, 0x72, 0x61, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, + 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x68, + 0x65, 0x6C, 0x70, 0x20, 0x61, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x63, 0x6F, 0x6E, + 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x5C, 0x6E, 0x61, 0x20, 0x6E, 0x65, 0x77, 0x20, 0x74, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, - 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, - 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x61, 0x70, 0x70, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x70, 0x70, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x69, 0x6E, 0x64, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x70, 0x70, 0x20, 0x69, - 0x6E, 0x64, 0x65, 0x78, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x70, 0x70, 0x20, 0x63, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, - 0x78, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, - 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, - 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x70, 0x70, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x61, 0x73, 0x73, 0x65, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, + 0x68, 0x62, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, + 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, + 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x6F, + 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x69, + 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6F, 0x6E, + 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x20, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x5C, 0x6E, 0x61, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x4C, + 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x43, 0x6F, 0x6E, 0x73, 0x65, 0x6E, 0x73, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, - 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, 0x69, 0x6E, - 0x64, 0x65, 0x78, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, - 0x74, 0x78, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, - 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, - 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, - 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, - 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, - 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, - 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x65, 0x20, 0x61, 0x6E, 0x6E, 0x6F, 0x74, - 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x6D, 0x6F, - 0x64, 0x65, 0x6C, 0x20, 0x73, 0x6F, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6C, 0x65, 0x67, 0x61, - 0x63, 0x79, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x73, 0x5C, 0x6E, 0x63, 0x61, 0x6E, 0x20, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6C, 0x79, 0x20, 0x69, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x20, - 0x61, 0x20, 0x73, 0x77, 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x64, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x6D, 0x6F, 0x64, 0x65, - 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x20, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, - 0x73, 0x69, 0x73, 0x5F, 0x69, 0x64, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x5F, 0x68, 0x61, 0x73, 0x68, 0x5F, 0x62, - 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, - 0x69, 0x6C, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, - 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x69, - 0x6C, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, - 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, - 0x73, 0x5F, 0x68, 0x61, 0x73, 0x68, 0x5F, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x46, 0x65, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x66, 0x65, 0x65, 0x5C, 0x6E, 0x46, 0x65, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6E, 0x20, 0x75, 0x6E, 0x69, 0x74, 0x73, 0x20, 0x6F, 0x66, + 0x20, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x2D, 0x41, 0x6C, 0x67, 0x6F, 0x73, 0x20, 0x70, 0x65, 0x72, + 0x20, 0x62, 0x79, 0x74, 0x65, 0x2E, 0x5C, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x6D, 0x61, 0x79, 0x20, + 0x66, 0x61, 0x6C, 0x6C, 0x20, 0x74, 0x6F, 0x20, 0x7A, 0x65, 0x72, 0x6F, 0x20, 0x62, 0x75, 0x74, + 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x6D, 0x75, + 0x73, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6C, 0x6C, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, + 0x66, 0x65, 0x65, 0x20, 0x6F, 0x66, 0x5C, 0x6E, 0x61, 0x74, 0x20, 0x6C, 0x65, 0x61, 0x73, 0x74, + 0x20, 0x4D, 0x69, 0x6E, 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6E, 0x65, 0x74, 0x77, 0x6F, + 0x72, 0x6B, 0x20, 0x70, 0x72, 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, + 0x69, 0x73, 0x49, 0x44, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x20, 0x49, 0x44, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, + 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x68, 0x61, 0x73, 0x68, 0x62, 0x36, + 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, @@ -3969,243 +3794,362 @@ func init() { 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x5F, 0x69, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, - 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, - 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, - 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, - 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, - 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x49, 0x44, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x49, 0x64, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, - 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x78, 0x49, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x6C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, + 0x64, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6C, 0x61, 0x73, 0x74, 0x20, 0x72, 0x6F, 0x75, 0x6E, 0x64, 0x20, 0x73, 0x65, 0x65, 0x6E, 0x22, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, + 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, + 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4C, 0x61, 0x73, 0x74, 0x52, 0x6F, 0x75, 0x6E, 0x64, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x6D, 0x69, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, 0x64, 0x20, 0x69, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x65, 0x6E, 0x63, 0x6F, - 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6D, 0x69, 0x6E, + 0x69, 0x6D, 0x75, 0x6D, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x20, 0x66, 0x65, 0x65, 0x20, 0x28, 0x6E, 0x6F, 0x74, 0x20, 0x70, 0x65, 0x72, 0x20, 0x62, 0x79, + 0x74, 0x65, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x6F, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x5C, 0x6E, 0x74, 0x78, 0x6E, 0x20, 0x74, 0x6F, 0x20, 0x76, 0x61, 0x6C, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x20, 0x66, 0x6F, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, + 0x72, 0x72, 0x65, 0x6E, 0x74, 0x20, 0x6E, 0x65, 0x74, 0x77, 0x6F, 0x72, 0x6B, 0x20, 0x70, 0x72, + 0x6F, 0x74, 0x6F, 0x63, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, + 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, + 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x4D, 0x69, 0x6E, + 0x54, 0x78, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, + 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, + 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, + 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x52, 0x65, 0x73, 0x75, 0x6C, 0x74, 0x73, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, + 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x61, 0x62, 0x6F, + 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x64, 0x65, 0x20, 0x65, 0x66, 0x66, 0x65, + 0x63, 0x74, 0x73, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x61, 0x70, 0x70, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x70, + 0x70, 0x49, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x70, 0x70, 0x20, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x6F, + 0x66, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x70, 0x70, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x78, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, - 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, + 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, + 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x70, 0x70, 0x49, 0x6E, 0x64, 0x65, + 0x78, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x61, 0x73, + 0x73, 0x65, 0x74, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, + 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, + 0x64, 0x65, 0x78, 0x20, 0x69, 0x6E, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x20, 0x6F, 0x66, + 0x20, 0x61, 0x6E, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x78, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, + 0x20, 0x22, 0x75, 0x69, 0x6E, 0x74, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, - 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, 0x44, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, - 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, - 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, - 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, - 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, - 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x72, - 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x41, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, - 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x49, 0x6E, - 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x63, - 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, - 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, - 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, - 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, 0x69, 0x6E, 0x66, - 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x3A, 0x20, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x73, 0x73, 0x65, 0x74, 0x49, + 0x6E, 0x64, 0x65, 0x78, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, + 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, + 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, + 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x4E, 0x6F, 0x74, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x77, 0x65, 0x20, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x20, 0x73, 0x6F, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6C, 0x65, 0x67, 0x61, 0x63, 0x79, 0x20, 0x63, 0x6C, 0x69, + 0x65, 0x6E, 0x74, 0x73, 0x5C, 0x6E, 0x63, 0x61, 0x6E, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, + 0x6C, 0x79, 0x20, 0x69, 0x6D, 0x70, 0x6F, 0x72, 0x74, 0x20, 0x61, 0x20, 0x73, 0x77, 0x61, 0x67, + 0x67, 0x65, 0x72, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x6D, 0x6F, 0x64, 0x65, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, + 0x6A, 0x65, 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x69, + 0x74, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x20, 0x63, + 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6E, 0x74, 0x20, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x5F, 0x69, 0x64, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, + 0x73, 0x69, 0x73, 0x5F, 0x68, 0x61, 0x73, 0x68, 0x5F, 0x62, 0x36, 0x34, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x22, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, + 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x62, 0x75, 0x69, 0x6C, 0x64, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, + 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, + 0x73, 0x2F, 0x42, 0x75, 0x69, 0x6C, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, 0x5F, 0x68, 0x61, 0x73, 0x68, + 0x5F, 0x62, 0x36, 0x34, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, 0x6F, + 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x73, 0x69, 0x73, + 0x48, 0x61, 0x73, 0x68, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x67, 0x65, 0x6E, 0x65, 0x73, 0x69, + 0x73, 0x5F, 0x69, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, + 0x65, 0x73, 0x69, 0x73, 0x49, 0x44, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x72, 0x72, 0x61, + 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, + 0x74, 0x65, 0x6D, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, + 0x72, 0x69, 0x6E, 0x67, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, + 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, + 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, + 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x64, + 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, 0x69, 0x2F, + 0x73, 0x70, 0x65, 0x63, 0x2F, 0x63, 0x6F, 0x6D, 0x6D, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x20, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x6F, 0x62, 0x6A, 0x65, + 0x63, 0x74, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x72, 0x65, 0x64, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x74, 0x78, 0x49, 0x64, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5D, 0x2C, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6F, 0x70, 0x65, 0x72, 0x74, 0x69, + 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x74, 0x78, 0x49, 0x64, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, 0x64, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x74, 0x72, 0x69, 0x6E, 0x67, 0x20, 0x65, 0x6E, 0x63, 0x6F, 0x64, 0x69, 0x6E, 0x67, 0x20, 0x6F, + 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x68, 0x61, 0x73, 0x68, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, + 0x6E, 0x67, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x78, 0x49, + 0x44, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x67, + 0x6F, 0x2D, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x78, 0x2D, 0x67, 0x6F, 0x2D, 0x70, 0x61, 0x63, 0x6B, 0x61, 0x67, 0x65, 0x22, 0x3A, 0x20, + 0x22, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x61, 0x6C, 0x67, 0x6F, + 0x72, 0x61, 0x6E, 0x64, 0x2F, 0x67, 0x6F, 0x2D, 0x61, 0x6C, 0x67, 0x6F, 0x72, 0x61, 0x6E, 0x64, + 0x2F, 0x64, 0x61, 0x65, 0x6D, 0x6F, 0x6E, 0x2F, 0x61, 0x6C, 0x67, 0x6F, 0x64, 0x2F, 0x61, 0x70, + 0x69, 0x2F, 0x73, 0x70, 0x65, 0x63, 0x2F, 0x76, 0x31, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x63, 0x63, 0x6F, + 0x75, 0x6E, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, + 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x41, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, + 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x6E, 0x20, 0x61, 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x20, + 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, + 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, + 0x63, 0x63, 0x6F, 0x75, 0x6E, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x73, 0x73, 0x65, + 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, + 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, + 0x73, 0x73, 0x65, 0x74, 0x49, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x52, + 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, + 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, + 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, + 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, + 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, 0x50, 0x61, 0x72, 0x61, 0x6D, + 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, + 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, + 0x66, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, - 0x74, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x73, 0x73, - 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x74, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x42, 0x6C, 0x6F, 0x63, 0x6B, + 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, + 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, + 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, + 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x42, + 0x6C, 0x6F, 0x63, 0x6B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, + 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, + 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, + 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x28, 0x70, 0x6F, 0x74, 0x65, 0x6E, 0x74, 0x69, 0x61, + 0x6C, 0x6C, 0x79, 0x20, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, 0x74, 0x65, 0x64, 0x29, 0x20, 0x6C, + 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6F, 0x6E, 0x73, 0x20, 0x61, 0x6E, 0x64, 0x5C, 0x6E, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6F, 0x74, + 0x61, 0x6C, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, + 0x74, 0x6C, 0x79, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6F, 0x6F, 0x6C, 0x2E, + 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, + 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, + 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, + 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x41, 0x73, 0x73, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, + 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, + 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x4E, + 0x6F, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, + 0x75, 0x70, 0x70, 0x6C, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x72, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6C, + 0x79, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x53, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, + 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, + 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, + 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, - 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x22, 0x2C, + 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x22, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, + 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, + 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, + 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, + 0x61, 0x72, 0x61, 0x6D, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, + 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, 0x66, 0x6F, 0x72, 0x5C, 0x6E, 0x63, + 0x6F, 0x6E, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6E, 0x67, 0x20, 0x61, 0x20, 0x6E, 0x65, + 0x77, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, - 0x73, 0x2F, 0x41, 0x73, 0x73, 0x65, 0x74, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, - 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x52, 0x65, - 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, - 0x62, 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x42, 0x6C, 0x6F, 0x63, 0x6B, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, 0x67, 0x54, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x28, 0x70, 0x6F, - 0x74, 0x65, 0x6E, 0x74, 0x69, 0x61, 0x6C, 0x6C, 0x79, 0x20, 0x74, 0x72, 0x75, 0x6E, 0x63, 0x61, - 0x74, 0x65, 0x64, 0x29, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, 0x61, 0x6E, 0x64, 0x5C, 0x6E, 0x74, - 0x68, 0x65, 0x20, 0x74, 0x6F, 0x74, 0x61, 0x6C, 0x20, 0x6E, 0x75, 0x6D, 0x62, 0x65, 0x72, 0x20, - 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x20, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6E, 0x74, 0x6C, 0x79, 0x20, 0x69, 0x6E, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x70, 0x6F, 0x6F, 0x6C, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, - 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x50, 0x65, 0x6E, 0x64, 0x69, 0x6E, - 0x67, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, - 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, - 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6E, 0x6F, 0x64, 0x65, 0x27, 0x73, 0x20, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, - 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, - 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, - 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x4E, 0x6F, 0x64, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, - 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, - 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x53, 0x75, - 0x70, 0x70, 0x6C, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, - 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x65, 0x64, 0x67, 0x65, 0x72, - 0x20, 0x73, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x53, 0x75, 0x70, 0x70, 0x6C, 0x79, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, 0x65, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x46, - 0x65, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, - 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, - 0x66, 0x65, 0x65, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x46, 0x65, 0x65, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x49, 0x44, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, - 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, - 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x20, - 0x66, 0x6F, 0x72, 0x5C, 0x6E, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x6E, - 0x67, 0x20, 0x61, 0x20, 0x6E, 0x65, 0x77, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x2E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, - 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, - 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, 0x61, 0x6D, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, - 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, - 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, - 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x61, 0x6E, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, - 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, - 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, - 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, + 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x50, 0x61, 0x72, + 0x61, 0x6D, 0x73, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, - 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, - 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, - 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, - 0x74, 0x6F, 0x20, 0x27, 0x47, 0x45, 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, - 0x73, 0x27, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, - 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, - 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, - 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x65, 0x66, - 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x61, 0x70, 0x69, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, - 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x68, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x2E, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, - 0x65, 0x20, 0x67, 0x65, 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x75, 0x73, 0x69, 0x6E, - 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x6F, 0x61, 0x6C, 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x61, - 0x6E, 0x64, 0x20, 0x6C, 0x69, 0x6E, 0x65, 0x20, 0x74, 0x6F, 0x6F, 0x6C, 0x2E, 0x20, 0x45, 0x78, - 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x3D, 0x27, 0x62, 0x37, - 0x65, 0x33, 0x38, 0x34, 0x64, 0x30, 0x33, 0x31, 0x37, 0x62, 0x38, 0x30, 0x35, 0x30, 0x63, 0x65, - 0x34, 0x35, 0x39, 0x30, 0x30, 0x61, 0x39, 0x34, 0x61, 0x31, 0x39, 0x33, 0x31, 0x65, 0x32, 0x38, - 0x35, 0x34, 0x30, 0x65, 0x31, 0x66, 0x36, 0x39, 0x62, 0x32, 0x64, 0x32, 0x34, 0x32, 0x62, 0x34, - 0x32, 0x34, 0x36, 0x35, 0x39, 0x63, 0x33, 0x34, 0x31, 0x62, 0x34, 0x36, 0x39, 0x37, 0x27, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, - 0x22, 0x61, 0x70, 0x69, 0x4B, 0x65, 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x22, 0x6E, 0x61, 0x6D, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x58, 0x2D, 0x41, 0x6C, 0x67, 0x6F, 0x2D, - 0x41, 0x50, 0x49, 0x2D, 0x54, 0x6F, 0x6B, 0x65, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x69, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, - 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x78, 0x2D, 0x65, 0x78, 0x61, 0x6D, 0x70, - 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x62, 0x37, 0x65, 0x33, 0x38, 0x34, 0x64, 0x30, 0x33, 0x31, - 0x37, 0x62, 0x38, 0x30, 0x35, 0x30, 0x63, 0x65, 0x34, 0x35, 0x39, 0x30, 0x30, 0x61, 0x39, 0x34, - 0x61, 0x31, 0x39, 0x33, 0x31, 0x65, 0x32, 0x38, 0x35, 0x34, 0x30, 0x65, 0x31, 0x66, 0x36, 0x39, - 0x62, 0x32, 0x64, 0x32, 0x34, 0x32, 0x62, 0x34, 0x32, 0x34, 0x36, 0x35, 0x39, 0x63, 0x33, 0x34, - 0x31, 0x62, 0x34, 0x36, 0x39, 0x37, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, - 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x22, 0x3A, - 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x61, 0x70, 0x69, 0x5F, 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x5D, 0x0A, 0x20, 0x20, 0x20, - 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x5D, 0x0A, 0x7D, + 0x6F, 0x6E, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, + 0x69, 0x6E, 0x73, 0x20, 0x61, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, + 0x6E, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x2C, 0x0A, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, + 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, + 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, + 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, + 0x22, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x61, 0x69, 0x6E, 0x73, 0x20, 0x61, + 0x20, 0x6C, 0x69, 0x73, 0x74, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, + 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, + 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, 0x54, 0x72, 0x61, 0x6E, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x4C, 0x69, 0x73, 0x74, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x22, 0x3A, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 0x65, 0x20, 0x74, 0x6F, 0x20, 0x27, 0x47, 0x45, + 0x54, 0x20, 0x2F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x73, 0x27, 0x22, 0x2C, 0x0A, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x73, 0x63, 0x68, 0x65, 0x6D, 0x61, 0x22, 0x3A, 0x20, 0x7B, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x24, 0x72, 0x65, 0x66, 0x22, 0x3A, + 0x20, 0x22, 0x23, 0x2F, 0x64, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x2F, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E, 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7D, + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, 0x73, + 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x44, 0x65, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x6F, + 0x6E, 0x73, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x69, 0x5F, + 0x6B, 0x65, 0x79, 0x22, 0x3A, 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x22, 0x3A, 0x20, 0x22, 0x47, 0x65, + 0x6E, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x70, + 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x2E, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x74, + 0x6F, 0x6B, 0x65, 0x6E, 0x20, 0x63, 0x61, 0x6E, 0x20, 0x62, 0x65, 0x20, 0x67, 0x65, 0x6E, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x75, 0x73, 0x69, 0x6E, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x47, 0x6F, 0x61, 0x6C, 0x20, 0x63, 0x6F, 0x6D, 0x6D, 0x61, 0x6E, 0x64, 0x20, 0x6C, 0x69, 0x6E, + 0x65, 0x20, 0x74, 0x6F, 0x6F, 0x6C, 0x2E, 0x20, 0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, + 0x76, 0x61, 0x6C, 0x75, 0x65, 0x20, 0x3D, 0x27, 0x62, 0x37, 0x65, 0x33, 0x38, 0x34, 0x64, 0x30, + 0x33, 0x31, 0x37, 0x62, 0x38, 0x30, 0x35, 0x30, 0x63, 0x65, 0x34, 0x35, 0x39, 0x30, 0x30, 0x61, + 0x39, 0x34, 0x61, 0x31, 0x39, 0x33, 0x31, 0x65, 0x32, 0x38, 0x35, 0x34, 0x30, 0x65, 0x31, 0x66, + 0x36, 0x39, 0x62, 0x32, 0x64, 0x32, 0x34, 0x32, 0x62, 0x34, 0x32, 0x34, 0x36, 0x35, 0x39, 0x63, + 0x33, 0x34, 0x31, 0x62, 0x34, 0x36, 0x39, 0x37, 0x27, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3A, 0x20, 0x22, 0x61, 0x70, 0x69, 0x4B, 0x65, + 0x79, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6E, 0x61, 0x6D, 0x65, 0x22, + 0x3A, 0x20, 0x22, 0x58, 0x2D, 0x41, 0x6C, 0x67, 0x6F, 0x2D, 0x41, 0x50, 0x49, 0x2D, 0x54, 0x6F, + 0x6B, 0x65, 0x6E, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x69, 0x6E, 0x22, + 0x3A, 0x20, 0x22, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x22, 0x2C, 0x0A, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x78, 0x2D, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x22, 0x3A, 0x20, 0x22, + 0x62, 0x37, 0x65, 0x33, 0x38, 0x34, 0x64, 0x30, 0x33, 0x31, 0x37, 0x62, 0x38, 0x30, 0x35, 0x30, + 0x63, 0x65, 0x34, 0x35, 0x39, 0x30, 0x30, 0x61, 0x39, 0x34, 0x61, 0x31, 0x39, 0x33, 0x31, 0x65, + 0x32, 0x38, 0x35, 0x34, 0x30, 0x65, 0x31, 0x66, 0x36, 0x39, 0x62, 0x32, 0x64, 0x32, 0x34, 0x32, + 0x62, 0x34, 0x32, 0x34, 0x36, 0x35, 0x39, 0x63, 0x33, 0x34, 0x31, 0x62, 0x34, 0x36, 0x39, 0x37, + 0x22, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x7D, 0x2C, 0x0A, 0x20, 0x20, 0x22, + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x0A, 0x20, 0x20, 0x20, + 0x20, 0x7B, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x61, 0x70, 0x69, 0x5F, 0x6B, 0x65, + 0x79, 0x22, 0x3A, 0x20, 0x5B, 0x5D, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x7D, 0x0A, 0x20, 0x20, 0x5D, + 0x0A, 0x7D, }) } diff --git a/daemon/algod/api/server/v1/handlers/handlers.go b/daemon/algod/api/server/v1/handlers/handlers.go index c1f08ffd22..d01034fecd 100644 --- a/daemon/algod/api/server/v1/handlers/handlers.go +++ b/daemon/algod/api/server/v1/handlers/handlers.go @@ -93,8 +93,8 @@ func txEncode(tx transactions.Transaction, ad transactions.ApplyData) (v1.Transa res = assetFreezeTxEncode(tx, ad) case protocol.ApplicationCallTx: res = applicationCallTxEncode(tx, ad) - case protocol.CompactCertTx: - res = compactCertTxEncode(tx, ad) + case protocol.StateProofTx: + res = stateProofTxEncode(tx) default: return res, errors.New(errUnknownTransactionType) } @@ -351,14 +351,14 @@ func assetFreezeTxEncode(tx transactions.Transaction, ad transactions.ApplyData) } } -func compactCertTxEncode(tx transactions.Transaction, ad transactions.ApplyData) v1.Transaction { - cc := v1.CompactCertTransactionType{ - CertRound: uint64(tx.CompactCertTxnFields.CertRound), - Cert: protocol.Encode(&tx.CompactCertTxnFields.Cert), +func stateProofTxEncode(tx transactions.Transaction) v1.Transaction { + sp := v1.StateProofTransactionType{ + StateProof: protocol.Encode(&tx.StateProofTxnFields.StateProof), + StateProofMessage: protocol.Encode(&tx.Message), } return v1.Transaction{ - CompactCert: &cc, + StateProof: &sp, } } @@ -504,13 +504,6 @@ func blockEncode(b bookkeeping.Block, c agreement.Certificate) (v1.Block, error) UpgradePropose: string(b.UpgradePropose), UpgradeApprove: b.UpgradeApprove, }, - CompactCertVotersTotal: b.CompactCert[protocol.CompactCertBasic].CompactCertVotersTotal.ToUint64(), - CompactCertNextRound: uint64(b.CompactCert[protocol.CompactCertBasic].CompactCertNextRound), - } - - if !b.CompactCert[protocol.CompactCertBasic].CompactCertVoters.IsEmpty() { - voters := b.CompactCert[protocol.CompactCertBasic].CompactCertVoters - block.CompactCertVoters = voters[:] } // Transactions diff --git a/daemon/algod/api/server/v2/errors.go b/daemon/algod/api/server/v2/errors.go index 9966728dc8..f20df6f2f4 100644 --- a/daemon/algod/api/server/v2/errors.go +++ b/daemon/algod/api/server/v2/errors.go @@ -34,7 +34,7 @@ var ( errFailedToParseSourcemap = "failed to parse sourcemap" errFailedToEncodeResponse = "failed to encode response" errInternalFailure = "internal failure" - errNoTxnSpecified = "no transaction ID was specified" + errNoValidTxnSpecified = "no valid transaction ID was specified" errInvalidHashType = "invalid hash type" errTransactionNotFound = "could not find the transaction in the transaction pool or in the last 1000 confirmed rounds" errServiceShuttingDown = "operation aborted as server is shutting down" @@ -44,4 +44,5 @@ var ( errFailedToStartCatchup = "failed to start catchup : %v" errOperationNotAvailableDuringCatchup = "operation not available during catchup" errRESTPayloadZeroLength = "payload was of zero length" + errRoundGreaterThanTheLatest = "given round is greater than the latest round" ) diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index 72eb4a7b19..9367f54efd 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -311,155 +311,159 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9/XPcNrLgv4KafVX+uOGM/JVdqyr1TrGcrC6O47KUfXfP9iUYsmcGKxJgAFCaiU//", - "+xUaAAmS4Az1scpzPf9kawg0Go1Go7vR3fg8SUVRCg5cq8nh50lJJS1Ag8S/aJqKiuuEZeavDFQqWamZ", - "4JND/40oLRlfTaYTZn4tqV5PphNOC2jamP7TiYTfKyYhmxxqWcF0otI1FNQA1tvStK4hbZKVSByIIwvi", - "5HhyteMDzTIJSvWx/JnnW8J4mlcZEC0pVzQ1nxS5ZHpN9Jop4joTxongQMSS6HWrMVkyyDM185P8vQK5", - "DWbpBh+e0lWDYiJFDn08X4liwTh4rKBGql4QogXJYImN1lQTM4LB1TfUgiigMl2TpZB7ULVIhPgCr4rJ", - "4YeJAp6BxNVKgV3gf5cS4A9INJUr0JNP09jklhpkolkRmdqJo74EVeVaEWyLc1yxC+DE9JqRnyqlyQII", - "5eT996/Is2fPXpqJFFRryByTDc6qGT2ck+0+OZxkVIP/3Oc1mq+EpDxL6vbvv3+F45+6CY5tRZWC+GY5", - "Ml/IyfHQBHzHCAsxrmGF69DiftMjsimanxewFBJGroltfKeLEo7/p65KSnW6LgXjOrIuBL8S+zkqw4Lu", - "u2RYjUCrfWkoJQ3QDwfJy0+fn0yfHFz95cNR8p/uzxfPrkZO/1UNdw8Fog3TSkrg6TZZSaC4W9aU9+nx", - "3vGDWosqz8iaXuDi0wJFvetLTF8rOi9oXhk+YakUR/lKKEIdG2WwpFWuiR+YVDw3YspAc9xOmCKlFBcs", - "g2xqpO/lmqVrklJlQWA7csny3PBgpSAb4rX47HZspquQJAavG9EDJ/RflxjNvPZQAjYoDZI0FwoSLfYc", - "T/7EoTwj4YHSnFXqeocVOVsDwcHNB3vYIu244ek83xKN65oRqggl/miaErYkW1GRS1ycnJ1jfzcbQ7WC", - "GKLh4rTOUbN5h8jXI0aEeAshcqAcief3XZ9kfMlWlQRFLteg1+7Mk6BKwRUQsfgnpNos+/86/fktEZL8", - "BErRFbyj6TkBnopseI3doLET/J9KmAUv1Kqk6Xn8uM5ZwSIo/0Q3rKgKwqtiAdKslz8ftCASdCX5EEIW", - "4h4+K+imP+iZrHiKi9sM21LUDCsxVeZ0OyMnS1LQzbcHU4eOIjTPSQk8Y3xF9IYPKmlm7P3oJVJUPBuh", - "w2izYMGpqUpI2ZJBRmooOzBxw+zDh/Hr4dNoVgE6HsggOvUoe9DhsInwjNm65gsp6QoClpmRX5zkwq9a", - "nAOvBRxZbPFTKeGCiUrVnQZwxKF3q9dcaEhKCUsW4bFTRw4jPWwbJ14Lp+CkgmvKOGRG8iLSQoOVRIM4", - "BQPuNmb6R/SCKvjm+dAB3nwdufpL0V31nSs+arWxUWK3ZORcNF/dho2rTa3+I4y/cGzFVon9ubeQbHVm", - "jpIly/GY+adZP0+GSqEQaBHCHzyKrTjVlYTDj/yx+Ysk5FRTnlGZmV8K+9NPVa7ZKVuZn3L70xuxYukp", - "Ww0Qs8Y1ak1ht8L+Y+DFxbHeRI2GN0KcV2U4obRllS625OR4aJEtzOsy5lFtyoZWxdnGWxrX7aE39UIO", - "IDlIu5KahuewlWCwpekS/9kskZ/oUv5h/inLPEZTw8DuoEWngHMWHJVlzlJqqPfefTZfze4Hax7QpsUc", - "T9LDzwFupRQlSM0sUFqWSS5SmidKU42Q/k3CcnI4+cu88arMbXc1DwZ/Y3qdYiejiFrlJqFleQ0Y74xC", - "o3ZICSOZ8RPKByvvUBVi3K6e4SFmZG8OF5TrWWOItARBvXM/uJEaelsdxtK7Y1gNEpzYhgtQVq+1DR8o", - "EpCeIFkJkhXVzFUuFvUPD4/KsqEgfj8qS0sP1AmBoboFG6a0eoTTp80WCsc5OZ6RH0LYqGALnm/NqWB1", - "DHMoLN1x5Y6v2mPk5tBAfKAILqeQM7M0ngxGeb8LjkNjYS1yo+7s5RXT+O+ubchm5vdRnb8MFgtpO8xc", - "aD45ylnLBX8JTJaHHc7pM45z4szIUbfvzdjGQIkzzI14Zed6Wrg76FiT8FLS0iLovthDlHE0vWwji+st", - "pelIQRfFOdjDAa8hVjfea3v3QxQTZIUODt/lIj2/g/2+MHD62w7BkzXQDCTJqKbBvnL7JX5YY8e/Yz+U", - "CCAjGv3P+B+aE/PZML6RixassdQZ8q8I/OqZMXCt2mxHMg3Q8BaksDYtMbbotbB81QzekxGWLGNkxGtr", - "RhPs4Sdhpt44yY4WQt6MXzqMwEnj+iPUQA22y7Szsti0KhNHn4j7wDboAGpuW/paZEihLvgYrVpUONX0", - "X0AFZaDeBRXagO6aCqIoWQ53sF/XVK37kzD23LOn5PTvRy+ePP316YtvjEFSSrGStCCLrQZFHjo1mii9", - "zeFRf2aoz1a5jkP/5rl3GLXhxuAoUckUClr2QVlHlD20bDNi2vWp1iYzzrpGcMy2PAMjXizZifWxGtSO", - "mTJnYrG4k8UYIljWjJIRh0kGe5nputNrhtmGU5RbWd2F8QFSChlxheAW0yIVeXIBUjER8Wq/cy2Ia+EV", - "krL7u8WWXFJFzNjopat4BnIW4yy94Yga01CofQeqBX224Q1tHEAqJd32yG/nG5mdG3fMurSJ750+ipQg", - "E73hJINFtWrprkspCkJJhh3x4HgrMjB2R6XuQFo2wBpkzEKEKNCFqDShhIsM0EipVFyODlxxoW8drwR0", - "KJr12p7TCzAKcUqr1VqTqiTo8O4tbdMxoaldlATPVDXgEaxdubaVHc5en+QSaGYUZeBELJzbzTkEcZIU", - "vfXaSyInxSOmQwuvUooUlDIGjlVb96Lm29lV1jvohIgjwvUoRAmypPKGyGqhab4HUWwTQ7dWu5yvso/1", - "uOF3LWB38HAZqTQ2juUCo+OZ3Z2DhiESjqTJBUj02f1L188PctPlq8qBG3WnqZyxAk0lTrlQkAqeqSiw", - "nCqd7Nu2plFLnTIzCHZKbKci4AFz/Q1V2npuGc9QtbbiBsexdrwZYhjhwRPFQP6HP0z6sFMjJ7mqVH2y", - "qKoshdSQxebAYbNjrLewqccSywB2fXxpQSoF+yAPUSmA74hlZ2IJRHXt53BXG/3JoTfAnAPbKClbSDSE", - "2IXIqW8VUDe8VRxAxNhhdU9kHKY6nFNfZU4nSouyNPtPJxWv+w2R6dS2PtK/NG37zEV1I9czAWZ07XFy", - "mF9aytr75DU1OjBCJgU9N2cTarTWxdzH2WzGRDGeQrKL8822PDWtwi2wZ5MOGBMuYiUYrbM5OvwbZbpB", - "JtizCkMTHrBs3lGpWcpK1CR+hO2du0W6A0Q9JCQDTZnRtoMPKMBR9tb9ib0z6MK8maI1Sgnto9/TQiPT", - "yZnCA6ON/Dls0VX6zl5GnwVX2HegKUagmt1NOUFE/RWXOZDDJrChqc635pjTa9iSS5BAVLUomNY2uqCt", - "SGpRJiGAqIG/Y0TnYrEXuX4Fxvh8ThFUML3+UkwnVm3Zjd9ZR3FpkcMpTKUQ+QhXdI8YUQxGuapJKcyq", - "MxfM4iMePCe1kHRKDPrXauH5QLXIjDMg/0dUJKUcFbBKQ30iCIliFo9fM4I5wOoxnVO6oRDkUIDVK/HL", - "48fdiT9+7NacKbKESx8BZhp2yfH4MVpJ74TSrc11Bxav2W4nEdmOng9zUDgdritTZntNewd5zEq+6wCv", - "3SVmTynlGNdM/9YCoLMzN2PmHvLImqr1/rkj3FFOjQB0bN523aUQyztypMUjANA4cZf6phVZVtwiVSln", - "juA9l3doiOW0jvKw0d2HBEMA1tR749yfT198M5k2V/f1d3Mm26+fIholyzaxAI0MNrE1cVsMrakHxvTY", - "KojeiqFgFstIjBbI89zNrCM6SAFmT6s1Kw3IJp5kq6EVi/p/H/774Yej5D9p8sdB8vJ/zD99fn716HHv", - "x6dX3377/9o/Pbv69tG//1vUrajZIu7+/LtZJbEkTsRv+Am3FxhLIa09tnVqnljeP95aAmRQ6nUs+LOU", - "oFA02iDOUq+bRQXo+FBKKS6ATwmbwawrYrMVKO9MyoEuMQgRbQox5lK03g6W3zxzBFQPJzJKjsX4B6/4", - "kDdxMxujI9/egfJiARHZpqc31pX9KpZh5KzbKGqrNBR9f5ft+uuAtv/e68q9TSV4zjgkheCwjSaLMA4/", - "4cdYb3vcDXRGxWOob9eWaOHfQas9zpjFvC19cbUD+f6uvti+g8Xvwu24OsOYYXTVQF4SStKcoSNHcKVl", - "leqPnKKpGLBr5DrJG8DDzoNXvkncWxFxJjhQHzlVhoa1ARl1gS8hcmR9D+B9CKparUDpjtK8BPjIXSvG", - "ScWZxrEKs16JXbASJN7pzGzLgm7Jkubo6/gDpCCLSrfVSDz0lGZ57vyuZhgilh851UYGKU1+Yvxsg+B8", - "BKHnGQ76UsjzmgrxI2oFHBRTSVzu/2C/ovh301+7owDzTOxnL2/uW+573GOBdw7zk2NnYp0cox7deFx7", - "uN+bG65gPIkymdGLCsYxfrvDW+ShsQY8Az1qfLdu1T9yveGGkS5ozjKjO92EHboirrcX7e7ocE1rITpe", - "FT/XT7GwgZVISpqe463xZMX0ulrMUlHMvWk5X4nazJxnFArB8Vs2pyWbqxLS+cWTPXruLeQViYirq+nE", - "SR11544YBzg2oe6YtT/T/60FefDD6zMydyulHtgoXAs6CJ+MeANchFDrwspM3maR2TDkj/wjP4Yl48x8", - "P/zIM6rpfEEVS9W8UiC/oznlKcxWghz6oKNjqulH3hPxg4meQbgXKatFzlJyHh7Fzda0yTt9CB8/fjAM", - "8vHjp97tR//gdENF96gdILlkei0qnbjshETCJZVZBHVVR6cjZJtbtGvUKXGwLUe67AcHPy6qaVmqbrBq", - "f/plmZvpB2yoXCimWTKitJBeCBrJaLHB9X0rnMkl6aVPbakUKPJbQcsPjOtPJPlYHRw8A9KK3vzNyRrD", - "k9sSWn6jGwXTdn1GOHGrUMFGS5qUdAUqOn0NtMTVx4O6QA9lnhPs1ooa9TEWCKqZgKfH8AJYPK4dAYeT", - "O7W9fJppfAr4CZcQ2xjp1Dj+b7peQRzpjZerE4vaW6VKrxOzt6OzUobF/crU2WcrI5P9bYxiK242gUvU", - "WwBJ15CeQ4Y5Q1CUejttdfcXfu6E86KDKZtbZwPdMAEEXWwLIFWZUacDUL7tRuIr0NqnH7yHc9ieiSZ/", - "5Dqh9+2AcDW0UZFTg8PIMGu4bR2M7uK7y2MMgi1LH1eNMYSeLQ5rvvB9hjeyPSHvYBPHmKIVsDxECCoj", - "hLDMP0CCG0zUwLsV68emZ9SbhT35Im4eL/uJa9Jobe4COJwNxmHb7wVgoq64VGRBFWREuBxTG/QcSLFK", - "0RUM+J5CL+fI0OKWZxSB7Dv3oiedWHYPtN55E0XZNk7MnKOcAuaLYRV0E3au/f1I1pGOM5gRLB3hCLbI", - "UU2qIw6s0KGy5W22ufBDqMUZGCRvFA6PRpsioWazpsqnv2KWsN/Lo3SAf2EQ/66crZPgxjpIBa4zsrzM", - "7e7Tnt/WZW75dC2foxU6bUfkW00nLogqthyCowKUQQ4rO3Hb2DNKk1DQLJDB4+flMmccSBK7/KZKiZTZ", - "/OXmmHFjgNGPHxNifU9kNIQYGwdo4wURAiZvRbg3+eo6SHKXEEE9bLxaCv6GeCSgDW8yKo8ojQhnfCAw", - "zUsA6iIm6vOrE7eDYAjjU2LE3AXNjZhzTtQGSC+DCNXWTr6Qu6J8NKTO7nD92YPlWnOyR9FNZhPqTB7p", - "uEK3A+PdqkRsCRTSy5m+Na2GztIxQw8c30O0ehjkHt0IgY4noinP4yy/vRZa+2zun2SNSJ82ybQ+MjPG", - "+0P8E12lAfr1HcF1ttC77nEdNdLbV5ftRKlAf4qJYrNH+q7RvgNWQQ6oESctDSI5jznMjWIPKG5PfbfA", - "csd0LMq3j4L7cAkrpjQ0ritzKnlf7H1fd1FM/xZiOTw7Xcqlmd97IWoZbdMM7fVdOM17n8GF0JAsmVQ6", - "Qb9fdAqm0fcKLcrvTdO4otC+cbeVUFgWlw047Dlsk4zlVZxf3bg/Hpth39ZOGFUtzmGL6iDQdE0WWLkn", - "GoezY2gbqrVzwm/shN/QO5vvuN1gmpqBpWGX9hhfyL7oSN5d4iDCgDHm6K/aIEl3CEg8+I8h17GMpUBp", - "sJszMw1nu1yPvc2Uedi7DKUAi+EzykKKziWwlnfOgmH0gTH3mA4K3/TTBgb2AC1Llm06jkALddBcpNey", - "9n1icYcKuLoO2B4KBE6/WGSqBNXOIW+0W1vCiIdzm42izFk70zsUCOFQTPkCfH1CGdbGKlH7aHUGNP8R", - "tv8wbXE6k6vp5HZ+wxitHcQ9tH5XL2+UznghZv1IrWuAa5KclqUUFzRPnHd1iDWluHCsic29M/aeRV3c", - "h3f2+ujNO4f+1XSS5kBlUqsKg7PCduUXMyubrj6wQXyBL2PweJ3dqpLB4tdpxKFH9nINrphSoI32ij80", - "3vZgKzoP7TJ+L7/X3+ouBuwUd1wQQFnfDzS+K3s90L4SoBeU5d5p5LEduEPHyY2rIBKVCiGAW18tBDdE", - "yZ2Km97uju+Ohrv2yKRwrB3lngpb0UwRwbshWUaFRF8UsmpBsXSDdQn0hROvisRsv0TlLI07GPlCGebg", - "9uLINCbYeEAZNRArNnAPySsWwDLN1AhDt4NkMEaUmL4MyBDtFsKVoq04+70CwjLg2nySuCs7GxVrZThX", - "c/84NbpDfywH2LqnG/C30THCsiXdEw+R2K1ghNdUPXSPa5PZT7R2x5gfAn/8NW67wxF7R+KOm2rHH46b", - "bcjQun3dFFaO7cs/wxi2ytj+srXeeHX1UwbGiJahZSpZSvEHxO08NI8jYeu+UAvDqMk/gM8i2T9dEVN7", - "d5pqus3og8s9pN2EXqj2Df0A1+PKB3dSWBTDu2cpt0ttq0K24kLiDBPGcs0t/IZhHM69+LecXi5orGKI", - "UTIMTkfN7WfLkawF8Z097Z3Pm7naOTMSXKTWbZlN6CpBNhkl/eThGyoMdtjRqkKjGSDXhjrB1F5+5UpE", - "wFT8knJbXNT0s1vJ9VZgnV+m16WQmI6p4j7vDFJW0DyuOWRI/Xb6asZWzJbWrBQEtRsdIFuT2HKRq39p", - "75cb0pwsycE0qA7rViNjF0yxRQ7Y4oltsaAKJXntiKq7mOkB12uFzZ+OaL6ueCYh02tlCasEqZU6NG/q", - "m5sF6EsATg6w3ZOX5CHeWSl2AY8MFd35PDl88hKdrvaPg9gB4Gro7pImGYqT/3DiJM7HeGlnYRjB7aDO", - "osmFtvD5sODasZts1zF7CVs6Wbd/LxWU0xXEwySKPTjZvria6Ejr0IVntmqv0lJsCdPx8UFTI58GYj6N", - "+LNokFQUBdOFu9lQojD81BRmtIN6cLYEsKse5PHyH/GCsPT3Ix0j8n6dpvZ8i80ar3Hf0gLaZJ0SanNw", - "c9Zc3fuCX+TEZ/JjOaW6ipKljRnLTB3VHLzJX5JSMq7RsKj0MvkbSddU0tSIv9kQusnim+eRElLtqjH8", - "eojfO90lKJAXcdLLAbb3OoTrSx5ywZPCSJTsURNjHezKwZvMeLSYl+jdYMHdoMcqZQZKMshuVYvdaCCp", - "b8V4fAfAW7JiPZ9r8eO1Z3bvnFnJOHvQyqzQL+/fOC2jEDJW16XZ7k7jkKAlgwsMXIsvkoF5y7WQ+ahV", - "uA32f+7Ng1c5A7XM7+WYIfBdxfLsH03OSKcKn6Q8XUf9/gvT8demSnI9ZbuPo2VE1pRzyKPg7Jn5qz9b", - "I6f/P8XYcQrGR7btVtez0+1MrkG8jaZHyg9oyMt0bgYIqdoOoq+jLvOVyAiO09SsaLisXzAwqKD1ewVK", - "x5L28ION/ED/jrELbAEnAjxDrXpGfrCvnKyBtFLqUZtlRZXb9GzIViCd47Eqc0GzKTFwzl4fvSF2VNvH", - "lvy0BaRWqMy1Z9Gx64MCN+NiCH31znh883g4uwMuzayVxgoXStOijKWumBZnvgHmx4S+TlTzQurMyLHV", - "sJXX3+wghh+WTBZGM62hWRmPPGH+ozVN16i6tqTJMMuPr3zmuVIFheHrOq91jRrcdwZvV/zM1j6bEmHs", - "i0um7OMWcAHtbJk6dcyZTj57pj09WXFuOSUqo3elNt6E7B45e6Ht3aFRzDqEv6biYgsHXrcQ3Cn2ihZ9", - "6FaV61WEt1nFdYlS/2hRSrngLMWSC8FzGjXK7qGMMXcFI6pTdJ1Rfou7HRrZXNFadnU4kaPiYHU7Lwgd", - "4frOyuCrWVTLHfZPjS8yrKkmK9DKSTbIpr4ko/OXMK7A1RzCN1MCOSlk6/4FJWT0Si+pXb/XZCOMnR9Q", - "gL8339468wiDSs8ZR0XIkc3Fr1qPBtbx10Z7YpqsBCg3n3Zqvvpg+swwPT2DzaeZr/uPMOz1hZm2vavr", - "gzryN3fupsy0fWXaEht1WP/cClO0gx6VpRt0uGBnVB/QGz5I4MgNTOJd4AFxa/ghtB3stvPKHc9Tw2hw", - "gRd2UOI53GOMunhlp1rvBc0ry1HYgthQl2h+JeMRNN4wDs2rFJEDIo0eCbgwuF8H+qlUUm1VwFEy7Qxo", - "jrd0MYGmtHPR3hZUZ4GRJDhHP8bwMjZ1NwcER92gUdwo39aPYRjuDpSJV/gKjyNkv4omalVOicow7LhT", - "VzMmOIzg9pV72wdAfxv0dSLbXUtqd851TqKhTLJFla1AJzTLYsXavsOvBL+SrELNATaQVnWxq7IkKWZs", - "t1PY+9zmBkoFV1WxYyzf4JbDpSKmR7/FAZSPq26AzwiKXyN6j1+/e//61dHZ62N7Xhiz3KaSGZ1bQmEE", - "orFjlQajOlcKyG8hGX/Dfr91JhxHM6inG2HasKavZ0QMqF9s8d9YQaphBnJ36teO6vIX6Njx2up9G1JP", - "OTdbL1FslYynBB59tydHM/TN9mPT/043ZC5WbUTuuXLMLmEcrlFMDL8251uYBd6rsmZPwDpJG2OohC/N", - "j9ZtnV7YFp544vbKrqHvvq6yvtt7MlwvfYpn9EAkZVAvh1o1wF4GDcVTpoPhv1S7LBxNyU5JiUXOYxBs", - "MIYtrm7fZYw6woYCMGz8hfnc6z1Oge2ZAwh7J0F9ZE8foR992CApKXM3nY2w6FPWBRj3Q77HhB42C9yd", - "hAvbRSCxmfSqKe7mkF7YdpB6YIvezcan/x/V18h4uYUly1fAXc3ydkDm6LCw5RJSzS72hMn/hzEtmhDs", - "qTc+7IMYQdQ8q8OM/POd17SJGoR2RbHvxCeoMXJrdIaCZM9h+0CRFjdEq/BNPaPeJLsUKYD1VxLDIkLF", - "rmmst8R5zpmqOQOp4K9FbXdoSl8Nlj8Okj5uOJZnSULDRJAdQ16ImLk1aizT9VrpURgxMxRJ3y9AOnx6", - "HWO9V1WXrq/f5wxUUWNVd6vjXbrsVkxqqB2EPs8VlP/NZzDZUey7r02BZnTHXlKZ+RZR+8KbLslAbFo3", - "2tsG1bM40st6ZNYEsfQDniNVITBUKc2FYnyVDMV7teNGwqej8HYMPTlY2RXxWoJ0hdm1f1Y30cIHvezC", - "Yxcp3DNHNyGCGqxxaJEbzI9+3ySAYyksah9Vdjd/4QSNsUENdjJI0x4ecxexX9nvPsLXl0IaYUY5fk32", - "5ln78CWmekQMuX5J3Gm5P3L4JqYK49y+e6FiOdvckDJ0+ZVSZFVqD+hwYzSG4diKCDtESVTLT/uz7Cls", - "OdYHeRPkYZzDdm6VpnRNeVOopb2tbelGO4cg77Gz2ndqxcUV1nxlJ7C6Ezz/TEtoOimFyJMBH99JP/W8", - "uwfOWXoOGTFnh7/4HyiBTB6ia6m+xLlcb32qdVkCh+zRjBBjSxWl3vr7nHbRtc7g/IHeNf4GR80qWw3C", - "GWmzjzwes2KfKb+lfPNgdks1BUb43XIoC2RPbvdmIO1d0stIQfCxb75Fbli6RZobprJYxLSUGyb6jdrf", - "fUMtwvphisYe++e8ZdXZskKdWxUh4Y6tu8CdfE3rrp98MnZ6OA+UapWC/jxHL0CLtgO0H0P4xjXRJ+6w", - "R0EvxngU4iVQTHd0aViCYP0ggqiS3578RiQssZ6gII8f4wCPH09d09+etj8b6+vx4+jOvDdnRutpOTdu", - "jGP+MXQLb2+aBwI+OutRsTzbxxit8J2mticGqPzqAp3+lOqiv1oTub9VXaHF67hRu4uAhInMtTV4MFQQ", - "mDMiJsd1m0Uf/1OQVpLpLeZfeYuK/RrNa/+hdsK490rriH0XMK7FOdQZfI3LpnnM/QdhHwsszFmPTmyN", - "rx+83tCizMFtlG8fLP4Kz/72PDt49uSvi78dvDhI4fmLlwcH9OVz+uTlsyfw9G8vnh/Ak+U3LxdPs6fP", - "ny6eP33+zYuX6bPnTxbPv3n51wf+JXWLaPNK+f/GErzJ0buT5Mwg29CElqx+9MSwsS/nSVPcicYmySeH", - "/qf/6XfYLBVFA97/OnHBhJO11qU6nM8vLy9nYZf5Cm20RIsqXc/9OP3HJt6d1IFONkEFV9TGsBhWwEV1", - "rHCE396/Pj0jR+9OZg3DTA4nB7OD2ROsml0CpyWbHE6e4U+4e9a47nPHbJPDz1fTyXwNNMdS6uaPArRk", - "qf+kLulqBXLm6pqany6ezn2cxPyzs0+vdn2bhyWC5p9bZny2pydWUZl/9slBu1u3sm+c+yLoMBKL4SHt", - "c2rzz2gPDv7eRuOz3rDsau7dT66He5Zo/rl5J+zK7sIcYq4jG/hGg2fFpsZex2dmlf3VbDwfb89U+1m5", - "motOMsM9pter+s20oNTA4Yee+mUBEQ8Jt5rho2YntEZqhJ2WFYTZ77Uob7VvBPqHg+Tlp89Ppk8Orv5i", - "BLb788Wzq5E+4OZZXHJaS+ORDT9hsDpas7hBnh4c/Dd7VPj5NWe8U+duXZNFiht/RzPiY0Fx7Cf3N/YJ", - "Rw+8EZzEHgxX08mL+5z9CTcsT3OCLYMsqf7S/8LPubjkvqU5xauioHLrt7FqCQX/EiKeFXSl0AKT7IJq", - "mHxCEz8WNDAgXPD15msLF3yS+qtwuS/h8mW81f30mhv8y5/xV3H6pYnTUyvuxotTp8rZdIO5fa+l0fB6", - "xXhXEM17wAwEuut1wq6E/QF077HFyS1FzJ/27uJ/733y/OD5/WHQriT5I2zJW6HJ93jt9YXu2XHbZ5cm", - "1LGMsqzH5Fb8g9LfiWy7g0KFWpUuRDiilywYNyj3T5f+Sya9xxDPYUvsVbB3+bvHgNv60NUtZcAX+27j", - "VxnyVYZIO/yz+xv+FOQFS4GcQVEKSSXLt+QXXid43dysy7JomF176/dkmrFGUpHBCnjiBFayENnWF/dp", - "ATwH65ruKSrzz+0Kndb9NeiWOsbf64eD+kgvtuTkuKfB2G5dSfvdFpt2LMaITdhFcadl2JVFA8bYLjY3", - "E1kJTSwVMjepr4Lnq+C5lfIyevPE9JeoNeEdOd0zeeoznWO1AKjuDz3G5vhTt+t/2Wfwv4qEryLh5iLh", - "B4hsRty1TkhEmO4mnt6+gMDIq6xb5x7DF3zzKqeSKBjrpjhCiM45cR9S4r6NtCitrI1GOYENU/huS2TB", - "7tZu+yrivoq4L+jWar+gaSsi17Z0zmFb0LK2b9S60pm4tBWColIRi+fS3FXaw9p3dSSGFsQDaBKcyM8u", - "oy/f4vvxLDNqnGYFGJWqlnWmsw9bbeJmDYTmwcMV4zgAigocxZaUpEHqgIJUcPs8WOeuzWH21tqEMSH7", - "ewUo0RxtHI6TaeuyxS1jpIDjrfWv/t3I1Q5fev3GV+vv+SVlOlkK6TKHkEL9KAwNNJ+7WhidX5u8zt4X", - "TFYNfgxiN+K/zuuaxtGP3aiT2FcXFOIbNWFlYZgWrmEdoPXhk1kKLInnlreJOjqczzHcfi2Unk+upp87", - "EUnhx0819T/XJ69bhatPV/8/AAD//75BkK9YsgAA", + "H4sIAAAAAAAC/+x9/XPcNrLgv4KafVWOfcMZyR/ZtapS7xTLyeriOC5L2Xf3bF+CIXtmsCIBBgClmfj0", + "v1+hAZAgCc5QH6u81PNPtob4aDQajf7G50kqilJw4FpNjj5PSippARok/kXTVFRcJywzf2WgUslKzQSf", + "HPlvRGnJ+GoynTDza0n1ejKdcFpA08b0n04k/FYxCdnkSMsKphOVrqGgZmC9LU3reqRNshKJG+LYDnF6", + "Mrne8YFmmQSl+lD+xPMtYTzNqwyIlpQrmppPilwxvSZ6zRRxnQnjRHAgYkn0utWYLBnkmZr5Rf5WgdwG", + "q3STDy/pugExkSKHPpyvRLFgHDxUUANVbwjRgmSwxEZrqomZwcDqG2pBFFCZrslSyD2gWiBCeIFXxeTo", + "w0QBz0DibqXALvG/SwnwOySayhXoyadpbHFLDTLRrIgs7dRhX4Kqcq0ItsU1rtglcGJ6zciPldJkAYRy", + "8v67V+TZs2cvzUIKqjVkjsgGV9XMHq7Jdp8cTTKqwX/u0xrNV0JSniV1+/ffvcL5z9wCx7aiSkH8sByb", + "L+T0ZGgBvmOEhBjXsMJ9aFG/6RE5FM3PC1gKCSP3xDa+100J5/9DdyWlOl2XgnEd2ReCX4n9HOVhQfdd", + "PKwGoNW+NJiSZtAPB8nLT58Pp4cH13/5cJz8p/vzxbPrkct/VY+7BwPRhmklJfB0m6wkUDwta8r7+Hjv", + "6EGtRZVnZE0vcfNpgaze9SWmr2WdlzSvDJ2wVIrjfCUUoY6MMljSKtfET0wqnhs2ZUZz1E6YIqUUlyyD", + "bGq479WapWuSUmWHwHbkiuW5ocFKQTZEa/HV7ThM1yFKDFy3wgcu6L8uMpp17cEEbJAbJGkuFCRa7Lme", + "/I1DeUbCC6W5q9TNLityvgaCk5sP9rJF3HFD03m+JRr3NSNUEUr81TQlbEm2oiJXuDk5u8D+bjUGawUx", + "SMPNad2j5vAOoa+HjAjyFkLkQDkiz5+7Psr4kq0qCYpcrUGv3Z0nQZWCKyBi8U9Itdn2/3X201siJPkR", + "lKIreEfTCwI8FdnwHrtJYzf4P5UwG16oVUnTi/h1nbOCRUD+kW5YURWEV8UCpNkvfz9oQSToSvIhgOyI", + "e+isoJv+pOey4ilubjNtS1AzpMRUmdPtjJwuSUE33xxMHTiK0DwnJfCM8RXRGz4opJm594OXSFHxbIQM", + "o82GBbemKiFlSwYZqUfZAYmbZh88jN8MnkayCsDxgwyCU8+yBxwOmwjNmKNrvpCSriAgmRn52XEu/KrF", + "BfCawZHFFj+VEi6ZqFTdaQBGnHq3eM2FhqSUsGQRGjtz6DDcw7Zx7LVwAk4quKaMQ2Y4LwItNFhONAhT", + "MOFuZaZ/RS+ogq+fD13gzdeRu78U3V3fueOjdhsbJfZIRu5F89Ud2LjY1Oo/QvkL51ZsldifexvJVufm", + "KlmyHK+Zf5r982ioFDKBFiL8xaPYilNdSTj6yJ+Yv0hCzjTlGZWZ+aWwP/1Y5ZqdsZX5Kbc/vRErlp6x", + "1QAya1ij2hR2K+w/Zrw4O9abqNLwRoiLqgwXlLa00sWWnJ4MbbId86aEeVyrsqFWcb7xmsZNe+hNvZED", + "QA7irqSm4QVsJRhoabrEfzZLpCe6lL+bf8oyN711uYyh1tCxu2/RNuBsBsdlmbOUGiS+d5/NV8MEwGoJ", + "tGkxxwv16HMAYilFCVIzOygtyyQXKc0TpanGkf5NwnJyNPnLvDGuzG13NQ8mf2N6nWEnI49aGSehZXmD", + "Md4ZuUbtYBaGQeMnZBOW7aFExLjdRENKzLDgHC4p17NGH2nxg/oAf3AzNfi2oozFd0e/GkQ4sQ0XoKx4", + "axs+UiRAPUG0EkQrSpurXCzqH746LssGg/j9uCwtPlA0BIZSF2yY0uoxLp82Jymc5/RkRr4Px0Y5W/B8", + "ay4HK2qYu2Hpbi13i9WGI7eGZsRHiuB2CjkzW+PRYGT4+6A41BnWIjdSz15aMY3/7tqGZGZ+H9X5z0Fi", + "IW6HiQu1KIc5q8DgL4Hm8lWHcvqE42w5M3Lc7Xs7sjGjxAnmVrSycz/tuDvwWKPwStLSAui+2LuUcdTA", + "bCML6x256UhGF4U5OMMBrSFUtz5re89DFBIkhQ4M3+YivbiH874w4/SPHQ5P1kAzkCSjmgbnyp2X+J2N", + "Hf+O/ZAjgIwI9j/hf2hOzGdD+IYv2mGNws6QfkVgXs+MnmulZzuTaYD6tyCFVW2JUUlvBOWrZvIej7Bo", + "GcMjXlttmmAPvwiz9MZWdrwQ8nb00iEEThoLIKFm1OC4TDs7i02rMnH4iVgRbIPOQI3TpS9MhhjqDh/D", + "VQsLZ5r+C7CgzKj3gYX2QPeNBVGULId7OK9rqtb9RRi17tlTcvb34xeHT395+uJro5eUUqwkLchiq0GR", + "r5w0TZTe5vC4vzKUZ6tcx0f/+rm3G7XHjY2jRCVTKGjZH8rao+ylZZsR066PtTaacdU1gGOO5TkY9mLR", + "Tqyp1YB2wpS5E4vFvWzGEMKyZpaMOEgy2EtMN11eM802XKLcyuo+lA+QUsiIRQSPmBapyJNLkIqJiHH7", + "nWtBXAsvkJTd3y205IoqYuZGY13FM5CzGGXpDUfQmIZC7btQ7dDnG97gxg1IpaTbHvrteiOrc/OO2Zc2", + "8r3tR5ESZKI3nGSwqFYt2XUpRUEoybAjXhxv2Gqtg3v0nRRiee/iRnSW2JLwAxrYSW76uJvOygYI8FuR", + "gVGUKnUP7L0ZrMGeoZwQZ3QhKk0o4SID1KoqFWf8A6459AmgK0OHd4leW8FiAUaCT2llVluVBA31PVps", + "OiY0tVSUIGrUgCWzNkHbVnY66/bJJdDMSPbAiVg4c6EzZOIiKXoZtGed7tqJ6DotuEopUlDKaGRWzt4L", + "mm9nyVLvwBMCjgDXsxAlyJLKWwKrhab5HkCxTQzcWk50NtY+1OOm37WB3cnDbaTSKGWWCoxQag5cDhqG", + "UDgSJ5cg0db4L90/P8ltt68qByIBnGh1zgrU7TjlQkEqeKaig+VU6WTfsTWNWvKfWUFwUmInFQcesC+8", + "oUpbizPjGeoClt3gPNbwYKYYBnjwCjQj/8Pffv2xU8MnuapUfRWqqiyF1JDF1sBhs2Out7Cp5xLLYOz6", + "vtWCVAr2jTyEpWB8hyy7EosgqmvDjHPJ9BeH5gtzD2yjqGwB0SBiFyBnvlWA3dAbOgCIURzrnkg4THUo", + "p3bBTidKi7I0508nFa/7DaHpzLY+1j83bfvERXXD1zMBZnbtYXKQX1nMWj/4mhqhHUcmBb0wdxOK4NY0", + "3ofZHMZEMZ5CsovyzbE8M63CI7DnkA5oPy7SJpitczg69BslukEi2LMLQwseUMXeUalZykqUJH6A7b0L", + "Vt0JoiYdkoGmzKgHwQcrZJVhf2J9Hd0xbydojZKa++D3xObIcnKm8MJoA38BW7TtvrNO9PPA9X4PkmJk", + "VHO6KScIqHfNmQs5bAIbmup8a645vYYtuQIJRFWLgmltoyLagqQWZRIOELVI7JjR2YSsA9rvwBgj1RkO", + "FSyvvxXTiRVbdsN33hFcWuhwAlMpRD7Cdt5DRhSCUbZ1Ugqz68wF4fhIDU9JLSCdEIMGwZp5PlItNOMK", + "yP8RFUkpRwGs0lDfCEIim8Xr18xgLrB6TmdFbzAEORRg5Ur88uRJd+FPnrg9Z4os4cpHrpmGXXQ8eYJa", + "0juhdOtw3YOKbo7baYS3o6nGXBROhuvylNleW4QbecxOvusMXtt3zJlSyhGuWf6dGUDnZG7GrD2kkTVV", + "6/1rx3FHWWGCoWPrtvt+a/W9b/mLRy6gcuKCEUwrsqy4BapSTh1Bx5y3wIjltI5OsVHpRwRDF9bUmw/d", + "n09ffD2ZNiEH9XdzJ9uvnyISJcs2scCSDDaxPXFHDLWpR0b12CqIuvGQMYtlJLYM5EXuVtZhHaQAc6bV", + "mpVmyCYOZquhFUP7f7/696MPx8l/0uT3g+Tl/5h/+vz8+vGT3o9Pr7/55v+1f3p2/c3jf/+3qB1Us0Xc", + "Xvt3s0tiSRyL3/BTbj0uSyGtPrZ1Yp5YPjzcWgJkUOp1LGi1lKCQNdrg01Kvm00F6NhQSikugU8Jm8Gs", + "y2KzFShv/cqBLjF4EnUKMcaLWx8HS2+eOAKshwsZxcdi9IM+SaRNPMwYD/CvMcg1Q8eA608ceFGbj0OO", + "VKMs5dt7ELrsQES26cAbGZT9KpZhpLI74GqrNBR9O53t+suAlvLey/g9ZiB4zjgkheCwjSbnMA4/4sdY", + "b3tND3RGgWmob1cHasHfAas9zxgivCt+cbeDe+ldHUFwD5vfHbdjog1jtNHEBHlJKElzhgYowZWWVao/", + "cooqbnDMIn47r7gPGz1e+SZxK0vECOKG+sipMjisFd+or2EJkav2OwBv+1DVagVKd4T9JcBH7loxTirO", + "NM5VmP1K7IaVINF5NrMtC7olS5qjjeZ3kIIsKt0Wf/GyVprlubMXm2mIWH7kVBveqTT5kfHzDQ7nIzY9", + "zXDQV0Je1FiIX60r4KCYSuL31ff2K15bbvlrd4VhXo/97PnkQ99XHvZYoKOD/PTEqYanJyj/N5biHuwP", + "Zj4sGE+iRGbkuYJxjJfv0Bb5ymgxnoAeNzZnt+sfud5wQ0iXNGeZkfluQw5dFtc7i/Z0dKimtREda5Bf", + "66dYfMZKJCVNL9A9P1kxva4Ws1QUc68Sz1eiVo/nGYVCcPyWzWnJ5qqEdH55uEc+vwO/IhF2dT2dOK6j", + "7l0QcAPHFtSds7bD+r+1II++f31O5m6n1CMb9WyHDuJUI1YMF4rVcrSZxdusPRv2/ZF/5CewZJyZ70cf", + "eUY1nS+oYqmaVwrktzSnPIXZSpAjH911QjX9yHssfjCxNoirI2W1yFlKLsKruDmaNlmqP8LHjx8MgXz8", + "+KnntelfnG6q6Bm1EyRXTK9FpROXDZJIuKIyi4Cu6mwAHNnmcu2adUrc2JYiXbaJGz/OqmlZqm5UcH/5", + "ZZmb5QdkqFzMq9kyorSQngkazmihwf19K5yqKOmVTyWqFCjya0HLD4zrTyT5WB0cPAPSCpP91fEaQ5Pb", + "Elr2rltFLXdtXbhwK1DBRkualHQFKrp8DbTE3ceLukDLap4T7NYKz/XBLDhUswCPj+ENsHDcONQQF3dm", + "e/m03vgS8BNuIbYx3KlxWNx2v4KA3VtvVyfot7dLlV4n5mxHV6UMifudqbP9VoYney+SYituDoFLjFwA", + "SdeQXkCGOVpQlHo7bXX3jkp3w3nWwZTNZbQRhZhwg6bBBZCqzKiTASjfdjMfFGjt0z3ewwVsz0WTr3OT", + "VId25L0aOqhIqcFlZIg1PLZujO7mO6c3RhuXpQ9gx2BNTxZHNV34PsMH2d6Q93CIY0TRigwfQgSVEURY", + "4h9AwS0Wasa7E+nHlmfEm4W9+SLmKc/7iWvSSG3OcR2uBgPe7fcCMDFaXCmyoAoyIlxOr40uD7hYpegK", + "BmxmoXV2ZAx3y6KLg+y796I3nVh2L7TefRMF2TZOzJqjlALmiyEVNG92whX8TNYBgCuYESzV4RC2yFFM", + "qiMlLNOhsmUlt7UHhkCLEzBI3ggcHow2RkLJZk2VTzfGrGx/lkfJAP/CbIldOXKngac9SL2uM+A8z+2e", + "05692WXK+fQ4nxMXGptH5LdNJy74K7YdgqMAlEEOK7tw29gTSpO50WyQgeOn5TJnHEgSc9pTpUTKbL54", + "c824OcDIx08IsbYnMnqEGBkHYKNjCwcmb0V4NvnqJkByl3lC/djoEgv+hnjIpQ3LMiKPKA0LZ3wgoM5z", + "AOoiPer7qxNvhMMQxqfEsLlLmhs254y/zSC9VC0UWzuJWc61+nhInN1h+rMXy43WZK+i26wmlJk80HGB", + "bgfEu0WJ2BYoxJdTfWtcDd2lY6YeuL6HcPVVkOR1KwA6loimHJLT/PZqaO27uX+TNSx92iQv+4jSGO0P", + "0U90lwbw1zcE12lZ77rXdVRJb7tc2xlpgfwUY8XmjPRNo30DrIIcUCJOWhJEchEzmBvBHpDdnvlugeaO", + "eW+Ubx8HfnwJK6Y0NKYrcyt5W+xDu+koptsLsRxenS7l0qzvvRA1j7b5nNbtGC7zwVdwKTQkSyaVTtDu", + "F12CafSdQo3yO9M0Lii0IwVs5RmWxXkDTnsB2yRjeRWnVzfvDydm2re1EUZViwvYojgINF2TBVZKisYP", + "7ZjahpjtXPAbu+A39N7WO+40mKZmYmnIpT3Hn+RcdDjvLnYQIcAYcfR3bRClOxgkXvwnkOtYalggNNjD", + "mZmGs12mx95hyvzYe521ForhO8qOFF1LoC3vXAXDqAmj7jEdFBrqpzsMnAFalizbdAyBdtRBdZHeSNv3", + "GdwdLODuusH2YCAw+sUiaiWodrJ+I93aklE8XNtsFGbO2yn1IUMIp2LKFzzsI8qQNlbl2oerc6D5D7D9", + "h2mLy5lcTyd3sxvGcO1G3IPrd/X2RvGMDjFrR2q5AW6IclqWUlzSPHHW1SHSlOLSkSY298bYB2Z1cRve", + "+evjN+8c+NfTSZoDlUktKgyuCtuVf5pV2boAAwfEF1QzCo+X2a0oGWx+na8dWmSv1uCKVwXSaK/KRmNt", + "D46is9Au4375vfZW5xiwS9zhIICy9g80tivrHmi7BOglZbk3GnloB3zouLhxpVqiXCEc4M6uhcBDlNwr", + "u+md7vjpaKhrD08K59pRXquwFeQUEbwbSmZESLRFIakWFGtkWJNAnznxqkjM8UtUztK4gZEvlCEObh1H", + "pjHBxgPCqBmxYgN+SF6xYCzTTI1QdDtABnNEkenrrQzhbiFc6d+Ks98qICwDrs0niaeyc1CxKIkzNfev", + "UyM79OdyA1vzdDP8XWSMsD5M98ZDIHYLGKGbqgfuSa0y+4XW5hjzQ2CPv4G3O5yxdyXu8FQ7+nDUbEOG", + "1m13U1ipt8//DGHYqm77ywR75dUVqhmYI1r2l6lkKcXvENfzUD2OhNv7ijgMoz1/Bz6LZC11WUxt3Wmq", + "FzezD273kHQTWqHaHvoBqsedD3xSWH3Em2cpt1ttq3C24kLiBBPGcs3t+A3BOJh78W85vVrQWGkWI2QY", + "mI4b72fLkKwF8Z097p3Nm7kiRTMSOFLrtswmopUgm0yYftLzLQUGO+1oUaGRDJBqQ5lgap1fuRKRYSp+", + "Rbkt5mr62aPkeiuwxi/T60pITCNVcZt3BikraB6XHDLEfjvtNmMrZkuZVgqCWpluIFsD2lKRqzdq/csN", + "ak6X5GAaVON1u5GxS6bYIgdscWhbLKhCTl4bououZnnA9Vph86cjmq8rnknI9FpZxCpBaqEO1Zvac7MA", + "fQXAyQG2O3xJvkKflWKX8Nhg0d3Pk6PDl2h0tX8cxC4AV7N4FzfJkJ38h2MncTpGp50dwzBuN+osmhRp", + "C80PM64dp8l2HXOWsKXjdfvPUkE5XUE8TKLYA5Pti7uJhrQOXnhmqyQrLcWWMB2fHzQ1/Gkg5tOwPwsG", + "SUVRMF04z4YShaGnphCmndQPZ0suuzJNHi7/ER2EpfePdJTIhzWa2vsttmp0476lBbTROiXU5g7nrHHd", + "+8pq5NRXIMC6VXW5KosbM5dZOoo56MlfklIyrlGxqPQy+RtJ11TS1LC/2RC4yeLr55FaXe3yPPxmgD84", + "3iUokJdx1MsBsvcyhOtLvuKCJ4XhKNnjJsY6OJWDnsx4tJjn6N1gwd1DjxXKzCjJILlVLXKjAae+E+Hx", + "HQPekRTr9dyIHm+8sgenzErGyYNWZod+fv/GSRmFkLF6NM1xdxKHBC0ZXGLgWnyTzJh33AuZj9qFu0D/", + "x3oevMgZiGX+LMcUgW8rlmf/aHJGOuUOJeXpOmr3X5iOvzRVqesl23McLX+yppxDHh3O3pm/+Ls1cvv/", + "U4ydp2B8ZNtuGUO73M7iGsDbYHqg/IQGvUznZoIQq+0g+jrqMl+JjOA8Ta2Nhsr6lRmDUmW/VaB0LNkQ", + "P9jID7TvGL3AVsoiwDOUqmfke/uqzBpIqxQASrOsqHKbVg7ZCqQzPFZlLmg2JWac89fHb4id1faxtVVt", + "pa4VCnPtVXT0+qAwz7gYQl8mNR7fPH6c3QGXZtVKY2UOpWlRxlJXTItz3wDzY0JbJ4p5IXZm5MRK2MrL", + "b3YSQw9LJgsjmdajWR6PNGH+ozVN1yi6trjJMMmPLzHnqVIFhfjrgrp1bR08dwZuV2XOFpmbEmH0iyum", + "7GMicAntbJk6dcypTj57pr08WXFuKSXKo3elNt4G7R4469D25tAoZB3E31BwsRUab1px7wx7RYtVdMv3", + "9Srw22zouhasfyQqpVxwlmKpiOD5khpk9zDJGF/BiKoaXWOUP+LuhEYOV7RoYB1O5LA4WEbQM0KHuL6x", + "MvhqNtVSh/1T4wsYa6rJCrRynA2yqa996ewljCtwtZLwjZqATwrZ8r8gh4y69JLa9HtDMsLY+QEB+Dvz", + "7a1TjzCo9IJxFIQc2lz8qrVo4LsJ2khPTJOVAOXW0y4poD6YPjNMq89g82nm31nAMaz7wizb+ur6Qx17", + "z53zlJm2r0xbm2jd/NwKU7STHpelm3S4MmpUHtAbPojgiAcm8SbwALn1+OFoO8htp8sd71NDaHCJDjso", + "8R7uEUZdJbRTFvmS5pWlKGxBbKhLNL+S8QgYbxiH5hWQyAWRRq8E3Bg8rwP9VCqptiLgKJ52DjRHL12M", + "oSntTLR3HaqzwYgSXKOfY3gbmwKnA4yjbtAIbpRv68dHDHUHwsQrfPXIIbJfrhSlKidEZRh23ClgGmMc", + "hnH7EsntC6B/DPoyke2uJbUn5yY30VAm2aLKVqATmmWxInPf4leCX0lWoeQAG0irukhXWZIUM7bbKex9", + "anMTpYKrqtgxl29wx+lSEZOj3+IEysdVN4PPCLJfw3pPXr97//rV8fnrE3tfGLXcppIZmVtCYRii0WOV", + "BiM6VwrIryEaf8V+v3YWHAczKFwcIdqweLInRAyoX2zx31ghrWECcj71G0d1eQc6dryxeN8eqSecm6OX", + "KLZKxmMCr767o6OZ+nbnsel/rwcyF6s2IA9c8WYXMw73KMaGX5v7LcwC71WHszdgnaSNMVTCv4GA2m2d", + "Xthmnnjj9srFoe2+Lme/23oyXJh+inf0QCRlUOeHWjHAOoOG4inTwfBfql0WjqZkJ6fEavKxEWwwhq1i", + "b9/BjBrChgIwbPyF+dzrPU6A7akDOPZOhPrInj5AP/iwQVJS5jydDbPoY9YFGPdDvseEHjYb3F2EC9vF", + "QWIriVcHjwlctmRSU2YLr4FSKNZUtIyVDR8ZVnKOlb+DymH9sbxP9xJSbYT6wFclAW5SR8xMFjxy8KX0", + "1oD6UUffuIpbu8ps9WuX7mE2vQyAIIvF1n2cja8kcVxHJKCfFJ8ZWAF37wy0Y3tHRxgul5Bqdrkn4+I/", + "jJbaRPNPvR5rH7EJEjBYHbHmX969oXrdALQrIWInPEG5mjuDMxRvfQHbR4q0qCFaiHLqed5tEpURA8gd", + "EkMiQsU8ftbw5pwwTNWUgVjwHnbbHZrqb4MVwIP8oVvO5UmS0DCnaMeUlyKmuY+ay3S9UaYdBl8NJWX0", + "a/AOC0InWPJY1a831E/rBloNOe0XiLxyidKYH1Pbmn3KNCj/m0+Gs7PYJ5ubGuVo2b+iMvMtoqqq14KT", + "HfdRL5PC14/tAr2sZ2ZNPFQ/dj5SYASj3tJcKMZXyVDoYDsEKXzuDR2teB1gcWOEawnSvU2g/YvYiRY+", + "fmoXHLtQ4Z4muw0S1GCZTwvcYKr9+6aWAFZVo/Y9dOdEDhdo9FZqoJNBxv/wnLuQ/cp+98HivqrWCI3c", + "0WuyN2XfR8Ix1UNiSPVL4m7L/UHot9F6Gef2rRoVS//nBpWh9biUIqtSe0GHB6OxMYwtrrGDlUQVxrS/", + "yp7sn2OpmTdBSs8FbOdW/k7XlDc1f9rH2opQdg1BCm1nt+/VIBDXffKVXcDqXuD8I5Xq6aQUIk8GzMWn", + "/SoG3TNwwdILyIi5O3wMyUAVcPIVWilrf+DVeuuz9ssSOGSPZ4QYtbwo9da7Btv1+zqT80d61/wbnDWr", + "bGERp+/PPvJ4+BOW/JB35G9+mN1cTYFhfnecyg6yp0zAZqCCgqRXkZr4Y99pjDjrunXKG6KyUMSklFvm", + "jI46332dP0L6QW3f3dpPmFLusz5TIa3pCKUlb9DpCi8/Dj1wGGq1rvPD67W7EBACGFQy9vzuD4K5Q2c/", + "1mgPlhKjtTCxa89mX7RsQbYYWccXKyTcs00ocELd0CbUT1kbuzxcB25opaC/ztFnrYXbyDFr1jbWoNlH", + "7rAdUi/G2CHjhZNMdzSEWoRg1TGCoJJfD38lEpZYhVSQJ09wgidPpq7pr0/bn42i/eRJlAk/mAm09fKn", + "mzdGMf8Yit2x8SkDYWKd/ahYnu0jjFbQX1MRGMPafnHhkX9ITeJfrDWkf1RdedabOF+6m4CIiay1NXkw", + "VRDONyKSz3WbRd9mVZBWkuktZm165Zn9Eq2G8X1tb3P22jrPx6WZaHEBdd5vY52rlK/B+L2wb7kW5kZE", + "15fGt15eb2hR5uAOyjePFn+FZ397nh08O/zr4m8HLw5SeP7i5cEBffmcHr58dghP//bi+QEcLr9+uXia", + "PX3+dPH86fOvX7xMnz0/XDz/+uVfHxk+ZEC2gE58jsDkf2Ph7uT43WlyboBtcEJLVj/xZMjYFwGmKZ5E", + "o37mkyP/0//0J2yWiqIZ3v86cSHIk7XWpTqaz6+urmZhl/kK1fFEiypdz/08/ad13p3W4ZE2rQ131Ea+", + "GVLATXWkcIzf3r8+OyfH705nDcFMjiYHs4PZIdbaL4HTkk2OJs/wJzw9a9z3uSO2ydHn6+lkvgaao/Xa", + "/FGAliz1n9QVXa1Azlw1ZPPT5dO5j66af3amiOtd3+ZhYbH555bFJtvTE2svzT/7lMLdrVs5e85SFXQY", + "CcXwlPbxyPlnVP0Hf5+ju8OS49xbGOMtWwB/1huWXXd7uOfa5p+b9xOv7XnNIWZPtIG1NHhucUqYe3hc", + "2V/NEfX5PEy1n9us6e00M3Rmer2q35IMSpkcfejJ5HYg4kfCQ2korjkzrZkatqhlBWF1jZrpt9o3rP/D", + "QfLy0+fD6eHB9V8Ma3d/vnh2PdIx0LxvTs5qvj2y4SdMhkETBx6lpwcH/81eh39+wxXvVMRabvhI8fRv", + "aUZ8rDnOffhwc59ydMsYFkvsFXI9nbx4yNWfckPyNCfYMsjC7G/9z/yCiyvuW5r7vioKKrf+GKsWU/Av", + "xOKtQlcK1XLJLqmGySe0+8SCkgaYCz7Df2PmcmZ6fWEuD8VccJPug7m0B7pn5vL0hgf8z7/iL+z0z8ZO", + "zyy7G89OnShn05nm9j2oRsLrFfteQTSvCjOc6K5XW7sc9nvQvUdoJ3dkMX/Ye7T/vc/J84PnDwdBu1Lt", + "D7Alb4Um36Ev9E96Zscdn12SUEczyrIekVv2D0p/K7LtDgwValW6FISIXLJg3IDcv136LyX1Hom9gC2x", + "8QHeD+QeSW/LQ9d35AF/2vdsv/CQLzxE2umfPdz0ZyAvWQrkHIpSSCpZviU/8zqB9PZqXZZFYy/bR7/H", + "04w2kooMVsATx7CShci2vnhYa8ALsEbsnqAy/9yuAGwNZYNmqRP8vX6YrA/0YktOT3oSjO3W5bTfbrFp", + "R2OM6IRdEHdqhl1eNKCM7SJzs5CV0MRiIXOL+sJ4vjCeOwkvow9PTH6JahPekNO9k6e+kkKs1gjV/anH", + "6Bx/6HG9l43u6zMx/cXGqEJGgg82maKL5i8s4QtLuBtL+B4ihxFPrWMSEaK7jaW3zyAwHC/rvqOBgQ6+", + "eZVTSRSMNVMc44jOOPEQXOKhlbQorqyORjmBDVP4LlRkw+5Xb/vC4r6wuD+R12o/o2kLIjfWdC5gW9Cy", + "1m/UutKZuLIVyKJcEYtz09xV8sR8xTpmQwviB2iy3shPLmM432JyH8uMGKdZAUakqnmd6exjmZtgajNC", + "86DqinGcAFkFzmJL1tIgn0RBKrh9frDja3OQvbU6YYzJ/lYBcjSHGwfjZNpytrhtjBSIvbP81feNXO+w", + "pSNVIFFE4jHqBwZbf8+vKNPJUkiXa4bo63fWQPO5K8TT+bVJKu99wUz54McgsCP+67wuqB792A1eiX11", + "ESO+UROdFkZ74QbXcV4fPpl9wnqcbu+b4KWj+RwTNNZC6fnkevq5E9gUfvxUb83n+lp2W3T96fr/BwAA", + "///X/FsWRbgAAA==", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index 17485641d5..83d9ea4020 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -366,6 +366,19 @@ type EvalDeltaKeyValue struct { Value EvalDelta `json:"value"` } +// LightBlockHeaderProof defines model for LightBlockHeaderProof. +type LightBlockHeaderProof struct { + + // The index of the light block header in the vector commitment tree + Index uint64 `json:"index"` + + // The encoded proof. + Proof []byte `json:"proof"` + + // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. + Treedepth uint64 `json:"treedepth"` +} + // ParticipationKey defines model for ParticipationKey. type ParticipationKey struct { @@ -443,6 +456,16 @@ type PendingTransactionResponse struct { // StateDelta defines model for StateDelta. type StateDelta []EvalDeltaKeyValue +// StateProof defines model for StateProof. +type StateProof struct { + + // The encoded message. + Message []byte `json:"Message"` + + // The encoded StateProof for the message. + StateProof []byte `json:"StateProof"` +} + // TealKeyValue defines model for TealKeyValue. type TealKeyValue struct { Key string `json:"key"` @@ -637,6 +660,9 @@ type DryrunResponse struct { Txns []DryrunTxnResult `json:"txns"` } +// LightBlockHeaderProofResponse defines model for LightBlockHeaderProofResponse. +type LightBlockHeaderProofResponse LightBlockHeaderProof + // NodeStatusResponse defines model for NodeStatusResponse. type NodeStatusResponse struct { @@ -737,6 +763,9 @@ type ProofResponse struct { Treedepth uint64 `json:"treedepth"` } +// StateProofResponse defines model for StateProofResponse. +type StateProofResponse StateProof + // SupplyResponse defines model for SupplyResponse. type SupplyResponse struct { diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index fa62e543f5..3539f8f9a3 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -38,12 +38,18 @@ type ServerInterface interface { // Get the block for the given round. // (GET /v2/blocks/{round}) GetBlock(ctx echo.Context, round uint64, params GetBlockParams) error + // Gets a proof for a given light block header inside a state proof commitment + // (GET /v2/blocks/{round}/lightheader/proof) + GetProofForLightBlockHeader(ctx echo.Context, round uint64) error // Get a Merkle proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) GetProof(ctx echo.Context, round uint64, txid string, params GetProofParams) error // Get the current supply reported by the ledger. // (GET /v2/ledger/supply) GetSupply(ctx echo.Context) error + // Get a state proof that covers a given round + // (GET /v2/stateproofs/{round}) + GetStateProof(ctx echo.Context, round uint64) error // Gets the current node status. // (GET /v2/status) GetStatus(ctx echo.Context) error @@ -391,6 +397,36 @@ func (w *ServerInterfaceWrapper) GetBlock(ctx echo.Context) error { return err } +// GetProofForLightBlockHeader converts echo context to params. +func (w *ServerInterfaceWrapper) GetProofForLightBlockHeader(ctx echo.Context) error { + + validQueryParams := map[string]bool{ + "pretty": true, + } + + // Check for unknown query parameters. + for name, _ := range ctx.QueryParams() { + if _, ok := validQueryParams[name]; !ok { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unknown parameter detected: %s", name)) + } + } + + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameter("simple", false, "round", ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set("api_key.Scopes", []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetProofForLightBlockHeader(ctx, round) + return err +} + // GetProof converts echo context to params. func (w *ServerInterfaceWrapper) GetProof(ctx echo.Context) error { @@ -476,6 +512,36 @@ func (w *ServerInterfaceWrapper) GetSupply(ctx echo.Context) error { return err } +// GetStateProof converts echo context to params. +func (w *ServerInterfaceWrapper) GetStateProof(ctx echo.Context) error { + + validQueryParams := map[string]bool{ + "pretty": true, + } + + // Check for unknown query parameters. + for name, _ := range ctx.QueryParams() { + if _, ok := validQueryParams[name]; !ok { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unknown parameter detected: %s", name)) + } + } + + var err error + // ------------- Path parameter "round" ------------- + var round uint64 + + err = runtime.BindStyledParameter("simple", false, "round", ctx.Param("round"), &round) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter round: %s", err)) + } + + ctx.Set("api_key.Scopes", []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetStateProof(ctx, round) + return err +} + // GetStatus converts echo context to params. func (w *ServerInterfaceWrapper) GetStatus(ctx echo.Context) error { @@ -771,8 +837,10 @@ func RegisterHandlers(router interface { router.GET("/v2/applications/:application-id", wrapper.GetApplicationByID, m...) router.GET("/v2/assets/:asset-id", wrapper.GetAssetByID, m...) router.GET("/v2/blocks/:round", wrapper.GetBlock, m...) + router.GET("/v2/blocks/:round/lightheader/proof", wrapper.GetProofForLightBlockHeader, m...) router.GET("/v2/blocks/:round/transactions/:txid/proof", wrapper.GetProof, m...) router.GET("/v2/ledger/supply", wrapper.GetSupply, m...) + router.GET("/v2/stateproofs/:round", wrapper.GetStateProof, m...) router.GET("/v2/status", wrapper.GetStatus, m...) router.GET("/v2/status/wait-for-block-after/:round", wrapper.WaitForBlock, m...) router.POST("/v2/teal/compile", wrapper.TealCompile, m...) @@ -788,206 +856,212 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "+/j/AwAA//92ZqolGSgBAA==", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index 53983915d6..ec2587265b 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -366,6 +366,19 @@ type EvalDeltaKeyValue struct { Value EvalDelta `json:"value"` } +// LightBlockHeaderProof defines model for LightBlockHeaderProof. +type LightBlockHeaderProof struct { + + // The index of the light block header in the vector commitment tree + Index uint64 `json:"index"` + + // The encoded proof. + Proof []byte `json:"proof"` + + // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. + Treedepth uint64 `json:"treedepth"` +} + // ParticipationKey defines model for ParticipationKey. type ParticipationKey struct { @@ -443,6 +456,16 @@ type PendingTransactionResponse struct { // StateDelta defines model for StateDelta. type StateDelta []EvalDeltaKeyValue +// StateProof defines model for StateProof. +type StateProof struct { + + // The encoded message. + Message []byte `json:"Message"` + + // The encoded StateProof for the message. + StateProof []byte `json:"StateProof"` +} + // TealKeyValue defines model for TealKeyValue. type TealKeyValue struct { Key string `json:"key"` @@ -637,6 +660,9 @@ type DryrunResponse struct { Txns []DryrunTxnResult `json:"txns"` } +// LightBlockHeaderProofResponse defines model for LightBlockHeaderProofResponse. +type LightBlockHeaderProofResponse LightBlockHeaderProof + // NodeStatusResponse defines model for NodeStatusResponse. type NodeStatusResponse struct { @@ -737,6 +763,9 @@ type ProofResponse struct { Treedepth uint64 `json:"treedepth"` } +// StateProofResponse defines model for StateProofResponse. +type StateProofResponse StateProof + // SupplyResponse defines model for SupplyResponse. type SupplyResponse struct { diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 9e18ca7d25..141f71882f 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -46,6 +46,7 @@ import ( "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" + "github.com/algorand/go-algorand/stateproof" "github.com/algorand/go-codec/codec" ) @@ -74,6 +75,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) } // NodeInterface represents node fns used by the handlers. @@ -116,7 +118,7 @@ func convertParticipationRecord(record account.ParticipationRecord) generated.Pa } if record.StateProof != nil { - tmp := record.StateProof[:] + tmp := record.StateProof.Commitment[:] participationKey.Key.StateProofKey = &tmp } @@ -145,6 +147,31 @@ func convertParticipationRecord(record account.ParticipationRecord) generated.Pa return participationKey } +// ErrNoStateProofForRound returned when a state proof transaction could not be found +var ErrNoStateProofForRound = errors.New("no state proof can be found for that round") + +// GetStateProofTransactionForRound searches for a state proof transaction that can be used to prove on the given round (i.e the round is within the +// attestation period). the latestRound should be provided as an upper bound for the search +func GetStateProofTransactionForRound(txnFetcher LedgerForAPI, round basics.Round, latestRound basics.Round) (transactions.Transaction, error) { + for i := round + 1; i <= latestRound; i++ { + txns, err := txnFetcher.AddressTxns(transactions.StateProofSender, i) + if err != nil { + return transactions.Transaction{}, err + } + for _, txn := range txns { + if txn.Txn.Type != protocol.StateProofTx { + continue + } + + if txn.Txn.StateProofTxnFields.Message.FirstAttestedRound <= uint64(round) && + uint64(round) <= txn.Txn.StateProofTxnFields.Message.LastAttestedRound { + return txn.Txn, nil + } + } + } + return transactions.Transaction{}, ErrNoStateProofForRound +} + // GetParticipationKeys Return a list of participation keys // (GET /v2/participation) func (v2 *Handlers) GetParticipationKeys(ctx echo.Context) error { @@ -572,7 +599,7 @@ func (v2 *Handlers) GetProof(ctx echo.Context, round uint64, txid string, params var txID transactions.Txid err := txID.UnmarshalText([]byte(txid)) if err != nil { - return badRequest(ctx, err, errNoTxnSpecified, v2.Log) + return badRequest(ctx, err, errNoValidTxnSpecified, v2.Log) } if params.Hashtype != nil && *params.Hashtype != "sha512_256" && *params.Hashtype != "sha256" { @@ -914,7 +941,7 @@ func (v2 *Handlers) PendingTransactionInformation(ctx echo.Context, txid string, txID := transactions.Txid{} if err := txID.UnmarshalText([]byte(txid)); err != nil { - return badRequest(ctx, err, errNoTxnSpecified, v2.Log) + return badRequest(ctx, err, errNoValidTxnSpecified, v2.Log) } txn, ok := v2.Node.GetPendingTransaction(txID) @@ -1211,6 +1238,68 @@ func (v2 *Handlers) TealCompile(ctx echo.Context, params generated.TealCompilePa return ctx.JSON(http.StatusOK, response) } +// GetStateProof returns the state proof for a given round. +// (GET /v2/stateproofs/{round}) +func (v2 *Handlers) GetStateProof(ctx echo.Context, round uint64) error { + ledger := v2.Node.LedgerForAPI() + if ledger.Latest() < basics.Round(round) { + return internalError(ctx, errors.New(errRoundGreaterThanTheLatest), errRoundGreaterThanTheLatest, v2.Log) + } + tx, err := GetStateProofTransactionForRound(ledger, basics.Round(round), ledger.Latest()) + if err != nil { + if errors.Is(err, ErrNoStateProofForRound) { + return notFound(ctx, err, err.Error(), v2.Log) + } + return internalError(ctx, err, err.Error(), v2.Log) + } + + response := generated.StateProofResponse{ + Message: protocol.Encode(&tx.Message), + StateProof: protocol.Encode(&tx.StateProof), + } + + return ctx.JSON(http.StatusOK, response) +} + +// GetProofForLightBlockHeader Gets a proof of a light block header for a given round +// (GET /v2/blocks/{round}/lightheader/proof) +func (v2 *Handlers) GetProofForLightBlockHeader(ctx echo.Context, round uint64) error { + ledger := v2.Node.LedgerForAPI() + if ledger.Latest() < basics.Round(round) { + return internalError(ctx, errors.New(errRoundGreaterThanTheLatest), errRoundGreaterThanTheLatest, v2.Log) + } + + stateProof, err := GetStateProofTransactionForRound(ledger, basics.Round(round), ledger.Latest()) + if err != nil { + if errors.Is(err, ErrNoStateProofForRound) { + return notFound(ctx, err, err.Error(), v2.Log) + } + return internalError(ctx, err, err.Error(), v2.Log) + } + + lastAttestedRound := stateProof.Message.LastAttestedRound + firstAttestedRound := stateProof.Message.FirstAttestedRound + stateProofInterval := lastAttestedRound - firstAttestedRound + 1 + + lightHeaders, err := stateproof.FetchLightHeaders(ledger, stateProofInterval, basics.Round(lastAttestedRound)) + if err != nil { + return notFound(ctx, err, err.Error(), v2.Log) + } + + blockIndex := round - firstAttestedRound + leafproof, err := stateproof.GenerateProofOfLightBlockHeaders(stateProofInterval, lightHeaders, blockIndex) + if err != nil { + return internalError(ctx, err, err.Error(), v2.Log) + } + + response := generated.LightBlockHeaderProofResponse{ + Index: blockIndex, + Proof: leafproof.GetConcatenatedProof(), + Treedepth: uint64(leafproof.TreeDepth), + } + return ctx.JSON(http.StatusOK, response) +} + // TealDisassemble disassembles the program bytecode in base64 into TEAL code. // (POST /v2/teal/disassemble) func (v2 *Handlers) TealDisassemble(ctx echo.Context) error { 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 c8c7c7a111..b3062b42b1 100644 --- a/daemon/algod/api/server/v2/test/handlers_resources_test.go +++ b/daemon/algod/api/server/v2/test/handlers_resources_test.go @@ -28,6 +28,7 @@ import ( generatedV2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/logging" @@ -41,6 +42,7 @@ import ( type mockLedger struct { accounts map[basics.Address]basics.AccountData latest basics.Round + blocks []bookkeeping.Block } func (l *mockLedger) LookupAccount(round basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, basics.MicroAlgos, error) { @@ -112,6 +114,25 @@ func (l *mockLedger) Block(rnd basics.Round) (blk bookkeeping.Block, err error) panic("not implemented") } +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) + } + } + return res, nil +} + func randomAccountWithResources(N int) basics.AccountData { a := ledgertesting.RandomAccountData(0) a.Assets = make(map[basics.AssetIndex]basics.AssetHolding) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index af970f4b14..10e89deaf9 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -41,16 +41,20 @@ import ( "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/node" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/stateproof" "github.com/algorand/go-algorand/test/partitiontest" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-codec/codec" ) +const stateProofIntervalForHandlerTests = uint64(256) + func setupTestForMethodGet(t *testing.T) (v2.Handlers, echo.Context, *httptest.ResponseRecorder, []account.Root, []transactions.SignedTxn, func()) { numAccounts := 1 numTransactions := 1 @@ -942,18 +946,8 @@ func TestGetProofDefault(t *testing.T) { blkHdr, err := l.BlockHdr(1) a.NoError(err) - // Build merklearray.Proof from ProofResponse - var proof merklearray.Proof - proof.HashFactory = crypto.HashFactory{HashType: crypto.Sha512_256} - proof.TreeDepth = uint8(resp.Treedepth) - a.NotEqual(proof.TreeDepth, 0) - proofconcat := resp.Proof - for len(proofconcat) > 0 { - var d crypto.Digest - copy(d[:], proofconcat) - proof.Path = append(proof.Path, d[:]) - proofconcat = proofconcat[len(d):] - } + singleLeafProof, err := merklearray.ProofDataToSingleLeafProof(resp.Hashtype, resp.Treedepth, resp.Proof) + a.NoError(err) element := TxnMerkleElemRaw{Txn: crypto.Digest(txid)} copy(element.Stib[:], resp.Stibhash[:]) @@ -961,6 +955,207 @@ func TestGetProofDefault(t *testing.T) { elems[0] = &element // Verifies that the default proof is using SHA512_256 - err = merklearray.Verify(blkHdr.TxnCommitments.NativeSha512_256Commitment.ToSlice(), elems, &proof) + err = merklearray.Verify(blkHdr.TxnCommitments.NativeSha512_256Commitment.ToSlice(), elems, singleLeafProof.ToProof()) + a.NoError(err) +} + +func newEmptyBlock(a *require.Assertions, lastBlock bookkeeping.Block, genBlk bookkeeping.Block, l v2.LedgerForAPI) bookkeeping.Block { + totalsRound, totals, err := l.LatestTotals() + a.NoError(err) + a.Equal(l.Latest(), totalsRound) + + totalRewardUnits := totals.RewardUnits() + poolBal, _, _, err := l.LookupLatest(poolAddr) + a.NoError(err) + + latestBlock := lastBlock + + var blk bookkeeping.Block + blk.BlockHeader = bookkeeping.BlockHeader{ + GenesisID: genBlk.GenesisID(), + GenesisHash: genBlk.GenesisHash(), + Round: l.Latest() + 1, + Branch: latestBlock.Hash(), + RewardsState: latestBlock.NextRewardsState(l.Latest()+1, proto, poolBal.MicroAlgos, totalRewardUnits, logging.Base()), + UpgradeState: latestBlock.UpgradeState, + } + + blk.BlockHeader.TxnCounter = latestBlock.TxnCounter + + blk.RewardsPool = latestBlock.RewardsPool + blk.FeeSink = latestBlock.FeeSink + blk.CurrentProtocol = latestBlock.CurrentProtocol + blk.TimeStamp = latestBlock.TimeStamp + 1 + + blk.BlockHeader.TxnCounter++ + blk.TxnCommitments, err = blk.PaysetCommit() + a.NoError(err) + + return blk +} + +func addStateProofIfNeeded(blk bookkeeping.Block) bookkeeping.Block { + round := uint64(blk.Round()) + if round%stateProofIntervalForHandlerTests == (stateProofIntervalForHandlerTests/2+18) && round > stateProofIntervalForHandlerTests*2 { + return blk + } + stateProofRound := (round - round%stateProofIntervalForHandlerTests) - stateProofIntervalForHandlerTests + tx := transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.StateProofTx, + Header: transactions.Header{Sender: transactions.StateProofSender}, + StateProofTxnFields: transactions.StateProofTxnFields{ + StateProofType: 0, + Message: stateproofmsg.Message{ + BlockHeadersCommitment: []byte{0x0, 0x1, 0x2}, + FirstAttestedRound: stateProofRound + 1, + LastAttestedRound: stateProofRound + stateProofIntervalForHandlerTests, + }, + }, + }, + } + txnib := transactions.SignedTxnInBlock{SignedTxnWithAD: transactions.SignedTxnWithAD{SignedTxn: tx}} + blk.Payset = append(blk.Payset, txnib) + + return blk +} + +func insertRounds(a *require.Assertions, h v2.Handlers, numRounds int) { + ledger := h.Node.LedgerForAPI() + + genBlk, err := ledger.Block(0) + a.NoError(err) + + lastBlk := genBlk + for i := 0; i < numRounds; i++ { + blk := newEmptyBlock(a, lastBlk, genBlk, ledger) + blk = addStateProofIfNeeded(blk) + blk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + a.NoError(ledger.(*data.Ledger).AddBlock(blk, agreement.Certificate{})) + lastBlk = blk + } +} + +func TestStateProofNotFound(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + insertRounds(a, handler, 700) + + a.NoError(handler.GetStateProof(ctx, 650)) + a.Equal(404, responseRecorder.Code) +} + +func TestStateProofHigherRoundThanLatest(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + a.NoError(handler.GetStateProof(ctx, 2)) + a.Equal(500, responseRecorder.Code) +} + +func TestStateProof200(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + insertRounds(a, handler, 1000) + + a.NoError(handler.GetStateProof(ctx, stateProofIntervalForHandlerTests+1)) + a.Equal(200, responseRecorder.Code) + + stprfResp := generated.StateProofResponse{} + a.NoError(json.Unmarshal(responseRecorder.Body.Bytes(), &stprfResp)) + + msg := stateproofmsg.Message{} + a.NoError(protocol.Decode(stprfResp.Message, &msg)) + a.Equal([]byte{0x0, 0x1, 0x2}, msg.BlockHeadersCommitment) +} + +func TestHeaderProofRoundTooHigh(t *testing.T) { + partitiontest.PartitionTest(t) + + a := require.New(t) + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + a.NoError(handler.GetProofForLightBlockHeader(ctx, 2)) + a.Equal(500, responseRecorder.Code) +} + +func TestHeaderProofStateProofNotFound(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + insertRounds(a, handler, 700) + + a.NoError(handler.GetProofForLightBlockHeader(ctx, 650)) + a.Equal(404, responseRecorder.Code) +} + +func TestGetBlockProof200(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) + defer releasefunc() + + insertRounds(a, handler, 1000) + + a.NoError(handler.GetProofForLightBlockHeader(ctx, stateProofIntervalForHandlerTests*2+2)) + a.Equal(200, responseRecorder.Code) + + blkHdrArr, err := stateproof.FetchLightHeaders(handler.Node.LedgerForAPI(), stateProofIntervalForHandlerTests, basics.Round(stateProofIntervalForHandlerTests*3)) a.NoError(err) + + leafproof, err := stateproof.GenerateProofOfLightBlockHeaders(stateProofIntervalForHandlerTests, blkHdrArr, 1) + a.NoError(err) + + proofResp := generated.LightBlockHeaderProofResponse{} + a.NoError(json.Unmarshal(responseRecorder.Body.Bytes(), &proofResp)) + a.Equal(proofResp.Proof, leafproof.GetConcatenatedProof()) + a.Equal(proofResp.Treedepth, uint64(leafproof.TreeDepth)) +} + +func TestStateproofTransactionForRound(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + ledger := mockLedger{blocks: make([]bookkeeping.Block, 0, 1000)} + for i := 0; i <= 1000; i++ { + var blk bookkeeping.Block + blk.BlockHeader = bookkeeping.BlockHeader{ + Round: basics.Round(i), + } + blk = addStateProofIfNeeded(blk) + ledger.blocks = append(ledger.blocks, blk) + } + + txn, err := v2.GetStateProofTransactionForRound(&ledger, basics.Round(stateProofIntervalForHandlerTests*2+1), 1000) + a.NoError(err) + a.Equal(2*stateProofIntervalForHandlerTests+1, txn.Message.FirstAttestedRound) + a.Equal(3*stateProofIntervalForHandlerTests, txn.Message.LastAttestedRound) + a.Equal([]byte{0x0, 0x1, 0x2}, txn.Message.BlockHeadersCommitment) + + txn, err = v2.GetStateProofTransactionForRound(&ledger, basics.Round(2*stateProofIntervalForHandlerTests), 1000) + a.NoError(err) + a.Equal(stateProofIntervalForHandlerTests+1, txn.Message.FirstAttestedRound) + a.Equal(2*stateProofIntervalForHandlerTests, txn.Message.LastAttestedRound) + + txn, err = v2.GetStateProofTransactionForRound(&ledger, 999, 1000) + a.ErrorIs(err, v2.ErrNoStateProofForRound) + + txn, err = v2.GetStateProofTransactionForRound(&ledger, basics.Round(2*stateProofIntervalForHandlerTests), basics.Round(2*stateProofIntervalForHandlerTests)) + a.ErrorIs(err, v2.ErrNoStateProofForRound) } diff --git a/daemon/algod/api/server/v2/test/helpers.go b/daemon/algod/api/server/v2/test/helpers.go index 3ed69127e0..ba2a295491 100644 --- a/daemon/algod/api/server/v2/test/helpers.go +++ b/daemon/algod/api/server/v2/test/helpers.go @@ -88,6 +88,7 @@ type mockNode struct { err error id account.ParticipationID keys account.StateProofKeys + usertxns map[basics.Address][]node.TxnWithStatus } func (m mockNode) InstallParticipationKey(partKeyBinary []byte) (account.ParticipationID, error) { @@ -117,7 +118,9 @@ func makeMockNode(ledger v2.LedgerForAPI, genesisID string, nodeError error) *mo ledger: ledger, genesisID: genesisID, config: config.GetDefaultLocal(), - err: nodeError} + err: nodeError, + usertxns: map[basics.Address][]node.TxnWithStatus{}, + } } func (m mockNode) LedgerForAPI() v2.LedgerForAPI { @@ -167,7 +170,12 @@ func (m mockNode) ListeningAddress() (string, bool) { func (m mockNode) Stop() {} func (m mockNode) ListTxns(addr basics.Address, minRound basics.Round, maxRound basics.Round) ([]node.TxnWithStatus, error) { - return nil, fmt.Errorf("listtxns not implemented") + txns, ok := m.usertxns[addr] + if !ok { + return nil, fmt.Errorf("no txns for %s", addr) + } + + return txns, nil } func (m mockNode) GetTransaction(addr basics.Address, txID transactions.Txid, minRound basics.Round, maxRound basics.Round) (node.TxnWithStatus, bool) { diff --git a/daemon/algod/api/spec/v1/model.go b/daemon/algod/api/spec/v1/model.go index b01db103e4..9ae6fe23ce 100644 --- a/daemon/algod/api/spec/v1/model.go +++ b/daemon/algod/api/spec/v1/model.go @@ -524,10 +524,10 @@ type Transaction struct { // required: true ApplicationCall *ApplicationCallTransactionType `json:"app,omitempty"` - // CompactCert + // StateProof // // required: true - CompactCert *CompactCertTransactionType `json:"compactcert,omitempty"` + StateProof *StateProofTransactionType `json:"sp,omitempty"` // FromRewards is the amount of pending rewards applied to the From // account as part of this transaction. @@ -776,19 +776,20 @@ type ApplicationCallTransactionType struct { OnCompletion string `json:"oncompletion"` } -// CompactCertTransactionType contains the additional fields for a compact cert transaction -// swagger:model CompactCertTransactionType -type CompactCertTransactionType struct { - // CertRound is the round whose block this compact cert refers to. +// StateProofTransactionType contains the additional fields for a state proof transaction +// swagger:model StateProofTransactionType +type StateProofTransactionType struct { + // StateProof is the msgpack encoding of the state proof. // // required: true - CertRound uint64 `json:"rnd"` + // swagger:strfmt byte + StateProof []byte `json:"sp"` - // Cert is the msgpack encoding of the compact cert. + // StateProofMessage is the msgpack encoding of the state proof message. // // required: true // swagger:strfmt byte - Cert []byte `json:"cert"` + StateProofMessage []byte `json:"spmsg"` } // TransactionList contains a list of transactions @@ -941,24 +942,6 @@ type Block struct { UpgradeState UpgradeVote - - // CompactCertVoters is the root of the merkle tree of voters for compact certs. - // - // required: true - // swagger:strfmt byte - CompactCertVoters []byte `json:"compactCertVoters"` - - // CompactCertVotersTotal is the total amount of microalgos held by the voters in - // the CompactCertVoters merkle tree. - // - // required: true - CompactCertVotersTotal uint64 `json:"compactCertVotersTotal"` - - // CompactCertNextRound is the next round for which a compact certificate is - // expected. - // - // required: true - CompactCertNextRound uint64 `json:"compactCertNextRound"` } // UpgradeState contains the information about a current state of an upgrade diff --git a/daemon/algod/api/swagger.json b/daemon/algod/api/swagger.json index 5a788df54d..0455702221 100644 --- a/daemon/algod/api/swagger.json +++ b/daemon/algod/api/swagger.json @@ -1247,30 +1247,9 @@ "round", "period", "txnRoot", - "timestamp", - "compactCertVoters", - "compactCertVotersTotal", - "compactCertNextRound" + "timestamp" ], "properties": { - "compactCertNextRound": { - "description": "CompactCertNextRound is the next round for which a compact certificate is\nexpected.", - "type": "integer", - "format": "uint64", - "x-go-name": "CompactCertNextRound" - }, - "compactCertVoters": { - "description": "CompactCertVoters is the root of the merkle tree of voters for compact certs.", - "type": "string", - "format": "byte", - "x-go-name": "CompactCertVoters" - }, - "compactCertVotersTotal": { - "description": "CompactCertVotersTotal is the total amount of microalgos held by the voters in\nthe CompactCertVoters merkle tree.", - "type": "integer", - "format": "uint64", - "x-go-name": "CompactCertVotersTotal" - }, "currentProtocol": { "description": "CurrentProtocol is a string that represents the current protocol", "type": "string", @@ -1418,29 +1397,6 @@ }, "x-go-package": "github.com/algorand/go-algorand/daemon/algod/api/spec/common" }, - "CompactCertTransactionType": { - "description": "CompactCertTransactionType contains the additional fields for a compact cert transaction", - "type": "object", - "required": [ - "rnd", - "cert" - ], - "properties": { - "cert": { - "description": "Cert is the msgpack encoding of the compact cert.", - "type": "string", - "format": "byte", - "x-go-name": "Cert" - }, - "rnd": { - "description": "CertRound is the round whose block this compact cert refers to.", - "type": "integer", - "format": "uint64", - "x-go-name": "CertRound" - } - }, - "x-go-package": "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" - }, "KeyregTransactionType": { "description": "KeyregTransactionType contains the additional fields for a keyreg Transaction", "type": "object", @@ -1654,6 +1610,29 @@ }, "x-go-package": "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" }, + "StateProofTransactionType": { + "description": "StateProofTransactionType contains the additional fields for a state proof transaction", + "type": "object", + "required": [ + "sp", + "spmsg" + ], + "properties": { + "sp": { + "description": "StateProof is the msgpack encoding of the state proof.", + "type": "string", + "format": "byte", + "x-go-name": "StateProof" + }, + "spmsg": { + "description": "StateProofMessage is the msgpack encoding of the state proof message.", + "type": "string", + "format": "byte", + "x-go-name": "StateProofMessage" + } + }, + "x-go-package": "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" + }, "StateSchema": { "description": "swagger: model StateSchema", "type": "object", @@ -1719,7 +1698,7 @@ "first-round", "last-round", "app", - "compactcert", + "sp", "genesisID", "genesishashb64" ], @@ -1727,9 +1706,6 @@ "app": { "$ref": "#/definitions/ApplicationCallTransactionType" }, - "compactcert": { - "$ref": "#/definitions/CompactCertTransactionType" - }, "curcfg": { "$ref": "#/definitions/AssetConfigTransactionType" }, @@ -1814,6 +1790,9 @@ "format": "uint64", "x-go-name": "ConfirmedRound" }, + "sp": { + "$ref": "#/definitions/StateProofTransactionType" + }, "tx": { "description": "TxID is the transaction ID", "type": "string", diff --git a/data/account/msgp_gen.go b/data/account/msgp_gen.go index 5fa8daa6e5..96934260cf 100644 --- a/data/account/msgp_gen.go +++ b/data/account/msgp_gen.go @@ -4,8 +4,6 @@ package account import ( "github.com/algorand/msgp/msgp" - - "github.com/algorand/go-algorand/crypto/merklesignature" ) // The following msgp objects are implemented in this file: @@ -25,14 +23,6 @@ import ( // |-----> Msgsize // |-----> MsgIsZero // -// StateProofVerifier -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // MarshalMsg implements msgp.Marshaler func (z *ParticipationKeyIdentity) MarshalMsg(b []byte) (o []byte) { @@ -327,31 +317,3 @@ func (z StateProofKeys) Msgsize() (s int) { func (z StateProofKeys) MsgIsZero() bool { return len(z) == 0 } - -// MarshalMsg implements msgp.Marshaler -func (z *StateProofVerifier) MarshalMsg(b []byte) []byte { - return ((*(merklesignature.Verifier))(z)).MarshalMsg(b) -} -func (_ *StateProofVerifier) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*StateProofVerifier) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *StateProofVerifier) UnmarshalMsg(bts []byte) ([]byte, error) { - return ((*(merklesignature.Verifier))(z)).UnmarshalMsg(bts) -} -func (_ *StateProofVerifier) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*StateProofVerifier) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *StateProofVerifier) Msgsize() int { - return ((*(merklesignature.Verifier))(z)).Msgsize() -} - -// MsgIsZero returns whether this is a zero value -func (z *StateProofVerifier) MsgIsZero() bool { - return ((*(merklesignature.Verifier))(z)).MsgIsZero() -} diff --git a/data/account/participation.go b/data/account/participation.go index 6b05ff4ba4..3209fd7ea0 100644 --- a/data/account/participation.go +++ b/data/account/participation.go @@ -48,7 +48,7 @@ type Participation struct { VRF *crypto.VRFSecrets Voting *crypto.OneTimeSignatureSecrets - // StateProofSecrets is used to sign compact certificates. + // StateProofSecrets is used to sign state proofs. StateProofSecrets *merklesignature.Secrets // The first and last rounds for which this account is valid, respectively. @@ -141,7 +141,7 @@ func (part Participation) VotingSecrets() *crypto.OneTimeSignatureSecrets { return part.Voting } -// StateProofSigner returns the key used to sign on Compact Certificates. +// StateProofSigner returns the key used to sign on State Proofs. // might return nil! func (part Participation) StateProofSigner() *merklesignature.Secrets { return part.StateProofSecrets @@ -168,9 +168,9 @@ func (part Participation) GenerateRegistrationTransaction(fee basics.MicroAlgos, SelectionPK: part.VRF.PK, }, } - if cert := part.StateProofSigner(); cert != nil { + if stateProofSigner := part.StateProofSigner(); stateProofSigner != nil { if includeStateProofKeys { // TODO: remove this check and parameter after the network had enough time to upgrade - t.KeyregTxnFields.StateProofPK = *(cert.GetVerifier()) + t.KeyregTxnFields.StateProofPK = stateProofSigner.GetVerifier().Commitment } } t.KeyregTxnFields.VoteFirst = part.FirstValid @@ -220,10 +220,7 @@ func FillDBWithParticipationKeys(store db.Accessor, address basics.Address, firs return } - // TODO: change to ConsensusCurrentVersion when updated - interval := config.Consensus[protocol.ConsensusFuture].CompactCertRounds maxValidPeriod := config.Consensus[protocol.ConsensusCurrentVersion].MaxKeyregValidPeriod - if maxValidPeriod != 0 && uint64(lastValid-firstValid) > maxValidPeriod { return PersistedParticipation{}, fmt.Errorf("the validity period for mss is too large: the limit is %d", maxValidPeriod) } @@ -239,8 +236,8 @@ func FillDBWithParticipationKeys(store db.Accessor, address basics.Address, firs // Generate a new VRF key, which lives in the participation keys db vrf := crypto.GenerateVRFSecrets() - // Generate a new key which signs the compact certificates - stateProofSecrets, err := merklesignature.New(uint64(firstValid), uint64(lastValid), interval) + // Generate a new key which signs the state proof + stateProofSecrets, err := merklesignature.New(uint64(firstValid), uint64(lastValid), merklesignature.KeyLifetimeDefault) if err != nil { return PersistedParticipation{}, err } diff --git a/data/account/participationRegistry.go b/data/account/participationRegistry.go index a6544a91d7..e36f224516 100644 --- a/data/account/participationRegistry.go +++ b/data/account/participationRegistry.go @@ -79,14 +79,11 @@ type ( EffectiveFirst basics.Round EffectiveLast basics.Round - StateProof *StateProofVerifier + StateProof *merklesignature.Verifier VRF *crypto.VRFSecrets Voting *crypto.OneTimeSignatureSecrets } - // StateProofVerifier defined the type used for the stateproofs public key - StateProofVerifier merklesignature.Verifier - // StateProofKeys represents a set of ephemeral stateproof keys with their corresponding round //msgp:allocbound StateProofKeys 1000 StateProofKeys []merklesignature.KeyRoundPair @@ -97,10 +94,10 @@ type ( ParticipationRecord } - // StateProofRecordForRound contains participant's state proof secrets that corresponds to + // StateProofSecretsForRound contains participant's state proof secrets that corresponds to // one specific round. In Addition, it also returns the participation metadata. // If there are no secrets for the round a nil is returned in Stateproof field. - StateProofRecordForRound struct { + StateProofSecretsForRound struct { ParticipationRecord StateProofSecrets *merklesignature.Signer @@ -145,10 +142,11 @@ func (r ParticipationRecord) Duplicate() ParticipationRecord { voting = r.Voting.Snapshot() } - var stateProof *StateProofVerifier + var stateProof *merklesignature.Verifier if r.StateProof != nil { - stateProof = &StateProofVerifier{} - copy(stateProof[:], r.StateProof[:]) + stateProof = &merklesignature.Verifier{} + copy(stateProof.Commitment[:], r.StateProof.Commitment[:]) + stateProof.KeyLifetime = r.StateProof.KeyLifetime } dupParticipation := ParticipationRecord{ @@ -231,6 +229,9 @@ type ParticipationRegistry interface { // once, an error will occur when the data is flushed when inserting a duplicate key. AppendKeys(id ParticipationID, keys StateProofKeys) error + // DeleteStateProofKeys removes all stateproof keys preceding a given round (including) + DeleteStateProofKeys(id ParticipationID, round basics.Round) error + // Delete removes a record from storage. Delete(id ParticipationID) error @@ -246,8 +247,8 @@ type ParticipationRegistry interface { // GetForRound fetches a record with voting secrets for a particular round. GetForRound(id ParticipationID, round basics.Round) (ParticipationRecordForRound, error) - // GetStateProofForRound fetches a record with stateproof secrets for a particular round. - GetStateProofForRound(id ParticipationID, round basics.Round) (StateProofRecordForRound, error) + // GetStateProofSecretsForRound fetches a record with stateproof secrets for a particular round. + GetStateProofSecretsForRound(id ParticipationID, round basics.Round) (StateProofSecretsForRound, error) // HasLiveKeys quickly tests to see if there is a valid participation key over some range of rounds HasLiveKeys(from, to basics.Round) bool @@ -343,6 +344,7 @@ const ( insertKeysetQuery = `INSERT INTO Keysets (participationID, account, firstValidRound, lastValidRound, keyDilution, vrf, stateProof) VALUES (?, ?, ?, ?, ?, ?, ?)` insertRollingQuery = `INSERT INTO Rolling (pk, voting) VALUES (?, ?)` appendStateProofKeysQuery = `INSERT INTO StateProofKeys (pk, round, key) VALUES(?, ?, ?)` + deleteStateProofKeysQuery = `DELETE FROM StateProofKeys WHERE pk=? AND round<=?` // SELECT pk FROM Keysets WHERE participationID = ? selectPK = `SELECT pk FROM Keysets WHERE participationID = ? LIMIT 1` @@ -362,6 +364,7 @@ const ( AND pk IN (SELECT pk FROM Keysets WHERE participationID=?)` deleteKeysets = `DELETE FROM Keysets WHERE pk=?` deleteRolling = `DELETE FROM Rolling WHERE pk=?` + deleteStateProofByPK = `DELETE FROM StateProofKeys WHERE pk=?` updateRollingFieldsSQL = `UPDATE Rolling SET lastVoteRound=?, lastBlockProposalRound=?, @@ -412,6 +415,22 @@ type participationDB struct { flushTimeout time.Duration } +// DeleteStateProofKeys is a non-blocking operation, responsible for removing state-proof keys from the DB. +func (db *participationDB) DeleteStateProofKeys(id ParticipationID, round basics.Round) error { + db.mutex.Lock() + defer db.mutex.Unlock() + + if _, ok := db.cache[id]; !ok { + return ErrParticipationIDNotFound + } + + db.writeQueue <- makeOpRequest(&deleteStateProofKeysOp{ + ParticipationID: id, + round: round, + }) + return nil +} + type updatingParticipationRecord struct { ParticipationRecord @@ -444,6 +463,7 @@ func (db *participationDB) initializeCache() error { func (db *participationDB) writeThread() { defer close(db.writeQueueDone) var lastErr error + for op := range db.writeQueue { if err := op.operation.apply(db); err != nil { lastErr = err @@ -501,11 +521,11 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err *voting = record.Voting.Snapshot() } - var stateProofVeriferPtr *StateProofVerifier + var stateProofVerifierPtr *merklesignature.Verifier if record.StateProofSecrets != nil { - stateProofVeriferPtr = &StateProofVerifier{} - copy(stateProofVeriferPtr[:], record.StateProofSecrets.GetVerifier()[:]) - + stateProofVerifierPtr = &merklesignature.Verifier{} + copy(stateProofVerifierPtr.Commitment[:], record.StateProofSecrets.GetVerifier().Commitment[:]) + stateProofVerifierPtr.KeyLifetime = record.StateProofSecrets.GetVerifier().KeyLifetime } // update cache. @@ -520,7 +540,7 @@ func (db *participationDB) Insert(record Participation) (id ParticipationID, err LastStateProof: 0, EffectiveFirst: 0, EffectiveLast: 0, - StateProof: stateProofVeriferPtr, + StateProof: stateProofVerifierPtr, Voting: voting, VRF: vrf, } @@ -607,7 +627,7 @@ func scanRecords(rows *sql.Rows) ([]ParticipationRecord, error) { var lastVote sql.NullInt64 var lastBlockProposal sql.NullInt64 - var lastCompactCertificate sql.NullInt64 + var lastStateProof sql.NullInt64 var effectiveFirst sql.NullInt64 var effectiveLast sql.NullInt64 @@ -621,7 +641,7 @@ func scanRecords(rows *sql.Rows) ([]ParticipationRecord, error) { &rawStateProof, &lastVote, &lastBlockProposal, - &lastCompactCertificate, + &lastStateProof, &effectiveFirst, &effectiveLast, &rawVoting, @@ -647,8 +667,9 @@ func scanRecords(rows *sql.Rows) ([]ParticipationRecord, error) { if err != nil { return nil, fmt.Errorf("unable to decode stateproof: %w", err) } - var stateProofVerifer StateProofVerifier - copy(stateProofVerifer[:], stateProof.GetVerifier()[:]) + var stateProofVerifer merklesignature.Verifier + copy(stateProofVerifer.Commitment[:], stateProof.GetVerifier().Commitment[:]) + stateProofVerifer.KeyLifetime = stateProof.GetVerifier().KeyLifetime record.StateProof = &stateProofVerifer } @@ -669,8 +690,8 @@ func scanRecords(rows *sql.Rows) ([]ParticipationRecord, error) { record.LastBlockProposal = basics.Round(lastBlockProposal.Int64) } - if lastCompactCertificate.Valid { - record.LastStateProof = basics.Round(lastCompactCertificate.Int64) + if lastStateProof.Valid { + record.LastStateProof = basics.Round(lastStateProof.Int64) } if effectiveFirst.Valid { @@ -740,20 +761,25 @@ func (db *participationDB) HasLiveKeys(from, to basics.Round) bool { return false } -// GetStateProofForRound returns the state proof data required to sign the compact certificate for this round -func (db *participationDB) GetStateProofForRound(id ParticipationID, round basics.Round) (StateProofRecordForRound, error) { +// GetStateProofSecretsForRound returns the state proof data required to sign the compact certificate for this round +func (db *participationDB) GetStateProofSecretsForRound(id ParticipationID, round basics.Round) (StateProofSecretsForRound, error) { partRecord, err := db.GetForRound(id, round) if err != nil { - return StateProofRecordForRound{}, err + return StateProofSecretsForRound{}, err } - var result StateProofRecordForRound + var result StateProofSecretsForRound result.ParticipationRecord = partRecord.ParticipationRecord var rawStateProofKey []byte err = db.store.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { // fetch secret key - row := tx.QueryRow(selectStateProofKey, round, id[:]) - err := row.Scan(&rawStateProofKey) + keyFirstValidRound, err := partRecord.StateProof.FirstRoundInKeyLifetime(uint64(round)) + if err != nil { + return err + } + + row := tx.QueryRow(selectStateProofKey, keyFirstValidRound, id[:]) + err = row.Scan(&rawStateProofKey) if err == sql.ErrNoRows { return ErrSecretNotFound } @@ -763,13 +789,8 @@ func (db *participationDB) GetStateProofForRound(id ParticipationID, round basic return nil }) - switch err { - case nil: - // no error, continue - case ErrSecretNotFound: // not considered an error (yet), since some accounts may not have registered state proof yet - return result, nil - default: - return StateProofRecordForRound{}, err + if err != nil { + return StateProofSecretsForRound{}, fmt.Errorf("failed to fetch state proof for round %d: %w", round, err) } // Init stateproof fields after being able to retrieve key from database @@ -779,7 +800,7 @@ func (db *participationDB) GetStateProofForRound(id ParticipationID, round basic err = protocol.Decode(rawStateProofKey, result.StateProofSecrets.SigningKey) if err != nil { - return StateProofRecordForRound{}, err + return StateProofSecretsForRound{}, err } var rawSignerContext []byte @@ -793,11 +814,11 @@ func (db *participationDB) GetStateProofForRound(id ParticipationID, round basic return nil }) if err != nil { - return StateProofRecordForRound{}, err + return StateProofSecretsForRound{}, err } err = protocol.Decode(rawSignerContext, &result.StateProofSecrets.SignerContext) if err != nil { - return StateProofRecordForRound{}, err + return StateProofSecretsForRound{}, err } return result, nil } diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index 394f4bbe6c..61b78adac7 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -48,7 +48,7 @@ import ( ) // TODO: change to ConsensusCurrentVersion when updated -var CompactCertRounds = config.Consensus[protocol.ConsensusFuture].CompactCertRounds +var stateProofIntervalForTests = config.Consensus[protocol.ConsensusFuture].StateProofInterval func getRegistry(t testing.TB) (registry *participationDB, dbfile string) { return getRegistryImpl(t, true, false) @@ -82,16 +82,21 @@ func assertParticipation(t testing.TB, p Participation, pr ParticipationRecord) require.Equal(t, p.KeyDilution, pr.KeyDilution) require.Equal(t, p.Parent, pr.Account) if p.StateProofSecrets != nil { - require.Equal(t, p.StateProofSecrets.GetVerifier()[:], pr.StateProof[:]) + require.Equal(t, p.StateProofSecrets.GetVerifier().Commitment[:], pr.StateProof.Commitment[:]) + require.Equal(t, p.StateProofSecrets.GetVerifier().KeyLifetime, pr.StateProof.KeyLifetime) } } func makeTestParticipation(a *require.Assertions, addrID int, first, last basics.Round, dilution uint64) Participation { + return makeTestParticipationWithLifetime(a, addrID, first, last, dilution, uint64((last+1)/2)) +} + +func makeTestParticipationWithLifetime(a *require.Assertions, addrID int, first, last basics.Round, dilution uint64, keyLifetime uint64) Participation { a.True(first < last) // Generate sample of stateproof keys. because it might take time we will reduce the number always to get 2 keys - stateProofSecrets, err := merklesignature.New(uint64(first), uint64(last), (uint64(last)+1)/2) + stateProofSecrets, err := merklesignature.New(uint64(first), uint64(last), keyLifetime) a.NoError(err) // Generate part keys like in partGenerateCmd and FillDBWithParticipationKeys @@ -316,6 +321,67 @@ func TestParticipation_DeleteExpired(t *testing.T) { checkExpired(registry.GetAll()) } +func TestParticipation_CleanupTablesAfterDeleteExpired(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + registry, dbfile := getRegistryImpl(t, false, true) // inMem=false, erasable=true + defer registryCloseTest(t, registry, dbfile) + + keyDilution := 1 + for i := 10; i < 20; i++ { + p := makeTestParticipation(a, i, 1, basics.Round(i), uint64(keyDilution)) + id, err := registry.Insert(p) + a.NoError(err) + a.Equal(p.ID(), id) + + err = registry.AppendKeys(id, p.StateProofSecrets.GetAllKeys()) + a.NoError(err) + } + + a.NoError(registry.Flush(defaultTimeout)) + + latestRound := basics.Round(50) + err := registry.DeleteExpired(latestRound, config.Consensus[protocol.ConsensusCurrentVersion]) + a.NoError(err) + + a.NoError(registry.Flush(defaultTimeout)) + var numOfRecords int + // make sure tables are clean + err = registry.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + row := tx.QueryRow(`select count(*) from Keysets`) + err = row.Scan(&numOfRecords) + if err != nil { + return fmt.Errorf("unable to scan pk: %w", err) + } + return nil + }) + + a.NoError(err) + a.Equal(0, numOfRecords) + + err = registry.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + row := tx.QueryRow(`select count(*) from Rolling`) + err = row.Scan(&numOfRecords) + if err != nil { + return fmt.Errorf("unable to scan pk: %w", err) + } + return nil + }) + a.NoError(err) + a.Equal(0, numOfRecords) + + err = registry.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + row := tx.QueryRow(`select count(*) from stateproofkeys`) + err = row.Scan(&numOfRecords) + if err != nil { + return fmt.Errorf("unable to scan pk: %w", err) + } + return nil + }) + a.NoError(err) + a.Equal(0, numOfRecords) +} + // Make sure the register function properly sets effective first/last for all effected records. func TestParticipation_Register(t *testing.T) { partitiontest.PartitionTest(t) @@ -849,7 +915,7 @@ func TestAddStateProofKeys(t *testing.T) { // Install a key to add StateProof keys. max := uint64(20) - p := makeTestParticipation(a, 1, 0, basics.Round(max), 3) + p := makeTestParticipationWithLifetime(a, 1, 0, basics.Round(max), 3, 3) id, err := registry.Insert(p) a.NoError(err) a.Equal(p.ID(), id) @@ -861,15 +927,7 @@ func TestAddStateProofKeys(t *testing.T) { signer, err := merklesignature.New(1, max, 3) a.NoError(err) // Initialize keys array. - var keys StateProofKeys - for i := uint64(1); i < max; i++ { - k := signer.GetKey(i) - if k == nil { - continue - } - keysRound := merklesignature.KeyRoundPair{Round: i, Key: k} - keys = append(keys, keysRound) - } + keys := signer.GetAllKeys() err = registry.AppendKeys(id, keys) a.NoError(err) @@ -878,18 +936,26 @@ func TestAddStateProofKeys(t *testing.T) { err = registry.Flush(10 * time.Second) a.NoError(err) - j := 0 + _, err = registry.GetStateProofSecretsForRound(id, basics.Round(1)) + a.Error(err) + _, err = registry.GetStateProofSecretsForRound(id, basics.Round(2)) + a.Error(err) + // Make sure we're able to fetch the same data that was put in. - for i := uint64(1); i < max; i++ { - r, err := registry.GetStateProofForRound(id, basics.Round(i)) + for i := uint64(3); i < max; i++ { + r, err := registry.GetStateProofSecretsForRound(id, basics.Round(i)) a.NoError(err) if r.StateProofSecrets != nil { + j := i/3 - 1 // idx in keys array + a.Equal(*keys[j].Key, *r.StateProofSecrets.SigningKey) - a.Equal(keys[j].Round, i) - j++ - } + keyFirstValidRound, err := r.StateProofSecrets.FirstRoundInKeyLifetime() + a.NoError(err) + + a.Equal(keys[j].Round, keyFirstValidRound) + } } } @@ -905,12 +971,9 @@ func TestSecretNotFound(t *testing.T) { a.NoError(err) a.Equal(p.ID(), id) - r, err := registry.GetStateProofForRound(id, basics.Round(2)) + _, err = registry.GetForRound(id, basics.Round(2)) a.NoError(err) - // Empty stateproof key - a.Nil(r.StateProofSecrets) - _, err = registry.GetForRound(id, basics.Round(100)) a.ErrorIs(err, ErrRequestedRoundOutOfRange) } @@ -926,7 +989,7 @@ func TestAddingSecretTwice(t *testing.T) { panic(err) } root, err := GenerateRoot(access) - p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(CompactCertRounds*2), 3) + p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(stateProofIntervalForTests*2), 3) access.Close() a.NoError(err) @@ -938,7 +1001,7 @@ func TestAddingSecretTwice(t *testing.T) { // Append key var keys StateProofKeys - keysRound := merklesignature.KeyRoundPair{Round: CompactCertRounds, Key: p.StateProofSecrets.GetKey(CompactCertRounds)} + keysRound := merklesignature.KeyRoundPair{Round: stateProofIntervalForTests, Key: p.StateProofSecrets.GetKey(stateProofIntervalForTests)} keys = append(keys, keysRound) err = registry.AppendKeys(id, keys) @@ -964,7 +1027,7 @@ func TestGetRoundSecretsWithoutStateProof(t *testing.T) { panic(err) } root, err := GenerateRoot(access) - p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(CompactCertRounds*2), 3) + p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(stateProofIntervalForTests*2), 3) access.Close() a.NoError(err) @@ -974,34 +1037,109 @@ func TestGetRoundSecretsWithoutStateProof(t *testing.T) { a.NoError(registry.Flush(defaultTimeout)) - partPerRound, err := registry.GetStateProofForRound(id, 1) - a.NoError(err) - a.Nil(partPerRound.StateProofSecrets) + partPerRound, err := registry.GetStateProofSecretsForRound(id, 1) + a.Error(err) - // Should return nil as well since no state proof keys were added - partPerRound, err = registry.GetStateProofForRound(id, basics.Round(CompactCertRounds)) - a.NoError(err) - a.Nil(partPerRound.StateProofSecrets) + partPerRound, err = registry.GetStateProofSecretsForRound(id, basics.Round(stateProofIntervalForTests)) + a.Error(err) // Append key keys := make(StateProofKeys, 1) - keys[0] = merklesignature.KeyRoundPair{Round: CompactCertRounds, Key: p.StateProofSecrets.GetKey(CompactCertRounds)} + keys[0] = merklesignature.KeyRoundPair{Round: stateProofIntervalForTests, Key: p.StateProofSecrets.GetKey(stateProofIntervalForTests)} err = registry.AppendKeys(id, keys) a.NoError(err) a.NoError(registry.Flush(defaultTimeout)) - partPerRound, err = registry.GetStateProofForRound(id, basics.Round(CompactCertRounds)-1) - a.NoError(err) - a.Nil(partPerRound.StateProofSecrets) + partPerRound, err = registry.GetStateProofSecretsForRound(id, basics.Round(stateProofIntervalForTests)-1) + a.Error(err) - partPerRound, err = registry.GetStateProofForRound(id, basics.Round(CompactCertRounds)) + partPerRound, err = registry.GetStateProofSecretsForRound(id, basics.Round(stateProofIntervalForTests)) a.NoError(err) a.NotNil(partPerRound.StateProofSecrets) a.Equal(*partPerRound.StateProofSecrets.SigningKey, *keys[0].Key) - a.Equal(CompactCertRounds, keys[0].Round) + a.Equal(stateProofIntervalForTests, keys[0].Round) +} + +type keypairs []merklesignature.KeyRoundPair + +func (k keypairs) findPairForSpecificRound(round uint64) merklesignature.KeyRoundPair { + for _, pair := range k { + if pair.Round == round { + return pair + } + } + return merklesignature.KeyRoundPair{} +} + +func TestDeleteStateProofKeys(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + registry, dbfile := getRegistry(t) + defer registryCloseTest(t, registry, dbfile) + + // Install a key to add StateProof keys. + maxRound := uint64(20) + p := makeTestParticipationWithLifetime(a, 1, 0, basics.Round(maxRound), 3, 4) + id, err := registry.Insert(p) + a.NoError(err) + a.Equal(p.ID(), id) + + // Wait for async DB operations to finish. + a.NoError(registry.Flush(10 * time.Second)) + + keys := keypairs(p.StateProofSecrets.GetAllKeys()) + + a.NoError(registry.AppendKeys(id, StateProofKeys(keys))) + + // Wait for async DB operations to finish. + a.NoError(registry.Flush(10 * time.Second)) + + // Make sure we're able to fetch the same data that was put in. + for i := uint64(4); i < maxRound; i += 4 { + r, err := registry.GetStateProofSecretsForRound(id, basics.Round(i)) + a.NoError(err) + + a.Equal(keys.findPairForSpecificRound(i).Key, r.StateProofSecrets.SigningKey) + } + + removeKeysRound := basics.Round(maxRound / 2) + a.NoError(registry.DeleteStateProofKeys(id, removeKeysRound)) + + a.NoError(registry.Flush(10 * time.Second)) + + // verify that the db does not contain any state proof key with round less than 10 + + registry.store.Rdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + var pk int + a.NoError(tx.QueryRow(selectPK, id[:]).Scan(&pk)) + + // make certain keys below the cutting round do not exist in the db. + var num int + a.NoError( + tx.QueryRow( + "SELECT COUNT(*) FROM StateProofKeys where pk=? AND round <=?", + pk, + removeKeysRound, + ).Scan(&num), + ) + a.Zero(num) + + // make certain keys above the cutting round exist in the db. + a.NoError( + tx.QueryRow( + "SELECT COUNT(*) FROM StateProofKeys where pk=? AND round >?", + pk, + removeKeysRound, + ).Scan(&num), + ) + + // includes removeKeysRound + a.Equal(int(maxRound)/4-int(removeKeysRound)/4, num) // 1 DELETED 1 NOT + return nil + }) } // test that sets up an error that should come up while flushing, and ensures that flush resets the last error @@ -1015,7 +1153,7 @@ func TestFlushResetsLastError(t *testing.T) { a.NoError(err) root, err := GenerateRoot(access) - p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(CompactCertRounds*2), 3) + p, err := FillDBWithParticipationKeys(access, root.Address(), 0, basics.Round(stateProofIntervalForTests*2), 3) access.Close() a.NoError(err) @@ -1027,7 +1165,7 @@ func TestFlushResetsLastError(t *testing.T) { // Append key var keys StateProofKeys - keysRound := merklesignature.KeyRoundPair{Round: CompactCertRounds, Key: p.StateProofSecrets.GetKey(CompactCertRounds)} + keysRound := merklesignature.KeyRoundPair{Round: stateProofIntervalForTests, Key: p.StateProofSecrets.GetKey(stateProofIntervalForTests)} keys = append(keys, keysRound) err = registry.AppendKeys(id, keys) @@ -1100,7 +1238,7 @@ func TestParticipationDB_Locking(t *testing.T) { time.Sleep(time.Second) goto repeat } - _, err = registry.GetStateProofForRound(id2, basics.Round(256)) + _, err = registry.GetStateProofSecretsForRound(id2, basics.Round(256)) // The error we're trying to avoid is "database is locked", since we're reading from StateProofKeys table, // while the main thread is updating the Rolling table. a.NoError(err) @@ -1113,6 +1251,57 @@ func TestParticipationDB_Locking(t *testing.T) { wg.Wait() } +func TestParticipationDBInstallWhileReading(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + if testing.Short() { + t.Skip() + } + + dbName := strings.Replace(t.Name(), "/", "_", -1) + + dbpair, err := db.OpenErasablePair(dbName + ".sqlite3") + a.NoError(err) + + registry, err := makeParticipationRegistry(dbpair, logging.TestingLog(t)) + require.NoError(t, err) + require.NotNil(t, registry) + defer registryCloseTest(t, registry, dbName) + + var sampledPartID ParticipationID + for i := 0; i < 3; i++ { + part := makeTestParticipation(a, 1, 0, 511, config.Consensus[protocol.ConsensusCurrentVersion].DefaultKeyDilution) + id, err := registry.Insert(part) + if i == 0 { + sampledPartID = id + } + a.NoError(err) + a.NoError(registry.AppendKeys(id, part.StateProofSecrets.GetAllKeys())) + a.NoError(registry.Flush(defaultTimeout)) + a.Equal(id, part.ID()) + } + + appendedKeys := make(chan struct{}) + newPart := makeTestParticipationWithLifetime(a, 1, 0, 3000000, config.Consensus[protocol.ConsensusCurrentVersion].DefaultKeyDilution, merklesignature.KeyLifetimeDefault) + go func() { + id, err := registry.Insert(newPart) + a.NoError(err) + a.NoError(registry.AppendKeys(id, newPart.StateProofSecrets.GetAllKeys())) + appendedKeys <- struct{}{} + a.NoError(registry.Flush(defaultTimeout)) + a.Equal(id, newPart.ID()) + }() + + <-appendedKeys // Makes sure we start fetching keys after the append keys operation has already started + for i := 0; i < 50; i++ { + _, err = registry.GetStateProofSecretsForRound(sampledPartID, basics.Round(256)) + // The error we're trying to avoid is "database is locked", since we're reading from StateProofKeys table, + // while a different go routine is installing new keys. + a.NoError(err) + } +} + // based on BenchmarkOldKeysDeletion func BenchmarkDeleteExpired(b *testing.B) { for _, erasable := range []bool{true, false} { diff --git a/data/account/participation_test.go b/data/account/participation_test.go index 19eab3f731..9a48bb7a05 100644 --- a/data/account/participation_test.go +++ b/data/account/participation_test.go @@ -435,7 +435,7 @@ func BenchmarkFillDB(b *testing.B) { tmp := config.Consensus[protocol.ConsensusCurrentVersion] cpy := config.Consensus[protocol.ConsensusCurrentVersion] - cpy.CompactCertRounds = 256 + cpy.StateProofInterval = 256 config.Consensus[protocol.ConsensusCurrentVersion] = cpy defer func() { config.Consensus[protocol.ConsensusCurrentVersion] = tmp }() diff --git a/data/account/registeryDbOps.go b/data/account/registeryDbOps.go index 691ca5124a..6fa69bb153 100644 --- a/data/account/registeryDbOps.go +++ b/data/account/registeryDbOps.go @@ -21,6 +21,7 @@ import ( "database/sql" "errors" "fmt" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/protocol" "strings" ) @@ -53,6 +54,40 @@ type appendKeysOp struct { id ParticipationID keys StateProofKeys } +type deleteStateProofKeysOp struct { + ParticipationID ParticipationID + round basics.Round +} + +func (d deleteStateProofKeysOp) apply(db *participationDB) error { + err := db.store.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) error { + + // Fetch primary key + var pk int + row := tx.QueryRow(selectPK, d.ParticipationID[:]) + err := row.Scan(&pk) + if err != nil { + return fmt.Errorf("unable to scan pk: %w", err) + } + + stmt, err := tx.Prepare(deleteStateProofKeysQuery) + if err != nil { + return fmt.Errorf("unable to prepare state proof delete: %w", err) + } + defer stmt.Close() + + _, err = stmt.Exec(pk, d.round) + if err != nil { + return fmt.Errorf("unable to exec state proof delete (pk,rnd) == (%d,%d): %w", pk, d.round, err) + } + return nil + }) + + if err != nil { + db.log.Warnf("participationDB unable to delete stateProof key: %w", err) + } + return err +} func makeOpRequest(operation dbOp) opRequest { return opRequest{operation: operation} @@ -167,6 +202,11 @@ func (d *deleteOp) apply(db *participationDB) error { return err } + _, err = tx.Exec(deleteStateProofByPK, pk) + if err != nil { + return err + } + return nil }) return err diff --git a/data/accountManager.go b/data/accountManager.go index 9763c44812..d44091f806 100644 --- a/data/accountManager.go +++ b/data/accountManager.go @@ -45,6 +45,11 @@ type AccountManager struct { log logging.Logger } +// DeleteStateProofKey deletes all keys connected to ParticipationID that came before (including) the given round. +func (manager *AccountManager) DeleteStateProofKey(id account.ParticipationID, round basics.Round) error { + return manager.registry.DeleteStateProofKeys(id, round) +} + // MakeAccountManager creates a new AccountManager with a custom logger func MakeAccountManager(log logging.Logger, registry account.ParticipationRegistry) *AccountManager { manager := &AccountManager{} @@ -72,12 +77,12 @@ func (manager *AccountManager) Keys(rnd basics.Round) (out []account.Participati } // StateProofKeys returns a list of Participation accounts, and their stateproof secrets -func (manager *AccountManager) StateProofKeys(rnd basics.Round) (out []account.StateProofRecordForRound) { +func (manager *AccountManager) StateProofKeys(rnd basics.Round) (out []account.StateProofSecretsForRound) { for _, part := range manager.registry.GetAll() { if part.OverlapsInterval(rnd, rnd) { - partRndSecrets, err := manager.registry.GetStateProofForRound(part.ParticipationID, rnd) + partRndSecrets, err := manager.registry.GetStateProofSecretsForRound(part.ParticipationID, rnd) if err != nil { - manager.log.Warnf("error while loading round secrets from participation registry: %w", err) + manager.log.Errorf("error while loading round secrets from participation registry: %w", err) continue } out = append(out, partRndSecrets) diff --git a/data/accountManager_test.go b/data/accountManager_test.go index 9fe6009dc3..9d464cba17 100644 --- a/data/accountManager_test.go +++ b/data/accountManager_test.go @@ -25,10 +25,12 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/components/mocks" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/merklesignature" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" @@ -174,3 +176,75 @@ func testAccountManagerKeys(t *testing.T, registry account.ParticipationRegistry require.Lessf(t, keysTotalDuration, testDuration/100, fmt.Sprintf("the time to aquire the keys via Keys() was %v whereas blocking on keys deletion took %v", keysTotalDuration, testDuration)) t.Logf("Calling AccountManager.Keys() while AccountManager.DeleteOldKeys() was busy, 10 times in a row, resulted in accumulated delay of %v\n", keysTotalDuration) } + +func TestAccountManagerOverlappingStateProofKeys(t *testing.T) { + partitiontest.PartitionTest(t) + a := assert.New(t) + + registry, dbName := getRegistryImpl(t, false, true) + defer registryCloseTest(t, registry, dbName) + + log := logging.TestingLog(t) + log.SetLevel(logging.Error) + + acctManager := MakeAccountManager(log, registry) + + databaseFiles := make([]string, 0) + defer func() { + for _, fileName := range databaseFiles { + os.Remove(fileName) + os.Remove(fileName + "-shm") + os.Remove(fileName + "-wal") + os.Remove(fileName + "-journal") + } + }() + + // Generate 2 participations under the same account + store, err := db.MakeAccessor("stateprooftest", false, true) + a.NoError(err) + root, err := account.GenerateRoot(store) + a.NoError(err) + part1, err := account.FillDBWithParticipationKeys(store, root.Address(), 0, basics.Round(merklesignature.KeyLifetimeDefault*2), 3) + a.NoError(err) + store.Close() + + store, err = db.MakeAccessor("stateprooftest", false, true) + a.NoError(err) + part2, err := account.FillDBWithParticipationKeys(store, root.Address(), basics.Round(merklesignature.KeyLifetimeDefault), basics.Round(merklesignature.KeyLifetimeDefault*3), 3) + a.NoError(err) + store.Close() + + keys1 := part1.StateProofSecrets.GetAllKeys() + keys2 := part2.StateProofSecrets.GetAllKeys() + + // Add participations to the registry and append StateProof keys as well + part1ID, err := acctManager.registry.Insert(part1.Participation) + a.NoError(err) + err = registry.AppendKeys(part1ID, keys1) + a.NoError(err) + + err = acctManager.registry.Flush(10 * time.Second) + a.NoError(err) + + res := acctManager.StateProofKeys(basics.Round(merklesignature.KeyLifetimeDefault)) + a.Equal(1, len(res)) + res = acctManager.StateProofKeys(basics.Round(merklesignature.KeyLifetimeDefault * 2)) + a.Equal(1, len(res)) + + part2ID, err := acctManager.registry.Insert(part2.Participation) + a.NoError(err) + err = registry.AppendKeys(part2ID, keys2) + a.NoError(err) + + err = acctManager.registry.Flush(10 * time.Second) + a.NoError(err) + + res = acctManager.StateProofKeys(0) + a.Equal(1, len(res)) + res = acctManager.StateProofKeys(basics.Round(merklesignature.KeyLifetimeDefault)) + a.Equal(2, len(res)) + res = acctManager.StateProofKeys(basics.Round(merklesignature.KeyLifetimeDefault * 2)) + a.Equal(2, len(res)) + res = acctManager.StateProofKeys(basics.Round(merklesignature.KeyLifetimeDefault * 3)) + a.Equal(1, len(res)) +} diff --git a/data/basics/ccertpart.go b/data/basics/stateProofParticipant.go similarity index 87% rename from data/basics/ccertpart.go rename to data/basics/stateProofParticipant.go index 481f607c58..5dcc3ad85c 100644 --- a/data/basics/ccertpart.go +++ b/data/basics/stateProofParticipant.go @@ -55,16 +55,20 @@ type Participant struct { // be bad for creating SNARK func (p Participant) ToBeHashed() (protocol.HashID, []byte) { - weightAsBytes := make([]byte, 8) - binary.LittleEndian.PutUint64(weightAsBytes, p.Weight) + var weightAsBytes [8]byte + binary.LittleEndian.PutUint64(weightAsBytes[:], p.Weight) - publicKeyBytes := p.PK + var keyLifetimeBytes [8]byte + binary.LittleEndian.PutUint64(keyLifetimeBytes[:], p.PK.KeyLifetime) - partCommitment := make([]byte, 0, len(weightAsBytes)+len(publicKeyBytes)) - partCommitment = append(partCommitment, weightAsBytes...) + publicKeyBytes := p.PK.Commitment + + partCommitment := make([]byte, 0, len(weightAsBytes)+len(publicKeyBytes)+len(keyLifetimeBytes)) + partCommitment = append(partCommitment, weightAsBytes[:]...) + partCommitment = append(partCommitment, keyLifetimeBytes[:]...) partCommitment = append(partCommitment, publicKeyBytes[:]...) - return protocol.CompactCertPart, partCommitment + return protocol.StateProofPart, partCommitment } // ParticipantsArray implements merklearray.Array and is used to commit diff --git a/data/basics/userBalance.go b/data/basics/userBalance.go index 253709a8c4..2b0a2699e4 100644 --- a/data/basics/userBalance.go +++ b/data/basics/userBalance.go @@ -101,7 +101,7 @@ func UnmarshalStatus(value string) (s Status, err error) { type VotingData struct { VoteID crypto.OneTimeSignatureVerifier SelectionID crypto.VRFVerifier - StateProofID merklesignature.Verifier + StateProofID merklesignature.Commitment VoteFirstValid Round VoteLastValid Round @@ -168,7 +168,7 @@ type AccountData struct { VoteID crypto.OneTimeSignatureVerifier `codec:"vote"` SelectionID crypto.VRFVerifier `codec:"sel"` - StateProofID merklesignature.Verifier `codec:"stprf"` + StateProofID merklesignature.Commitment `codec:"stprf"` VoteFirstValid Round `codec:"voteFst"` VoteLastValid Round `codec:"voteLst"` diff --git a/data/basics/userBalance_test.go b/data/basics/userBalance_test.go index ddaf593152..050ea28827 100644 --- a/data/basics/userBalance_test.go +++ b/data/basics/userBalance_test.go @@ -109,7 +109,7 @@ func makeString(len int) string { func getSampleAccountData() AccountData { oneTimeSecrets := crypto.GenerateOneTimeSignatureSecrets(0, 1) vrfSecrets := crypto.GenerateVRFSecrets() - var stateProofID merklesignature.Verifier + var stateProofID merklesignature.Commitment crypto.RandBytes(stateProofID[:]) return AccountData{ diff --git a/data/bookkeeping/block.go b/data/bookkeeping/block.go index 584e0394df..702344235a 100644 --- a/data/bookkeeping/block.go +++ b/data/bookkeeping/block.go @@ -119,10 +119,10 @@ type ( // started being supported). TxnCounter uint64 `codec:"tc"` - // CompactCert tracks the state of compact certs, potentially - // for multiple types of certs. - //msgp:sort protocol.CompactCertType protocol.SortCompactCertType - CompactCert map[protocol.CompactCertType]CompactCertState `codec:"cc,allocbound=protocol.NumCompactCertTypes"` + // StateProofTracking tracks the status of the state proofs, potentially + // for multiple types of ASPs (Algorand's State Proofs). + //msgp:sort protocol.StateProofType protocol.SortStateProofType + StateProofTracking map[protocol.StateProofType]StateProofTrackingData `codec:"spt,allocbound=protocol.NumStateProofTypes"` // ParticipationUpdates contains the information needed to mark // certain accounts offline because their participation keys expired @@ -214,27 +214,26 @@ type ( NextProtocolSwitchOn basics.Round `codec:"nextswitch"` } - // CompactCertState tracks the state of compact certificates. - CompactCertState struct { + // StateProofTrackingData tracks the status of state proofs. + StateProofTrackingData struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - // CompactCertVoters is the root of a Merkle tree containing the - // online accounts that will help sign a compact certificate. The - // Merkle root, and the compact certificate, happen on blocks that - // are a multiple of ConsensusParams.CompactCertRounds. For blocks - // that are not a multiple of ConsensusParams.CompactCertRounds, + // StateProofVotersCommitment is the root of a vector commitment containing the + // online accounts that will help sign a state proof. The + // VC root, and the state proof, happen on blocks that + // are a multiple of ConsensusParams.StateProofRounds. For blocks + // that are not a multiple of ConsensusParams.StateProofRounds, // this value is zero. - CompactCertVoters crypto.GenericDigest `codec:"v"` + StateProofVotersCommitment crypto.GenericDigest `codec:"v"` - // CompactCertVotersTotal is the total number of microalgos held by - // the accounts in CompactCertVoters (or zero, if the merkle root is - // zero). This is intended for computing the threshold of votes to - // expect from CompactCertVoters. - CompactCertVotersTotal basics.MicroAlgos `codec:"t"` + // StateProofOnlineTotalWeight is the total number of microalgos held by the online accounts + // during the StateProof round (or zero, if the merkle root is zero - no commitment for StateProof voters). + // This is intended for computing the threshold of votes to expect from StateProofVotersCommitment. + StateProofOnlineTotalWeight basics.MicroAlgos `codec:"t"` - // CompactCertNextRound is the next round for which we will accept - // a CompactCert transaction. - CompactCertNextRound basics.Round `codec:"n"` + // StateProofNextRound is the next round for which we will accept + // a StateProof transaction. + StateProofNextRound basics.Round `codec:"n"` } // A Block contains the Payset and metadata corresponding to a given Round. diff --git a/data/bookkeeping/block_test.go b/data/bookkeeping/block_test.go index d57736701d..5330c85cd7 100644 --- a/data/bookkeeping/block_test.go +++ b/data/bookkeeping/block_test.go @@ -863,7 +863,7 @@ func TestBlockHeader_Serialization(t *testing.T) { a := require.New(t) // This serialized block header was generated from V32 e2e test, using the old BlockHeader struct which contains only TxnCommitments SHA512_256 value - serializedBlkHdr := "8fa26363810081a16ecd0200a466656573c42007dacb4b6d9ed141b17576bd459ae6421d486da3d4ef2247c409a396b82ea221a466726163ce1dcd64fea367656ea7746573742d7631a26768c42032cb340d569e1f9e4d9690c1ba04d77759bae6f353e13af1becf42dcd7d3bdeba470726576c420a2270bc90e3cc48d56081b3b85c15d6a10e14303a6d42ca2537954ce90beec40a570726f746fa6667574757265a472617465ce0ee6b27fa3726e6402a6727763616c72ce0007a120a3727764c420ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa473656564c420a19005a25abad1ad28ec2298baeda9a17693a9ef12127a5ff3e5fa9258c7e9eba2746306a27473ce625ed0eaa374786ec420508f9330176e6064767b0fb7eb0e8bf68ffbaf995a4c7b37ca0217c5a82b4a60" + serializedBlkHdr := "8fa3737074810081a16ecd0200a466656573c42007dacb4b6d9ed141b17576bd459ae6421d486da3d4ef2247c409a396b82ea221a466726163ce1dcd64fea367656ea7746573742d7631a26768c42032cb340d569e1f9e4d9690c1ba04d77759bae6f353e13af1becf42dcd7d3bdeba470726576c420a2270bc90e3cc48d56081b3b85c15d6a10e14303a6d42ca2537954ce90beec40a570726f746fa6667574757265a472617465ce0ee6b27fa3726e6402a6727763616c72ce0007a120a3727764c420ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa473656564c420a19005a25abad1ad28ec2298baeda9a17693a9ef12127a5ff3e5fa9258c7e9eba2746306a27473ce625ed0eaa374786ec420508f9330176e6064767b0fb7eb0e8bf68ffbaf995a4c7b37ca0217c5a82b4a60" bytesBlkHdr, err := hex.DecodeString(serializedBlkHdr) a.NoError(err) diff --git a/data/bookkeeping/lightBlockHeader.go b/data/bookkeeping/lightBlockHeader.go new file mode 100644 index 0000000000..e9530faa18 --- /dev/null +++ b/data/bookkeeping/lightBlockHeader.go @@ -0,0 +1,61 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package bookkeeping + +import ( + "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" +) + +// LightBlockHeader represents a minimal block header. It contains all the necessary fields +// for verifying proofs on transactions. +// In addition, this struct is designed to be used on environments where only SHA256 function exists +type LightBlockHeader struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + /* + The seed is to mitigate against the (remote) possibility that an attacker can put itself in better position to + find a collision in the future -- perhaps with quantum -- e.g., by doing some precomputation, + knowing or even controlling the data to be hashed, etc. Starting the hash data with a value that is + uncontrollable and unpredictable (to today’s attackers) makes the attacker’s task more like breaking 2nd + preimage resistance (2PR/TCR), versus the easier goal of merely breaking collision resistance. + In addition, we make sure that the Seed (The unpredictable value) would be the first field that gets + hashed (give it the lowest codec value in the LightBlockHeader struct) to mitigate a collision attack + on the merkle damgard construction. + */ + Seed committee.Seed `codec:"0"` + Round basics.Round `codec:"r"` + GenesisHash crypto.Digest `codec:"gh"` + Sha256TxnCommitment crypto.GenericDigest `codec:"tc,allocbound=crypto.Sha256Size"` +} + +// ToLightBlockHeader creates returns a LightBlockHeader from a given block header +func (bh *BlockHeader) ToLightBlockHeader() LightBlockHeader { + return LightBlockHeader{ + Seed: bh.Seed, + GenesisHash: bh.GenesisHash, + Round: bh.Round, + Sha256TxnCommitment: bh.Sha256Commitment[:], + } +} + +// ToBeHashed implements the crypto.Hashable interface +func (bh *LightBlockHeader) ToBeHashed() (protocol.HashID, []byte) { + return protocol.BlockHeader256, protocol.Encode(bh) +} diff --git a/data/bookkeeping/lightBlockHeader_test.go b/data/bookkeeping/lightBlockHeader_test.go new file mode 100644 index 0000000000..c9d39c0084 --- /dev/null +++ b/data/bookkeeping/lightBlockHeader_test.go @@ -0,0 +1,64 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package bookkeeping + +import ( + "strings" + "testing" + + "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/require" +) + +func TestConvertSha256Header(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var gh crypto.Digest + crypto.RandBytes(gh[:]) + + var txnCommit TxnCommitments + crypto.RandBytes(txnCommit.Sha256Commitment[:]) + blockHeader := BlockHeader{Round: 200, GenesisHash: gh, TxnCommitments: txnCommit} + sha256Header := blockHeader.ToLightBlockHeader() + + a.Equal(basics.Round(200), sha256Header.Round) + a.Equal(txnCommit.Sha256Commitment[:], []byte(sha256Header.Sha256TxnCommitment)) + a.Equal(gh, sha256Header.GenesisHash) +} + +func TestFirstFieldsAreCommitteeSeed(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var gh crypto.Digest + crypto.RandBytes(gh[:]) + + blockHeader := LightBlockHeader{ + Seed: committee.Seed{'1', '2', '3', '4', '5', '6', '7', '8', '9', 'a'}, + Round: 200, + GenesisHash: gh, + } + + o := protocol.Encode(&blockHeader) + + a.True(strings.HasPrefix(string(o[5:]), "123456789a")) +} diff --git a/data/bookkeeping/msgp_gen.go b/data/bookkeeping/msgp_gen.go index 0e994fcff4..de90680aae 100644 --- a/data/bookkeeping/msgp_gen.go +++ b/data/bookkeeping/msgp_gen.go @@ -38,14 +38,6 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // -// CompactCertState -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // Genesis // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -62,6 +54,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// LightBlockHeader +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // ParticipationUpdates // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -78,6 +78,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// StateProofTrackingData +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // TxnCommitments // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -101,75 +109,75 @@ func (z *Block) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0004Len := uint32(26) var zb0004Mask uint32 /* 31 bits */ - if len((*z).BlockHeader.CompactCert) == 0 { + if (*z).BlockHeader.RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x20 } - if (*z).BlockHeader.RewardsState.RewardsLevel == 0 { + if (*z).BlockHeader.RewardsState.FeeSink.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40 } - if (*z).BlockHeader.RewardsState.FeeSink.MsgIsZero() { + if (*z).BlockHeader.RewardsState.RewardsResidue == 0 { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).BlockHeader.RewardsState.RewardsResidue == 0 { + if (*z).BlockHeader.GenesisID == "" { zb0004Len-- zb0004Mask |= 0x100 } - if (*z).BlockHeader.GenesisID == "" { + if (*z).BlockHeader.GenesisHash.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200 } - if (*z).BlockHeader.GenesisHash.MsgIsZero() { + if (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400 } - if (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero() { + if (*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800 } - if (*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero() { + if (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000 } - if (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero() { + if (*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0 { zb0004Len-- zb0004Mask |= 0x2000 } - if (*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0 { + if len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { zb0004Len-- zb0004Mask |= 0x4000 } - if len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0 { + if (*z).BlockHeader.Branch.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x8000 } - if (*z).BlockHeader.Branch.MsgIsZero() { + if (*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x10000 } - if (*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero() { + if (*z).BlockHeader.RewardsState.RewardsRate == 0 { zb0004Len-- zb0004Mask |= 0x20000 } - if (*z).BlockHeader.RewardsState.RewardsRate == 0 { + if (*z).BlockHeader.Round.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40000 } - if (*z).BlockHeader.Round.MsgIsZero() { + if (*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80000 } - if (*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero() { + if (*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x100000 } - if (*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero() { + if (*z).BlockHeader.Seed.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200000 } - if (*z).BlockHeader.Seed.MsgIsZero() { + if len((*z).BlockHeader.StateProofTracking) == 0 { zb0004Len-- zb0004Mask |= 0x400000 } @@ -209,71 +217,51 @@ func (z *Block) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendMapHeader(o, zb0004Len) if zb0004Len != 0 { if (zb0004Mask & 0x20) == 0 { // if not empty - // string "cc" - o = append(o, 0xa2, 0x63, 0x63) - if (*z).BlockHeader.CompactCert == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).BlockHeader.CompactCert))) - } - zb0001_keys := make([]protocol.CompactCertType, 0, len((*z).BlockHeader.CompactCert)) - for zb0001 := range (*z).BlockHeader.CompactCert { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(protocol.SortCompactCertType(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).BlockHeader.CompactCert[zb0001] - _ = zb0002 - o = zb0001.MarshalMsg(o) - o = zb0002.MarshalMsg(o) - } - } - if (zb0004Mask & 0x40) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsLevel) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0004Mask & 0x40) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).BlockHeader.RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0004Mask & 0x80) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsResidue) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0004Mask & 0x100) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).BlockHeader.GenesisID) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0004Mask & 0x200) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).BlockHeader.GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0004Mask & 0x400) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0004Mask & 0x800) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).BlockHeader.UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0004Mask & 0x1000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0004Mask & 0x2000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).BlockHeader.UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0004Mask & 0x4000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -285,41 +273,61 @@ func (z *Block) MarshalMsg(b []byte) (o []byte) { o = (*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0004Mask & 0x8000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).BlockHeader.Branch.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0004Mask & 0x10000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).BlockHeader.UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0004Mask & 0x20000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).BlockHeader.RewardsState.RewardsRate) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0004Mask & 0x40000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).BlockHeader.Round.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0004Mask & 0x80000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).BlockHeader.RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x100000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).BlockHeader.RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x200000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).BlockHeader.Seed.MarshalMsg(o) } + if (zb0004Mask & 0x400000) == 0 { // if not empty + // string "spt" + o = append(o, 0xa3, 0x73, 0x70, 0x74) + if (*z).BlockHeader.StateProofTracking == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).BlockHeader.StateProofTracking))) + } + zb0001_keys := make([]protocol.StateProofType, 0, len((*z).BlockHeader.StateProofTracking)) + for zb0001 := range (*z).BlockHeader.StateProofTracking { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(protocol.SortStateProofType(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).BlockHeader.StateProofTracking[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = zb0002.MarshalMsg(o) + } + } if (zb0004Mask & 0x800000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) @@ -572,34 +580,34 @@ func (z *Block) UnmarshalMsg(bts []byte) (o []byte, err error) { var zb0007 bool zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0006 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + if zb0006 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } if zb0007 { - (*z).BlockHeader.CompactCert = nil - } else if (*z).BlockHeader.CompactCert == nil { - (*z).BlockHeader.CompactCert = make(map[protocol.CompactCertType]CompactCertState, zb0006) + (*z).BlockHeader.StateProofTracking = nil + } else if (*z).BlockHeader.StateProofTracking == nil { + (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0006) } for zb0006 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 CompactCertState + var zb0001 protocol.StateProofType + var zb0002 StateProofTrackingData zb0006-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert", zb0001) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking", zb0001) return } - (*z).BlockHeader.CompactCert[zb0001] = zb0002 + (*z).BlockHeader.StateProofTracking[zb0001] = zb0002 } } if zb0004 > 0 { @@ -800,39 +808,39 @@ func (z *Block) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TxnCounter") return } - case "cc": + case "spt": var zb0010 int var zb0011 bool zb0010, zb0011, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } - if zb0010 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0010), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "CompactCert") + if zb0010 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0010), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "StateProofTracking") return } if zb0011 { - (*z).BlockHeader.CompactCert = nil - } else if (*z).BlockHeader.CompactCert == nil { - (*z).BlockHeader.CompactCert = make(map[protocol.CompactCertType]CompactCertState, zb0010) + (*z).BlockHeader.StateProofTracking = nil + } else if (*z).BlockHeader.StateProofTracking == nil { + (*z).BlockHeader.StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0010) } for zb0010 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 CompactCertState + var zb0001 protocol.StateProofType + var zb0002 StateProofTrackingData zb0010-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert", zb0001) + err = msgp.WrapError(err, "StateProofTracking", zb0001) return } - (*z).BlockHeader.CompactCert[zb0001] = zb0002 + (*z).BlockHeader.StateProofTracking[zb0001] = zb0002 } case "partupdrmv": var zb0012 int @@ -887,9 +895,9 @@ func (_ *Block) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *Block) Msgsize() (s int) { - s = 3 + 4 + (*z).BlockHeader.Round.Msgsize() + 5 + (*z).BlockHeader.Branch.Msgsize() + 5 + (*z).BlockHeader.Seed.Msgsize() + 4 + (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).BlockHeader.GenesisID) + 3 + (*z).BlockHeader.GenesisHash.Msgsize() + 5 + (*z).BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 3 + msgp.MapHeaderSize - if (*z).BlockHeader.CompactCert != nil { - for zb0001, zb0002 := range (*z).BlockHeader.CompactCert { + s = 3 + 4 + (*z).BlockHeader.Round.Msgsize() + 5 + (*z).BlockHeader.Branch.Msgsize() + 5 + (*z).BlockHeader.Seed.Msgsize() + 4 + (*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).BlockHeader.TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).BlockHeader.GenesisID) + 3 + (*z).BlockHeader.GenesisHash.Msgsize() + 5 + (*z).BlockHeader.RewardsState.FeeSink.Msgsize() + 4 + (*z).BlockHeader.RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).BlockHeader.RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).BlockHeader.UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).BlockHeader.UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).BlockHeader.UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).BlockHeader.UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + if (*z).BlockHeader.StateProofTracking != nil { + for zb0001, zb0002 := range (*z).BlockHeader.StateProofTracking { _ = zb0001 _ = zb0002 s += 0 + zb0001.Msgsize() + zb0002.Msgsize() @@ -905,7 +913,7 @@ func (z *Block) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *Block) MsgIsZero() bool { - return ((*z).BlockHeader.Round.MsgIsZero()) && ((*z).BlockHeader.Branch.MsgIsZero()) && ((*z).BlockHeader.Seed.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).BlockHeader.TimeStamp == 0) && ((*z).BlockHeader.GenesisID == "") && ((*z).BlockHeader.GenesisHash.MsgIsZero()) && ((*z).BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).BlockHeader.RewardsState.RewardsRate == 0) && ((*z).BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).BlockHeader.TxnCounter == 0) && (len((*z).BlockHeader.CompactCert) == 0) && (len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Payset.MsgIsZero()) + return ((*z).BlockHeader.Round.MsgIsZero()) && ((*z).BlockHeader.Branch.MsgIsZero()) && ((*z).BlockHeader.Seed.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).BlockHeader.TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).BlockHeader.TimeStamp == 0) && ((*z).BlockHeader.GenesisID == "") && ((*z).BlockHeader.GenesisHash.MsgIsZero()) && ((*z).BlockHeader.RewardsState.FeeSink.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsPool.MsgIsZero()) && ((*z).BlockHeader.RewardsState.RewardsLevel == 0) && ((*z).BlockHeader.RewardsState.RewardsRate == 0) && ((*z).BlockHeader.RewardsState.RewardsResidue == 0) && ((*z).BlockHeader.RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocol.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolApprovals == 0) && ((*z).BlockHeader.UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).BlockHeader.UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).BlockHeader.UpgradeVote.UpgradeApprove == false) && ((*z).BlockHeader.TxnCounter == 0) && (len((*z).BlockHeader.StateProofTracking) == 0) && (len((*z).BlockHeader.ParticipationUpdates.ExpiredParticipationAccounts) == 0) && ((*z).Payset.MsgIsZero()) } // MarshalMsg implements msgp.Marshaler @@ -942,75 +950,75 @@ func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0004Len := uint32(25) var zb0004Mask uint32 /* 30 bits */ - if len((*z).CompactCert) == 0 { + if (*z).RewardsState.RewardsLevel == 0 { zb0004Len-- zb0004Mask |= 0x20 } - if (*z).RewardsState.RewardsLevel == 0 { + if (*z).RewardsState.FeeSink.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40 } - if (*z).RewardsState.FeeSink.MsgIsZero() { + if (*z).RewardsState.RewardsResidue == 0 { zb0004Len-- zb0004Mask |= 0x80 } - if (*z).RewardsState.RewardsResidue == 0 { + if (*z).GenesisID == "" { zb0004Len-- zb0004Mask |= 0x100 } - if (*z).GenesisID == "" { + if (*z).GenesisHash.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200 } - if (*z).GenesisHash.MsgIsZero() { + if (*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x400 } - if (*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero() { + if (*z).UpgradeState.NextProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x800 } - if (*z).UpgradeState.NextProtocol.MsgIsZero() { + if (*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x1000 } - if (*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero() { + if (*z).UpgradeState.NextProtocolApprovals == 0 { zb0004Len-- zb0004Mask |= 0x2000 } - if (*z).UpgradeState.NextProtocolApprovals == 0 { + if len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0 { zb0004Len-- zb0004Mask |= 0x4000 } - if len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0 { + if (*z).Branch.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x8000 } - if (*z).Branch.MsgIsZero() { + if (*z).UpgradeState.CurrentProtocol.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x10000 } - if (*z).UpgradeState.CurrentProtocol.MsgIsZero() { + if (*z).RewardsState.RewardsRate == 0 { zb0004Len-- zb0004Mask |= 0x20000 } - if (*z).RewardsState.RewardsRate == 0 { + if (*z).Round.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x40000 } - if (*z).Round.MsgIsZero() { + if (*z).RewardsState.RewardsRecalculationRound.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x80000 } - if (*z).RewardsState.RewardsRecalculationRound.MsgIsZero() { + if (*z).RewardsState.RewardsPool.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x100000 } - if (*z).RewardsState.RewardsPool.MsgIsZero() { + if (*z).Seed.MsgIsZero() { zb0004Len-- zb0004Mask |= 0x200000 } - if (*z).Seed.MsgIsZero() { + if len((*z).StateProofTracking) == 0 { zb0004Len-- zb0004Mask |= 0x400000 } @@ -1046,71 +1054,51 @@ func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendMapHeader(o, zb0004Len) if zb0004Len != 0 { if (zb0004Mask & 0x20) == 0 { // if not empty - // string "cc" - o = append(o, 0xa2, 0x63, 0x63) - if (*z).CompactCert == nil { - o = msgp.AppendNil(o) - } else { - o = msgp.AppendMapHeader(o, uint32(len((*z).CompactCert))) - } - zb0001_keys := make([]protocol.CompactCertType, 0, len((*z).CompactCert)) - for zb0001 := range (*z).CompactCert { - zb0001_keys = append(zb0001_keys, zb0001) - } - sort.Sort(protocol.SortCompactCertType(zb0001_keys)) - for _, zb0001 := range zb0001_keys { - zb0002 := (*z).CompactCert[zb0001] - _ = zb0002 - o = zb0001.MarshalMsg(o) - o = zb0002.MarshalMsg(o) - } - } - if (zb0004Mask & 0x40) == 0 { // if not empty // string "earn" o = append(o, 0xa4, 0x65, 0x61, 0x72, 0x6e) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsLevel) } - if (zb0004Mask & 0x80) == 0 { // if not empty + if (zb0004Mask & 0x40) == 0 { // if not empty // string "fees" o = append(o, 0xa4, 0x66, 0x65, 0x65, 0x73) o = (*z).RewardsState.FeeSink.MarshalMsg(o) } - if (zb0004Mask & 0x100) == 0 { // if not empty + if (zb0004Mask & 0x80) == 0 { // if not empty // string "frac" o = append(o, 0xa4, 0x66, 0x72, 0x61, 0x63) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsResidue) } - if (zb0004Mask & 0x200) == 0 { // if not empty + if (zb0004Mask & 0x100) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).GenesisID) } - if (zb0004Mask & 0x400) == 0 { // if not empty + if (zb0004Mask & 0x200) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).GenesisHash.MarshalMsg(o) } - if (zb0004Mask & 0x800) == 0 { // if not empty + if (zb0004Mask & 0x400) == 0 { // if not empty // string "nextbefore" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65) o = (*z).UpgradeState.NextProtocolVoteBefore.MarshalMsg(o) } - if (zb0004Mask & 0x1000) == 0 { // if not empty + if (zb0004Mask & 0x800) == 0 { // if not empty // string "nextproto" o = append(o, 0xa9, 0x6e, 0x65, 0x78, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).UpgradeState.NextProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x2000) == 0 { // if not empty + if (zb0004Mask & 0x1000) == 0 { // if not empty // string "nextswitch" o = append(o, 0xaa, 0x6e, 0x65, 0x78, 0x74, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68) o = (*z).UpgradeState.NextProtocolSwitchOn.MarshalMsg(o) } - if (zb0004Mask & 0x4000) == 0 { // if not empty + if (zb0004Mask & 0x2000) == 0 { // if not empty // string "nextyes" o = append(o, 0xa7, 0x6e, 0x65, 0x78, 0x74, 0x79, 0x65, 0x73) o = msgp.AppendUint64(o, (*z).UpgradeState.NextProtocolApprovals) } - if (zb0004Mask & 0x8000) == 0 { // if not empty + if (zb0004Mask & 0x4000) == 0 { // if not empty // string "partupdrmv" o = append(o, 0xaa, 0x70, 0x61, 0x72, 0x74, 0x75, 0x70, 0x64, 0x72, 0x6d, 0x76) if (*z).ParticipationUpdates.ExpiredParticipationAccounts == nil { @@ -1122,41 +1110,61 @@ func (z *BlockHeader) MarshalMsg(b []byte) (o []byte) { o = (*z).ParticipationUpdates.ExpiredParticipationAccounts[zb0003].MarshalMsg(o) } } - if (zb0004Mask & 0x10000) == 0 { // if not empty + if (zb0004Mask & 0x8000) == 0 { // if not empty // string "prev" o = append(o, 0xa4, 0x70, 0x72, 0x65, 0x76) o = (*z).Branch.MarshalMsg(o) } - if (zb0004Mask & 0x20000) == 0 { // if not empty + if (zb0004Mask & 0x10000) == 0 { // if not empty // string "proto" o = append(o, 0xa5, 0x70, 0x72, 0x6f, 0x74, 0x6f) o = (*z).UpgradeState.CurrentProtocol.MarshalMsg(o) } - if (zb0004Mask & 0x40000) == 0 { // if not empty + if (zb0004Mask & 0x20000) == 0 { // if not empty // string "rate" o = append(o, 0xa4, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendUint64(o, (*z).RewardsState.RewardsRate) } - if (zb0004Mask & 0x80000) == 0 { // if not empty + if (zb0004Mask & 0x40000) == 0 { // if not empty // string "rnd" o = append(o, 0xa3, 0x72, 0x6e, 0x64) o = (*z).Round.MarshalMsg(o) } - if (zb0004Mask & 0x100000) == 0 { // if not empty + if (zb0004Mask & 0x80000) == 0 { // if not empty // string "rwcalr" o = append(o, 0xa6, 0x72, 0x77, 0x63, 0x61, 0x6c, 0x72) o = (*z).RewardsState.RewardsRecalculationRound.MarshalMsg(o) } - if (zb0004Mask & 0x200000) == 0 { // if not empty + if (zb0004Mask & 0x100000) == 0 { // if not empty // string "rwd" o = append(o, 0xa3, 0x72, 0x77, 0x64) o = (*z).RewardsState.RewardsPool.MarshalMsg(o) } - if (zb0004Mask & 0x400000) == 0 { // if not empty + if (zb0004Mask & 0x200000) == 0 { // if not empty // string "seed" o = append(o, 0xa4, 0x73, 0x65, 0x65, 0x64) o = (*z).Seed.MarshalMsg(o) } + if (zb0004Mask & 0x400000) == 0 { // if not empty + // string "spt" + o = append(o, 0xa3, 0x73, 0x70, 0x74) + if (*z).StateProofTracking == nil { + o = msgp.AppendNil(o) + } else { + o = msgp.AppendMapHeader(o, uint32(len((*z).StateProofTracking))) + } + zb0001_keys := make([]protocol.StateProofType, 0, len((*z).StateProofTracking)) + for zb0001 := range (*z).StateProofTracking { + zb0001_keys = append(zb0001_keys, zb0001) + } + sort.Sort(protocol.SortStateProofType(zb0001_keys)) + for _, zb0001 := range zb0001_keys { + zb0002 := (*z).StateProofTracking[zb0001] + _ = zb0002 + o = zb0001.MarshalMsg(o) + o = zb0002.MarshalMsg(o) + } + } if (zb0004Mask & 0x800000) == 0 { // if not empty // string "tc" o = append(o, 0xa2, 0x74, 0x63) @@ -1404,34 +1412,34 @@ func (z *BlockHeader) UnmarshalMsg(bts []byte) (o []byte, err error) { var zb0007 bool zb0006, zb0007, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } - if zb0006 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + if zb0006 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0006), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } if zb0007 { - (*z).CompactCert = nil - } else if (*z).CompactCert == nil { - (*z).CompactCert = make(map[protocol.CompactCertType]CompactCertState, zb0006) + (*z).StateProofTracking = nil + } else if (*z).StateProofTracking == nil { + (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0006) } for zb0006 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 CompactCertState + var zb0001 protocol.StateProofType + var zb0002 StateProofTrackingData zb0006-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert") + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCert", zb0001) + err = msgp.WrapError(err, "struct-from-array", "StateProofTracking", zb0001) return } - (*z).CompactCert[zb0001] = zb0002 + (*z).StateProofTracking[zb0001] = zb0002 } } if zb0004 > 0 { @@ -1624,39 +1632,39 @@ func (z *BlockHeader) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "TxnCounter") return } - case "cc": + case "spt": var zb0010 int var zb0011 bool zb0010, zb0011, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } - if zb0010 > protocol.NumCompactCertTypes { - err = msgp.ErrOverflow(uint64(zb0010), uint64(protocol.NumCompactCertTypes)) - err = msgp.WrapError(err, "CompactCert") + if zb0010 > protocol.NumStateProofTypes { + err = msgp.ErrOverflow(uint64(zb0010), uint64(protocol.NumStateProofTypes)) + err = msgp.WrapError(err, "StateProofTracking") return } if zb0011 { - (*z).CompactCert = nil - } else if (*z).CompactCert == nil { - (*z).CompactCert = make(map[protocol.CompactCertType]CompactCertState, zb0010) + (*z).StateProofTracking = nil + } else if (*z).StateProofTracking == nil { + (*z).StateProofTracking = make(map[protocol.StateProofType]StateProofTrackingData, zb0010) } for zb0010 > 0 { - var zb0001 protocol.CompactCertType - var zb0002 CompactCertState + var zb0001 protocol.StateProofType + var zb0002 StateProofTrackingData zb0010-- bts, err = zb0001.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert") + err = msgp.WrapError(err, "StateProofTracking") return } bts, err = zb0002.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CompactCert", zb0001) + err = msgp.WrapError(err, "StateProofTracking", zb0001) return } - (*z).CompactCert[zb0001] = zb0002 + (*z).StateProofTracking[zb0001] = zb0002 } case "partupdrmv": var zb0012 int @@ -1705,9 +1713,9 @@ func (_ *BlockHeader) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *BlockHeader) Msgsize() (s int) { - s = 3 + 4 + (*z).Round.Msgsize() + 5 + (*z).Branch.Msgsize() + 5 + (*z).Seed.Msgsize() + 4 + (*z).TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).GenesisID) + 3 + (*z).GenesisHash.Msgsize() + 5 + (*z).RewardsState.FeeSink.Msgsize() + 4 + (*z).RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 3 + msgp.MapHeaderSize - if (*z).CompactCert != nil { - for zb0001, zb0002 := range (*z).CompactCert { + s = 3 + 4 + (*z).Round.Msgsize() + 5 + (*z).Branch.Msgsize() + 5 + (*z).Seed.Msgsize() + 4 + (*z).TxnCommitments.NativeSha512_256Commitment.Msgsize() + 7 + (*z).TxnCommitments.Sha256Commitment.Msgsize() + 3 + msgp.Int64Size + 4 + msgp.StringPrefixSize + len((*z).GenesisID) + 3 + (*z).GenesisHash.Msgsize() + 5 + (*z).RewardsState.FeeSink.Msgsize() + 4 + (*z).RewardsState.RewardsPool.Msgsize() + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 5 + msgp.Uint64Size + 7 + (*z).RewardsState.RewardsRecalculationRound.Msgsize() + 6 + (*z).UpgradeState.CurrentProtocol.Msgsize() + 10 + (*z).UpgradeState.NextProtocol.Msgsize() + 8 + msgp.Uint64Size + 11 + (*z).UpgradeState.NextProtocolVoteBefore.Msgsize() + 11 + (*z).UpgradeState.NextProtocolSwitchOn.Msgsize() + 12 + (*z).UpgradeVote.UpgradePropose.Msgsize() + 13 + (*z).UpgradeVote.UpgradeDelay.Msgsize() + 11 + msgp.BoolSize + 3 + msgp.Uint64Size + 4 + msgp.MapHeaderSize + if (*z).StateProofTracking != nil { + for zb0001, zb0002 := range (*z).StateProofTracking { _ = zb0001 _ = zb0002 s += 0 + zb0001.Msgsize() + zb0002.Msgsize() @@ -1722,182 +1730,30 @@ func (z *BlockHeader) Msgsize() (s int) { // MsgIsZero returns whether this is a zero value func (z *BlockHeader) MsgIsZero() bool { - return ((*z).Round.MsgIsZero()) && ((*z).Branch.MsgIsZero()) && ((*z).Seed.MsgIsZero()) && ((*z).TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).TimeStamp == 0) && ((*z).GenesisID == "") && ((*z).GenesisHash.MsgIsZero()) && ((*z).RewardsState.FeeSink.MsgIsZero()) && ((*z).RewardsState.RewardsPool.MsgIsZero()) && ((*z).RewardsState.RewardsLevel == 0) && ((*z).RewardsState.RewardsRate == 0) && ((*z).RewardsState.RewardsResidue == 0) && ((*z).RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocolApprovals == 0) && ((*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).UpgradeVote.UpgradeApprove == false) && ((*z).TxnCounter == 0) && (len((*z).CompactCert) == 0) && (len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0) + return ((*z).Round.MsgIsZero()) && ((*z).Branch.MsgIsZero()) && ((*z).Seed.MsgIsZero()) && ((*z).TxnCommitments.NativeSha512_256Commitment.MsgIsZero()) && ((*z).TxnCommitments.Sha256Commitment.MsgIsZero()) && ((*z).TimeStamp == 0) && ((*z).GenesisID == "") && ((*z).GenesisHash.MsgIsZero()) && ((*z).RewardsState.FeeSink.MsgIsZero()) && ((*z).RewardsState.RewardsPool.MsgIsZero()) && ((*z).RewardsState.RewardsLevel == 0) && ((*z).RewardsState.RewardsRate == 0) && ((*z).RewardsState.RewardsResidue == 0) && ((*z).RewardsState.RewardsRecalculationRound.MsgIsZero()) && ((*z).UpgradeState.CurrentProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocol.MsgIsZero()) && ((*z).UpgradeState.NextProtocolApprovals == 0) && ((*z).UpgradeState.NextProtocolVoteBefore.MsgIsZero()) && ((*z).UpgradeState.NextProtocolSwitchOn.MsgIsZero()) && ((*z).UpgradeVote.UpgradePropose.MsgIsZero()) && ((*z).UpgradeVote.UpgradeDelay.MsgIsZero()) && ((*z).UpgradeVote.UpgradeApprove == false) && ((*z).TxnCounter == 0) && (len((*z).StateProofTracking) == 0) && (len((*z).ParticipationUpdates.ExpiredParticipationAccounts) == 0) } // MarshalMsg implements msgp.Marshaler -func (z *CompactCertState) MarshalMsg(b []byte) (o []byte) { +func (z *Genesis) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(3) - var zb0001Mask uint8 /* 4 bits */ - if (*z).CompactCertNextRound.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x2 + zb0002Len := uint32(9) + var zb0002Mask uint16 /* 10 bits */ + if len((*z).Allocation) == 0 { + zb0002Len-- + zb0002Mask |= 0x2 } - if (*z).CompactCertVotersTotal.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x4 + if (*z).Comment == "" { + zb0002Len-- + zb0002Mask |= 0x4 } - if (*z).CompactCertVoters.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x8 + if (*z).DevMode == false { + zb0002Len-- + zb0002Mask |= 0x8 } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "n" - o = append(o, 0xa1, 0x6e) - o = (*z).CompactCertNextRound.MarshalMsg(o) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "t" - o = append(o, 0xa1, 0x74) - o = (*z).CompactCertVotersTotal.MarshalMsg(o) - } - if (zb0001Mask & 0x8) == 0 { // if not empty - // string "v" - o = append(o, 0xa1, 0x76) - o = (*z).CompactCertVoters.MarshalMsg(o) - } - } - return -} - -func (_ *CompactCertState) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactCertState) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *CompactCertState) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).CompactCertVoters.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCertVoters") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).CompactCertVotersTotal.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCertVotersTotal") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).CompactCertNextRound.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CompactCertNextRound") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = CompactCertState{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "v": - bts, err = (*z).CompactCertVoters.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "CompactCertVoters") - return - } - case "t": - bts, err = (*z).CompactCertVotersTotal.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "CompactCertVotersTotal") - return - } - case "n": - bts, err = (*z).CompactCertNextRound.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "CompactCertNextRound") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *CompactCertState) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactCertState) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *CompactCertState) Msgsize() (s int) { - s = 1 + 2 + (*z).CompactCertVoters.Msgsize() + 2 + (*z).CompactCertVotersTotal.Msgsize() + 2 + (*z).CompactCertNextRound.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *CompactCertState) MsgIsZero() bool { - return ((*z).CompactCertVoters.MsgIsZero()) && ((*z).CompactCertVotersTotal.MsgIsZero()) && ((*z).CompactCertNextRound.MsgIsZero()) -} - -// MarshalMsg implements msgp.Marshaler -func (z *Genesis) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0002Len := uint32(9) - var zb0002Mask uint16 /* 10 bits */ - if len((*z).Allocation) == 0 { - zb0002Len-- - zb0002Mask |= 0x2 - } - if (*z).Comment == "" { - zb0002Len-- - zb0002Mask |= 0x4 - } - if (*z).DevMode == false { - zb0002Len-- - zb0002Mask |= 0x8 - } - if (*z).FeeSink == "" { - zb0002Len-- - zb0002Mask |= 0x10 + if (*z).FeeSink == "" { + zb0002Len-- + zb0002Mask |= 0x10 } if (*z).SchemaID == "" { zb0002Len-- @@ -2348,6 +2204,181 @@ func (z *GenesisAllocation) MsgIsZero() bool { return ((*z).Address == "") && ((*z).Comment == "") && ((*z).State.MsgIsZero()) } +// MarshalMsg implements msgp.Marshaler +func (z *LightBlockHeader) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(4) + var zb0001Mask uint8 /* 5 bits */ + if (*z).Seed.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x1 + } + if (*z).GenesisHash.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).Round.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + if (*z).Sha256TxnCommitment.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x10 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x1) == 0 { // if not empty + // string "0" + o = append(o, 0xa1, 0x30) + o = (*z).Seed.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "gh" + o = append(o, 0xa2, 0x67, 0x68) + o = (*z).GenesisHash.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "r" + o = append(o, 0xa1, 0x72) + o = (*z).Round.MarshalMsg(o) + } + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "tc" + o = append(o, 0xa2, 0x74, 0x63) + o = (*z).Sha256TxnCommitment.MarshalMsg(o) + } + } + return +} + +func (_ *LightBlockHeader) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*LightBlockHeader) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *LightBlockHeader) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Seed.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Seed") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Round.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Round") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).GenesisHash.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "GenesisHash") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Sha256TxnCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Sha256TxnCommitment") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = LightBlockHeader{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "0": + bts, err = (*z).Seed.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Seed") + return + } + case "r": + bts, err = (*z).Round.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Round") + return + } + case "gh": + bts, err = (*z).GenesisHash.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "GenesisHash") + return + } + case "tc": + bts, err = (*z).Sha256TxnCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Sha256TxnCommitment") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *LightBlockHeader) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*LightBlockHeader) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *LightBlockHeader) Msgsize() (s int) { + s = 1 + 2 + (*z).Seed.Msgsize() + 2 + (*z).Round.Msgsize() + 3 + (*z).GenesisHash.Msgsize() + 3 + (*z).Sha256TxnCommitment.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *LightBlockHeader) MsgIsZero() bool { + return ((*z).Seed.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).GenesisHash.MsgIsZero()) && ((*z).Sha256TxnCommitment.MsgIsZero()) +} + // MarshalMsg implements msgp.Marshaler func (z *ParticipationUpdates) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -2727,6 +2758,158 @@ func (z *RewardsState) MsgIsZero() bool { return ((*z).FeeSink.MsgIsZero()) && ((*z).RewardsPool.MsgIsZero()) && ((*z).RewardsLevel == 0) && ((*z).RewardsRate == 0) && ((*z).RewardsResidue == 0) && ((*z).RewardsRecalculationRound.MsgIsZero()) } +// MarshalMsg implements msgp.Marshaler +func (z *StateProofTrackingData) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).StateProofNextRound.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).StateProofOnlineTotalWeight.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).StateProofVotersCommitment.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "n" + o = append(o, 0xa1, 0x6e) + o = (*z).StateProofNextRound.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "t" + o = append(o, 0xa1, 0x74) + o = (*z).StateProofOnlineTotalWeight.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "v" + o = append(o, 0xa1, 0x76) + o = (*z).StateProofVotersCommitment.MarshalMsg(o) + } + } + return +} + +func (_ *StateProofTrackingData) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofTrackingData) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofTrackingData) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProofVotersCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProofVotersCommitment") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProofOnlineTotalWeight.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProofOnlineTotalWeight") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProofNextRound.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProofNextRound") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = StateProofTrackingData{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "v": + bts, err = (*z).StateProofVotersCommitment.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProofVotersCommitment") + return + } + case "t": + bts, err = (*z).StateProofOnlineTotalWeight.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProofOnlineTotalWeight") + return + } + case "n": + bts, err = (*z).StateProofNextRound.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProofNextRound") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *StateProofTrackingData) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofTrackingData) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *StateProofTrackingData) Msgsize() (s int) { + s = 1 + 2 + (*z).StateProofVotersCommitment.Msgsize() + 2 + (*z).StateProofOnlineTotalWeight.Msgsize() + 2 + (*z).StateProofNextRound.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *StateProofTrackingData) MsgIsZero() bool { + return ((*z).StateProofVotersCommitment.MsgIsZero()) && ((*z).StateProofOnlineTotalWeight.MsgIsZero()) && ((*z).StateProofNextRound.MsgIsZero()) +} + // MarshalMsg implements msgp.Marshaler func (z *TxnCommitments) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/data/bookkeeping/msgp_gen_test.go b/data/bookkeeping/msgp_gen_test.go index cdf62e5c7a..1f61ae8250 100644 --- a/data/bookkeeping/msgp_gen_test.go +++ b/data/bookkeeping/msgp_gen_test.go @@ -134,9 +134,9 @@ func BenchmarkUnmarshalBlockHeader(b *testing.B) { } } -func TestMarshalUnmarshalCompactCertState(t *testing.T) { +func TestMarshalUnmarshalGenesis(t *testing.T) { partitiontest.PartitionTest(t) - v := CompactCertState{} + v := Genesis{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -155,12 +155,12 @@ func TestMarshalUnmarshalCompactCertState(t *testing.T) { } } -func TestRandomizedEncodingCompactCertState(t *testing.T) { - protocol.RunEncodingTest(t, &CompactCertState{}) +func TestRandomizedEncodingGenesis(t *testing.T) { + protocol.RunEncodingTest(t, &Genesis{}) } -func BenchmarkMarshalMsgCompactCertState(b *testing.B) { - v := CompactCertState{} +func BenchmarkMarshalMsgGenesis(b *testing.B) { + v := Genesis{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -168,8 +168,8 @@ func BenchmarkMarshalMsgCompactCertState(b *testing.B) { } } -func BenchmarkAppendMsgCompactCertState(b *testing.B) { - v := CompactCertState{} +func BenchmarkAppendMsgGenesis(b *testing.B) { + v := Genesis{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -180,8 +180,8 @@ func BenchmarkAppendMsgCompactCertState(b *testing.B) { } } -func BenchmarkUnmarshalCompactCertState(b *testing.B) { - v := CompactCertState{} +func BenchmarkUnmarshalGenesis(b *testing.B) { + v := Genesis{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -194,9 +194,9 @@ func BenchmarkUnmarshalCompactCertState(b *testing.B) { } } -func TestMarshalUnmarshalGenesis(t *testing.T) { +func TestMarshalUnmarshalGenesisAllocation(t *testing.T) { partitiontest.PartitionTest(t) - v := Genesis{} + v := GenesisAllocation{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -215,12 +215,12 @@ func TestMarshalUnmarshalGenesis(t *testing.T) { } } -func TestRandomizedEncodingGenesis(t *testing.T) { - protocol.RunEncodingTest(t, &Genesis{}) +func TestRandomizedEncodingGenesisAllocation(t *testing.T) { + protocol.RunEncodingTest(t, &GenesisAllocation{}) } -func BenchmarkMarshalMsgGenesis(b *testing.B) { - v := Genesis{} +func BenchmarkMarshalMsgGenesisAllocation(b *testing.B) { + v := GenesisAllocation{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -228,8 +228,8 @@ func BenchmarkMarshalMsgGenesis(b *testing.B) { } } -func BenchmarkAppendMsgGenesis(b *testing.B) { - v := Genesis{} +func BenchmarkAppendMsgGenesisAllocation(b *testing.B) { + v := GenesisAllocation{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -240,8 +240,8 @@ func BenchmarkAppendMsgGenesis(b *testing.B) { } } -func BenchmarkUnmarshalGenesis(b *testing.B) { - v := Genesis{} +func BenchmarkUnmarshalGenesisAllocation(b *testing.B) { + v := GenesisAllocation{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -254,9 +254,9 @@ func BenchmarkUnmarshalGenesis(b *testing.B) { } } -func TestMarshalUnmarshalGenesisAllocation(t *testing.T) { +func TestMarshalUnmarshalLightBlockHeader(t *testing.T) { partitiontest.PartitionTest(t) - v := GenesisAllocation{} + v := LightBlockHeader{} bts := v.MarshalMsg(nil) left, err := v.UnmarshalMsg(bts) if err != nil { @@ -275,12 +275,12 @@ func TestMarshalUnmarshalGenesisAllocation(t *testing.T) { } } -func TestRandomizedEncodingGenesisAllocation(t *testing.T) { - protocol.RunEncodingTest(t, &GenesisAllocation{}) +func TestRandomizedEncodingLightBlockHeader(t *testing.T) { + protocol.RunEncodingTest(t, &LightBlockHeader{}) } -func BenchmarkMarshalMsgGenesisAllocation(b *testing.B) { - v := GenesisAllocation{} +func BenchmarkMarshalMsgLightBlockHeader(b *testing.B) { + v := LightBlockHeader{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -288,8 +288,8 @@ func BenchmarkMarshalMsgGenesisAllocation(b *testing.B) { } } -func BenchmarkAppendMsgGenesisAllocation(b *testing.B) { - v := GenesisAllocation{} +func BenchmarkAppendMsgLightBlockHeader(b *testing.B) { + v := LightBlockHeader{} bts := make([]byte, 0, v.Msgsize()) bts = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -300,8 +300,8 @@ func BenchmarkAppendMsgGenesisAllocation(b *testing.B) { } } -func BenchmarkUnmarshalGenesisAllocation(b *testing.B) { - v := GenesisAllocation{} +func BenchmarkUnmarshalLightBlockHeader(b *testing.B) { + v := LightBlockHeader{} bts := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) @@ -434,6 +434,66 @@ func BenchmarkUnmarshalRewardsState(b *testing.B) { } } +func TestMarshalUnmarshalStateProofTrackingData(t *testing.T) { + partitiontest.PartitionTest(t) + v := StateProofTrackingData{} + 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 TestRandomizedEncodingStateProofTrackingData(t *testing.T) { + protocol.RunEncodingTest(t, &StateProofTrackingData{}) +} + +func BenchmarkMarshalMsgStateProofTrackingData(b *testing.B) { + v := StateProofTrackingData{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgStateProofTrackingData(b *testing.B) { + v := StateProofTrackingData{} + 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 BenchmarkUnmarshalStateProofTrackingData(b *testing.B) { + v := StateProofTrackingData{} + 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 TestMarshalUnmarshalTxnCommitments(t *testing.T) { partitiontest.PartitionTest(t) v := TxnCommitments{} diff --git a/data/pools/transactionPool.go b/data/pools/transactionPool.go index cb547a0cdd..d5efc6c894 100644 --- a/data/pools/transactionPool.go +++ b/data/pools/transactionPool.go @@ -92,6 +92,10 @@ type TransactionPool struct { // proposalAssemblyTime is the ProposalAssemblyTime configured for this node. proposalAssemblyTime time.Duration + + // stateproofOverflowed indicates that a stateproof transaction was allowed to + // exceed the txPoolMaxSize. This flag is reset to false OnNewBlock + stateproofOverflowed bool } // BlockEvaluator defines the block evaluator interface exposed by the ledger package. @@ -238,6 +242,7 @@ func (pool *TransactionPool) rememberCommit(flush bool) { if flush { pool.pendingTxGroups = pool.rememberedTxGroups + pool.stateproofOverflowed = false pool.pendingTxids = pool.rememberedTxids pool.ledger.VerifiedTransactionCache().UpdatePinned(pool.pendingTxids) } else { @@ -270,12 +275,22 @@ func (pool *TransactionPool) pendingCountNoLock() int { } // checkPendingQueueSize tests to see if we can grow the pending group transaction list -// by adding txCount more transactions. The limits comes from the total number of transactions +// by adding len(txnGroup) more transactions. The limits comes from the total number of transactions // and not from the total number of transaction groups. // As long as we haven't surpassed the size limit, we should be good to go. -func (pool *TransactionPool) checkPendingQueueSize(txCount int) error { +func (pool *TransactionPool) checkPendingQueueSize(txnGroup []transactions.SignedTxn) error { pendingSize := pool.pendingTxIDsCount() + txCount := len(txnGroup) if pendingSize+txCount > pool.txPoolMaxSize { + // Allow the state proof transaction to go over the txPoolMaxSize if it already didn't + if len(txnGroup) == 1 && txnGroup[0].Txn.Type == protocol.StateProofTx { + pool.pendingMu.Lock() + defer pool.pendingMu.Unlock() + if !pool.stateproofOverflowed { + pool.stateproofOverflowed = true + return nil + } + } return fmt.Errorf("TransactionPool.checkPendingQueueSize: transaction pool have reached capacity") } return nil @@ -329,12 +344,12 @@ func (pool *TransactionPool) computeFeePerByte() uint64 { // checkSufficientFee take a set of signed transactions and verifies that each transaction has // sufficient fee to get into the transaction pool func (pool *TransactionPool) checkSufficientFee(txgroup []transactions.SignedTxn) error { - // Special case: the compact cert transaction, if issued from the - // special compact-cert-sender address, in a singleton group, pays + // Special case: the state proof transaction, if issued from the + // special state-proof-sender address, in a singleton group, pays // no fee. if len(txgroup) == 1 { t := txgroup[0].Txn - if t.Type == protocol.CompactCertTx && t.Sender == transactions.CompactCertSender && t.Fee.IsZero() { + if t.Type == protocol.StateProofTx && t.Sender == transactions.StateProofSender && t.Fee.IsZero() { return nil } } @@ -356,7 +371,7 @@ func (pool *TransactionPool) checkSufficientFee(txgroup []transactions.SignedTxn // Test performs basic duplicate detection and well-formedness checks // on a transaction group without storing the group. func (pool *TransactionPool) Test(txgroup []transactions.SignedTxn) error { - if err := pool.checkPendingQueueSize(len(txgroup)); err != nil { + if err := pool.checkPendingQueueSize(txgroup); err != nil { return err } @@ -443,7 +458,7 @@ func (pool *TransactionPool) RememberOne(t transactions.SignedTxn) error { // Remember stores the provided transaction group. // Precondition: Only Remember() properly-signed and well-formed transactions (i.e., ensure t.WellFormed()) func (pool *TransactionPool) Remember(txgroup []transactions.SignedTxn) error { - if err := pool.checkPendingQueueSize(len(txgroup)); err != nil { + if err := pool.checkPendingQueueSize(txgroup); err != nil { return err } @@ -771,6 +786,34 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact return } +func (pool *TransactionPool) getStateProofStats(txib *transactions.SignedTxnInBlock, encodedLen int) telemetryspec.StateProofStats { + stateProofStats := telemetryspec.StateProofStats{ + ProvenWeight: 0, + SignedWeight: txib.Txn.StateProofTxnFields.StateProof.SignedWeight, + NumReveals: len(txib.Txn.StateProofTxnFields.StateProof.Reveals), + NumPosToReveal: len(txib.Txn.StateProofTxnFields.StateProof.PositionsToReveal), + TxnSize: encodedLen, + } + + lastSPRound := basics.Round(txib.Txn.StateProofTxnFields.Message.LastAttestedRound) + lastRoundHdr, err := pool.ledger.BlockHdr(lastSPRound) + if err != nil { + return stateProofStats + } + + proto := config.Consensus[lastRoundHdr.CurrentProtocol] + votersRound := lastSPRound.SubSaturate(basics.Round(proto.StateProofInterval)) + votersRoundHdr, err := pool.ledger.BlockHdr(votersRound) + if err != nil { + return stateProofStats + } + + totalWeight := votersRoundHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.Raw + stateProofStats.ProvenWeight, _ = basics.Muldiv(totalWeight, uint64(proto.StateProofWeightThreshold), 1<<32) + + return stateProofStats +} + // AssembleBlock assembles a block for a given round, trying not to // take longer than deadline to finish. func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Time) (assembled *ledgercore.ValidatedBlock, err error) { @@ -816,6 +859,10 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim } } stats.TotalLength += uint64(encodedLen) + stats.StateProofNextRound = uint64(assembled.Block().StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + if txib.Txn.Type == protocol.StateProofTx { + stats.StateProofStats = pool.getStateProofStats(&txib, encodedLen) + } } stats.AverageFee = totalFees / uint64(stats.IncludedCount) diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index 2f1bd75b6c..d4c863d440 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -17,24 +17,33 @@ package pools import ( + "bufio" + "bytes" "fmt" "math/rand" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/merklesignature" + cryptostateproof "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/stateproof" + "github.com/algorand/go-algorand/stateproof/verify" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -1271,3 +1280,222 @@ func TestTxPoolSizeLimits(t *testing.T) { } } } + +func TestTStateProofLogging(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + + cfg := config.GetDefaultLocal() + cfg.TxPoolSize = testPoolSize + cfg.EnableProcessBlockStats = false + + // Create 5 accounts, the last 3 uesd for signing the SP + numOfAccounts := 20 + // Generate accounts + secrets := make([]*crypto.SignatureSecrets, numOfAccounts) + addresses := make([]basics.Address, numOfAccounts) + for i := 0; i < numOfAccounts; i++ { + secret := keypair() + addr := basics.Address(secret.SignatureVerifier) + secrets[i] = secret + addresses[i] = addr + } + accountsBalances := make(map[basics.Address]uint64) + for _, addr := range addresses { + accountsBalances[addr] = 1000000000 + } + initAccounts := initAcc(accountsBalances) + + // Prepare the SP signing keys + allKeys := make([]*merklesignature.Secrets, 0, 3) + stateproofIntervals := uint64(256) + for a := 2; a < numOfAccounts; a++ { + keys, err := merklesignature.New(0, uint64(512), stateproofIntervals) + require.NoError(t, err) + + acct := initAccounts[addresses[a]] + acct.StateProofID = keys.GetVerifier().Commitment + acct.Status = basics.Online + acct.VoteLastValid = 100000 + initAccounts[addresses[a]] = acct + + allKeys = append(allKeys, keys) + } + + // 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}) + var buf bytes.Buffer + logger.SetOutput(&buf) + + // Set the ledger and the transaction pool + mockLedger := makeMockLedgerFuture(t, initAccounts) + transactionPool := MakeTransactionPool(mockLedger, cfg, logger) + transactionPool.logAssembleStats = true + + // Set the first round block + var b bookkeeping.Block + b.BlockHeader.GenesisID = "pooltest" + b.BlockHeader.GenesisHash = mockLedger.GenesisHash() + b.CurrentProtocol = protocol.ConsensusFuture + b.BlockHeader.Round = 1 + + phdr, err := mockLedger.BlockHdr(0) + require.NoError(t, err) + b.BlockHeader.Branch = phdr.Hash() + + eval, err := mockLedger.StartEvaluator(b.BlockHeader, 0, 10000) + require.NoError(t, err) + + // Simulate the blocks up to round 512 without any transactions + for i := 1; true; i++ { + blk, err := transactionPool.AssembleBlock(basics.Round(i), time.Time{}) + require.NoError(t, err) + + err = mockLedger.AddValidatedBlock(*blk, agreement.Certificate{}) + require.NoError(t, err) + + // Move to the next round + b.BlockHeader.Round++ + transactionPool.OnNewBlock(blk.Block(), ledgercore.StateDelta{}) + + phdr, err := mockLedger.BlockHdr(basics.Round(i)) + require.NoError(t, err) + b.BlockHeader.Branch = phdr.Hash() + b.BlockHeader.TimeStamp = phdr.TimeStamp + 10 + + if i == 513 { + break + } + + eval, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000) + require.NoError(t, err) + } + + // Prepare the transaction with the SP + round := basics.Round(512) + spRoundHdr, err := mockLedger.BlockHdr(round) + require.NoError(t, err) + + votersRound := round.SubSaturate(basics.Round(proto.StateProofInterval)) + votersRoundHdr, err := mockLedger.BlockHdr(votersRound) + require.NoError(t, err) + + provenWeight, err := verify.GetProvenWeight(&votersRoundHdr, &spRoundHdr) + require.NoError(t, err) + + lookback := votersRound.SubSaturate(basics.Round(proto.StateProofVotersLookback)) + voters, err := mockLedger.VotersForStateProof(lookback) + require.NoError(t, err) + require.NotNil(t, voters) + + // Get the message + msg, err := stateproof.GenerateStateProofMessage(mockLedger, uint64(votersRound), spRoundHdr) + + // Get the SP + proof := generateProofForTesting(uint64(round), msg, provenWeight, voters.Participants, voters.Tree, allKeys, t) + + // Set the transaction with the SP + var stxn transactions.SignedTxn + stxn.Txn.Type = protocol.StateProofTx + stxn.Txn.Sender = transactions.StateProofSender + stxn.Txn.FirstValid = 512 + stxn.Txn.LastValid = 1024 + stxn.Txn.GenesisHash = mockLedger.GenesisHash() + stxn.Txn.StateProofType = protocol.StateProofBasic + stxn.Txn.StateProof = *proof + require.NoError(t, err) + stxn.Txn.Message = msg + + err = stxn.Txn.WellFormed(transactions.SpecialAddresses{}, proto) + require.NoError(t, err) + + // Add it to the transaction pool and assemble the block + eval, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 1000000) + require.NoError(t, err) + + err = eval.Transaction(stxn, transactions.ApplyData{}) + require.NoError(t, err) + + err = transactionPool.RememberOne(stxn) + require.NoError(t, err) + transactionPool.recomputeBlockEvaluator(nil, 0) + _, err = transactionPool.AssembleBlock(514, time.Time{}) + require.NoError(t, err) + + // parse the log messages and retreive the Metrics for SP in assmbe block + scanner := bufio.NewScanner(strings.NewReader(buf.String())) + lines := make([]string, 0) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + fmt.Println(lines[len(lines)-1]) + parts := strings.Split(lines[len(lines)-1], "StateProofNextRound:") + + // Verify the Metrics is correct + var nextRound, pWeight, signedWeight, numReveals, posToReveal, txnSize uint64 + var str1 string + fmt.Sscanf(parts[1], "%d, ProvenWeight:%d, SignedWeight:%d, NumReveals:%d, NumPosToReveal:%d, TxnSize:%d\"%s", + &nextRound, &pWeight, &signedWeight, &numReveals, &posToReveal, &txnSize, &str1) + require.Equal(t, uint64(768), nextRound) + require.Equal(t, provenWeight, pWeight) + require.Equal(t, proof.SignedWeight, signedWeight) + require.Less(t, numOfAccounts/2, int(numReveals)) + require.Greater(t, numOfAccounts, int(numReveals)) + require.Equal(t, len(proof.PositionsToReveal), int(posToReveal)) + stxn.Txn.GenesisHash = crypto.Digest{} + require.Equal(t, stxn.GetEncodedLength(), int(txnSize)) +} + +// Given the round number, partArray and partTree from the previous period block, the keys and the totalWeight +// return a stateProof which can be submitted in a transaction to the transaction pool and assembled into a new block. +func generateProofForTesting( + round uint64, + msg stateproofmsg.Message, + provenWeight uint64, + partArray basics.ParticipantsArray, + partTree *merklearray.Tree, + allKeys []*merklesignature.Secrets, + t *testing.T) *cryptostateproof.StateProof { + + data := msg.Hash() + + // Sign with the participation keys + sigs := make(map[merklesignature.Verifier]merklesignature.Signature) + for _, keys := range allKeys { + signerInRound := keys.GetSigner(round) + sig, err := signerInRound.SignBytes(data[:]) + require.NoError(t, err) + sigs[*keys.GetVerifier()] = sig + } + + // Prepare the builder + stateProofStrengthTargetForTests := config.Consensus[protocol.ConsensusFuture].StateProofStrengthTarget + b, err := cryptostateproof.MakeBuilder(data, round, provenWeight, + partArray, partTree, stateProofStrengthTargetForTests) + require.NoError(t, err) + + // Add the signatures + for i := range partArray { + p, err := b.Present(uint64(i)) + require.False(t, p) + require.NoError(t, err) + s := sigs[partArray[i].PK] + err = b.IsValid(uint64(i), &s, true) + require.NoError(t, err) + b.Add(uint64(i), s) + + // sanity check that the builder add the signature + isPresent, err := b.Present(uint64(i)) + require.NoError(t, err) + require.True(t, isPresent) + } + + // Build the SP + proof, err := b.Build() + require.NoError(t, err) + + return proof +} diff --git a/data/stateproofmsg/message.go b/data/stateproofmsg/message.go new file mode 100644 index 0000000000..aea1954756 --- /dev/null +++ b/data/stateproofmsg/message.go @@ -0,0 +1,50 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproofmsg + +import ( + "github.com/algorand/go-algorand/crypto" + sp "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/protocol" +) + +// Message represents the message that the state proofs are attesting to. This message can be +// used by lightweight client and gives it the ability to verify proofs on the Algorand's state. +// In addition to that proof, this message also contains fields that +// are needed in order to verify the next state proofs (VotersCommitment and LnProvenWeight). +type Message struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + // BlockHeadersCommitment contains a commitment on all light block headers within a state proof interval. + BlockHeadersCommitment []byte `codec:"b,allocbound=crypto.Sha256Size"` + VotersCommitment []byte `codec:"v,allocbound=crypto.SumhashDigestSize"` + LnProvenWeight uint64 `codec:"P"` + FirstAttestedRound uint64 `codec:"f"` + LastAttestedRound uint64 `codec:"l"` +} + +// ToBeHashed returns the bytes of the message. +func (m Message) ToBeHashed() (protocol.HashID, []byte) { + return protocol.StateProofMessage, protocol.Encode(&m) +} + +// Hash returns a hashed representation fitting the state proof messages. +func (m *Message) Hash() sp.MessageHash { + digest := crypto.GenericHashObj(crypto.HashFactory{HashType: sp.MessageHashType}.NewHash(), m) + result := sp.MessageHash{} + copy(result[:], digest) + return result +} diff --git a/data/stateproofmsg/msgp_gen.go b/data/stateproofmsg/msgp_gen.go new file mode 100644 index 0000000000..d8dfb3948f --- /dev/null +++ b/data/stateproofmsg/msgp_gen.go @@ -0,0 +1,257 @@ +package stateproofmsg + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/crypto" +) + +// The following msgp objects are implemented in this file: +// Message +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// + +// MarshalMsg implements msgp.Marshaler +func (z *Message) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(5) + var zb0001Mask uint8 /* 6 bits */ + if (*z).LnProvenWeight == 0 { + zb0001Len-- + zb0001Mask |= 0x1 + } + if len((*z).BlockHeadersCommitment) == 0 { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).FirstAttestedRound == 0 { + zb0001Len-- + zb0001Mask |= 0x8 + } + if (*z).LastAttestedRound == 0 { + zb0001Len-- + zb0001Mask |= 0x10 + } + if len((*z).VotersCommitment) == 0 { + zb0001Len-- + zb0001Mask |= 0x20 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x1) == 0 { // if not empty + // string "P" + o = append(o, 0xa1, 0x50) + o = msgp.AppendUint64(o, (*z).LnProvenWeight) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "b" + o = append(o, 0xa1, 0x62) + o = msgp.AppendBytes(o, (*z).BlockHeadersCommitment) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "f" + o = append(o, 0xa1, 0x66) + o = msgp.AppendUint64(o, (*z).FirstAttestedRound) + } + if (zb0001Mask & 0x10) == 0 { // if not empty + // string "l" + o = append(o, 0xa1, 0x6c) + o = msgp.AppendUint64(o, (*z).LastAttestedRound) + } + if (zb0001Mask & 0x20) == 0 { // if not empty + // string "v" + o = append(o, 0xa1, 0x76) + o = msgp.AppendBytes(o, (*z).VotersCommitment) + } + } + return +} + +func (_ *Message) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*Message) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *Message) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + var zb0003 int + zb0003, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "BlockHeadersCommitment") + return + } + if zb0003 > crypto.Sha256Size { + err = msgp.ErrOverflow(uint64(zb0003), uint64(crypto.Sha256Size)) + return + } + (*z).BlockHeadersCommitment, bts, err = msgp.ReadBytesBytes(bts, (*z).BlockHeadersCommitment) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "BlockHeadersCommitment") + return + } + } + if zb0001 > 0 { + zb0001-- + var zb0004 int + zb0004, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "VotersCommitment") + return + } + if zb0004 > crypto.SumhashDigestSize { + err = msgp.ErrOverflow(uint64(zb0004), uint64(crypto.SumhashDigestSize)) + return + } + (*z).VotersCommitment, bts, err = msgp.ReadBytesBytes(bts, (*z).VotersCommitment) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "VotersCommitment") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LnProvenWeight") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).FirstAttestedRound, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "FirstAttestedRound") + return + } + } + if zb0001 > 0 { + zb0001-- + (*z).LastAttestedRound, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "LastAttestedRound") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = Message{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "b": + var zb0005 int + zb0005, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "BlockHeadersCommitment") + return + } + if zb0005 > crypto.Sha256Size { + err = msgp.ErrOverflow(uint64(zb0005), uint64(crypto.Sha256Size)) + return + } + (*z).BlockHeadersCommitment, bts, err = msgp.ReadBytesBytes(bts, (*z).BlockHeadersCommitment) + if err != nil { + err = msgp.WrapError(err, "BlockHeadersCommitment") + return + } + case "v": + var zb0006 int + zb0006, err = msgp.ReadBytesBytesHeader(bts) + if err != nil { + err = msgp.WrapError(err, "VotersCommitment") + return + } + if zb0006 > crypto.SumhashDigestSize { + err = msgp.ErrOverflow(uint64(zb0006), uint64(crypto.SumhashDigestSize)) + return + } + (*z).VotersCommitment, bts, err = msgp.ReadBytesBytes(bts, (*z).VotersCommitment) + if err != nil { + err = msgp.WrapError(err, "VotersCommitment") + return + } + case "P": + (*z).LnProvenWeight, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LnProvenWeight") + return + } + case "f": + (*z).FirstAttestedRound, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "FirstAttestedRound") + return + } + case "l": + (*z).LastAttestedRound, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "LastAttestedRound") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *Message) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*Message) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *Message) Msgsize() (s int) { + s = 1 + 2 + msgp.BytesPrefixSize + len((*z).BlockHeadersCommitment) + 2 + msgp.BytesPrefixSize + len((*z).VotersCommitment) + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + 2 + msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z *Message) MsgIsZero() bool { + return (len((*z).BlockHeadersCommitment) == 0) && (len((*z).VotersCommitment) == 0) && ((*z).LnProvenWeight == 0) && ((*z).FirstAttestedRound == 0) && ((*z).LastAttestedRound == 0) +} diff --git a/data/stateproofmsg/msgp_gen_test.go b/data/stateproofmsg/msgp_gen_test.go new file mode 100644 index 0000000000..c8ce88e556 --- /dev/null +++ b/data/stateproofmsg/msgp_gen_test.go @@ -0,0 +1,75 @@ +//go:build !skip_msgp_testing +// +build !skip_msgp_testing + +package stateproofmsg + +// Code generated by github.com/algorand/msgp DO NOT EDIT. + +import ( + "testing" + + "github.com/algorand/msgp/msgp" + + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestMarshalUnmarshalMessage(t *testing.T) { + partitiontest.PartitionTest(t) + v := Message{} + 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 TestRandomizedEncodingMessage(t *testing.T) { + protocol.RunEncodingTest(t, &Message{}) +} + +func BenchmarkMarshalMsgMessage(b *testing.B) { + v := Message{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgMessage(b *testing.B) { + v := Message{} + 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 BenchmarkUnmarshalMessage(b *testing.B) { + v := Message{} + 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/data/transactions/keyreg.go b/data/transactions/keyreg.go index 16f7a1f327..401617ef72 100644 --- a/data/transactions/keyreg.go +++ b/data/transactions/keyreg.go @@ -28,7 +28,7 @@ type KeyregTxnFields struct { VotePK crypto.OneTimeSignatureVerifier `codec:"votekey"` SelectionPK crypto.VRFVerifier `codec:"selkey"` - StateProofPK merklesignature.Verifier `codec:"sprfkey"` + StateProofPK merklesignature.Commitment `codec:"sprfkey"` VoteFirst basics.Round `codec:"votefst"` VoteLast basics.Round `codec:"votelst"` VoteKeyDilution uint64 `codec:"votekd"` diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index ea65ab6c8e..7334485d9f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -346,7 +346,7 @@ func feeCredit(txgroup []transactions.SignedTxnWithAD, minFee uint64) uint64 { minFeeCount := uint64(0) feesPaid := uint64(0) for _, stxn := range txgroup { - if stxn.Txn.Type != protocol.CompactCertTx { + if stxn.Txn.Type != protocol.StateProofTx { minFeeCount++ } feesPaid = basics.AddSaturate(feesPaid, stxn.Txn.Fee.Raw) diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index cd0229321a..35d8f28e3d 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -711,7 +711,7 @@ func TestKeyReg(t *testing.T) { ` ep, tx, ledger := MakeSampleEnv() ep.Proto.EnableStateProofKeyregCheck = true - ep.Proto.MaxKeyregValidPeriod = ((1 << 16) * 256) - 1 // 2^16 StateProof keys times CompactCertRounds (interval) + ep.Proto.MaxKeyregValidPeriod = ((1 << 16) * 256) - 1 // 2^16 StateProof keys times StateProofInterval (interval) ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), ep.Proto.MinTxnFee) TestApp(t, params+keyreg, ep) @@ -729,7 +729,7 @@ func TestKeyReg(t *testing.T) { ` ep, tx, ledger := MakeSampleEnv() ep.Proto.EnableStateProofKeyregCheck = true - ep.Proto.MaxKeyregValidPeriod = ((1 << 16) * 256) - 1 // 2^16 StateProof keys times CompactCertRounds (interval) + ep.Proto.MaxKeyregValidPeriod = ((1 << 16) * 256) - 1 // 2^16 StateProof keys times StateProofInterval (interval) ledger.NewApp(tx.Receiver, 888, basics.AppParams{}) ledger.NewAccount(appAddr(888), ep.Proto.MinTxnFee) TestApp(t, params+keyreg, ep, "validity period for keyreg transaction is too long") // VoteLast is +1 over the limit diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 299d4da243..779a74d595 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -53,14 +53,6 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // -// CompactCertTxnFields -// |-----> (*) MarshalMsg -// |-----> (*) CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> (*) Msgsize -// |-----> (*) MsgIsZero -// // EvalDelta // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -149,6 +141,14 @@ import ( // |-----> (*) Msgsize // |-----> (*) MsgIsZero // +// StateProofTxnFields +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// // Transaction // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -1516,158 +1516,6 @@ func (z *AssetTransferTxnFields) MsgIsZero() bool { return ((*z).XferAsset.MsgIsZero()) && ((*z).AssetAmount == 0) && ((*z).AssetSender.MsgIsZero()) && ((*z).AssetReceiver.MsgIsZero()) && ((*z).AssetCloseTo.MsgIsZero()) } -// MarshalMsg implements msgp.Marshaler -func (z *CompactCertTxnFields) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - // omitempty: check for empty values - zb0001Len := uint32(3) - var zb0001Mask uint8 /* 4 bits */ - if (*z).Cert.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x2 - } - if (*z).CertRound.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x4 - } - if (*z).CertType.MsgIsZero() { - zb0001Len-- - zb0001Mask |= 0x8 - } - // variable map header, size zb0001Len - o = append(o, 0x80|uint8(zb0001Len)) - if zb0001Len != 0 { - if (zb0001Mask & 0x2) == 0 { // if not empty - // string "cert" - o = append(o, 0xa4, 0x63, 0x65, 0x72, 0x74) - o = (*z).Cert.MarshalMsg(o) - } - if (zb0001Mask & 0x4) == 0 { // if not empty - // string "certrnd" - o = append(o, 0xa7, 0x63, 0x65, 0x72, 0x74, 0x72, 0x6e, 0x64) - o = (*z).CertRound.MarshalMsg(o) - } - if (zb0001Mask & 0x8) == 0 { // if not empty - // string "certtype" - o = append(o, 0xa8, 0x63, 0x65, 0x72, 0x74, 0x74, 0x79, 0x70, 0x65) - o = (*z).CertType.MarshalMsg(o) - } - } - return -} - -func (_ *CompactCertTxnFields) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactCertTxnFields) - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *CompactCertTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var zb0001 int - var zb0002 bool - zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) - if _, ok := err.(msgp.TypeError); ok { - zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).CertRound.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CertRound") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).CertType.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CertType") - return - } - } - if zb0001 > 0 { - zb0001-- - bts, err = (*z).Cert.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Cert") - return - } - } - if zb0001 > 0 { - err = msgp.ErrTooManyArrayFields(zb0001) - if err != nil { - err = msgp.WrapError(err, "struct-from-array") - return - } - } - } else { - if err != nil { - err = msgp.WrapError(err) - return - } - if zb0002 { - (*z) = CompactCertTxnFields{} - } - for zb0001 > 0 { - zb0001-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - switch string(field) { - case "certrnd": - bts, err = (*z).CertRound.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "CertRound") - return - } - case "certtype": - bts, err = (*z).CertType.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "CertType") - return - } - case "cert": - bts, err = (*z).Cert.UnmarshalMsg(bts) - if err != nil { - err = msgp.WrapError(err, "Cert") - return - } - default: - err = msgp.ErrNoField(string(field)) - if err != nil { - err = msgp.WrapError(err) - return - } - } - } - } - o = bts - return -} - -func (_ *CompactCertTxnFields) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactCertTxnFields) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z *CompactCertTxnFields) Msgsize() (s int) { - s = 1 + 8 + (*z).CertRound.Msgsize() + 9 + (*z).CertType.Msgsize() + 5 + (*z).Cert.Msgsize() - return -} - -// MsgIsZero returns whether this is a zero value -func (z *CompactCertTxnFields) MsgIsZero() bool { - return ((*z).CertRound.MsgIsZero()) && ((*z).CertType.MsgIsZero()) && ((*z).Cert.MsgIsZero()) -} - // MarshalMsg implements msgp.Marshaler func (z *EvalDelta) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -4196,6 +4044,158 @@ func (z *SignedTxnWithAD) MsgIsZero() bool { return ((*z).SignedTxn.Sig.MsgIsZero()) && ((*z).SignedTxn.Msig.MsgIsZero()) && ((*z).SignedTxn.Lsig.MsgIsZero()) && ((*z).SignedTxn.Txn.MsgIsZero()) && ((*z).SignedTxn.AuthAddr.MsgIsZero()) && ((*z).ApplyData.ClosingAmount.MsgIsZero()) && ((*z).ApplyData.AssetClosingAmount == 0) && ((*z).ApplyData.SenderRewards.MsgIsZero()) && ((*z).ApplyData.ReceiverRewards.MsgIsZero()) && ((*z).ApplyData.CloseRewards.MsgIsZero()) && ((*z).ApplyData.EvalDelta.MsgIsZero()) && ((*z).ApplyData.ConfigAsset.MsgIsZero()) && ((*z).ApplyData.ApplicationID.MsgIsZero()) } +// MarshalMsg implements msgp.Marshaler +func (z *StateProofTxnFields) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).StateProof.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).Message.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).StateProofType.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "sp" + o = append(o, 0xa2, 0x73, 0x70) + o = (*z).StateProof.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "spmsg" + o = append(o, 0xa5, 0x73, 0x70, 0x6d, 0x73, 0x67) + o = (*z).Message.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "sptype" + o = append(o, 0xa6, 0x73, 0x70, 0x74, 0x79, 0x70, 0x65) + o = (*z).StateProofType.MarshalMsg(o) + } + } + return +} + +func (_ *StateProofTxnFields) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofTxnFields) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProofType.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProofType") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).StateProof.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "StateProof") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).Message.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "Message") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = StateProofTxnFields{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "sptype": + bts, err = (*z).StateProofType.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProofType") + return + } + case "sp": + bts, err = (*z).StateProof.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "StateProof") + return + } + case "spmsg": + bts, err = (*z).Message.UnmarshalMsg(bts) + if err != nil { + err = msgp.WrapError(err, "Message") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (_ *StateProofTxnFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofTxnFields) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *StateProofTxnFields) Msgsize() (s int) { + s = 1 + 7 + (*z).StateProofType.Msgsize() + 3 + (*z).StateProof.Msgsize() + 6 + (*z).Message.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *StateProofTxnFields) MsgIsZero() bool { + return ((*z).StateProofType.MsgIsZero()) && ((*z).StateProof.MsgIsZero()) && ((*z).Message.MsgIsZero()) +} + // MarshalMsg implements msgp.Marshaler func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -4278,83 +4278,83 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { zb0006Len-- zb0006Mask |= 0x8000000 } - if (*z).CompactCertTxnFields.Cert.MsgIsZero() { + if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x10000000 } - if (*z).CompactCertTxnFields.CertRound.MsgIsZero() { + if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x20000000 } - if (*z).CompactCertTxnFields.CertType.MsgIsZero() { + if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x40000000 } - if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() { + if (*z).Header.Fee.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x80000000 } - if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() { + if (*z).Header.FirstValid.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x100000000 } - if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() { + if (*z).Header.GenesisID == "" { zb0006Len-- zb0006Mask |= 0x200000000 } - if (*z).Header.Fee.MsgIsZero() { + if (*z).Header.GenesisHash.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x400000000 } - if (*z).Header.FirstValid.MsgIsZero() { + if (*z).Header.Group.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x800000000 } - if (*z).Header.GenesisID == "" { + if (*z).Header.LastValid.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x1000000000 } - if (*z).Header.GenesisHash.MsgIsZero() { + if (*z).Header.Lease == ([32]byte{}) { zb0006Len-- zb0006Mask |= 0x2000000000 } - if (*z).Header.Group.MsgIsZero() { + if (*z).KeyregTxnFields.Nonparticipation == false { zb0006Len-- zb0006Mask |= 0x4000000000 } - if (*z).Header.LastValid.MsgIsZero() { + if len((*z).Header.Note) == 0 { zb0006Len-- zb0006Mask |= 0x8000000000 } - if (*z).Header.Lease == ([32]byte{}) { + if (*z).PaymentTxnFields.Receiver.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x10000000000 } - if (*z).KeyregTxnFields.Nonparticipation == false { + if (*z).Header.RekeyTo.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x20000000000 } - if len((*z).Header.Note) == 0 { + if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x40000000000 } - if (*z).PaymentTxnFields.Receiver.MsgIsZero() { + if (*z).Header.Sender.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x80000000000 } - if (*z).Header.RekeyTo.MsgIsZero() { + if (*z).StateProofTxnFields.StateProof.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x100000000000 } - if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() { + if (*z).StateProofTxnFields.Message.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x200000000000 } - if (*z).Header.Sender.MsgIsZero() { + if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x400000000000 } - if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() { + if (*z).StateProofTxnFields.StateProofType.MsgIsZero() { zb0006Len-- zb0006Mask |= 0x800000000000 } @@ -4509,105 +4509,105 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).AssetConfigTxnFields.ConfigAsset.MarshalMsg(o) } if (zb0006Mask & 0x10000000) == 0 { // if not empty - // string "cert" - o = append(o, 0xa4, 0x63, 0x65, 0x72, 0x74) - o = (*z).CompactCertTxnFields.Cert.MarshalMsg(o) - } - if (zb0006Mask & 0x20000000) == 0 { // if not empty - // string "certrnd" - o = append(o, 0xa7, 0x63, 0x65, 0x72, 0x74, 0x72, 0x6e, 0x64) - o = (*z).CompactCertTxnFields.CertRound.MarshalMsg(o) - } - if (zb0006Mask & 0x40000000) == 0 { // if not empty - // string "certtype" - o = append(o, 0xa8, 0x63, 0x65, 0x72, 0x74, 0x74, 0x79, 0x70, 0x65) - o = (*z).CompactCertTxnFields.CertType.MarshalMsg(o) - } - if (zb0006Mask & 0x80000000) == 0 { // if not empty // string "close" o = append(o, 0xa5, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).PaymentTxnFields.CloseRemainderTo.MarshalMsg(o) } - if (zb0006Mask & 0x100000000) == 0 { // if not empty + if (zb0006Mask & 0x20000000) == 0 { // if not empty // string "fadd" o = append(o, 0xa4, 0x66, 0x61, 0x64, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAccount.MarshalMsg(o) } - if (zb0006Mask & 0x200000000) == 0 { // if not empty + if (zb0006Mask & 0x40000000) == 0 { // if not empty // string "faid" o = append(o, 0xa4, 0x66, 0x61, 0x69, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAsset.MarshalMsg(o) } - if (zb0006Mask & 0x400000000) == 0 { // if not empty + if (zb0006Mask & 0x80000000) == 0 { // if not empty // string "fee" o = append(o, 0xa3, 0x66, 0x65, 0x65) o = (*z).Header.Fee.MarshalMsg(o) } - if (zb0006Mask & 0x800000000) == 0 { // if not empty + if (zb0006Mask & 0x100000000) == 0 { // if not empty // string "fv" o = append(o, 0xa2, 0x66, 0x76) o = (*z).Header.FirstValid.MarshalMsg(o) } - if (zb0006Mask & 0x1000000000) == 0 { // if not empty + if (zb0006Mask & 0x200000000) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Header.GenesisID) } - if (zb0006Mask & 0x2000000000) == 0 { // if not empty + if (zb0006Mask & 0x400000000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Header.GenesisHash.MarshalMsg(o) } - if (zb0006Mask & 0x4000000000) == 0 { // if not empty + if (zb0006Mask & 0x800000000) == 0 { // if not empty // string "grp" o = append(o, 0xa3, 0x67, 0x72, 0x70) o = (*z).Header.Group.MarshalMsg(o) } - if (zb0006Mask & 0x8000000000) == 0 { // if not empty + if (zb0006Mask & 0x1000000000) == 0 { // if not empty // string "lv" o = append(o, 0xa2, 0x6c, 0x76) o = (*z).Header.LastValid.MarshalMsg(o) } - if (zb0006Mask & 0x10000000000) == 0 { // if not empty + if (zb0006Mask & 0x2000000000) == 0 { // if not empty // string "lx" o = append(o, 0xa2, 0x6c, 0x78) o = msgp.AppendBytes(o, ((*z).Header.Lease)[:]) } - if (zb0006Mask & 0x20000000000) == 0 { // if not empty + if (zb0006Mask & 0x4000000000) == 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 (zb0006Mask & 0x40000000000) == 0 { // if not empty + if (zb0006Mask & 0x8000000000) == 0 { // if not empty // string "note" o = append(o, 0xa4, 0x6e, 0x6f, 0x74, 0x65) o = msgp.AppendBytes(o, (*z).Header.Note) } - if (zb0006Mask & 0x80000000000) == 0 { // if not empty + if (zb0006Mask & 0x10000000000) == 0 { // if not empty // string "rcv" o = append(o, 0xa3, 0x72, 0x63, 0x76) o = (*z).PaymentTxnFields.Receiver.MarshalMsg(o) } - if (zb0006Mask & 0x100000000000) == 0 { // if not empty + if (zb0006Mask & 0x20000000000) == 0 { // if not empty // string "rekey" o = append(o, 0xa5, 0x72, 0x65, 0x6b, 0x65, 0x79) o = (*z).Header.RekeyTo.MarshalMsg(o) } - if (zb0006Mask & 0x200000000000) == 0 { // if not empty + if (zb0006Mask & 0x40000000000) == 0 { // if not empty // string "selkey" o = append(o, 0xa6, 0x73, 0x65, 0x6c, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.SelectionPK.MarshalMsg(o) } - if (zb0006Mask & 0x400000000000) == 0 { // if not empty + if (zb0006Mask & 0x80000000000) == 0 { // if not empty // string "snd" o = append(o, 0xa3, 0x73, 0x6e, 0x64) o = (*z).Header.Sender.MarshalMsg(o) } - if (zb0006Mask & 0x800000000000) == 0 { // if not empty + if (zb0006Mask & 0x100000000000) == 0 { // if not empty + // string "sp" + o = append(o, 0xa2, 0x73, 0x70) + o = (*z).StateProofTxnFields.StateProof.MarshalMsg(o) + } + if (zb0006Mask & 0x200000000000) == 0 { // if not empty + // string "spmsg" + o = append(o, 0xa5, 0x73, 0x70, 0x6d, 0x73, 0x67) + o = (*z).StateProofTxnFields.Message.MarshalMsg(o) + } + if (zb0006Mask & 0x400000000000) == 0 { // if not empty // string "sprfkey" o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x66, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.StateProofPK.MarshalMsg(o) } + if (zb0006Mask & 0x800000000000) == 0 { // if not empty + // string "sptype" + o = append(o, 0xa6, 0x73, 0x70, 0x74, 0x79, 0x70, 0x65) + o = (*z).StateProofTxnFields.StateProofType.MarshalMsg(o) + } if (zb0006Mask & 0x1000000000000) == 0 { // if not empty // string "type" o = append(o, 0xa4, 0x74, 0x79, 0x70, 0x65) @@ -5116,25 +5116,25 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0006 > 0 { zb0006-- - bts, err = (*z).CompactCertTxnFields.CertRound.UnmarshalMsg(bts) + bts, err = (*z).StateProofTxnFields.StateProofType.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CertRound") + err = msgp.WrapError(err, "struct-from-array", "StateProofType") return } } if zb0006 > 0 { zb0006-- - bts, err = (*z).CompactCertTxnFields.CertType.UnmarshalMsg(bts) + bts, err = (*z).StateProofTxnFields.StateProof.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "CertType") + err = msgp.WrapError(err, "struct-from-array", "StateProof") return } } if zb0006 > 0 { zb0006-- - bts, err = (*z).CompactCertTxnFields.Cert.UnmarshalMsg(bts) + bts, err = (*z).StateProofTxnFields.Message.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Cert") + err = msgp.WrapError(err, "struct-from-array", "Message") return } } @@ -5531,22 +5531,22 @@ func (z *Transaction) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "ExtraProgramPages") return } - case "certrnd": - bts, err = (*z).CompactCertTxnFields.CertRound.UnmarshalMsg(bts) + case "sptype": + bts, err = (*z).StateProofTxnFields.StateProofType.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CertRound") + err = msgp.WrapError(err, "StateProofType") return } - case "certtype": - bts, err = (*z).CompactCertTxnFields.CertType.UnmarshalMsg(bts) + case "sp": + bts, err = (*z).StateProofTxnFields.StateProof.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "CertType") + err = msgp.WrapError(err, "StateProof") return } - case "cert": - bts, err = (*z).CompactCertTxnFields.Cert.UnmarshalMsg(bts) + case "spmsg": + bts, err = (*z).StateProofTxnFields.Message.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "Cert") + err = msgp.WrapError(err, "Message") return } default: @@ -5585,13 +5585,13 @@ func (z *Transaction) Msgsize() (s int) { for zb0005 := range (*z).ApplicationCallTxnFields.ForeignAssets { s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0005].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 + 8 + (*z).CompactCertTxnFields.CertRound.Msgsize() + 9 + (*z).CompactCertTxnFields.CertType.Msgsize() + 5 + (*z).CompactCertTxnFields.Cert.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() return } // 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.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).CompactCertTxnFields.CertRound.MsgIsZero()) && ((*z).CompactCertTxnFields.CertType.MsgIsZero()) && ((*z).CompactCertTxnFields.Cert.MsgIsZero()) + 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.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()) } // MarshalMsg implements msgp.Marshaler diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index daf552f982..ef4764d450 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -314,66 +314,6 @@ func BenchmarkUnmarshalAssetTransferTxnFields(b *testing.B) { } } -func TestMarshalUnmarshalCompactCertTxnFields(t *testing.T) { - partitiontest.PartitionTest(t) - v := CompactCertTxnFields{} - 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 TestRandomizedEncodingCompactCertTxnFields(t *testing.T) { - protocol.RunEncodingTest(t, &CompactCertTxnFields{}) -} - -func BenchmarkMarshalMsgCompactCertTxnFields(b *testing.B) { - v := CompactCertTxnFields{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgCompactCertTxnFields(b *testing.B) { - v := CompactCertTxnFields{} - 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 BenchmarkUnmarshalCompactCertTxnFields(b *testing.B) { - v := CompactCertTxnFields{} - 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 TestMarshalUnmarshalEvalDelta(t *testing.T) { partitiontest.PartitionTest(t) v := EvalDelta{} @@ -914,6 +854,66 @@ func BenchmarkUnmarshalSignedTxnWithAD(b *testing.B) { } } +func TestMarshalUnmarshalStateProofTxnFields(t *testing.T) { + partitiontest.PartitionTest(t) + v := StateProofTxnFields{} + 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 TestRandomizedEncodingStateProofTxnFields(t *testing.T) { + protocol.RunEncodingTest(t, &StateProofTxnFields{}) +} + +func BenchmarkMarshalMsgStateProofTxnFields(b *testing.B) { + v := StateProofTxnFields{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgStateProofTxnFields(b *testing.B) { + v := StateProofTxnFields{} + 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 BenchmarkUnmarshalStateProofTxnFields(b *testing.B) { + v := StateProofTxnFields{} + 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 TestMarshalUnmarshalTransaction(t *testing.T) { partitiontest.PartitionTest(t) v := Transaction{} diff --git a/data/transactions/compactcert.go b/data/transactions/stateproof.go similarity index 58% rename from data/transactions/compactcert.go rename to data/transactions/stateproof.go index 4c05520260..35ffe0ff1f 100644 --- a/data/transactions/compactcert.go +++ b/data/transactions/stateproof.go @@ -18,40 +18,31 @@ package transactions import ( "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" + "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" ) -// CompactCertTxnFields captures the fields used for compact cert transactions. -type CompactCertTxnFields struct { +// StateProofTxnFields captures the fields used for stateproof transactions. +type StateProofTxnFields struct { _struct struct{} `codec:",omitempty,omitemptyarray"` - CertRound basics.Round `codec:"certrnd"` - CertType protocol.CompactCertType `codec:"certtype"` - Cert compactcert.Cert `codec:"cert"` + StateProofType protocol.StateProofType `codec:"sptype"` + StateProof stateproof.StateProof `codec:"sp"` + Message stateproofmsg.Message `codec:"spmsg"` } -// Empty returns whether the CompactCertTxnFields are all zero, +// Empty returns whether the StateProofTxnFields are all zero, // in the sense of being omitted in a msgpack encoding. -func (cc CompactCertTxnFields) Empty() bool { - if cc.CertRound != 0 { - return false - } - if !cc.Cert.SigCommit.IsEmpty() || cc.Cert.SignedWeight != 0 { - return false - } - if len(cc.Cert.SigProofs.Path) != 0 || len(cc.Cert.PartProofs.Path) != 0 { - return false - } - if len(cc.Cert.Reveals) != 0 { - return false - } - return true +func (sp StateProofTxnFields) Empty() bool { + return sp.StateProofType == protocol.StateProofBasic && + sp.StateProof.MsgIsZero() && + sp.Message.MsgIsZero() } //msgp:ignore specialAddr -// specialAddr is used to form a unique address that will send out compact certs. +// specialAddr is used to form a unique address that will send out state proofs. type specialAddr string // ToBeHashed implements the crypto.Hashable interface @@ -59,9 +50,9 @@ func (a specialAddr) ToBeHashed() (protocol.HashID, []byte) { return protocol.SpecialAddr, []byte(a) } -// CompactCertSender is the computed address for sending out compact certs. -var CompactCertSender basics.Address +// StateProofSender is the computed address for sending out state proofs. +var StateProofSender basics.Address func init() { - CompactCertSender = basics.Address(crypto.HashObj(specialAddr("CompactCertSender"))) + StateProofSender = basics.Address(crypto.HashObj(specialAddr("StateProofSender"))) } diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index 1c63c0c8a4..6198a06634 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -97,7 +97,7 @@ type Transaction struct { AssetTransferTxnFields AssetFreezeTxnFields ApplicationCallTxnFields - CompactCertTxnFields + StateProofTxnFields } // ApplyData contains information about the transaction's execution. @@ -290,6 +290,13 @@ var errKeyregTxnNotEmptyStateProofPK = errors.New("transaction field StateProofP 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 { @@ -343,7 +350,6 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa if !suppliesNullKeys { return errKeyregTxnGoingOnlineWithNonParticipating } - } if err := tx.stateProofPKWellFormed(proto); err != nil { @@ -472,32 +478,32 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return fmt.Errorf("tx.GlobalStateSchema too large, max number of keys is %d", proto.MaxGlobalSchemaEntries) } - case protocol.CompactCertTx: - if proto.CompactCertRounds == 0 { - return fmt.Errorf("compact certs not supported") + case protocol.StateProofTx: + if proto.StateProofInterval == 0 { + return errStateProofNotSupported } - // This is a placeholder transaction used to store compact certs + // 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 != CompactCertSender { - return fmt.Errorf("sender must be the compact-cert sender") + if tx.Sender != StateProofSender { + return errBadSenderInStateProofTxn } if !tx.Fee.IsZero() { - return fmt.Errorf("fee must be zero") + return errFeeMustBeZeroInStateproofTxn } if len(tx.Note) != 0 { - return fmt.Errorf("note must be empty") + return errNoteMustBeEmptyInStateproofTxn } if !tx.Group.IsZero() { - return fmt.Errorf("group must be zero") + return errGroupMustBeZeroInStateproofTxn } if !tx.RekeyTo.IsZero() { - return fmt.Errorf("rekey must be zero") + return errRekeyToMustBeZeroInStateproofTxn } if tx.Lease != [32]byte{} { - return fmt.Errorf("lease must be zero") + return errLeaseMustBeZeroInStateproofTxn } default: @@ -529,8 +535,8 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa nonZeroFields[protocol.ApplicationCallTx] = true } - if !tx.CompactCertTxnFields.Empty() { - nonZeroFields[protocol.CompactCertTx] = true + if !tx.StateProofTxnFields.Empty() { + nonZeroFields[protocol.StateProofTx] = true } for t, nonZero := range nonZeroFields { @@ -540,8 +546,8 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa } if !proto.EnableFeePooling && tx.Fee.LessThan(basics.MicroAlgos{Raw: proto.MinTxnFee}) { - if tx.Type == protocol.CompactCertTx { - // Zero fee allowed for compact cert txn. + if tx.Type == protocol.StateProofTx { + // Zero fee allowed for stateProof txn. } else { return makeMinFeeErrorf("transaction had fee %d, which is less than the minimum %d", tx.Fee.Raw, proto.MinTxnFee) } diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 4536fb122c..45fd1ca495 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -22,14 +22,16 @@ import ( "strings" "testing" - "github.com/algorand/go-algorand/crypto/merklesignature" + "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/stateproofmsg" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" - "github.com/stretchr/testify/require" ) func TestTransaction_EstimateEncodedSize(t *testing.T) { @@ -597,7 +599,7 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { type keyRegTestCase struct { votePK crypto.OneTimeSignatureVerifier selectionPK crypto.VRFVerifier - stateProofPK merklesignature.Verifier + stateProofPK merklesignature.Commitment voteFirst basics.Round voteLast basics.Round lastValid basics.Round @@ -611,7 +613,7 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { 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.Verifier([merklesignature.MerkleSignatureSchemeRootSize]byte{1}) + stateProofPK := merklesignature.Commitment([merklesignature.MerkleSignatureSchemeRootSize]byte{1}) maxValidPeriod := config.Consensus[protocol.ConsensusFuture].MaxKeyregValidPeriod // TODO: change to curProto.MaxKeyregValidPeriod runTestCase := func(testCase keyRegTestCase) error { @@ -1223,17 +1225,17 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { /* 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.Verifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: false, err: nil}, + /* 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.Verifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: false, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: errKeyRegEmptyStateProofPK}, + /* 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.Verifier{}, voteFirst: basics.Round(5), voteLast: basics.Round(10), lastValid: basics.Round(3), voteKeyDilution: 10000, nonParticipation: true, supportBecomeNonParticipatingTransactions: true, enableKeyregCoherencyCheck: false, enableStateProofKeyregCheck: true, err: nil}, + /* 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.Verifier{}, 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.Verifier{}, 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.Verifier{}, 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.Verifier{}, 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.Verifier{}, 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}, + /* 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) @@ -1241,3 +1243,111 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { 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, + 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, + }, + 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: 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}, + } + 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()) + }) + } +} + +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/verify/txn.go b/data/transactions/verify/txn.go index b00c2c295e..05d54caa39 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -166,7 +166,7 @@ func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.B err = fmt.Errorf("transaction %+v invalid : %w", stxn, err) return } - if stxn.Txn.Type != protocol.CompactCertTx { + if stxn.Txn.Type != protocol.StateProofTx { minFeeCount++ } feesPaid = basics.AddSaturate(feesPaid, stxn.Txn.Fee.Raw) @@ -210,11 +210,10 @@ func stxnVerifyCore(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex } if numSigs == 0 { // Special case: special sender address can issue special transaction - // types (compact cert txn) without any signature. The well-formed + // types (state proof txn) without any signature. The well-formed // check ensures that this transaction cannot pay any fee, and - // cannot have any other interesting fields, except for the compact - // cert payload. - if s.Txn.Sender == transactions.CompactCertSender && s.Txn.Type == protocol.CompactCertTx { + // cannot have any other interesting fields, except for the state proof payload. + if s.Txn.Sender == transactions.StateProofSender && s.Txn.Type == protocol.StateProofTx { return nil } diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index b01b4a3458..52cf298743 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -172,20 +172,20 @@ func TestTxnValidationEmptySig(t *testing.T) { } } -const ccProto = protocol.ConsensusVersion("test-compact-cert-enabled") +const spProto = protocol.ConsensusVersion("test-state-proof-enabled") -func TestTxnValidationCompactCert(t *testing.T) { +func TestTxnValidationStateProof(t *testing.T) { partitiontest.PartitionTest(t) proto := config.Consensus[protocol.ConsensusCurrentVersion] - proto.CompactCertRounds = 256 - config.Consensus[ccProto] = proto + proto.StateProofInterval = 256 + config.Consensus[spProto] = proto stxn := transactions.SignedTxn{ Txn: transactions.Transaction{ - Type: protocol.CompactCertTx, + Type: protocol.StateProofTx, Header: transactions.Header{ - Sender: transactions.CompactCertSender, + Sender: transactions.StateProofSender, FirstValid: 0, LastValid: 10, }, @@ -198,7 +198,7 @@ func TestTxnValidationCompactCert(t *testing.T) { RewardsPool: poolAddr, }, UpgradeState: bookkeeping.UpgradeState{ - CurrentProtocol: ccProto, + CurrentProtocol: spProto, }, } @@ -206,13 +206,13 @@ func TestTxnValidationCompactCert(t *testing.T) { require.NoError(t, err) err = Txn(&stxn, 0, groupCtx) - require.NoError(t, err, "compact cert txn %#v did not verify", stxn) + require.NoError(t, err, "state proof txn %#v did not verify", stxn) stxn2 := stxn stxn2.Txn.Type = protocol.PaymentTx stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee} err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "payment txn %#v verified from CompactCertSender", stxn2) + require.Error(t, err, "payment txn %#v verified from StateProofSender", stxn2) secret := keypair() stxn2 = stxn @@ -220,28 +220,28 @@ func TestTxnValidationCompactCert(t *testing.T) { stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee} stxn2 = stxn2.Txn.Sign(secret) err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "compact cert txn %#v verified from non-CompactCertSender", stxn2) + require.Error(t, err, "state proof txn %#v verified from non-StateProofSender", stxn2) - // Compact cert txns are not allowed to have non-zero values for many fields + // state proof txns are not allowed to have non-zero values for many fields stxn2 = stxn stxn2.Txn.Header.Fee = basics.MicroAlgos{Raw: proto.MinTxnFee} err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "compact cert txn %#v verified", stxn2) + require.Error(t, err, "state proof txn %#v verified", stxn2) stxn2 = stxn stxn2.Txn.Header.Note = []byte{'A'} err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "compact cert txn %#v verified", stxn2) + require.Error(t, err, "state proof txn %#v verified", stxn2) stxn2 = stxn stxn2.Txn.Lease[0] = 1 err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "compact cert txn %#v verified", stxn2) + require.Error(t, err, "state proof txn %#v verified", stxn2) stxn2 = stxn stxn2.Txn.RekeyTo = basics.Address(secret.SignatureVerifier) err = Txn(&stxn2, 0, groupCtx) - require.Error(t, err, "compact cert txn %#v verified", stxn2) + require.Error(t, err, "state proof txn %#v verified", stxn2) } func TestDecodeNil(t *testing.T) { diff --git a/data/txntest/txn.go b/data/txntest/txn.go index c137b171ff..988753a0d8 100644 --- a/data/txntest/txn.go +++ b/data/txntest/txn.go @@ -39,8 +39,9 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" + "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/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/protocol" @@ -100,9 +101,9 @@ type Txn struct { ClearStateProgram interface{} // string, nil or []bytes if already compiled ExtraProgramPages uint32 - CertRound basics.Round - CertType protocol.CompactCertType - Cert compactcert.Cert + StateProofType protocol.StateProofType + StateProof stateproof.StateProof + StateProofMsg stateproofmsg.Message } // Noted returns a new Txn with the given note field. @@ -240,10 +241,10 @@ func (tx Txn) Txn() transactions.Transaction { ClearStateProgram: assemble(tx.ClearStateProgram), ExtraProgramPages: tx.ExtraProgramPages, }, - CompactCertTxnFields: transactions.CompactCertTxnFields{ - CertRound: tx.CertRound, - CertType: tx.CertType, - Cert: tx.Cert, + StateProofTxnFields: transactions.StateProofTxnFields{ + StateProofType: tx.StateProofType, + StateProof: tx.StateProof, + Message: tx.StateProofMsg, }, } } diff --git a/gen/generate.go b/gen/generate.go index dc76a485db..804e893c23 100644 --- a/gen/generate.go +++ b/gen/generate.go @@ -272,7 +272,7 @@ func generateGenesisFiles(outDir string, protoVersion protocol.ConsensusVersion, data.VoteLastValid = part.LastValid data.VoteKeyDilution = part.KeyDilution if protoParams.EnableStateProofKeyregCheck { - data.StateProofID = *part.StateProofVerifier() + data.StateProofID = part.StateProofVerifier().Commitment } } diff --git a/go.mod b/go.mod index 28f0247f6e..dd46477e86 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/algorand/go-algorand go 1.17 require ( - github.com/algorand/falcon v0.0.0-20220130164023-c9e1d466f123 + github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 github.com/algorand/go-codec/codec v1.1.8 github.com/algorand/go-deadlock v0.2.2 github.com/algorand/go-sumhash v0.1.0 diff --git a/go.sum b/go.sum index 7ba25a5915..0537d11015 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/algorand/falcon v0.0.0-20220130164023-c9e1d466f123 h1:cnUjJ/iqUjJNbhUzgmxbfwHMVFnz+DLnNQx8uJcGaks= -github.com/algorand/falcon v0.0.0-20220130164023-c9e1d466f123/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= +github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414 h1:nwYN+GQ7Z5OOfZwqBO1ma7DSlP7S1YrKWICOyjkwqrc= +github.com/algorand/falcon v0.0.0-20220727072124-02a2a64c4414/go.mod h1:OkQyHlGvS0kLNcIWbC21/uQcnbfwSOQm+wiqWwBG9pQ= github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4= github.com/algorand/go-codec/codec v1.1.8 h1:lsFuhcOH2LiEhpBH3BVUUkdevVmwCRyvb7FCAAPeY6U= github.com/algorand/go-codec/codec v1.1.8/go.mod h1:tQ3zAJ6ijTps6V+wp8KsGDnPC2uhHVC7ANyrtkIY0bA= diff --git a/ledger/accountdb.go b/ledger/accountdb.go index a172cd42d2..be242e13e4 100644 --- a/ledger/accountdb.go +++ b/ledger/accountdb.go @@ -56,6 +56,7 @@ type accountsDbQueries struct { type onlineAccountsDbQueries struct { lookupOnlineStmt *sql.Stmt lookupOnlineHistoryStmt *sql.Stmt + lookupOnlineTotalsStmt *sql.Stmt } var accountsSchema = []string{ @@ -1360,7 +1361,7 @@ type baseVotingData struct { VoteFirstValid basics.Round `codec:"C"` VoteLastValid basics.Round `codec:"D"` VoteKeyDilution uint64 `codec:"E"` - StateProofID merklesignature.Verifier `codec:"F"` + StateProofID merklesignature.Commitment `codec:"F"` } type baseOnlineAccountData struct { @@ -2302,7 +2303,7 @@ func performOnlineAccountsTableMigration(ctx context.Context, tx *sql.Tx, progre if ba.Status != basics.Online && !ba.StateProofID.IsEmpty() { // store old data for account hash update state := acctState{old: ba, oldEnc: encodedAcctData} - ba.StateProofID = merklesignature.Verifier{} + ba.StateProofID = merklesignature.Commitment{} encodedOnlineAcctData := protocol.Encode(&ba) copy(addr[:], addrbuf) state.new = ba @@ -2515,6 +2516,11 @@ func onlineAccountsInitDbQueries(r db.Queryable) (*onlineAccountsDbQueries, erro if err != nil { return nil, err } + + qs.lookupOnlineTotalsStmt, err = r.Prepare("SELECT data FROM onlineroundparamstail WHERE rnd=?") + if err != nil { + return nil, err + } return qs, nil } @@ -2715,6 +2721,24 @@ func (qs *onlineAccountsDbQueries) lookupOnline(addr basics.Address, rnd basics. return } +func (qs *onlineAccountsDbQueries) lookupOnlineTotalsHistory(round basics.Round) (basics.MicroAlgos, error) { + data := ledgercore.OnlineRoundParamsData{} + err := db.Retry(func() error { + row := qs.lookupOnlineTotalsStmt.QueryRow(round) + var buf []byte + err := row.Scan(&buf) + if err != nil { + return err + } + err = protocol.Decode(buf, &data) + if err != nil { + return err + } + return nil + }) + return basics.MicroAlgos{Raw: data.OnlineSupply}, err +} + func (qs *onlineAccountsDbQueries) lookupOnlineHistory(addr basics.Address) (result []persistedOnlineAccountData, rnd basics.Round, err error) { err = db.Retry(func() error { rows, err := qs.lookupOnlineHistoryStmt.Query(addr[:]) @@ -3657,7 +3681,7 @@ func rowidsToChunkedArgs(rowids []int64) [][]interface{} { } } else { for i := 0; i < numChunks; i++ { - var chunkSize int = sqliteMaxVariableNumber + chunkSize := sqliteMaxVariableNumber if i == numChunks-1 { chunkSize = len(rowids) - (numChunks-1)*sqliteMaxVariableNumber } diff --git a/ledger/accountdb_test.go b/ledger/accountdb_test.go index cbc584e71c..a135472948 100644 --- a/ledger/accountdb_test.go +++ b/ledger/accountdb_test.go @@ -616,7 +616,7 @@ func generateRandomTestingAccountBalances(numAccounts int) (updates map[basics.A secrets := crypto.GenerateOneTimeSignatureSecrets(15, 500) pubVrfKey, _ := crypto.VrfKeygenFromSeed([32]byte{0, 1, 2, 3}) var stateProofID merklesignature.Verifier - crypto.RandBytes(stateProofID[:]) + crypto.RandBytes(stateProofID.Commitment[:]) updates = make(map[basics.Address]basics.AccountData, numAccounts) for i := 0; i < numAccounts; i++ { @@ -628,7 +628,7 @@ func generateRandomTestingAccountBalances(numAccounts int) (updates map[basics.A RewardedMicroAlgos: basics.MicroAlgos{Raw: 0x000ffffffffffffff / uint64(numAccounts)}, VoteID: secrets.OneTimeSignatureVerifier, SelectionID: pubVrfKey, - StateProofID: stateProofID, + StateProofID: stateProofID.Commitment, VoteFirstValid: basics.Round(0x000ffffffffffffff), VoteLastValid: basics.Round(0x000ffffffffffffff), VoteKeyDilution: 0x000ffffffffffffff, @@ -881,7 +881,7 @@ func TestAccountsReencoding(t *testing.T) { secrets := crypto.GenerateOneTimeSignatureSecrets(15, 500) pubVrfKey, _ := crypto.VrfKeygenFromSeed([32]byte{0, 1, 2, 3}) var stateProofID merklesignature.Verifier - crypto.RandBytes(stateProofID[:]) + crypto.RandBytes(stateProofID.Commitment[:]) err := dbs.Wdb.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { accountsInitTest(t, tx, make(map[basics.Address]basics.AccountData), protocol.ConsensusCurrentVersion) @@ -902,7 +902,7 @@ func TestAccountsReencoding(t *testing.T) { RewardedMicroAlgos: basics.MicroAlgos{Raw: 0x000ffffffffffffff}, VoteID: secrets.OneTimeSignatureVerifier, SelectionID: pubVrfKey, - StateProofID: stateProofID, + StateProofID: stateProofID.Commitment, VoteFirstValid: basics.Round(0x000ffffffffffffff), VoteLastValid: basics.Round(0x000ffffffffffffff), VoteKeyDilution: 0x000ffffffffffffff, @@ -3828,7 +3828,7 @@ func TestRemoveOfflineStateProofID(t *testing.T) { expectedAcct := acct if acct.Status != basics.Online { - expectedAcct.StateProofID = merklesignature.Verifier{} + expectedAcct.StateProofID = merklesignature.Commitment{} } expectedAccts[addr] = expectedAcct @@ -3889,7 +3889,7 @@ func TestRemoveOfflineStateProofID(t *testing.T) { err = protocol.Decode(encodedAcctData, &ba) require.NoError(t, err) if expected && ba.Status != basics.Online { - require.Equal(t, merklesignature.Verifier{}, ba.StateProofID) + require.Equal(t, merklesignature.Commitment{}, ba.StateProofID) } addHash := accountHashBuilderV6(addr, &ba, encodedAcctData) added, err := trie.Add(addHash) diff --git a/ledger/acctonline.go b/ledger/acctonline.go index f7b666a3cc..8462d7b6b9 100644 --- a/ledger/acctonline.go +++ b/ledger/acctonline.go @@ -99,7 +99,7 @@ type onlineAccounts struct { accountsReadCond *sync.Cond // voters keeps track of Merkle trees of online accounts, used for compact certificates. - voters *votersTracker + voters votersTracker // baseAccounts stores the most recently used accounts, at exactly dbRound baseOnlineAccounts lruOnlineAccounts @@ -136,9 +136,7 @@ func (ao *onlineAccounts) loadFromDisk(l ledgerForTracker, lastBalancesRound bas return err } - // the votes have a special dependency on the online accounts, so we need to initialize these separately. - ao.voters = &votersTracker{} - err = ao.voters.loadFromDisk(l, ao) + err = ao.voters.loadFromDisk(l, ao, lastBalancesRound) if err != nil { err = fmt.Errorf("voters tracker failed to loadFromDisk : %w", err) } @@ -198,10 +196,8 @@ func (ao *onlineAccounts) close() { ao.accountsq.close() ao.accountsq = nil } - if ao.voters != nil { - ao.voters.close() - ao.voters = nil - } + + ao.voters.close() ao.baseOnlineAccounts.prune(0) } @@ -251,9 +247,8 @@ func (ao *onlineAccounts) newBlockImpl(blk bookkeeping.Block, delta ledgercore.S newBaseAccountSize := (len(ao.accounts) + 1) + baseAccountsPendingAccountsBufferSize ao.baseOnlineAccounts.prune(newBaseAccountSize) - if ao.voters != nil { - ao.voters.newBlock(blk.BlockHeader) - } + ao.voters.newBlock(blk.BlockHeader) + } // committedUpTo implements the ledgerTracker interface for accountUpdates. @@ -306,10 +301,7 @@ func (ao *onlineAccounts) produceCommittingTask(committedRound basics.Round, dbR ao.log.Panicf("produceCommittingTask: block %d too far in the future, lookback %d, dbRound %d (cached %d), deltas %d", committedRound, dcr.lookback, dbRound, ao.cachedDBRoundOnline, len(ao.deltas)) } - var lowestRound basics.Round - if ao.voters != nil { - lowestRound = ao.voters.lowestRound(newBase) - } + lowestRound := ao.voters.lowestRound(newBase) offset = uint64(newBase - dbRound) offset = ao.consecutiveVersion(offset) @@ -512,13 +504,32 @@ func (ao *onlineAccounts) postCommit(ctx context.Context, dcc *deferredCommitCon func (ao *onlineAccounts) postCommitUnlocked(ctx context.Context, dcc *deferredCommitContext) { } -// OnlineTotals return the total online balance for the given round. -func (ao *onlineAccounts) OnlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { +// onlineTotals return the total online balance for the given round. +func (ao *onlineAccounts) onlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { ao.accountsMu.RLock() defer ao.accountsMu.RUnlock() return ao.onlineTotalsImpl(rnd) } +// onlineTotalsEx return the total online balance for the given round for extended rounds range +// by looking into DB +func (ao *onlineAccounts) onlineTotalsEx(rnd basics.Round) (basics.MicroAlgos, error) { + ao.accountsMu.RLock() + totalsOnline, err := ao.onlineTotalsImpl(rnd) + ao.accountsMu.RUnlock() + if err == nil { + return totalsOnline, err + } + + var roundOffsetError *RoundOffsetError + if !errors.As(err, &roundOffsetError) { + ao.log.Errorf("onlineTotalsImpl error: %w", err) + } + + totalsOnline, err = ao.accountsq.lookupOnlineTotalsHistory(rnd) + return totalsOnline, err +} + // onlineTotalsImpl returns the online totals of all accounts at the end of round rnd. func (ao *onlineAccounts) onlineTotalsImpl(rnd basics.Round) (basics.MicroAlgos, error) { offset, err := ao.roundParamsOffset(rnd) @@ -731,10 +742,13 @@ func (ao *onlineAccounts) lookupOnlineAccountData(rnd basics.Round, addr basics. } } -// onlineTop returns the top n online accounts, sorted by their normalized -// balance and address, whose voting keys are valid in voteRnd. See the -// normalization description in AccountData.NormalizedOnlineBalance(). -func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n uint64) ([]*ledgercore.OnlineAccount, error) { +// TopOnlineAccounts returns the top n online accounts, sorted by their normalized +// balance and address, whose voting keys are valid in voteRnd. +// The second return value represents the total stake that is online for round == rnd, but will +// not participate in round == voteRnd. +// See the normalization description in AccountData.NormalizedOnlineBalance(). +// The return value of totalOnlineStake represents the total stake that is online for voteRnd: it is an approximation since voteRnd did not yet occur. +func (ao *onlineAccounts) TopOnlineAccounts(rnd basics.Round, voteRnd basics.Round, n uint64) (topOnlineAccounts []*ledgercore.OnlineAccount, totalOnlineStake basics.MicroAlgos, err error) { genesisProto := ao.ledger.GenesisProto() ao.accountsMu.RLock() for { @@ -746,7 +760,7 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui var roundOffsetError *RoundOffsetError if !errors.As(err, &roundOffsetError) { ao.accountsMu.RUnlock() - return nil, err + return nil, basics.MicroAlgos{}, err } // the round number cannot be found in deltas, it is in history inMemory = false @@ -754,6 +768,9 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui } modifiedAccounts := make(map[basics.Address]*ledgercore.OnlineAccount) + // Online accounts that will not be valid in voteRnd. Used to calculate their total stake, + // to be removed from the total online stake if required (lower the upper bound of total online stake in voteRnd). + invalidOnlineAccounts := make(map[basics.Address]*ledgercore.OnlineAccount) if inMemory { // Determine how many accounts have been modified in-memory, // so that we obtain enough top accounts from disk (accountdb). @@ -772,6 +789,7 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui if !(d.VoteFirstValid <= voteRnd && voteRnd <= d.VoteLastValid) { modifiedAccounts[addr] = nil + invalidOnlineAccounts[addr] = accountDataToOnline(addr, &d, genesisProto) continue } @@ -808,7 +826,7 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui }) ledgerAccountsonlinetopMicros.AddMicrosecondsSince(start, nil) if err != nil { - return nil, err + return nil, basics.MicroAlgos{}, err } if dbRound != currentDbRound { @@ -817,6 +835,10 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui for addr, data := range accts { if !(data.VoteFirstValid <= voteRnd && voteRnd <= data.VoteLastValid) { + // If already exists it originated from the deltas, meaning its data is more recent + if _, ok := invalidOnlineAccounts[addr]; !ok { + invalidOnlineAccounts[addr] = data + } continue } candidates[addr] = data @@ -844,7 +866,7 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui } if dbRound < currentDbRound && dbRound != basics.Round(0) { ao.log.Errorf("onlineAccounts.onlineTop: database round %d is behind in-memory round %d", dbRound, currentDbRound) - return nil, &StaleDatabaseRoundError{databaseRound: dbRound, memoryRound: currentDbRound} + return nil, basics.MicroAlgos{}, &StaleDatabaseRoundError{databaseRound: dbRound, memoryRound: currentDbRound} } // Now update the candidates based on the in-memory deltas. @@ -867,13 +889,24 @@ func (ao *onlineAccounts) onlineTop(rnd basics.Round, voteRnd basics.Round, n ui heap.Push(topHeap, data) } - var res []*ledgercore.OnlineAccount - for topHeap.Len() > 0 && uint64(len(res)) < n { + for topHeap.Len() > 0 && uint64(len(topOnlineAccounts)) < n { acct := heap.Pop(topHeap).(*ledgercore.OnlineAccount) - res = append(res, acct) + topOnlineAccounts = append(topOnlineAccounts, acct) + } + + totalOnlineStake, err = ao.onlineTotalsEx(rnd) + if err != nil { + return nil, basics.MicroAlgos{}, err + } + ot := basics.OverflowTracker{} + for _, oa := range invalidOnlineAccounts { + totalOnlineStake = ot.SubA(totalOnlineStake, oa.MicroAlgos) + if ot.Overflowed { + return nil, basics.MicroAlgos{}, fmt.Errorf("TopOnlineAccounts: overflow in stakeOfflineInVoteRound") + } } - return res, nil + return topOnlineAccounts, totalOnlineStake, nil } } diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index da12c28c88..f4c4fc0b1e 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -111,7 +111,7 @@ func commitSyncPartialComplete(t *testing.T, oa *onlineAccounts, ml *mockLedgerF } } -func newBlock(t *testing.T, ml *mockLedgerForTracker, totals ledgercore.AccountTotals, testProtocolVersion protocol.ConsensusVersion, protoParams config.ConsensusParams, rnd basics.Round, base map[basics.Address]basics.AccountData, updates ledgercore.AccountDeltas, prevTotals ledgercore.AccountTotals) (newTotals ledgercore.AccountTotals) { +func newBlock(t *testing.T, ml *mockLedgerForTracker, testProtocolVersion protocol.ConsensusVersion, protoParams config.ConsensusParams, rnd basics.Round, base map[basics.Address]basics.AccountData, updates ledgercore.AccountDeltas, prevTotals ledgercore.AccountTotals) (newTotals ledgercore.AccountTotals) { rewardLevel := uint64(0) newTotals = ledgertesting.CalculateNewRoundAccountTotals(t, updates, rewardLevel, protoParams, base, prevTotals) @@ -124,7 +124,7 @@ func newBlock(t *testing.T, ml *mockLedgerForTracker, totals ledgercore.AccountT blk.CurrentProtocol = testProtocolVersion delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) delta.Accts.MergeAccounts(updates) - delta.Totals = totals + delta.Totals = newTotals ml.trackers.newBlock(blk, delta) @@ -208,7 +208,7 @@ func TestAcctOnline(t *testing.T) { genesisAccts = append(genesisAccts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, i, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, i, base, updates, totals) // commit changes synchroniously commitSync(t, oa, ml, i) @@ -401,7 +401,7 @@ func TestAcctOnline(t *testing.T) { genesisAccts = append(genesisAccts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, i, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, i, base, updates, totals) // flush all old deltas if uint64(i-start+1) == maxDeltaLookback { @@ -496,7 +496,7 @@ func TestAcctOnlineCache(t *testing.T) { genesisAccts = append(genesisAccts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, i, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, i, base, updates, totals) // commit changes synchroniously commitSync(t, oa, ml, i) @@ -778,7 +778,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { accts = append(accts, newAccts) if i > basics.Round(maxBalLookback) && i%10 == 0 { - onlineTotal, err := ao.OnlineTotals(i - basics.Round(maxBalLookback)) + onlineTotal, err := ao.onlineTotals(i - basics.Round(maxBalLookback)) require.NoError(t, err) require.Equal(t, allTotals[i-basics.Round(maxBalLookback)].Online.Money, onlineTotal) expectedConsensusVersion := testProtocolVersion1 @@ -814,7 +814,7 @@ func TestAcctOnlineRoundParamsCache(t *testing.T) { require.Equal(t, ao.onlineRoundParamsData[:basics.Round(maxBalLookback)], dbOnlineRoundParams) for i := ml.Latest() - basics.Round(maxBalLookback); i < ml.Latest(); i++ { - onlineTotal, err := ao.OnlineTotals(i) + onlineTotal, err := ao.onlineTotals(i) require.NoError(t, err) require.Equal(t, allTotals[i].Online.Money, onlineTotal) } @@ -886,7 +886,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { accounts = append(accounts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, 1, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, 1, base, updates, totals) // commit changes synchroniously commitSync(t, oa, ml, 1) @@ -894,7 +894,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { for i := 2; i <= maxBalLookback; i++ { var updates ledgercore.AccountDeltas base := accounts[i-1] - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) accounts = append(accounts, newAccts) commitSync(t, oa, ml, basics.Round(i)) } @@ -916,7 +916,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { updates = ledgercore.AccountDeltas{} rnd := maxBalLookback + 1 base = accounts[rnd-1] - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(rnd), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(rnd), base, updates, totals) dcc := commitSyncPartial(t, oa, ml, basics.Round(rnd)) // defer in order to recover from ml.trackers.accountsWriting.Wait() defer func() { @@ -966,7 +966,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { accounts = append(accounts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, 1, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, 1, base, updates, totals) // commit changes synchroniously commitSync(t, oa, ml, 1) @@ -974,7 +974,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { for i := 2; i <= maxBalLookback; i++ { var updates ledgercore.AccountDeltas base := accounts[i-1] - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) accounts = append(accounts, newAccts) commitSync(t, oa, ml, basics.Round(i)) } @@ -1016,7 +1016,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { accounts = append(accounts, newAccts) // prepare block - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, 1, base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, 1, base, updates, totals) // commit changes synchroniously commitSync(t, oa, ml, 1) @@ -1024,7 +1024,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { for i := 2; i <= maxBalLookback; i++ { var updates ledgercore.AccountDeltas base := accounts[i-1] - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) accounts = append(accounts, newAccts) commitSync(t, oa, ml, basics.Round(i)) } @@ -1049,7 +1049,7 @@ func TestAcctOnlineCacheDBSync(t *testing.T) { updates = ledgercore.AccountDeltas{} rnd := maxBalLookback + 1 base = accounts[rnd-1] - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(rnd), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(rnd), base, updates, totals) dcc := commitSyncPartial(t, oa, ml, basics.Round(rnd)) // defer in order to recover from ml.trackers.accountsWriting.Wait() defer func() { @@ -1117,9 +1117,8 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { const seedLookback = 3 const seedInteval = 4 const maxBalLookback = 2 * seedLookback * seedInteval - const compactCertRounds = maxBalLookback / 2 // have it less than maxBalLookback but greater than default deltas size (8) - const compactCertVotersLookback = 2 - const compactCertSecKQ = compactCertRounds / 2 + const stateProofRounds = maxBalLookback / 2 // have it less than maxBalLookback but greater than default deltas size (8) + const stateProofVotersLookback = 2 const numAccts = maxBalLookback * 5 genesisAccts := []map[basics.Address]basics.AccountData{{}} @@ -1140,9 +1139,8 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { protoParams.MaxBalLookback = maxBalLookback protoParams.SeedLookback = seedLookback protoParams.SeedRefreshInterval = seedInteval - protoParams.CompactCertRounds = compactCertRounds - protoParams.CompactCertVotersLookback = compactCertVotersLookback - protoParams.CompactCertSecKQ = compactCertSecKQ + protoParams.StateProofInterval = stateProofRounds + protoParams.StateProofVotersLookback = stateProofVotersLookback config.Consensus[testProtocolVersion] = protoParams defer func() { delete(config.Consensus, testProtocolVersion) @@ -1164,7 +1162,7 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { updates.Upsert(addrA, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(100 * i)}}) base := genesisAccts[i-1] newAccts := applyPartialDeltas(base, updates) - totals = newBlock(t, ml, totals, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) + totals = newBlock(t, ml, testProtocolVersion, protoParams, basics.Round(i), base, updates, totals) genesisAccts = append(genesisAccts, newAccts) commitSync(t, oa, ml, basics.Round(i)) } @@ -1172,8 +1170,8 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { require.Equal(t, basics.Round(maxBlocks-int(conf.MaxAcctLookback)), oa.cachedDBRoundOnline) // voters stalls after the first interval lowest := oa.voters.lowestRound(oa.cachedDBRoundOnline) - require.Equal(t, basics.Round(compactCertRounds-compactCertVotersLookback), lowest) - require.Equal(t, maxBlocks/compactCertRounds, len(oa.voters.round)) + require.Equal(t, basics.Round(stateProofRounds-stateProofVotersLookback), lowest) + require.Equal(t, maxBlocks/stateProofRounds, len(oa.voters.votersForRoundCache)) retain, lookback := oa.committedUpTo(oa.latest()) require.Equal(t, lowest, retain) require.Equal(t, conf.MaxAcctLookback, uint64(lookback)) @@ -1188,10 +1186,17 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { dbOnlineRoundParams, endRound, err = accountsOnlineRoundParams(tx) return err }) + require.NoError(t, err) require.Equal(t, oa.latest()-basics.Round(conf.MaxAcctLookback), endRound) require.Equal(t, maxBlocks-int(lowest)-int(conf.MaxAcctLookback)+1, len(dbOnlineRoundParams)) + _, err = oa.onlineTotalsEx(lowest) + require.NoError(t, err) + + _, err = oa.onlineTotalsEx(lowest - 1) + require.ErrorIs(t, err, sql.ErrNoRows) + // ensure the cache size for addrA does not have more entries than maxBalLookback + 1 // +1 comes from the deletion before X without checking account state at X require.Equal(t, maxBalLookback+1, oa.onlineAccountsCache.accounts[addrA].Len()) @@ -1240,15 +1245,17 @@ func addSinkAndPoolAccounts(genesisAccts []map[basics.Address]basics.AccountData genesisAccts[0][testSinkAddr] = sinkdata } -func newBlockWithUpdates(genesisAccts []map[basics.Address]basics.AccountData, updates ledgercore.AccountDeltas, totals ledgercore.AccountTotals, t *testing.T, ml *mockLedgerForTracker, round int, oa *onlineAccounts) { +func newBlockWithUpdates(genesisAccts []map[basics.Address]basics.AccountData, updates ledgercore.AccountDeltas, prevTotals ledgercore.AccountTotals, t *testing.T, ml *mockLedgerForTracker, round int, oa *onlineAccounts) ledgercore.AccountTotals { base := genesisAccts[0] - totals = newBlock(t, ml, totals, protocol.ConsensusCurrentVersion, config.Consensus[protocol.ConsensusCurrentVersion], basics.Round(round), base, updates, totals) + newTotals := newBlock(t, ml, protocol.ConsensusCurrentVersion, config.Consensus[protocol.ConsensusCurrentVersion], basics.Round(round), base, updates, prevTotals) commitSync(t, oa, ml, basics.Round(round)) + return newTotals } func TestAcctOnlineTop(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) + algops := MicroAlgoOperations{a: a} const numAccts = 20 allAccts := make([]basics.BalanceRecord, numAccts) @@ -1294,66 +1301,65 @@ func TestAcctOnlineTop(t *testing.T) { conf := config.GetDefaultLocal() au, oa := newAcctUpdates(t, ml, conf) defer oa.close() - - top, err := oa.onlineTop(0, 0, 5) + initialOnlineTotals, err := oa.onlineTotals(0) a.NoError(err) + top := compareOnlineTotals(a, oa, 0, 0, 5, initialOnlineTotals, initialOnlineTotals) compareTopAccounts(a, top, allAccts) _, totals, err := au.LatestTotals() - require.NoError(t, err) + a.NoError(err) // mark one of the top N accounts as offline - we expect that it will be removed form the top N var updates ledgercore.AccountDeltas - updates.Upsert(allAccts[numAccts-3].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline}, VotingData: ledgercore.VotingData{}}) - newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 1, oa) - - accountToBeUpdated := allAccts[numAccts-3] + ac := allAccts[numAccts-3] + updates.Upsert(ac.Addr, ledgercore.AccountData{ + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: ac.MicroAlgos}, VotingData: ledgercore.VotingData{}}) + totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 1, oa) + accountToBeUpdated := ac accountToBeUpdated.Status = basics.Offline allAccts[numAccts-3] = accountToBeUpdated - top, err = oa.onlineTop(1, 1, 5) - a.NoError(err) + updatedOnlineStake := algops.Sub(initialOnlineTotals, ac.MicroAlgos) + top = compareOnlineTotals(a, oa, 1, 1, 5, updatedOnlineStake, updatedOnlineStake) compareTopAccounts(a, top, allAccts) // update an account to have expired keys updates = ledgercore.AccountDeltas{} updates.Upsert(allAccts[numAccts-2].Addr, ledgercore.AccountData{ - AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online}, + AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[numAccts-2].MicroAlgos}, VotingData: ledgercore.VotingData{ VoteFirstValid: 0, VoteLastValid: 1, }}) - newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 2, oa) - + totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 2, oa) // we expect the previous account to be removed from the top N accounts since its keys are expired. // remove it from the expected allAccts slice by marking it as offline accountToBeUpdated = allAccts[numAccts-2] accountToBeUpdated.Status = basics.Offline allAccts[numAccts-2] = accountToBeUpdated - top, err = oa.onlineTop(2, 2, 5) - a.NoError(err) + notValidAccountStake := accountToBeUpdated.MicroAlgos + voteRndExpectedOnlineStake := algops.Sub(updatedOnlineStake, notValidAccountStake) + top = compareOnlineTotals(a, oa, 2, 2, 5, updatedOnlineStake, voteRndExpectedOnlineStake) compareTopAccounts(a, top, allAccts) // mark an account with high stake as online - it should be pushed to the top of the list updates.Upsert(allAccts[numAccts-1].Addr, ledgercore.AccountData{ AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[numAccts-1].MicroAlgos}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(1000)}}) - newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 3, oa) - + totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, 3, oa) accountToBeUpdated = allAccts[numAccts-1] accountToBeUpdated.Status = basics.Online accountToBeUpdated.MicroAlgos = allAccts[numAccts-1].MicroAlgos accountToBeUpdated.VoteLastValid = basics.Round(1000) allAccts[numAccts-1] = accountToBeUpdated - top, err = oa.onlineTop(3, 3, 5) - a.NoError(err) + updatedOnlineStake = algops.Add(updatedOnlineStake, accountToBeUpdated.MicroAlgos) + voteRndExpectedOnlineStake = algops.Add(voteRndExpectedOnlineStake, accountToBeUpdated.MicroAlgos) + top = compareOnlineTotals(a, oa, 3, 3, 5, updatedOnlineStake, voteRndExpectedOnlineStake) compareTopAccounts(a, top, allAccts) a.Equal(top[0].Address, allAccts[numAccts-1].Addr) - } func TestAcctOnlineTopInBatches(t *testing.T) { @@ -1386,7 +1392,7 @@ func TestAcctOnlineTopInBatches(t *testing.T) { _, oa := newAcctUpdates(t, ml, conf) defer oa.close() - top, err := oa.onlineTop(0, 0, 2048) + top, _, err := oa.TopOnlineAccounts(0, 0, 2048) a.NoError(err) compareTopAccounts(a, top, allAccts) } @@ -1431,7 +1437,7 @@ func TestAcctOnlineTopBetweenCommitAndPostCommit(t *testing.T) { defer oa.close() ml.trackers.trackers = append([]ledgerTracker{stallingTracker}, ml.trackers.trackers...) - top, err := oa.onlineTop(0, 0, 5) + top, _, err := oa.TopOnlineAccounts(0, 0, 5) a.NoError(err) compareTopAccounts(a, top, allAccts) @@ -1469,7 +1475,7 @@ func TestAcctOnlineTopBetweenCommitAndPostCommit(t *testing.T) { time.Sleep(2 * time.Second) stallingTracker.postCommitReleaseLock <- struct{}{} }() - top, err = oa.onlineTop(2, 2, 5) + top, _, err = oa.TopOnlineAccounts(2, 2, 5) a.NoError(err) accountToBeUpdated := allAccts[numAccts-1] @@ -1522,7 +1528,7 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { defer oa.close() ml.trackers.trackers = append([]ledgerTracker{stallingTracker}, ml.trackers.trackers...) - top, err := oa.onlineTop(0, 0, 5) + top, _, err := oa.TopOnlineAccounts(0, 0, 5) a.NoError(err) compareTopAccounts(a, top, allAccts) @@ -1565,7 +1571,7 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { }) stallingTracker.postCommitReleaseLock <- struct{}{} }() - _, err = oa.onlineTop(2, 2, 5) + _, _, err = oa.TopOnlineAccounts(2, 2, 5) a.Error(err) a.Contains(err.Error(), "is behind in-memory round") @@ -1573,3 +1579,113 @@ func TestAcctOnlineTopDBBehindMemRound(t *testing.T) { a.FailNow("timedout while waiting for post commit") } } + +func TestAcctOnlineTop_ChangeOnlineStake(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + algops := MicroAlgoOperations{a: a} + + const numAccts = 20 + allAccts := make([]basics.BalanceRecord, numAccts) + genesisAccts := []map[basics.Address]basics.AccountData{{}} + genesisAccts[0] = make(map[basics.Address]basics.AccountData, numAccts) + for i := 0; i < numAccts-1; i++ { + allAccts[i] = basics.BalanceRecord{ + Addr: ledgertesting.RandomAddress(), + AccountData: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(i + 1)}, + Status: basics.Online, + VoteLastValid: 1000, + VoteFirstValid: 0, + RewardsBase: 0}, + } + genesisAccts[0][allAccts[i].Addr] = allAccts[i].AccountData + } + // Online but only valid until round 1 + allAccts[numAccts-1] = basics.BalanceRecord{ + Addr: ledgertesting.RandomAddress(), + AccountData: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: uint64(numAccts)}, + Status: basics.Online, + VoteLastValid: 1, + VoteFirstValid: 0, + RewardsBase: 0}, + } + genesisAccts[0][allAccts[numAccts-1].Addr] = allAccts[numAccts-1].AccountData + acctInvalidFromRnd2 := allAccts[numAccts-1] + + addSinkAndPoolAccounts(genesisAccts) + + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, genesisAccts) + defer ml.Close() + + conf := config.GetDefaultLocal() + au, oa := newAcctUpdates(t, ml, conf) + defer oa.close() + + _, totals, err := au.LatestTotals() + a.NoError(err) + + // Add 20 blocks (> max lookback) to test both the database and deltas + for i := 1; i <= 20; i++ { + var updates ledgercore.AccountDeltas + if i == 15 { // round 15 should be in deltas (memory) + // turn account `i` offline + updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Offline, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: ledgercore.VotingData{}}) + } + if i == 18 { + updates.Upsert(allAccts[i].Addr, ledgercore.AccountData{AccountBaseData: ledgercore.AccountBaseData{Status: basics.Online, MicroAlgos: allAccts[i].MicroAlgos}, VotingData: ledgercore.VotingData{VoteLastValid: basics.Round(18)}}) + } // else: insert empty block + totals = newBlockWithUpdates(genesisAccts, updates, totals, t, ml, i, oa) + } + + initialOnlineStake, err := oa.onlineTotals(0) + a.NoError(err) + rnd15TotalOnlineStake := algops.Sub(initialOnlineStake, allAccts[15].MicroAlgos) // 15 is offline + + // Case 1: sanity check + top := compareOnlineTotals(a, oa, 0, 1, 5, initialOnlineStake, initialOnlineStake) + compareTopAccounts(a, top, allAccts) + + // Case 2: In db + voteRndExpectedStake := algops.Sub(initialOnlineStake, acctInvalidFromRnd2.MicroAlgos) // Online on rnd but not valid on voteRnd + top = compareOnlineTotals(a, oa, 0, 2, 5, initialOnlineStake, voteRndExpectedStake) + updatedAccts := allAccts[:numAccts-1] + compareTopAccounts(a, top, updatedAccts) + + // Case 3: In memory (deltas) + voteRndExpectedStake = algops.Sub(rnd15TotalOnlineStake, acctInvalidFromRnd2.MicroAlgos) + voteRndExpectedStake = algops.Sub(voteRndExpectedStake, allAccts[18].MicroAlgos) // Online on rnd but not valid on voteRnd + updatedAccts[15].Status = basics.Offline // Mark account 15 offline for comparison + updatedAccts[18].Status = basics.Offline // Mark account 18 offline for comparison + top = compareOnlineTotals(a, oa, 18, 19, 5, rnd15TotalOnlineStake, voteRndExpectedStake) + compareTopAccounts(a, top, updatedAccts) +} + +type MicroAlgoOperations struct { + a *require.Assertions + ot basics.OverflowTracker +} + +func (m *MicroAlgoOperations) Sub(x, y basics.MicroAlgos) basics.MicroAlgos { + res := m.ot.SubA(x, y) + m.a.False(m.ot.Overflowed) + return res +} + +func (m *MicroAlgoOperations) Add(x, y basics.MicroAlgos) basics.MicroAlgos { + res := m.ot.AddA(x, y) + m.a.False(m.ot.Overflowed) + return res +} + +func compareOnlineTotals(a *require.Assertions, oa *onlineAccounts, rnd, voteRnd basics.Round, n uint64, expectedForRnd, expectedForVoteRnd basics.MicroAlgos) []*ledgercore.OnlineAccount { + top, onlineTotalVoteRnd, err := oa.TopOnlineAccounts(rnd, voteRnd, n) + a.NoError(err) + a.Equal(expectedForVoteRnd, onlineTotalVoteRnd) + onlineTotalsRnd, err := oa.onlineTotals(rnd) + a.NoError(err) + a.Equal(expectedForRnd, onlineTotalsRnd) + a.LessOrEqual(onlineTotalVoteRnd.Raw, onlineTotalsRnd.Raw) + return top +} diff --git a/ledger/acctupdates.go b/ledger/acctupdates.go index 08e463c4ec..852df6df41 100644 --- a/ledger/acctupdates.go +++ b/ledger/acctupdates.go @@ -548,8 +548,8 @@ func (aul *accountUpdatesLedgerEvaluator) GenesisProto() config.ConsensusParams return aul.au.ledger.GenesisProto() } -// CompactCertVoters returns the top online accounts at round rnd. -func (aul *accountUpdatesLedgerEvaluator) CompactCertVoters(rnd basics.Round) (voters *ledgercore.VotersForRound, err error) { +// VotersForStateProof returns the top online accounts at round rnd. +func (aul *accountUpdatesLedgerEvaluator) VotersForStateProof(rnd basics.Round) (voters *ledgercore.VotersForRound, err error) { return aul.ao.voters.getVoters(rnd) } diff --git a/ledger/acctupdates_test.go b/ledger/acctupdates_test.go index 1ff0657979..740ac91d03 100644 --- a/ledger/acctupdates_test.go +++ b/ledger/acctupdates_test.go @@ -282,7 +282,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base latest := au.latest() require.Equal(t, latestRnd, latest) - _, err := ao.OnlineTotals(latest + 1) + _, err := ao.onlineTotals(latest + 1) require.Error(t, err) var validThrough basics.Round @@ -291,7 +291,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base require.Equal(t, basics.Round(0), validThrough) if base > 0 { - _, err := ao.OnlineTotals(base - basics.Round(ao.maxBalLookback())) + _, err := ao.onlineTotals(base - basics.Round(ao.maxBalLookback())) require.Error(t, err) _, validThrough, err = au.LookupWithoutRewards(base-1, ledgertesting.RandomAddress()) @@ -354,7 +354,7 @@ func checkAcctUpdates(t *testing.T, au *accountUpdates, ao *onlineAccounts, base bll := accts[rnd] require.Equal(t, all, bll) - totals, err := ao.OnlineTotals(rnd) + totals, err := ao.onlineTotals(rnd) require.NoError(t, err) require.Equal(t, totals.Raw, totalOnline) @@ -457,6 +457,11 @@ func TestAcctUpdates(t *testing.T) { if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { t.Skip("This test is too slow on ARM and causes travis builds to time out") } + + // The next operations are heavy on the memory. + // Garbage collection helps prevent trashing + runtime.GC() + proto := config.Consensus[protocol.ConsensusCurrentVersion] conf := config.GetDefaultLocal() @@ -760,7 +765,6 @@ func BenchmarkBalancesChanges(b *testing.B) { b.N = int(time.Second / singleIterationTime) // and now, wait for the reminder of the second. time.Sleep(time.Second - deltaTime) - } func BenchmarkCalibrateNodesPerPage(b *testing.B) { diff --git a/ledger/apply/apply.go b/ledger/apply/apply.go index 9acd48454e..34017e383d 100644 --- a/ledger/apply/apply.go +++ b/ledger/apply/apply.go @@ -19,11 +19,19 @@ package apply import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/ledgercore" ) +// StateProofsApplier allows fetching and updating state-proofs state on the ledger +type StateProofsApplier interface { + BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) + GetStateProofNextRound() basics.Round + SetStateProofNextRound(rnd basics.Round) +} + // Balances allow to move MicroAlgos from one address to another and to update balance records, or to access and modify individual balance records // After a call to Put (or Move), future calls to Get or Move will reflect the updated balance record(s) type Balances interface { diff --git a/ledger/apply/keyreg_test.go b/ledger/apply/keyreg_test.go index 472b0cac3e..09699e3f3d 100644 --- a/ledger/apply/keyreg_test.go +++ b/ledger/apply/keyreg_test.go @@ -282,7 +282,7 @@ func createTestTxnWithPeriod(t *testing.T, src basics.Address, secretParticipati KeyregTxnFields: transactions.KeyregTxnFields{ VotePK: crypto.OneTimeSignatureVerifier(secretParticipation.SignatureVerifier), SelectionPK: vrfSecrets.PK, - StateProofPK: *signer.GetVerifier(), + StateProofPK: signer.GetVerifier().Commitment, VoteFirst: 0, VoteLast: 100, }, diff --git a/ledger/apply/stateproof.go b/ledger/apply/stateproof.go new file mode 100644 index 0000000000..fca56c0f3a --- /dev/null +++ b/ledger/apply/stateproof.go @@ -0,0 +1,71 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package apply + +import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/stateproof/verify" +) + +// Errors for apply stateproof +var ( + ErrStateProofTypeNotSupported = errors.New("state proof type not supported") + ErrExpectedDifferentStateProofRound = errors.New("expected different state proof round") +) + +// StateProof applies the StateProof transaction and setting the next StateProof round +func StateProof(tx transactions.StateProofTxnFields, atRound basics.Round, sp StateProofsApplier, validate bool) error { + spType := tx.StateProofType + if spType != protocol.StateProofBasic { + return fmt.Errorf("applyStateProof: %w - type %d ", ErrStateProofTypeNotSupported, spType) + } + + lastRoundInInterval := basics.Round(tx.Message.LastAttestedRound) + lastRoundHdr, err := sp.BlockHdr(lastRoundInInterval) + if err != nil { + return err + } + + nextStateProofRnd := sp.GetStateProofNextRound() + if nextStateProofRnd == 0 || nextStateProofRnd != lastRoundInInterval { + return fmt.Errorf("applyStateProof: %w - expecting state proof for %d, but new state proof is for %d", + ErrExpectedDifferentStateProofRound, nextStateProofRnd, lastRoundInInterval) + } + + proto := config.Consensus[lastRoundHdr.CurrentProtocol] + if validate { + votersRnd := lastRoundInInterval.SubSaturate(basics.Round(proto.StateProofInterval)) + votersHdr, err := sp.BlockHdr(votersRnd) + if err != nil { + return err + } + + err = verify.ValidateStateProof(&lastRoundHdr, &tx.StateProof, &votersHdr, atRound, &tx.Message) + if err != nil { + return err + } + } + + sp.SetStateProofNextRound(lastRoundInInterval + basics.Round(proto.StateProofInterval)) + return nil +} diff --git a/ledger/blockHeaderCache.go b/ledger/blockHeaderCache.go new file mode 100644 index 0000000000..5e2be47d29 --- /dev/null +++ b/ledger/blockHeaderCache.go @@ -0,0 +1,86 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + + "github.com/algorand/go-deadlock" +) + +const latestHeaderCacheSize = 512 +const blockHeadersLRUCacheSize = 10 + +// blockHeaderCache is a wrapper for all block header cache mechanisms used within the Ledger. +type blockHeaderCache struct { + lruCache heapLRUCache + latestHeaderCache latestBlockHeaderCache +} + +type latestBlockHeaderCache struct { + blockHeaders [latestHeaderCacheSize]bookkeeping.BlockHeader + mutex deadlock.RWMutex +} + +func (c *blockHeaderCache) initialize() { + c.lruCache.maxEntries = blockHeadersLRUCacheSize +} + +func (c *blockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) { + // check latestHeaderCache first + blockHeader, exists = c.latestHeaderCache.get(round) + if exists { + return + } + + // if not found in latestHeaderCache, check LRUCache + value, exists := c.lruCache.Get(round) + if exists { + blockHeader = value.(bookkeeping.BlockHeader) + } + + return +} + +func (c *blockHeaderCache) put(blockHeader bookkeeping.BlockHeader) { + c.latestHeaderCache.put(blockHeader) + c.lruCache.Put(blockHeader.Round, blockHeader) +} + +func (c *latestBlockHeaderCache) get(round basics.Round) (blockHeader bookkeeping.BlockHeader, exists bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + idx := round % latestHeaderCacheSize + if round == 0 || c.blockHeaders[idx].Round != round { // blockHeader is empty or not requested round + return bookkeeping.BlockHeader{}, false + } + blockHeader = c.blockHeaders[idx] + + return blockHeader, true +} + +func (c *latestBlockHeaderCache) put(blockHeader bookkeeping.BlockHeader) { + c.mutex.Lock() + defer c.mutex.Unlock() + + idx := blockHeader.Round % latestHeaderCacheSize + if blockHeader.Round > c.blockHeaders[idx].Round { // provided blockHeader is more recent than cached one + c.blockHeaders[idx] = blockHeader + } +} diff --git a/ledger/blockHeaderCache_test.go b/ledger/blockHeaderCache_test.go new file mode 100644 index 0000000000..6c89734357 --- /dev/null +++ b/ledger/blockHeaderCache_test.go @@ -0,0 +1,95 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package ledger + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestBlockHeaderCache(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var cache blockHeaderCache + cache.initialize() + for i := basics.Round(1024); i < 1024+latestHeaderCacheSize; i++ { + hdr := bookkeeping.BlockHeader{Round: i} + cache.put(hdr) + } + + rnd := basics.Round(120) + hdr := bookkeeping.BlockHeader{Round: rnd} + cache.put(hdr) + + _, exists := cache.get(rnd) + a.True(exists) + + _, exists = cache.lruCache.Get(rnd) + a.True(exists) + + _, exists = cache.latestHeaderCache.get(rnd) + a.False(exists) + + rnd = basics.Round(2048) + hdr = bookkeeping.BlockHeader{Round: rnd} + cache.put(hdr) + + _, exists = cache.latestHeaderCache.get(rnd) + a.True(exists) + + _, exists = cache.lruCache.Get(rnd) + a.True(exists) + +} + +func TestLatestBlockHeaderCache(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var cache latestBlockHeaderCache + for i := basics.Round(123); i < latestHeaderCacheSize; i++ { + hdr := bookkeeping.BlockHeader{Round: i} + cache.put(hdr) + } + + for i := basics.Round(0); i < 123; i++ { + _, exists := cache.get(i) + a.False(exists) + } + + for i := basics.Round(123); i < latestHeaderCacheSize; i++ { + hdr, exists := cache.get(i) + a.True(exists) + a.Equal(i, hdr.Round) + } +} + +func TestCacheSizeConsensus(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + // TODO Stateproof: change to CurrentVersion when feature is enabled + a.Equal(uint64(latestHeaderCacheSize), config.Consensus[protocol.ConsensusFuture].StateProofInterval*2) +} diff --git a/ledger/catchpointwriter_test.go b/ledger/catchpointwriter_test.go index d8e34500af..fa1819d972 100644 --- a/ledger/catchpointwriter_test.go +++ b/ledger/catchpointwriter_test.go @@ -56,7 +56,7 @@ func makeTestEncodedBalanceRecordV5(t *testing.T) encodedBalanceRecordV5 { oneTimeSecrets := crypto.GenerateOneTimeSignatureSecrets(0, 1) vrfSecrets := crypto.GenerateVRFSecrets() var stateProofID merklesignature.Verifier - crypto.RandBytes(stateProofID[:]) + crypto.RandBytes(stateProofID.Commitment[:]) ad := basics.AccountData{ Status: basics.NotParticipating, @@ -65,7 +65,7 @@ func makeTestEncodedBalanceRecordV5(t *testing.T) encodedBalanceRecordV5 { RewardedMicroAlgos: basics.MicroAlgos{}, VoteID: oneTimeSecrets.OneTimeSignatureVerifier, SelectionID: vrfSecrets.PK, - StateProofID: stateProofID, + StateProofID: stateProofID.Commitment, VoteFirstValid: basics.Round(0x1234123412341234), VoteLastValid: basics.Round(0x1234123412341234), VoteKeyDilution: 0x1234123412341234, diff --git a/ledger/evalindexer.go b/ledger/evalindexer.go index d4ae2fab39..5f11874c4a 100644 --- a/ledger/evalindexer.go +++ b/ledger/evalindexer.go @@ -197,10 +197,10 @@ func (l indexerLedgerConnector) LatestTotals() (rnd basics.Round, totals ledgerc return } -// CompactCertVoters is part of LedgerForEvaluator interface. -func (l indexerLedgerConnector) CompactCertVoters(_ basics.Round) (*ledgercore.VotersForRound, error) { +// VotersForStateProof is part of LedgerForEvaluator interface. +func (l indexerLedgerConnector) VotersForStateProof(_ basics.Round) (*ledgercore.VotersForRound, error) { // This function is not used by evaluator. - return nil, errors.New("CompactCertVoters() not implemented") + return nil, errors.New("VotersForStateProof() not implemented") } func makeIndexerLedgerConnector(il indexerLedgerForEval, genesisHash crypto.Digest, genesisProto config.ConsensusParams, latestRound basics.Round, roundResources EvalForIndexerResources) indexerLedgerConnector { diff --git a/ledger/internal/appcow_test.go b/ledger/internal/appcow_test.go index edc8ef369e..76b4f1f8d2 100644 --- a/ledger/internal/appcow_test.go +++ b/ledger/internal/appcow_test.go @@ -91,7 +91,7 @@ func (ml *emptyLedger) txnCounter() uint64 { return 0 } -func (ml *emptyLedger) blockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { +func (ml *emptyLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { return bookkeeping.BlockHeader{}, nil } @@ -99,7 +99,7 @@ func (ml *emptyLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader return bookkeeping.BlockHeader{}, nil } -func (ml *emptyLedger) compactCertNext() basics.Round { +func (ml *emptyLedger) GetStateProofNextRound() basics.Round { return basics.Round(0) } diff --git a/ledger/internal/compactcert.go b/ledger/internal/compactcert.go deleted file mode 100644 index ca2be6d19e..0000000000 --- a/ledger/internal/compactcert.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package internal - -import ( - "fmt" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/compactcert" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/protocol" -) - -// AcceptableCompactCertWeight computes the acceptable signed weight -// of a compact cert if it were to appear in a transaction with a -// particular firstValid round. Earlier rounds require a smaller cert. -// votersHdr specifies the block that contains the Merkle commitment of -// the voters for this compact cert (and thus the compact cert is for -// votersHdr.Round() + CompactCertRounds). -// -// logger must not be nil; use at least logging.Base() -func AcceptableCompactCertWeight(votersHdr bookkeeping.BlockHeader, firstValid basics.Round, logger logging.Logger) uint64 { - proto := config.Consensus[votersHdr.CurrentProtocol] - certRound := votersHdr.Round + basics.Round(proto.CompactCertRounds) - total := votersHdr.CompactCert[protocol.CompactCertBasic].CompactCertVotersTotal - - // The acceptable weight depends on the elapsed time (in rounds) - // from the block we are trying to construct a certificate for. - // Start by subtracting the round number of the block being certified. - // If that round hasn't even passed yet, require 100% votes in cert. - offset := firstValid.SubSaturate(certRound) - if offset == 0 { - return total.ToUint64() - } - - // During the first proto.CompactCertRound/2 + 1 + 1 blocks, the - // signatures are still being broadcast, so, continue requiring - // 100% votes. - // - // The first +1 comes from CompactCertWorker.broadcastSigs: it only - // broadcasts signatures for round R starting with round R+1, to - // ensure nodes have the block for round R already in their ledger, - // to check the sig. - // - // The second +1 comes from the fact that, if we are checking this - // acceptable weight to decide whether to allow this transaction in - // a block, the transaction was sent out one round ago. - offset = offset.SubSaturate(basics.Round(proto.CompactCertRounds/2 + 2)) - if offset == 0 { - return total.ToUint64() - } - - // In the next proto.CompactCertRounds/2 blocks, linearly scale - // the acceptable weight from 100% to CompactCertWeightThreshold. - // If we are outside of that window, accept any weight at or above - // CompactCertWeightThreshold. - provenWeight, overflowed := basics.Muldiv(total.ToUint64(), uint64(proto.CompactCertWeightThreshold), 1<<32) - if overflowed || provenWeight > total.ToUint64() { - // Shouldn't happen, but a safe fallback is to accept a larger cert. - logger.Warnf("AcceptableCompactCertWeight(%d, %d, %d, %d) overflow provenWeight", - total, proto.CompactCertRounds, certRound, firstValid) - return 0 - } - - if offset >= basics.Round(proto.CompactCertRounds/2) { - return provenWeight - } - - scaledWeight, overflowed := basics.Muldiv(total.ToUint64()-provenWeight, proto.CompactCertRounds/2-uint64(offset), proto.CompactCertRounds/2) - if overflowed { - // Shouldn't happen, but a safe fallback is to accept a larger cert. - logger.Warnf("AcceptableCompactCertWeight(%d, %d, %d, %d) overflow scaledWeight", - total, proto.CompactCertRounds, certRound, firstValid) - return 0 - } - - w, overflowed := basics.OAdd(provenWeight, scaledWeight) - if overflowed { - // Shouldn't happen, but a safe fallback is to accept a larger cert. - logger.Warnf("AcceptableCompactCertWeight(%d, %d, %d, %d) overflow provenWeight (%d) + scaledWeight (%d)", - total, proto.CompactCertRounds, certRound, firstValid, provenWeight, scaledWeight) - return 0 - } - - return w -} - -// CompactCertParams computes the parameters for building or verifying -// a compact cert for block hdr, using voters from block votersHdr. -func CompactCertParams(votersHdr bookkeeping.BlockHeader, hdr bookkeeping.BlockHeader) (res compactcert.Params, err error) { - proto := config.Consensus[votersHdr.CurrentProtocol] - - if proto.CompactCertRounds == 0 { - err = fmt.Errorf("compact certs not enabled") - return - } - - if votersHdr.Round%basics.Round(proto.CompactCertRounds) != 0 { - err = fmt.Errorf("votersHdr %d not a multiple of %d", - votersHdr.Round, proto.CompactCertRounds) - return - } - - if hdr.Round != votersHdr.Round+basics.Round(proto.CompactCertRounds) { - err = fmt.Errorf("certifying block %d not %d ahead of voters %d", - hdr.Round, proto.CompactCertRounds, votersHdr.Round) - return - } - - totalWeight := votersHdr.CompactCert[protocol.CompactCertBasic].CompactCertVotersTotal.ToUint64() - provenWeight, overflowed := basics.Muldiv(totalWeight, uint64(proto.CompactCertWeightThreshold), 1<<32) - if overflowed { - err = fmt.Errorf("overflow computing provenWeight[%d]: %d * %d / (1<<32)", - hdr.Round, totalWeight, proto.CompactCertWeightThreshold) - return - } - - res = compactcert.Params{ - Msg: hdr, - ProvenWeight: provenWeight, - SigRound: hdr.Round, - SecKQ: proto.CompactCertSecKQ, - } - return -} - -// validateCompactCert checks that a compact cert is valid. -func validateCompactCert(certHdr bookkeeping.BlockHeader, cert compactcert.Cert, votersHdr bookkeeping.BlockHeader, nextCertRnd basics.Round, atRound basics.Round) error { - proto := config.Consensus[certHdr.CurrentProtocol] - - if proto.CompactCertRounds == 0 { - return fmt.Errorf("compact certs not enabled: rounds = %d", proto.CompactCertRounds) - } - - if certHdr.Round%basics.Round(proto.CompactCertRounds) != 0 { - return fmt.Errorf("cert at %d for non-multiple of %d", certHdr.Round, proto.CompactCertRounds) - } - - votersRound := certHdr.Round.SubSaturate(basics.Round(proto.CompactCertRounds)) - if votersRound != votersHdr.Round { - return fmt.Errorf("new cert is for %d (voters %d), but votersHdr from %d", - certHdr.Round, votersRound, votersHdr.Round) - } - - if nextCertRnd == 0 || nextCertRnd != certHdr.Round { - return fmt.Errorf("expecting cert for %d, but new cert is for %d (voters %d)", - nextCertRnd, certHdr.Round, votersRound) - } - - acceptableWeight := AcceptableCompactCertWeight(votersHdr, atRound, logging.Base()) - if cert.SignedWeight < acceptableWeight { - return fmt.Errorf("insufficient weight at %d: %d < %d", - atRound, cert.SignedWeight, acceptableWeight) - } - - ccParams, err := CompactCertParams(votersHdr, certHdr) - if err != nil { - return err - } - - verif := compactcert.MkVerifier(ccParams, votersHdr.CompactCert[protocol.CompactCertBasic].CompactCertVoters) - return verif.Verify(&cert) -} diff --git a/ledger/internal/compactcert_test.go b/ledger/internal/compactcert_test.go deleted file mode 100644 index 9bf83361c7..0000000000 --- a/ledger/internal/compactcert_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package internal - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/compactcert" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/bookkeeping" - "github.com/algorand/go-algorand/logging" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func TestValidateCompactCert(t *testing.T) { - partitiontest.PartitionTest(t) - - var certHdr bookkeeping.BlockHeader - var cert compactcert.Cert - var votersHdr bookkeeping.BlockHeader - var nextCertRnd basics.Round - var atRound basics.Round - - // will definitely fail with nothing set up - err := validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - t.Log(err) - require.NotNil(t, err) - - certHdr.CurrentProtocol = "TestValidateCompactCert" - certHdr.Round = 1 - proto := config.Consensus[certHdr.CurrentProtocol] - proto.CompactCertRounds = 2 - config.Consensus[certHdr.CurrentProtocol] = proto - - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - certHdr.Round = 4 - votersHdr.Round = 4 - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - votersHdr.Round = 2 - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - nextCertRnd = 4 - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - votersHdr.CurrentProtocol = certHdr.CurrentProtocol - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - votersHdr.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState) - cc := votersHdr.CompactCert[protocol.CompactCertBasic] - cc.CompactCertVotersTotal.Raw = 100 - votersHdr.CompactCert[protocol.CompactCertBasic] = cc - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - cert.SignedWeight = 101 - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - // still err, but a different err case to cover - t.Log(err) - require.NotNil(t, err) - - // Above cases leave validateCompactCert() with 100% coverage. - // crypto/compactcert.Verify has its own tests -} - -func TestAcceptableCompactCertWeight(t *testing.T) { - partitiontest.PartitionTest(t) - - var votersHdr bookkeeping.BlockHeader - var firstValid basics.Round - logger := logging.TestingLog(t) - - votersHdr.CurrentProtocol = "TestAcceptableCompactCertWeight" - proto := config.Consensus[votersHdr.CurrentProtocol] - proto.CompactCertRounds = 2 - config.Consensus[votersHdr.CurrentProtocol] = proto - out := AcceptableCompactCertWeight(votersHdr, firstValid, logger) - require.Equal(t, uint64(0), out) - - votersHdr.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState) - cc := votersHdr.CompactCert[protocol.CompactCertBasic] - cc.CompactCertVotersTotal.Raw = 100 - votersHdr.CompactCert[protocol.CompactCertBasic] = cc - out = AcceptableCompactCertWeight(votersHdr, firstValid, logger) - require.Equal(t, uint64(100), out) - - // this should exercise the second return case - firstValid = basics.Round(5) - out = AcceptableCompactCertWeight(votersHdr, firstValid, logger) - require.Equal(t, uint64(100), out) - - firstValid = basics.Round(6) - proto.CompactCertWeightThreshold = 999999999 - config.Consensus[votersHdr.CurrentProtocol] = proto - out = AcceptableCompactCertWeight(votersHdr, firstValid, logger) - require.Equal(t, uint64(0x17), out) - - proto.CompactCertRounds = 10000 - votersHdr.Round = 10000 - firstValid = basics.Round(29000) - config.Consensus[votersHdr.CurrentProtocol] = proto - cc.CompactCertVotersTotal.Raw = 0x7fffffffffffffff - votersHdr.CompactCert[protocol.CompactCertBasic] = cc - proto.CompactCertWeightThreshold = 0x7fffffff - config.Consensus[votersHdr.CurrentProtocol] = proto - out = AcceptableCompactCertWeight(votersHdr, firstValid, logger) - require.Equal(t, uint64(0x4cd35a85213a92a2), out) - - // Covers everything except "overflow that shouldn't happen" branches -} - -func TestCompactCertParams(t *testing.T) { - partitiontest.PartitionTest(t) - - var votersHdr bookkeeping.BlockHeader - var hdr bookkeeping.BlockHeader - - res, err := CompactCertParams(votersHdr, hdr) - require.Error(t, err) // not enabled - - votersHdr.CurrentProtocol = "TestCompactCertParams" - proto := config.Consensus[votersHdr.CurrentProtocol] - proto.CompactCertRounds = 2 - config.Consensus[votersHdr.CurrentProtocol] = proto - votersHdr.Round = 1 - res, err = CompactCertParams(votersHdr, hdr) - require.Error(t, err) // wrong round - - votersHdr.Round = 2 - hdr.Round = 3 - res, err = CompactCertParams(votersHdr, hdr) - require.Error(t, err) // wrong round - - hdr.Round = 4 - res, err = CompactCertParams(votersHdr, hdr) - require.NoError(t, err) - require.Equal(t, hdr.Round, res.SigRound) - - // Covers all cases except overflow -} diff --git a/ledger/internal/cow.go b/ledger/internal/cow.go index 0e21f6f546..0baaa9fc69 100644 --- a/ledger/internal/cow.go +++ b/ledger/internal/cow.go @@ -53,8 +53,8 @@ type roundCowParent interface { checkDup(basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error txnCounter() uint64 getCreator(cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) - compactCertNext() basics.Round - blockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) + GetStateProofNextRound() basics.Round + BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) getStorageCounts(addr basics.Address, aidx basics.AppIndex, global bool) (basics.StateSchema, error) // note: getStorageLimits is redundant with the other methods @@ -217,15 +217,15 @@ func (cb *roundCowState) txnCounter() uint64 { return cb.lookupParent.txnCounter() + cb.txnCount } -func (cb *roundCowState) compactCertNext() basics.Round { - if cb.mods.CompactCertNext != 0 { - return cb.mods.CompactCertNext +func (cb *roundCowState) GetStateProofNextRound() basics.Round { + if cb.mods.StateProofNext != 0 { + return cb.mods.StateProofNext } - return cb.lookupParent.compactCertNext() + return cb.lookupParent.GetStateProofNextRound() } -func (cb *roundCowState) blockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { - return cb.lookupParent.blockHdr(r) +func (cb *roundCowState) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { + return cb.lookupParent.BlockHdr(r) } func (cb *roundCowState) blockHdrCached(r basics.Round) (bookkeeping.BlockHeader, error) { @@ -244,8 +244,8 @@ func (cb *roundCowState) addTx(txn transactions.Transaction, txid transactions.T } } -func (cb *roundCowState) setCompactCertNext(rnd basics.Round) { - cb.mods.CompactCertNext = rnd +func (cb *roundCowState) SetStateProofNextRound(rnd basics.Round) { + cb.mods.StateProofNext = rnd } func (cb *roundCowState) child(hint int) *roundCowState { @@ -253,7 +253,7 @@ func (cb *roundCowState) child(hint int) *roundCowState { lookupParent: cb, commitParent: cb, proto: cb.proto, - mods: ledgercore.MakeStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.CompactCertNext), + mods: ledgercore.MakeStateDelta(cb.mods.Hdr, cb.mods.PrevTimestamp, hint, cb.mods.StateProofNext), sdeltas: make(map[basics.Address]map[storagePtr]*storageDelta), } @@ -293,7 +293,7 @@ func (cb *roundCowState) commitToParent() { } } } - cb.commitParent.mods.CompactCertNext = cb.mods.CompactCertNext + cb.commitParent.mods.StateProofNext = cb.mods.StateProofNext } func (cb *roundCowState) modifiedAccounts() []basics.Address { diff --git a/ledger/internal/cow_test.go b/ledger/internal/cow_test.go index 147054b0a9..32e6a36e48 100644 --- a/ledger/internal/cow_test.go +++ b/ledger/internal/cow_test.go @@ -89,11 +89,11 @@ func (ml *mockLedger) txnCounter() uint64 { return 0 } -func (ml *mockLedger) compactCertNext() basics.Round { +func (ml *mockLedger) GetStateProofNextRound() basics.Round { return 0 } -func (ml *mockLedger) blockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { +func (ml *mockLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error) { err, hit := ml.blockErr[rnd] if hit { return bookkeeping.BlockHeader{}, err @@ -103,7 +103,7 @@ func (ml *mockLedger) blockHdr(rnd basics.Round) (bookkeeping.BlockHeader, error } func (ml *mockLedger) blockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return ml.blockHdr(rnd) + return ml.blockHdrCached(rnd) } func checkCowByUpdate(t *testing.T, cow *roundCowState, delta ledgercore.AccountDeltas) { diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index c34682a1e9..93429f2e0c 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -24,7 +24,6 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -108,11 +107,11 @@ type roundCowBase struct { // TxnCounter from previous block header. txnCount uint64 - // Round of the next expected compact cert. In the common case this - // is CompactCertNextRound from previous block header, except when - // compact certs are first enabled, in which case this gets set - // appropriately at the first block where compact certs are enabled. - compactCertNextRnd basics.Round + // Round of the next expected state proof. In the common case this + // is StateProofNextRound from previous block header, except when + // state proofs are first enabled, in which case this gets set + // appropriately at the first block where state proofs are enabled. + stateProofNextRnd basics.Round // The current protocol consensus params. proto config.ConsensusParams @@ -135,19 +134,19 @@ type roundCowBase struct { creators map[creatable]foundAddress } -func makeRoundCowBase(l LedgerForCowBase, rnd basics.Round, txnCount uint64, compactCertNextRnd basics.Round, proto config.ConsensusParams) *roundCowBase { +func makeRoundCowBase(l LedgerForCowBase, rnd basics.Round, txnCount uint64, stateProofNextRnd basics.Round, proto config.ConsensusParams) *roundCowBase { return &roundCowBase{ - l: l, - rnd: rnd, - txnCount: txnCount, - compactCertNextRnd: compactCertNextRnd, - proto: proto, - accounts: make(map[basics.Address]ledgercore.AccountData), - appParams: make(map[ledgercore.AccountApp]cachedAppParams), - assetParams: make(map[ledgercore.AccountAsset]cachedAssetParams), - appLocalStates: make(map[ledgercore.AccountApp]cachedAppLocalState), - assets: make(map[ledgercore.AccountAsset]cachedAssetHolding), - creators: make(map[creatable]foundAddress), + l: l, + rnd: rnd, + txnCount: txnCount, + stateProofNextRnd: stateProofNextRnd, + proto: proto, + accounts: make(map[basics.Address]ledgercore.AccountData), + appParams: make(map[ledgercore.AccountApp]cachedAppParams), + assetParams: make(map[ledgercore.AccountAsset]cachedAssetParams), + appLocalStates: make(map[ledgercore.AccountApp]cachedAppLocalState), + assets: make(map[ledgercore.AccountAsset]cachedAssetHolding), + creators: make(map[creatable]foundAddress), } } @@ -325,11 +324,11 @@ func (x *roundCowBase) txnCounter() uint64 { return x.txnCount } -func (x *roundCowBase) compactCertNext() basics.Round { - return x.compactCertNextRnd +func (x *roundCowBase) GetStateProofNextRound() basics.Round { + return x.stateProofNextRnd } -func (x *roundCowBase) blockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { +func (x *roundCowBase) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { return x.l.BlockHdr(r) } @@ -570,37 +569,6 @@ func (cs *roundCowState) ConsensusParams() config.ConsensusParams { return cs.proto } -func (cs *roundCowState) compactCert(certRnd basics.Round, certType protocol.CompactCertType, cert compactcert.Cert, atRound basics.Round, validate bool) error { - if certType != protocol.CompactCertBasic { - return fmt.Errorf("compact cert type %d not supported", certType) - } - - nextCertRnd := cs.compactCertNext() - - certHdr, err := cs.blockHdr(certRnd) - if err != nil { - return err - } - - proto := config.Consensus[certHdr.CurrentProtocol] - - if validate { - votersRnd := certRnd.SubSaturate(basics.Round(proto.CompactCertRounds)) - votersHdr, err := cs.blockHdr(votersRnd) - if err != nil { - return err - } - - err = validateCompactCert(certHdr, cert, votersHdr, nextCertRnd, atRound) - if err != nil { - return err - } - } - - cs.setCompactCertNext(certRnd + basics.Round(proto.CompactCertRounds)) - return nil -} - // BlockEvaluator represents an in-progress evaluation of a block // against the ledger. type BlockEvaluator struct { @@ -629,7 +597,7 @@ type LedgerForEvaluator interface { GenesisHash() crypto.Digest GenesisProto() config.ConsensusParams LatestTotals() (basics.Round, ledgercore.AccountTotals, error) - CompactCertVoters(basics.Round) (*ledgercore.VotersForRound, error) + VotersForStateProof(basics.Round) (*ledgercore.VotersForRound, error) } // EvaluatorOptions defines the evaluator creation options @@ -709,18 +677,18 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts eval.block.Payset = make([]transactions.SignedTxnInBlock, 0, evalOpts.PaysetHint) } - base.compactCertNextRnd = eval.prevHeader.CompactCert[protocol.CompactCertBasic].CompactCertNextRound + base.stateProofNextRnd = eval.prevHeader.StateProofTracking[protocol.StateProofBasic].StateProofNextRound - // Check if compact certs are being enabled as of this block. - if base.compactCertNextRnd == 0 && proto.CompactCertRounds != 0 { - // Determine the first block that will contain a Merkle + // Check if state proofs are being enabled as of this block. + if base.stateProofNextRnd == 0 && proto.StateProofInterval != 0 { + // Determine the first block that will contain a Vector // commitment to the voters. We need to account for the - // fact that the voters come from CompactCertVotersLookback + // fact that the voters come from StateProofVotersLookback // rounds ago. - votersRound := (hdr.Round + basics.Round(proto.CompactCertVotersLookback)).RoundUpToMultipleOf(basics.Round(proto.CompactCertRounds)) + votersRound := (hdr.Round + basics.Round(proto.StateProofVotersLookback)).RoundUpToMultipleOf(basics.Round(proto.StateProofInterval)) - // The first compact cert will appear CompactCertRounds after that. - base.compactCertNextRnd = votersRound + basics.Round(proto.CompactCertRounds) + // The first state proof will appear StateProofInterval after that. + base.stateProofNextRnd = votersRound + basics.Round(proto.StateProofInterval) } latestRound, prevTotals, err := l.LatestTotals() @@ -1018,12 +986,12 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit func (eval *BlockEvaluator) checkMinBalance(cow *roundCowState) error { rewardlvl := cow.rewardsLevel() for _, addr := range cow.modifiedAccounts() { - // Skip FeeSink, RewardsPool, and CompactCertSender MinBalance checks here. + // Skip FeeSink, RewardsPool, and StateProofSender MinBalance checks here. // There's only a few accounts, so space isn't an issue, and we don't // expect them to have low balances, but if they do, it may cause // surprises. if addr == eval.block.FeeSink || addr == eval.block.RewardsPool || - addr == transactions.CompactCertSender { + addr == transactions.StateProofSender { continue } @@ -1096,7 +1064,7 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * // Apply the transaction, updating the cow balances applyData, err := eval.applyTransaction(txn.Txn, cow, evalParams, gi, cow.txnCounter()) if err != nil { - return fmt.Errorf("transaction %v: %v", txid, err) + return fmt.Errorf("transaction %v: %w", txid, err) } // Validate applyData if we are validating an existing block. @@ -1138,47 +1106,47 @@ func (eval *BlockEvaluator) transaction(txn transactions.SignedTxn, evalParams * } // applyTransaction changes the balances according to this transaction. -func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balances *roundCowState, evalParams *logic.EvalParams, gi int, ctr uint64) (ad transactions.ApplyData, err error) { - params := balances.ConsensusParams() +func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, cow *roundCowState, evalParams *logic.EvalParams, gi int, ctr uint64) (ad transactions.ApplyData, err error) { + params := cow.ConsensusParams() // move fee to pool - err = balances.Move(tx.Sender, eval.specials.FeeSink, tx.Fee, &ad.SenderRewards, nil) + err = cow.Move(tx.Sender, eval.specials.FeeSink, tx.Fee, &ad.SenderRewards, nil) if err != nil { return } - err = apply.Rekey(balances, &tx) + err = apply.Rekey(cow, &tx) if err != nil { return } switch tx.Type { case protocol.PaymentTx: - err = apply.Payment(tx.PaymentTxnFields, tx.Header, balances, eval.specials, &ad) + err = apply.Payment(tx.PaymentTxnFields, tx.Header, cow, eval.specials, &ad) case protocol.KeyRegistrationTx: - err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, balances, eval.specials, &ad, balances.round()) + err = apply.Keyreg(tx.KeyregTxnFields, tx.Header, cow, eval.specials, &ad, cow.round()) case protocol.AssetConfigTx: - err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, balances, eval.specials, &ad, ctr) + err = apply.AssetConfig(tx.AssetConfigTxnFields, tx.Header, cow, eval.specials, &ad, ctr) case protocol.AssetTransferTx: - err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, balances, eval.specials, &ad) + err = apply.AssetTransfer(tx.AssetTransferTxnFields, tx.Header, cow, eval.specials, &ad) case protocol.AssetFreezeTx: - err = apply.AssetFreeze(tx.AssetFreezeTxnFields, tx.Header, balances, eval.specials, &ad) + err = apply.AssetFreeze(tx.AssetFreezeTxnFields, tx.Header, cow, eval.specials, &ad) case protocol.ApplicationCallTx: - err = apply.ApplicationCall(tx.ApplicationCallTxnFields, tx.Header, balances, &ad, gi, evalParams, ctr) + err = apply.ApplicationCall(tx.ApplicationCallTxnFields, tx.Header, cow, &ad, gi, evalParams, ctr) - case protocol.CompactCertTx: - // in case of a CompactCertTx transaction, we want to "apply" it only in validate or generate mode. This will deviate the cow's CompactCertNext depending of - // whether we're in validate/generate mode or not, however - given that this variable in only being used in these modes, it would be safe. + case protocol.StateProofTx: + // in case of a StateProofTx transaction, we want to "apply" it only in validate or generate mode. This will deviate the cow's StateProofNextRound depending on + // whether we're in validate/generate mode or not, however - given that this variable is only being used in these modes, it would be safe. // The reason for making this into an exception is that during initialization time, the accounts update is "converting" the recent 320 blocks into deltas to - // be stored in memory. These deltas don't care about the compact certificate, and so we can improve the node load time. Additionally, it save us from + // be stored in memory. These deltas don't care about the state proofs, and so we can improve the node load time. Additionally, it save us from // performing the validation during catchup, which is another performance boost. if eval.validate || eval.generate { - err = balances.compactCert(tx.CertRound, tx.CertType, tx.Cert, tx.Header.FirstValid, eval.validate) + err = apply.StateProof(tx.StateProofTxnFields, tx.Header.FirstValid, cow, eval.validate) } default: @@ -1209,28 +1177,24 @@ func (eval *BlockEvaluator) applyTransaction(tx transactions.Transaction, balanc return } -// compactCertVotersAndTotal returns the expected values of CompactCertVoters -// and CompactCertVotersTotal for a block. -func (eval *BlockEvaluator) compactCertVotersAndTotal() (root crypto.GenericDigest, total basics.MicroAlgos, err error) { - if eval.proto.CompactCertRounds == 0 { +// stateProofVotersAndTotal returns the expected values of StateProofVotersCommitment +// and StateProofOnlineTotalWeight for a block. +func (eval *BlockEvaluator) stateProofVotersAndTotal() (root crypto.GenericDigest, total basics.MicroAlgos, err error) { + if eval.proto.StateProofInterval == 0 { return } - if eval.block.Round()%basics.Round(eval.proto.CompactCertRounds) != 0 { + if eval.block.Round()%basics.Round(eval.proto.StateProofInterval) != 0 { return } - lookback := eval.block.Round().SubSaturate(basics.Round(eval.proto.CompactCertVotersLookback)) - voters, err := eval.l.CompactCertVoters(lookback) - if err != nil { + lookback := eval.block.Round().SubSaturate(basics.Round(eval.proto.StateProofVotersLookback)) + voters, err := eval.l.VotersForStateProof(lookback) + if err != nil || voters == nil { return } - if voters != nil { - root, total = voters.Tree.Root(), voters.TotalWeight - } - - return + return voters.Tree.Root(), voters.TotalWeight, nil } // TestingTxnCounter - the method returns the current evaluator transaction counter. The method is used for testing purposes only. @@ -1255,17 +1219,17 @@ func (eval *BlockEvaluator) endOfBlock() error { eval.generateExpiredOnlineAccountsList() - if eval.proto.CompactCertRounds > 0 { - var basicCompactCert bookkeeping.CompactCertState - basicCompactCert.CompactCertVoters, basicCompactCert.CompactCertVotersTotal, err = eval.compactCertVotersAndTotal() + if eval.proto.StateProofInterval > 0 { + var basicStateProof bookkeeping.StateProofTrackingData + basicStateProof.StateProofVotersCommitment, basicStateProof.StateProofOnlineTotalWeight, err = eval.stateProofVotersAndTotal() if err != nil { return err } - basicCompactCert.CompactCertNextRound = eval.state.compactCertNext() + basicStateProof.StateProofNextRound = eval.state.GetStateProofNextRound() - eval.block.CompactCert = make(map[protocol.CompactCertType]bookkeeping.CompactCertState) - eval.block.CompactCert[protocol.CompactCertBasic] = basicCompactCert + eval.block.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + eval.block.StateProofTracking[protocol.StateProofBasic] = basicStateProof } } @@ -1297,22 +1261,22 @@ func (eval *BlockEvaluator) endOfBlock() error { return fmt.Errorf("txn count wrong: %d != %d", eval.block.TxnCounter, expectedTxnCount) } - expectedVoters, expectedVotersWeight, err := eval.compactCertVotersAndTotal() + expectedVoters, expectedVotersWeight, err := eval.stateProofVotersAndTotal() if err != nil { return err } - if !eval.block.CompactCert[protocol.CompactCertBasic].CompactCertVoters.IsEqual(expectedVoters) { - return fmt.Errorf("CompactCertVoters wrong: %v != %v", eval.block.CompactCert[protocol.CompactCertBasic].CompactCertVoters, expectedVoters) + if !eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEqual(expectedVoters) { + return fmt.Errorf("StateProofVotersCommitment wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, expectedVoters) } - if eval.block.CompactCert[protocol.CompactCertBasic].CompactCertVotersTotal != expectedVotersWeight { - return fmt.Errorf("CompactCertVotersTotal wrong: %v != %v", eval.block.CompactCert[protocol.CompactCertBasic].CompactCertVotersTotal, expectedVotersWeight) + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { + return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) } - if eval.block.CompactCert[protocol.CompactCertBasic].CompactCertNextRound != eval.state.compactCertNext() { - return fmt.Errorf("CompactCertNextRound wrong: %v != %v", eval.block.CompactCert[protocol.CompactCertBasic].CompactCertNextRound, eval.state.compactCertNext()) + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound != eval.state.GetStateProofNextRound() { + return fmt.Errorf("StateProofNextRound wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound, eval.state.GetStateProofNextRound()) } - for ccType := range eval.block.CompactCert { - if ccType != protocol.CompactCertBasic { - return fmt.Errorf("CompactCertType %d unexpected", ccType) + for ccType := range eval.block.StateProofTracking { + if ccType != protocol.StateProofBasic { + return fmt.Errorf("StateProofType %d unexpected", ccType) } } } diff --git a/ledger/internal/eval_test.go b/ledger/internal/eval_test.go index 030a7c82a7..8f07612be0 100644 --- a/ledger/internal/eval_test.go +++ b/ledger/internal/eval_test.go @@ -30,13 +30,15 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/data/transactions/verify" + "github.com/algorand/go-algorand/ledger/apply" "github.com/algorand/go-algorand/ledger/ledgercore" ledgertesting "github.com/algorand/go-algorand/ledger/testing" "github.com/algorand/go-algorand/protocol" @@ -198,14 +200,15 @@ func TestEvalAppAllocStateWithTxnGroup(t *testing.T) { require.Equal(t, basics.TealValue{Type: basics.TealBytesType, Bytes: string(addr[:])}, state["creator"]) } -func TestCowCompactCert(t *testing.T) { +func TestCowStateProof(t *testing.T) { partitiontest.PartitionTest(t) - var certRnd basics.Round - var certType protocol.CompactCertType - var cert compactcert.Cert + var spType protocol.StateProofType + var stateProof stateproof.StateProof var atRound basics.Round var validate bool + msg := stateproofmsg.Message{} + accts0 := ledgertesting.RandomAccounts(20, true) blocks := make(map[basics.Round]bookkeeping.BlockHeader) blockErr := make(map[basics.Round]error) @@ -214,45 +217,59 @@ func TestCowCompactCert(t *testing.T) { &ml, bookkeeping.BlockHeader{}, config.Consensus[protocol.ConsensusCurrentVersion], 0, ledgercore.AccountTotals{}, 0) - certType = protocol.CompactCertType(1234) // bad cert type - err := c0.compactCert(certRnd, certType, cert, atRound, validate) - require.Error(t, err) + spType = protocol.StateProofType(1234) // bad stateproof type + stateProofTx := transactions.StateProofTxnFields{ + StateProofType: spType, + StateProof: stateProof, + Message: msg, + } + err := apply.StateProof(stateProofTx, atRound, c0, validate) + require.ErrorIs(t, err, apply.ErrStateProofTypeNotSupported) - // no certRnd block - certType = protocol.CompactCertBasic + // no spRnd block + stateProofTx.StateProofType = protocol.StateProofBasic noBlockErr := errors.New("no block") blockErr[3] = noBlockErr - certRnd = 3 - err = c0.compactCert(certRnd, certType, cert, atRound, validate) - require.Error(t, err) + stateProofTx.Message.LastAttestedRound = 3 + err = apply.StateProof(stateProofTx, atRound, c0, validate) + require.Contains(t, err.Error(), "no block") + + // stateproof txn doesn't confirm the next state proof round. expected is in the past + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + c0.SetStateProofNextRound(8) + err = apply.StateProof(stateProofTx, atRound, c0, validate) + require.ErrorIs(t, err, apply.ErrExpectedDifferentStateProofRound) + + // stateproof txn doesn't confirm the next state proof round. expected is in the future + validate = true + stateProofTx.Message.LastAttestedRound = uint64(16) + c0.SetStateProofNextRound(32) + err = apply.StateProof(stateProofTx, atRound, c0, validate) + require.ErrorIs(t, err, apply.ErrExpectedDifferentStateProofRound) // no votersRnd block // this is slightly a mess of things that don't quite line up with likely usage validate = true - var certHdr bookkeeping.BlockHeader - certHdr.CurrentProtocol = "TestCowCompactCert" - certHdr.Round = 1 - proto := config.Consensus[certHdr.CurrentProtocol] - proto.CompactCertRounds = 2 - config.Consensus[certHdr.CurrentProtocol] = proto - blocks[certHdr.Round] = certHdr - - certHdr.Round = 15 - blocks[certHdr.Round] = certHdr - certRnd = certHdr.Round + var spHdr bookkeeping.BlockHeader + spHdr.CurrentProtocol = "TestCowStateProof" + spHdr.Round = 1 + proto := config.Consensus[spHdr.CurrentProtocol] + proto.StateProofInterval = 2 + config.Consensus[spHdr.CurrentProtocol] = proto + blocks[spHdr.Round] = spHdr + + spHdr.Round = 15 + blocks[spHdr.Round] = spHdr + stateProofTx.Message.LastAttestedRound = uint64(spHdr.Round) + c0.SetStateProofNextRound(15) blockErr[13] = noBlockErr - err = c0.compactCert(certRnd, certType, cert, atRound, validate) - require.Error(t, err) - - // validate fail - certHdr.Round = 1 - certRnd = certHdr.Round - err = c0.compactCert(certRnd, certType, cert, atRound, validate) - require.Error(t, err) + err = apply.StateProof(stateProofTx, atRound, c0, validate) + require.Contains(t, err.Error(), "no block") // fall through to no err validate = false - err = c0.compactCert(certRnd, certType, cert, atRound, validate) + err = apply.StateProof(stateProofTx, atRound, c0, validate) require.NoError(t, err) // 100% coverage @@ -634,7 +651,7 @@ func (ledger *evalTestLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.Bloc return ledger.BlockHdrCached(rnd) } -func (ledger *evalTestLedger) CompactCertVoters(rnd basics.Round) (*ledgercore.VotersForRound, error) { +func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { return nil, errors.New("untested code path") } @@ -732,7 +749,7 @@ func (l *testCowBaseLedger) BlockHdr(basics.Round) (bookkeeping.BlockHeader, err } func (l *testCowBaseLedger) BlockHdrCached(rnd basics.Round) (bookkeeping.BlockHeader, error) { - return l.BlockHdr(rnd) + return l.BlockHdrCached(rnd) } func (l *testCowBaseLedger) CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error { @@ -885,7 +902,7 @@ func TestEvalFunctionForExpiredAccounts(t *testing.T) { acctData, _ := blkEval.state.lookup(recvAddr) - require.Equal(t, merklesignature.Verifier{}, acctData.StateProofID) + require.Equal(t, merklesignature.Verifier{}.Commitment, acctData.StateProofID) require.Equal(t, crypto.VRFVerifier{}, acctData.SelectionID) badBlock := *validatedBlock @@ -1127,11 +1144,11 @@ func TestExpiredAccountGeneration(t *testing.T) { recvAcct, err := eval.state.lookup(recvAddr) require.NoError(t, err) - require.Equal(t, recvAcct.Status, basics.Offline) - require.Equal(t, recvAcct.VoteFirstValid, basics.Round(0)) - require.Equal(t, recvAcct.VoteLastValid, basics.Round(0)) - require.Equal(t, recvAcct.VoteKeyDilution, uint64(0)) - require.Equal(t, recvAcct.VoteID, crypto.OneTimeSignatureVerifier{}) - require.Equal(t, recvAcct.SelectionID, crypto.VRFVerifier{}) - require.Equal(t, recvAcct.StateProofID, merklesignature.Verifier{}) + require.Equal(t, basics.Offline, recvAcct.Status) + require.Equal(t, basics.Round(0), recvAcct.VoteFirstValid) + require.Equal(t, basics.Round(0), recvAcct.VoteLastValid) + require.Equal(t, uint64(0), recvAcct.VoteKeyDilution) + require.Equal(t, crypto.OneTimeSignatureVerifier{}, recvAcct.VoteID) + require.Equal(t, crypto.VRFVerifier{}, recvAcct.SelectionID) + require.Equal(t, merklesignature.Verifier{}.Commitment, recvAcct.StateProofID) } diff --git a/ledger/internal/prefetcher/prefetcher.go b/ledger/internal/prefetcher/prefetcher.go index e011ad23e1..82e0d830c1 100644 --- a/ledger/internal/prefetcher/prefetcher.go +++ b/ledger/internal/prefetcher/prefetcher.go @@ -295,7 +295,7 @@ func (p *accountPrefetcher) prefetch(ctx context.Context) { case protocol.AssetTransferTx: case protocol.AssetFreezeTx: case protocol.ApplicationCallTx: - case protocol.CompactCertTx: + case protocol.StateProofTx: case protocol.KeyRegistrationTx: } // If you add new addresses here, also add them in getTxnAddresses(). diff --git a/ledger/internal/prefetcher/prefetcher_alignment_test.go b/ledger/internal/prefetcher/prefetcher_alignment_test.go index fbff1aa4dc..05a672d325 100644 --- a/ledger/internal/prefetcher/prefetcher_alignment_test.go +++ b/ledger/internal/prefetcher/prefetcher_alignment_test.go @@ -167,7 +167,7 @@ func (l *prefetcherAlignmentTestLedger) GenesisProto() config.ConsensusParams { func (l *prefetcherAlignmentTestLedger) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) { return 0, ledgercore.AccountTotals{}, nil } -func (l *prefetcherAlignmentTestLedger) CompactCertVoters(basics.Round) (*ledgercore.VotersForRound, error) { +func (l *prefetcherAlignmentTestLedger) VotersForStateProof(basics.Round) (*ledgercore.VotersForRound, error) { return nil, nil } @@ -746,7 +746,7 @@ func TestEvaluatorPrefetcherAlignmentKeyreg(t *testing.T) { var selectionPK crypto.VRFVerifier selectionPK[0] = 2 var stateProofPK merklesignature.Verifier - stateProofPK[0] = 3 + stateProofPK.Commitment[0] = 3 txn := transactions.Transaction{ Type: protocol.KeyRegistrationTx, @@ -757,7 +757,7 @@ func TestEvaluatorPrefetcherAlignmentKeyreg(t *testing.T) { KeyregTxnFields: transactions.KeyregTxnFields{ VotePK: votePK, SelectionPK: selectionPK, - StateProofPK: stateProofPK, + StateProofPK: stateProofPK.Commitment, VoteLast: 9, VoteKeyDilution: 10, }, @@ -1261,7 +1261,7 @@ func TestEvaluatorPrefetcherAlignmentApplicationCallForeignAssetsDeclaration(t * require.Equal(t, requested, prefetched) } -func TestEvaluatorPrefetcherAlignmentCompactCert(t *testing.T) { +func TestEvaluatorPrefetcherAlignmentStateProof(t *testing.T) { partitiontest.PartitionTest(t) addr := makeAddress(1) @@ -1281,12 +1281,12 @@ func TestEvaluatorPrefetcherAlignmentCompactCert(t *testing.T) { } txn := transactions.Transaction{ - Type: protocol.CompactCertTx, + Type: protocol.StateProofTx, Header: transactions.Header{ Sender: addr, GenesisHash: genesisHash(), }, - CompactCertTxnFields: transactions.CompactCertTxnFields{}, + StateProofTxnFields: transactions.StateProofTxnFields{}, } requested, prefetched := run(t, l, txn) diff --git a/ledger/internal/prefetcher/prefetcher_test.go b/ledger/internal/prefetcher/prefetcher_test.go index e79434ab31..40fe6949be 100644 --- a/ledger/internal/prefetcher/prefetcher_test.go +++ b/ledger/internal/prefetcher/prefetcher_test.go @@ -120,7 +120,7 @@ func (l *prefetcherTestLedger) GenesisProto() config.ConsensusParams { func (l *prefetcherTestLedger) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) { return l.round, ledgercore.AccountTotals{}, nil } -func (l *prefetcherTestLedger) CompactCertVoters(basics.Round) (*ledgercore.VotersForRound, error) { +func (l *prefetcherTestLedger) VotersForStateProof(basics.Round) (*ledgercore.VotersForRound, error) { return nil, nil } diff --git a/ledger/ledger.go b/ledger/ledger.go index 59e109bd44..f5e4b9a8fa 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -28,7 +28,6 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -87,7 +86,7 @@ type Ledger struct { trackers trackerRegistry trackerMu deadlock.RWMutex - headerCache heapLRUCache + headerCache blockHeaderCache // verifiedTxnCache holds all the verified transactions state verifiedTxnCache verify.VerifiedTransactionCache @@ -125,7 +124,7 @@ func OpenLedger( dbPathPrefix: dbPathPrefix, } - l.headerCache.maxEntries = 10 + l.headerCache.initialize() defer func() { if err != nil { @@ -438,10 +437,10 @@ func (l *Ledger) GetCreator(cidx basics.CreatableIndex, ctype basics.CreatableTy return l.accts.GetCreatorForRound(l.blockQ.latest(), cidx, ctype) } -// CompactCertVoters returns the top online accounts at round rnd. +// VotersForStateProof returns the top online accounts at round rnd. // The result might be nil, even with err=nil, if there are no voters -// for that round because compact certs were not enabled. -func (l *Ledger) CompactCertVoters(rnd basics.Round) (*ledgercore.VotersForRound, error) { +// for that round because state proofs were not enabled. +func (l *Ledger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { l.trackerMu.RLock() defer l.trackerMu.RUnlock() return l.acctsOnline.voters.getVoters(rnd) @@ -568,7 +567,7 @@ func (l *Ledger) LatestTotals() (basics.Round, ledgercore.AccountTotals, error) func (l *Ledger) OnlineTotals(rnd basics.Round) (basics.MicroAlgos, error) { l.trackerMu.RLock() defer l.trackerMu.RUnlock() - return l.acctsOnline.OnlineTotals(rnd) + return l.acctsOnline.onlineTotals(rnd) } // CheckDup return whether a transaction is a duplicate one. @@ -599,15 +598,14 @@ func (l *Ledger) Block(rnd basics.Round) (blk bookkeeping.Block, err error) { // BlockHdr returns the BlockHeader of the block for round rnd. func (l *Ledger) BlockHdr(rnd basics.Round) (blk bookkeeping.BlockHeader, err error) { - value, exists := l.headerCache.Get(rnd) + blk, exists := l.headerCache.get(rnd) if exists { - blk = value.(bookkeeping.BlockHeader) return } blk, err = l.blockQ.getBlockHdr(rnd) if err == nil { - l.headerCache.Put(rnd, blk) + l.headerCache.put(blk) } return } @@ -658,7 +656,7 @@ func (l *Ledger) AddValidatedBlock(vb ledgercore.ValidatedBlock, cert agreement. if err != nil { return err } - l.headerCache.Put(blk.Round(), blk.BlockHeader) + l.headerCache.put(blk.BlockHeader) l.trackers.newBlock(blk, vb.Delta()) l.log.Debugf("ledger.AddValidatedBlock: added blk %d", blk.Round()) return nil @@ -805,24 +803,6 @@ func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionP return &vb, nil } -// CompactCertParams computes the parameters for building or verifying -// a compact cert for block hdr, using voters from block votersHdr. -func CompactCertParams(votersHdr bookkeeping.BlockHeader, hdr bookkeeping.BlockHeader) (res compactcert.Params, err error) { - return internal.CompactCertParams(votersHdr, hdr) -} - -// AcceptableCompactCertWeight computes the acceptable signed weight -// of a compact cert if it were to appear in a transaction with a -// particular firstValid round. Earlier rounds require a smaller cert. -// votersHdr specifies the block that contains the Merkle commitment of -// the voters for this compact cert (and thus the compact cert is for -// votersHdr.Round() + CompactCertRounds). -// -// logger must not be nil; use at least logging.Base() -func AcceptableCompactCertWeight(votersHdr bookkeeping.BlockHeader, firstValid basics.Round, logger logging.Logger) uint64 { - return internal.AcceptableCompactCertWeight(votersHdr, firstValid, logger) -} - // DebuggerLedger defines the minimal set of method required for creating a debug balances. type DebuggerLedger = internal.LedgerForCowBase diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index 1a47c81440..d925cec61b 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "database/sql" + "errors" "fmt" "math/rand" "os" @@ -27,15 +28,13 @@ import ( "sort" "testing" - "github.com/algorand/go-algorand/data/account" - "github.com/algorand/go-algorand/util/db" - "github.com/algorand/go-deadlock" - "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" @@ -46,7 +45,9 @@ import ( "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/db" "github.com/algorand/go-algorand/util/execpool" + "github.com/algorand/go-deadlock" ) func sign(secrets map[basics.Address]*crypto.SignatureSecrets, t transactions.Transaction) transactions.SignedTxn { @@ -83,15 +84,15 @@ func initNextBlockHeader(correctHeader *bookkeeping.BlockHeader, lastBlock bookk correctHeader.TxnCounter = lastBlock.TxnCounter } - if proto.CompactCertRounds > 0 { - var ccBasic bookkeeping.CompactCertState - if lastBlock.CompactCert[protocol.CompactCertBasic].CompactCertNextRound == 0 { - ccBasic.CompactCertNextRound = (correctHeader.Round + basics.Round(proto.CompactCertVotersLookback)).RoundUpToMultipleOf(basics.Round(proto.CompactCertRounds)) + basics.Round(proto.CompactCertRounds) + if proto.StateProofInterval > 0 { + var ccBasic bookkeeping.StateProofTrackingData + if lastBlock.StateProofTracking[protocol.StateProofBasic].StateProofNextRound == 0 { + ccBasic.StateProofNextRound = (correctHeader.Round + basics.Round(proto.StateProofVotersLookback)).RoundUpToMultipleOf(basics.Round(proto.StateProofInterval)) + basics.Round(proto.StateProofInterval) } else { - ccBasic.CompactCertNextRound = lastBlock.CompactCert[protocol.CompactCertBasic].CompactCertNextRound + ccBasic.StateProofNextRound = lastBlock.StateProofTracking[protocol.StateProofBasic].StateProofNextRound } - correctHeader.CompactCert = map[protocol.CompactCertType]bookkeeping.CompactCertState{ - protocol.CompactCertBasic: ccBasic, + correctHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: ccBasic, } } } @@ -144,6 +145,18 @@ func makeNewEmptyBlock(t *testing.T, l *Ledger, GenesisID string, initAccounts m blk.RewardsPool = testPoolAddr blk.FeeSink = testSinkAddr blk.CurrentProtocol = lastBlock.CurrentProtocol + + if proto.StateProofInterval != 0 && uint64(blk.Round())%proto.StateProofInterval == 0 && uint64(blk.Round()) != 0 { + voters, err := l.VotersForStateProof(blk.Round() - basics.Round(proto.StateProofVotersLookback)) + require.NoError(t, err) + stateProofTracking := bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: voters.Tree.Root(), + StateProofOnlineTotalWeight: voters.TotalWeight, + StateProofNextRound: blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofNextRound, + } + blk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateProofTracking + } + return } @@ -1094,7 +1107,7 @@ func testLedgerSingleTxApplyData(t *testing.T, version protocol.ConsensusVersion signer := p.Participation.StateProofSecrets require.NoError(t, err) - correctKeyregFields.StateProofPK = *(signer.GetVerifier()) + correctKeyregFields.StateProofPK = signer.GetVerifier().Commitment } correctKeyreg := transactions.Transaction{ @@ -1367,6 +1380,7 @@ func testLedgerRegressionFaultyLeaseFirstValidCheck2f3880f7(t *testing.T, versio func TestLedgerBlockHdrCaching(t *testing.T) { partitiontest.PartitionTest(t) + a := require.New(t) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() @@ -1375,20 +1389,89 @@ func TestLedgerBlockHdrCaching(t *testing.T) { cfg.Archival = true log := logging.TestingLog(t) l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) - require.NoError(t, err) + a.NoError(err) defer l.Close() blk := genesisInitState.Block - for i := 0; i < 128; i++ { + for i := 0; i < 1024; i++ { blk.BlockHeader.Round++ blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) err := l.AddBlock(blk, agreement.Certificate{}) - require.NoError(t, err) + a.NoError(err) hdr, err := l.BlockHdr(blk.BlockHeader.Round) - require.NoError(t, err) - require.Equal(t, blk.BlockHeader, hdr) + a.NoError(err) + a.Equal(blk.BlockHeader, hdr) + } + + rnd := basics.Round(128) + hdr, err := l.BlockHdr(rnd) // should update LRU cache but not latestBlockHeaderCache + a.NoError(err) + a.Equal(rnd, hdr.Round) + + _, exists := l.headerCache.lruCache.Get(rnd) + a.True(exists) + + _, exists = l.headerCache.latestHeaderCache.get(rnd) + a.False(exists) +} + +func BenchmarkLedgerBlockHdrCaching(b *testing.B) { + benchLedgerCache(b, 1024-256+1) +} + +func BenchmarkLedgerBlockHdrWithoutCaching(b *testing.B) { + benchLedgerCache(b, 100) +} + +type nullWriter struct{} // logging output not required + +func (w nullWriter) Write(data []byte) (n int, err error) { + return len(data), nil +} + +func benchLedgerCache(b *testing.B, startRound basics.Round) { + a := require.New(b) + + dbName := fmt.Sprintf("%s.%d", b.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + const inMem = false // benchmark actual DB stored in disk instead of on memory + cfg := config.GetDefaultLocal() + cfg.Archival = true + log := logging.TestingLog(b) + log.SetOutput(nullWriter{}) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + a.NoError(err) + defer func() { // close ledger and remove temporary DB file + l.Close() + err := os.Remove(dbName + ".tracker.sqlite") + if err != nil { + fmt.Printf("os.Remove: %v \n", err) + } + err = os.Remove(dbName + ".block.sqlite") + if err != nil { + fmt.Printf("os.Remove: %v \n", err) + } + + }() + + blk := genesisInitState.Block + + // Fill ledger (and its cache) with blocks + for i := 0; i < 1024; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000) + err := l.AddBlock(blk, agreement.Certificate{}) + a.NoError(err) + } + + for i := 0; i < b.N; i++ { + for j := startRound; j < startRound+256; j++ { // these rounds should be in cache + hdr, err := l.BlockHdr(j) + a.NoError(err) + a.Equal(j, hdr.Round) + } } } @@ -1567,6 +1650,146 @@ func TestListAssetsAndApplications(t *testing.T) { require.Equal(t, appCount, len(results)) } +// TestLedgerKeepsOldBlocksForStateProof test that if stateproof chain is delayed for X intervals, the ledger will not +// remove old blocks from the database. When verifying old stateproof transaction, nodes must have the header of the corresponding +// voters round, if this won't be available the verification would fail. +// the voter tracker should prevent the remove needed blocks from the database. +func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { + partitiontest.PartitionTest(t) + + // since the first state proof is expected to happen on stateproofInterval*2 we would start give-up on state proofs we would + // give up on old state proofs only after stateproofInterval*3 + maxBlocks := int((config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 2) * config.Consensus[protocol.ConsensusFuture].StateProofInterval) + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 10000000000) + + // place real values on the participation period, so we would create a commitment with some stake. + accountsWithValid := make(map[basics.Address]basics.AccountData) + for addr, elem := range genesisInitState.Accounts { + newAccount := elem + newAccount.Status = basics.Online + newAccount.VoteFirstValid = 1 + newAccount.VoteLastValid = 10000 + newAccount.VoteKeyDilution = 10 + crypto.RandBytes(newAccount.VoteID[:]) + crypto.RandBytes(newAccount.SelectionID[:]) + crypto.RandBytes(newAccount.StateProofID[:]) + accountsWithValid[addr] = newAccount + } + genesisInitState.Accounts = accountsWithValid + + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = false + log := logging.TestingLog(t) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + lastBlock, err := l.Block(l.Latest()) + proto := config.Consensus[lastBlock.CurrentProtocol] + accounts := make(map[basics.Address]basics.AccountData, len(genesisInitState.Accounts)+maxBlocks) + keys := make(map[basics.Address]*crypto.SignatureSecrets, len(initKeys)+maxBlocks) + // regular addresses: all init accounts minus pools + + addresses := make([]basics.Address, len(genesisInitState.Accounts)-2, len(genesisInitState.Accounts)+maxBlocks) + i := 0 + for addr := range genesisInitState.Accounts { + if addr != testPoolAddr && addr != testSinkAddr { + addresses[i] = addr + i++ + } + accounts[addr] = genesisInitState.Accounts[addr] + keys[addr] = initKeys[addr] + } + + for i := 0; i < maxBlocks; i++ { + addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) + } + backlogPool := execpool.MakeBacklog(nil, 0, execpool.LowPriority, nil) + defer backlogPool.Shutdown() + + // On this round there is no give up on any state proof - so we would be able to verify an old state proof txn. + + // We now create block with stateproof transaction. since we don't want to complicate the test and create + // a cryptographically correct stateproof we would make sure that only the crypto part of the verification fails. + blk := createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) + _, err = l.Validate(context.Background(), blk, backlogPool) + require.ErrorContains(t, err, "state proof crypto error") + + for i := uint64(0); i < proto.StateProofInterval; i++ { + addDummyBlock(t, addresses, proto, l, initKeys, genesisInitState) + } + + l.WaitForCommit(l.Latest()) + // at the point the ledger would remove the voters round for the database. + // that will cause the stateproof transaction verification to fail because there are + // missing blocks + blk = createBlkWithStateproof(t, maxBlocks, proto, genesisInitState, l, accounts) + _, err = l.Validate(context.Background(), blk, backlogPool) + expectedErr := &ledgercore.ErrNoEntry{} + require.True(t, errors.As(err, expectedErr), fmt.Sprintf("got error %s", err)) +} + +func createBlkWithStateproof(t *testing.T, maxBlocks int, proto config.ConsensusParams, genesisInitState ledgercore.InitState, l *Ledger, accounts map[basics.Address]basics.AccountData) bookkeeping.Block { + sp := stateproof.StateProof{SignedWeight: 5000000000000000} + var stxn transactions.SignedTxn + stxn.Txn.Type = protocol.StateProofTx + stxn.Txn.Sender = transactions.StateProofSender + stxn.Txn.FirstValid = basics.Round(uint64(maxBlocks) - proto.StateProofInterval) + stxn.Txn.LastValid = stxn.Txn.FirstValid + basics.Round(proto.MaxTxnLife) + stxn.Txn.GenesisHash = genesisInitState.GenesisHash + stxn.Txn.StateProofType = protocol.StateProofBasic + stxn.Txn.Message.LastAttestedRound = 512 + stxn.Txn.StateProof = sp + + blk := makeNewEmptyBlock(t, l, t.Name(), accounts) + proto = config.Consensus[blk.CurrentProtocol] + for _, stx := range []transactions.SignedTxn{stxn} { + txib, err := blk.EncodeSignedTxn(stx, transactions.ApplyData{}) + require.NoError(t, err) + if proto.TxnCounter { + blk.TxnCounter = blk.TxnCounter + 1 + } + blk.Payset = append(blk.Payset, txib) + } + + var err error + blk.TxnCommitments, err = blk.PaysetCommit() + require.NoError(t, err) + return blk +} + +func addDummyBlock(t *testing.T, addresses []basics.Address, proto config.ConsensusParams, l *Ledger, initKeys map[basics.Address]*crypto.SignatureSecrets, genesisInitState ledgercore.InitState) { + stxns := make([]transactions.SignedTxn, 2) + for j := 0; j < 2; j++ { + txHeader := transactions.Header{ + Sender: addresses[0], + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee * 2}, + FirstValid: l.Latest() + 1, + LastValid: l.Latest() + 10, + GenesisID: t.Name(), + GenesisHash: crypto.Hash([]byte(t.Name())), + Note: []byte{uint8(j)}, + } + + payment := transactions.PaymentTxnFields{ + Receiver: addresses[0], + Amount: basics.MicroAlgos{Raw: 1000}, + } + + tx := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txHeader, + PaymentTxnFields: payment, + } + stxns[j] = sign(initKeys, tx) + } + err := l.addBlockTxns(t, genesisInitState.Accounts, stxns, transactions.ApplyData{}) + require.NoError(t, err) + +} + func TestLedgerMemoryLeak(t *testing.T) { partitiontest.PartitionTest(t) @@ -2420,3 +2643,179 @@ func TestLedgerKeyregFlip(t *testing.T) { } l.WaitForCommit(l.Latest()) } + +func verifyVotersContent(t *testing.T, expected map[basics.Round]*ledgercore.VotersForRound, actual map[basics.Round]*ledgercore.VotersForRound) { + require.Equal(t, len(expected), len(actual)) + for k, v := range actual { + require.NoError(t, v.Wait()) + require.Equal(t, expected[k].Tree, v.Tree) + require.Equal(t, expected[k].Participants, v.Participants) + } +} + +func TestVotersReloadFromDisk(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = basics.Round(proto.StateProofInterval * 2) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + // we add blocks to the ledger to test reload from disk. we would like the history of the acctonline to extend. + // but we don't want to go behind stateproof recovery interval + for i := uint64(0); i < (proto.StateProofInterval*(proto.StateProofMaxRecoveryIntervals-2) - proto.StateProofVotersLookback); i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + // at this point the database should contain the voter for round 256 but the voters for round 512 should be in deltas + l.WaitForCommit(blk.BlockHeader.Round) + vtSnapshot := l.acctsOnline.voters.votersForRoundCache + + // ensuring no tree was evicted. + for _, round := range []basics.Round{240, 496} { + require.Contains(t, vtSnapshot, round) + } + + err = l.reloadLedger() + require.NoError(t, err) + + verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) +} + +func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { + partitiontest.PartitionTest(t) + proto := config.Consensus[protocol.ConsensusFuture] + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + blk := genesisInitState.Block + + sp := bookkeeping.StateProofTrackingData{ + StateProofNextRound: basics.Round(proto.StateProofInterval * 2), + } + + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + for i := uint64(0); i < (proto.StateProofInterval*3 - proto.StateProofVotersLookback); i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + // we simulate that the stateproof for round 512 is confirmed on chain, and we can move to the next one. + sp.StateProofNextRound = basics.Round(proto.StateProofInterval * 3) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + for i := uint64(0); i < proto.StateProofInterval; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + l.WaitForCommit(blk.BlockHeader.Round) + vtSnapshot := l.acctsOnline.voters.votersForRoundCache + + // verifying that the tree for round 512 is still in the cache, but the tree for round 256 is evicted. + require.Contains(t, vtSnapshot, basics.Round(496)) + require.NotContains(t, vtSnapshot, basics.Round(240)) + + err = l.reloadLedger() + require.NoError(t, err) + + verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) +} + +func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { + partitiontest.PartitionTest(t) + proto := config.Consensus[protocol.ConsensusFuture] + + dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) + genesisInitState := getInitState() + genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + const inMem = true + cfg := config.GetDefaultLocal() + cfg.Archival = false + cfg.MaxAcctLookback = proto.StateProofInterval - proto.StateProofVotersLookback - 10 + log := logging.TestingLog(t) + l, err := OpenLedger(log, dbName, inMem, genesisInitState, cfg) + require.NoError(t, err) + defer l.Close() + + blk := genesisInitState.Block + var sp bookkeeping.StateProofTrackingData + sp.StateProofNextRound = basics.Round(proto.StateProofInterval * 2) + blk.BlockHeader.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: sp, + } + + // we push proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals + 2) block into the ledger + // the reason for + 2 is the first state proof is on 2*stateproofinterval. + for i := uint64(0); i < (proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals + 2)); i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + // the voters tracker should contain all the voters for each stateproof round. nothing should be removed + l.WaitForCommit(blk.BlockHeader.Round) + vtSnapshot := l.acctsOnline.voters.votersForRoundCache + beforeRemoveVotersLen := len(vtSnapshot) + err = l.reloadLedger() + require.NoError(t, err) + _, found := l.acctsOnline.voters.votersForRoundCache[basics.Round(proto.StateProofInterval-proto.StateProofVotersLookback)] + require.True(t, found) + verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) + + for i := uint64(0); i < proto.StateProofInterval; i++ { + blk.BlockHeader.Round++ + blk.BlockHeader.TimeStamp += 10 + err = l.AddBlock(blk, agreement.Certificate{}) + require.NoError(t, err) + } + + // the voters tracker should give up on voters for round 512 + l.WaitForCommit(blk.BlockHeader.Round) + vtSnapshot = l.acctsOnline.voters.votersForRoundCache + err = l.reloadLedger() + require.NoError(t, err) + + verifyVotersContent(t, vtSnapshot, l.acctsOnline.voters.votersForRoundCache) + _, found = l.acctsOnline.voters.votersForRoundCache[basics.Round(proto.StateProofInterval-proto.StateProofVotersLookback)] + require.False(t, found) + require.Equal(t, beforeRemoveVotersLen, len(l.acctsOnline.voters.votersForRoundCache)) +} diff --git a/ledger/ledgercore/accountdata.go b/ledger/ledgercore/accountdata.go index 355b409b0e..eb09706ff4 100644 --- a/ledger/ledgercore/accountdata.go +++ b/ledger/ledgercore/accountdata.go @@ -52,7 +52,7 @@ type AccountBaseData struct { type VotingData struct { VoteID crypto.OneTimeSignatureVerifier SelectionID crypto.VRFVerifier - StateProofID merklesignature.Verifier + StateProofID merklesignature.Commitment VoteFirstValid basics.Round VoteLastValid basics.Round diff --git a/ledger/ledgercore/onlineacct.go b/ledger/ledgercore/onlineacct.go index d73fe876fc..765067bb2a 100644 --- a/ledger/ledgercore/onlineacct.go +++ b/ledger/ledgercore/onlineacct.go @@ -24,7 +24,7 @@ import ( // An OnlineAccount corresponds to an account whose AccountData.Status // is Online. This is used for a Merkle tree commitment of online // accounts, which is subsequently used to validate participants for -// a compact certificate. +// a state proof. type OnlineAccount struct { // These are a subset of the fields from the corresponding AccountData. Address basics.Address @@ -33,5 +33,5 @@ type OnlineAccount struct { NormalizedOnlineBalance uint64 VoteFirstValid basics.Round VoteLastValid basics.Round - StateProofID merklesignature.Verifier + StateProofID merklesignature.Commitment } diff --git a/ledger/ledgercore/statedelta.go b/ledger/ledgercore/statedelta.go index b5432145e4..57bbbb6074 100644 --- a/ledger/ledgercore/statedelta.go +++ b/ledger/ledgercore/statedelta.go @@ -93,9 +93,9 @@ type StateDelta struct { // new block header; read-only Hdr *bookkeeping.BlockHeader - // next round for which we expect a compact cert. - // zero if no compact cert is expected. - CompactCertNext basics.Round + // next round for which we expect a state proof. + // zero if no state proof is expected. + StateProofNext basics.Round // previous block timestamp PrevTimestamp int64 @@ -178,7 +178,7 @@ type AccountDeltas struct { // MakeStateDelta creates a new instance of StateDelta. // hint is amount of transactions for evaluation, 2 * hint is for sender and receiver balance records. // This does not play well for AssetConfig and ApplicationCall transactions on scale -func MakeStateDelta(hdr *bookkeeping.BlockHeader, prevTimestamp int64, hint int, compactCertNext basics.Round) StateDelta { +func MakeStateDelta(hdr *bookkeeping.BlockHeader, prevTimestamp int64, hint int, stateProofNext basics.Round) StateDelta { return StateDelta{ Accts: MakeAccountDeltas(hint), Txids: make(map[transactions.Txid]IncludedTransactions, hint), @@ -186,7 +186,7 @@ func MakeStateDelta(hdr *bookkeeping.BlockHeader, prevTimestamp int64, hint int, // asset or application creation are considered as rare events so do not pre-allocate space for them Creatables: make(map[basics.CreatableIndex]ModifiedCreatable), Hdr: hdr, - CompactCertNext: compactCertNext, + StateProofNext: stateProofNext, PrevTimestamp: prevTimestamp, initialTransactionsCount: hint, } diff --git a/ledger/ledgercore/votersForRound.go b/ledger/ledgercore/votersForRound.go index ced0d35ed9..4dfb39da24 100644 --- a/ledger/ledgercore/votersForRound.go +++ b/ledger/ledgercore/votersForRound.go @@ -18,18 +18,27 @@ package ledgercore import ( "fmt" + "github.com/algorand/go-algorand/crypto/merklesignature" "sync" "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/compactcert" "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" ) +// OnlineAccountsFetcher captures the functionality of querying online accounts status +type OnlineAccountsFetcher interface { + // TopOnlineAccounts returns the top n online accounts, sorted by their normalized + // balance and address, whose voting keys are valid in voteRnd. See the + // normalization description in AccountData.NormalizedOnlineBalance(). + TopOnlineAccounts(rnd basics.Round, voteRnd basics.Round, n uint64) (topOnlineAccounts []*OnlineAccount, totalOnlineStake basics.MicroAlgos, err error) +} + // VotersForRound tracks the top online voting accounts as of a particular // round, along with a Merkle tree commitment to those voting accounts. type VotersForRound struct { @@ -51,7 +60,7 @@ type VotersForRound struct { // in participants. Proto config.ConsensusParams - // Participants is the array of top #CompactCertVoters online accounts + // Participants is the array of top StateProofTopVoters online accounts // in this round, sorted by normalized balance (to make sure heavyweight // accounts are biased to the front). Participants basics.ParticipantsArray @@ -68,9 +77,6 @@ type VotersForRound struct { TotalWeight basics.MicroAlgos } -// TopOnlineAccounts is the function signature for a method that would return the top online accounts. -type TopOnlineAccounts func(rnd basics.Round, voteRnd basics.Round, n uint64) ([]*OnlineAccount, error) - // MakeVotersForRound create a new VotersForRound object and initialize it's cond. func MakeVotersForRound() *VotersForRound { vr := &VotersForRound{} @@ -78,22 +84,42 @@ func MakeVotersForRound() *VotersForRound { return vr } -// LoadTree todo -func (tr *VotersForRound) LoadTree(onlineTop TopOnlineAccounts, hdr bookkeeping.BlockHeader) error { +func createStateProofParticipant(stateProofID *merklesignature.Commitment, money basics.MicroAlgos) basics.Participant { + var retPart basics.Participant + retPart.Weight = money.ToUint64() + // Some accounts might not have StateProof keys commitment. As a result, + // the commitment would be an array filled with zeroes: [0x0...0x0]. + // Since the commitment is created using the subset-sum hash function, for which the + // value [0x0..0x0] might be known, we avoid using such empty commitments. + // We replace it with a commitment for zero keys.. + if stateProofID.IsEmpty() { + copy(retPart.PK.Commitment[:], merklesignature.NoKeysCommitment[:]) + } else { + copy(retPart.PK.Commitment[:], stateProofID[:]) + + } + // KeyLifetime is set as a default value here (256) as the currently registered StateProof keys do not have a KeyLifetime value associated with them. + // In order to support changing the KeyLifetime in the future, we would need to update the Keyreg transaction and replace the value here with the one + // registered by the Account. + retPart.PK.KeyLifetime = merklesignature.KeyLifetimeDefault + return retPart +} + +// LoadTree loads the participation tree and other required fields, using the provided OnlineAccountsFetcher. +func (tr *VotersForRound) LoadTree(onlineAccountsFetcher OnlineAccountsFetcher, hdr bookkeeping.BlockHeader) error { r := hdr.Round - // certRound is the block that we expect to form a compact certificate for, + // stateProofRound is the block that we expect to form a state proof for, // using the balances from round r. - certRound := r + basics.Round(tr.Proto.CompactCertVotersLookback+tr.Proto.CompactCertRounds) + stateProofRound := r + basics.Round(tr.Proto.StateProofVotersLookback+tr.Proto.StateProofInterval) - top, err := onlineTop(r, certRound, tr.Proto.CompactCertTopVoters) + top, totalOnlineWeight, err := onlineAccountsFetcher.TopOnlineAccounts(r, stateProofRound, tr.Proto.StateProofTopVoters) if err != nil { return err } participants := make(basics.ParticipantsArray, len(top)) addrToPos := make(map[basics.Address]uint64) - var totalWeight basics.MicroAlgos for i, acct := range top { var ot basics.OverflowTracker @@ -103,19 +129,11 @@ func (tr *VotersForRound) LoadTree(onlineTop TopOnlineAccounts, hdr bookkeeping. return fmt.Errorf("votersTracker.LoadTree: overflow adding rewards %d + %d", acct.MicroAlgos, rewards) } - totalWeight = ot.AddA(totalWeight, money) - if ot.Overflowed { - return fmt.Errorf("votersTracker.LoadTree: overflow computing totalWeight %d + %d", totalWeight.ToUint64(), money.ToUint64()) - } - - participants[i] = basics.Participant{ - PK: acct.StateProofID, - Weight: money.ToUint64(), - } + participants[i] = createStateProofParticipant(&acct.StateProofID, money) addrToPos[acct.Address] = uint64(i) } - tree, err := merklearray.BuildVectorCommitmentTree(participants, crypto.HashFactory{HashType: compactcert.HashType}) + tree, err := merklearray.BuildVectorCommitmentTree(participants, crypto.HashFactory{HashType: stateproof.HashType}) if err != nil { return err } @@ -123,7 +141,7 @@ func (tr *VotersForRound) LoadTree(onlineTop TopOnlineAccounts, hdr bookkeeping. tr.mu.Lock() tr.AddrToPos = addrToPos tr.Participants = participants - tr.TotalWeight = totalWeight + tr.TotalWeight = totalOnlineWeight tr.Tree = tree tr.cond.Broadcast() tr.mu.Unlock() diff --git a/ledger/testing/randomAccounts.go b/ledger/testing/randomAccounts.go index b986076e80..0f20579a89 100644 --- a/ledger/testing/randomAccounts.go +++ b/ledger/testing/randomAccounts.go @@ -214,7 +214,7 @@ func RandomFullAccountData(rewardsLevel uint64, lastCreatableID *basics.Creatabl } else { data.VoteID = crypto.OneTimeSignatureVerifier{} data.SelectionID = crypto.VRFVerifier{} - data.StateProofID = merklesignature.Verifier{} + data.StateProofID = merklesignature.Commitment{} data.VoteFirstValid = 0 data.VoteLastValid = 0 data.VoteKeyDilution = 0 diff --git a/ledger/voters.go b/ledger/voters.go index 9e44f6640c..d0a76a6cd7 100644 --- a/ledger/voters.go +++ b/ledger/voters.go @@ -18,6 +18,7 @@ package ledger import ( "fmt" + "github.com/algorand/go-algorand/stateproof" "sync" "github.com/algorand/go-algorand/config" @@ -27,84 +28,95 @@ import ( "github.com/algorand/go-algorand/protocol" ) -// The votersTracker maintains the Merkle tree for the most recent -// commitments to online accounts for compact certificates. +// The votersTracker maintains the vector commitment for the most recent +// commitments to online accounts for state proofs. // -// We maintain multiple Merkle trees: we might commit to a new Merkle tree in -// block X, but we need the Merkle tree from block X-params.CompactCertBlocks -// to build the compact certificate for block X. +// We maintain multiple vector commitment: we might commit to a new VC in +// block X, but we need the VC from block X-params.StateProofBlocks +// to build the state proof for block X. // // votersTracker is kind-of like a tracker, but hangs off the acctupdates // rather than a direct ledger tracker. We don't have an explicit interface // for such an "accounts tracker" yet, however. type votersTracker struct { - // round contains the top online accounts in a given round. + // votersForRoundCache contains the top online accounts in a given Round. // - // To avoid increasing block latency, we include a Merkle commitment + // To avoid increasing block latency, we include a vector commitment // to the top online accounts as of block X in the block header of - // block X+CompactCertVotersLookback. This gives each node some time - // to construct this Merkle tree, before its root is needed in a block. + // block X+StateProofVotersLookback. This gives each node some time + // to construct this vector commitment, before its root is needed in a block. // - // This round map is indexed by the block X, using the terminology from - // the above example, to be used in X+CompactCertVotersLookback. + // This votersForRoundCache map is indexed by the block X, using the terminology from + // the above example, to be used in X+StateProofVotersLookback. // - // We maintain round entries for two reasons: + // We maintain votersForRoundCache entries for two reasons: // // The first is to maintain the tree for an upcoming block -- that is, // if X+Loookback. + +package ledger + +import ( + "testing" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "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/stretchr/testify/require" +) + +func addBlockToAccountsUpdate(blk bookkeeping.Block, ao *onlineAccounts) { + updates := ledgercore.MakeAccountDeltas(1) + delta := ledgercore.MakeStateDelta(&blk.BlockHeader, 0, updates.Len(), 0) + ao.newBlock(blk, delta) +} + +func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval + numOfIntervals := config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals - 1 + lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + + pooldata := basics.AccountData{} + pooldata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + pooldata.Status = basics.NotParticipating + accts[0][testPoolAddr] = pooldata + + sinkdata := basics.AccountData{} + sinkdata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + sinkdata.Status = basics.NotParticipating + accts[0][testSinkAddr] = sinkdata + + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + defer ml.Close() + + conf := config.GetDefaultLocal() + au, ao := newAcctUpdates(t, ml, conf) + defer au.close() + defer ao.close() + + i := uint64(1) + // adding blocks to the voterstracker (in order to pass the numOfIntervals*stateproofInterval we add 1) + for ; i < (numOfIntervals*intervalForTest)+1; i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + + a.Equal(numOfIntervals, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + block := randomBlock(basics.Round(i)) + i++ + block.block.CurrentProtocol = protocol.ConsensusFuture + + // committing stateproof that confirm the (numOfIntervals - 1)th interval + var stateTracking bookkeeping.StateProofTrackingData + stateTracking.StateProofNextRound = basics.Round((numOfIntervals - 1) * intervalForTest) + block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking + addBlockToAccountsUpdate(block.block, ao) + + // the tracker should have 3 entries + // - voters to confirm the numOfIntervals - 1 th interval + // - voters to confirm the numOfIntervals th interval + // - voters to confirm the numOfIntervals + 1 th interval + a.Equal(uint64(3), uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round((numOfIntervals-2)*intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + block = randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + stateTracking.StateProofNextRound = basics.Round(numOfIntervals * intervalForTest) + block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking + addBlockToAccountsUpdate(block.block, ao) + + a.Equal(uint64(2), uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round((numOfIntervals-1)*intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) +} + +func TestLimitVoterTracker(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval + recoveryIntervalForTests := config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + + pooldata := basics.AccountData{} + pooldata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + pooldata.Status = basics.NotParticipating + accts[0][testPoolAddr] = pooldata + + sinkdata := basics.AccountData{} + sinkdata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + sinkdata.Status = basics.NotParticipating + accts[0][testSinkAddr] = sinkdata + + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + defer ml.Close() + + conf := config.GetDefaultLocal() + au, ao := newAcctUpdates(t, ml, conf) + defer au.close() + defer ao.close() + + i := uint64(1) + + // since the first state proof is expected to happen on stateproofInterval*2 we would start give-up on state proofs + // after intervalForTest*(recoveryIntervalForTests+3) + + // should not give up on any state proof + for ; i < intervalForTest*(recoveryIntervalForTests+2); i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + + // the votersForRoundCache should contains recoveryIntervalForTests+2 elements: + // recoveryIntervalForTests - since this is the recovery interval + // + 1 - since votersForRoundCache would contain the votersForRound for the next state proof to come + // + 1 - in order to confirm recoveryIntervalForTests number of state proofs we need recoveryIntervalForTests + 1 headers (for the commitment) + a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters + for ; i < intervalForTest*(recoveryIntervalForTests+3)+1; i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + + a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*2-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters + for ; i < intervalForTest*(recoveryIntervalForTests+4)+1; i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + // if the last round of the intervalForTest has not been added to the ledger the votersTracker would + // retain one more element + for ; i < intervalForTest*(recoveryIntervalForTests+5); i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + a.Equal(recoveryIntervalForTests+3, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + + for ; i < intervalForTest*(recoveryIntervalForTests+5)+1; i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*4-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) +} + +func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval + lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + + accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} + + pooldata := basics.AccountData{} + pooldata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + pooldata.Status = basics.NotParticipating + accts[0][testPoolAddr] = pooldata + + sinkdata := basics.AccountData{} + sinkdata.MicroAlgos.Raw = 1000 * 1000 * 1000 * 1000 + sinkdata.Status = basics.NotParticipating + accts[0][testSinkAddr] = sinkdata + + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + defer ml.Close() + + conf := config.GetDefaultLocal() + au, ao := newAcctUpdates(t, ml, conf) + defer au.close() + defer ao.close() + + i := uint64(1) + for ; i < (intervalForTest)+1; i++ { + block := randomBlock(basics.Round(i)) + block.block.CurrentProtocol = protocol.ConsensusFuture + addBlockToAccountsUpdate(block.block, ao) + } + + top, err := ao.voters.getVoters(basics.Round(intervalForTest - lookbackForTest)) + a.NoError(err) + for j := 0; j < len(top.Participants); j++ { + a.Equal(merklesignature.NoKeysCommitment, top.Participants[j].PK.Commitment) + } +} diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index ea99545285..6aed7f0754 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1210,11 +1210,20 @@ func (c *Client) Dryrun(data []byte) (resp generatedV2.DryrunResponse, err error return } -// TxnProof returns a Merkle proof for a transaction in a block. -func (c *Client) TxnProof(txid string, round uint64, hashType crypto.HashType) (resp generatedV2.ProofResponse, err error) { +// TransactionProof returns a Merkle proof for a transaction in a block. +func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp generatedV2.ProofResponse, err error) { algod, err := c.ensureAlgodClient() if err == nil { - return algod.Proof(txid, round, hashType) + return algod.TransactionProof(txid, round, hashType) + } + return +} + +// LightBlockHeaderProof returns a Merkle proof for a block. +func (c *Client) LightBlockHeaderProof(round uint64) (resp generatedV2.LightBlockHeaderProofResponse, err error) { + algod, err := c.ensureAlgodClient() + if err == nil { + return algod.LightBlockHeaderProof(round) } return } diff --git a/libgoal/transactions.go b/libgoal/transactions.go index a03a9d5517..c28fd0216a 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -220,11 +220,11 @@ func generateRegistrationTransaction(part generated.ParticipationKey, fee basics return transactions.Transaction{}, fmt.Errorf("state proof key pointer is nil") } - if len(*part.Key.StateProofKey) != len(merklesignature.Verifier{}) { - return transactions.Transaction{}, fmt.Errorf("state proof key is the wrong size, should be %d but it is %d", len(merklesignature.Verifier{}), len(*part.Key.StateProofKey)) + if len(*part.Key.StateProofKey) != len(merklesignature.Commitment{}) { + return transactions.Transaction{}, fmt.Errorf("state proof key is the wrong size, should be %d but it is %d", len(merklesignature.Commitment{}), len(*part.Key.StateProofKey)) } - var stateProofPk merklesignature.Verifier + var stateProofPk merklesignature.Commitment copy(stateProofPk[:], (*part.Key.StateProofKey)[:]) t := transactions.Transaction{ diff --git a/logging/telemetryspec/metric.go b/logging/telemetryspec/metric.go index 209c14d8e6..5ce2a311df 100644 --- a/logging/telemetryspec/metric.go +++ b/logging/telemetryspec/metric.go @@ -17,6 +17,8 @@ package telemetryspec import ( + "bytes" + "fmt" "strconv" "strings" "time" @@ -57,6 +59,17 @@ type AssembleBlockStats struct { ProcessingTime transactionProcessingTimeDistibution BlockGenerationDuration uint64 TransactionsLoopStartTime int64 + StateProofNextRound uint64 // next round for which state proof if expected + StateProofStats StateProofStats +} + +// StateProofStats is the set of stats captured when a StateProof is present in the assembled block +type StateProofStats struct { + ProvenWeight uint64 + SignedWeight uint64 + NumReveals int + NumPosToReveal int + TxnSize int } // AssembleBlockTimeout represents AssemblePayset exiting due to timeout @@ -82,6 +95,37 @@ type AssembleBlockMetrics struct { func (m AssembleBlockMetrics) Identifier() Metric { return assembleBlockMetricsIdentifier } +func (m AssembleBlockStats) String() string { + b := &bytes.Buffer{} + b.WriteString(fmt.Sprintf("StartCount:%d, ", m.StartCount)) + b.WriteString(fmt.Sprintf("IncludedCount:%d, ", m.IncludedCount)) + b.WriteString(fmt.Sprintf("InvalidCount:%d, ", m.InvalidCount)) + b.WriteString(fmt.Sprintf("MinFee:%d, ", m.MinFee)) + b.WriteString(fmt.Sprintf("MaxFee:%d, ", m.MaxFee)) + b.WriteString(fmt.Sprintf("AverageFee:%d, ", m.AverageFee)) + b.WriteString(fmt.Sprintf("MinLength:%d, ", m.MinLength)) + b.WriteString(fmt.Sprintf("MaxLength:%d, ", m.MaxLength)) + b.WriteString(fmt.Sprintf("MinPriority:%d, ", m.MinPriority)) + b.WriteString(fmt.Sprintf("MaxPriority:%d, ", m.MaxPriority)) + b.WriteString(fmt.Sprintf("CommittedCount:%d, ", m.CommittedCount)) + b.WriteString(fmt.Sprintf("StopReason:%s, ", m.StopReason)) + b.WriteString(fmt.Sprintf("TotalLength:%d, ", m.TotalLength)) + b.WriteString(fmt.Sprintf("EarlyCommittedCount:%d, ", m.EarlyCommittedCount)) + b.WriteString(fmt.Sprintf("Nanoseconds:%d, ", m.Nanoseconds)) + b.WriteString(fmt.Sprintf("ProcessingTime:%v, ", m.ProcessingTime)) + b.WriteString(fmt.Sprintf("BlockGenerationDuration:%d, ", m.BlockGenerationDuration)) + b.WriteString(fmt.Sprintf("TransactionsLoopStartTime:%d, ", m.TransactionsLoopStartTime)) + b.WriteString(fmt.Sprintf("StateProofNextRound:%d, ", m.StateProofNextRound)) + emptySPStats := StateProofStats{} + if m.StateProofStats != emptySPStats { + b.WriteString(fmt.Sprintf("ProvenWeight:%d, ", m.StateProofStats.ProvenWeight)) + b.WriteString(fmt.Sprintf("SignedWeight:%d, ", m.StateProofStats.SignedWeight)) + b.WriteString(fmt.Sprintf("NumReveals:%d, ", m.StateProofStats.NumReveals)) + b.WriteString(fmt.Sprintf("NumPosToReveal:%d, ", m.StateProofStats.NumPosToReveal)) + b.WriteString(fmt.Sprintf("TxnSize:%d", m.StateProofStats.TxnSize)) + } + return b.String() +} //------------------------------------------------------- // ProcessBlock diff --git a/logging/telemetryspec/metric_test.go b/logging/telemetryspec/metric_test.go index 4b470c1c56..c6d6489da5 100644 --- a/logging/telemetryspec/metric_test.go +++ b/logging/telemetryspec/metric_test.go @@ -18,11 +18,13 @@ package telemetryspec import ( "encoding/json" + "reflect" "testing" "time" - "github.com/algorand/go-algorand/test/partitiontest" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/test/partitiontest" ) func TestTransactionProcessingTimeDistibutionFormatting(t *testing.T) { @@ -47,3 +49,40 @@ func TestTransactionProcessingTimeDistibutionFormatting(t *testing.T) { require.NoError(t, err) require.Equal(t, []byte("{\"ProcessingTime\":[2,3,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"), bytes) } + +func TestAssembleBlockStatsString(t *testing.T) { + partitiontest.PartitionTest(t) + + var abs AssembleBlockStats + localType := reflect.TypeOf(abs) + + // Empty StateProofStats will not be reported. Set a filed to check it printed + abs.StateProofStats.ProvenWeight = 1 + absString := abs.String() + for f := 0; f < localType.NumField(); f++ { + field := localType.Field(f) + if field.Type.Kind() == reflect.Struct && field.Type.NumField() > 1 { + for nf := 0; nf < field.Type.NumField(); nf++ { + nestedField := field.Type.Field(nf) + require.Contains(t, absString, nestedField.Name) + } + continue + } + require.Contains(t, absString, field.Name) + } + + // Make sure the StateProofStats is not reported if they are empty + abs.StateProofStats.ProvenWeight = 0 + absString = abs.String() + for f := 0; f < localType.NumField(); f++ { + field := localType.Field(f) + if field.Name == "StateProofStats" { + for nf := 0; nf < field.Type.NumField(); nf++ { + nestedField := field.Type.Field(nf) + require.NotContains(t, absString, nestedField.Name) + } + continue + } + require.Contains(t, absString, field.Name) + } +} diff --git a/network/wsNetwork.go b/network/wsNetwork.go index eefd3b0325..a7c874cdae 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -127,10 +127,10 @@ var incomingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_in var outgoingPeers = metrics.MakeGauge(metrics.MetricName{Name: "algod_network_outgoing_peers", Description: "Number of active outgoing peers."}) // peerDisconnectionAckDuration defines the time we would wait for the peer disconnection to compelete. -const peerDisconnectionAckDuration time.Duration = 5 * time.Second +const peerDisconnectionAckDuration = 5 * time.Second // peerShutdownDisconnectionAckDuration defines the time we would wait for the peer disconnection to compelete during shutdown. -const peerShutdownDisconnectionAckDuration time.Duration = 50 * time.Millisecond +const peerShutdownDisconnectionAckDuration = 50 * time.Millisecond // Peer opaque interface for referring to a neighbor in the network type Peer interface{} @@ -767,7 +767,7 @@ func (wn *WebsocketNetwork) setup() { wn.messagesOfInterestRefresh = make(chan struct{}, 2) wn.messagesOfInterestGeneration = 1 // something nonzero so that any new wsPeer needs updating if wn.relayMessages { - wn.RegisterMessageInterest(protocol.CompactCertSigTag) + wn.RegisterMessageInterest(protocol.StateProofSigTag) } } diff --git a/node/node.go b/node/node.go index 5d9c50beed..bea64849f1 100644 --- a/node/node.go +++ b/node/node.go @@ -31,7 +31,6 @@ import ( "github.com/algorand/go-algorand/agreement" "github.com/algorand/go-algorand/agreement/gossip" "github.com/algorand/go-algorand/catchup" - "github.com/algorand/go-algorand/compactcert" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data" @@ -50,6 +49,7 @@ import ( "github.com/algorand/go-algorand/node/indexer" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/rpcs" + "github.com/algorand/go-algorand/stateproof" "github.com/algorand/go-algorand/util/db" "github.com/algorand/go-algorand/util/execpool" "github.com/algorand/go-algorand/util/metrics" @@ -144,7 +144,7 @@ type AlgorandFullNode struct { tracer messagetracer.MessageTracer - compactCert *compactcert.Worker + stateProofWorker *stateproof.Worker } // TxnWithStatus represents information about a single transaction, @@ -308,13 +308,17 @@ func MakeFull(log logging.Logger, rootDir string, cfg config.Local, phonebookAdd node.tracer = messagetracer.NewTracer(log).Init(cfg) gossip.SetTrace(agreementParameters.Network, node.tracer) - compactCertPathname := filepath.Join(genesisDir, config.CompactCertFilename) - compactCertAccess, err := db.MakeAccessor(compactCertPathname, false, false) + // Delete the deprecated database file if it exists. This can be removed in future updates since this file should not exist by then. + oldCompactCertPath := filepath.Join(genesisDir, "compactcert.sqlite") + os.Remove(oldCompactCertPath) + + stateProofPathname := filepath.Join(genesisDir, config.StateProofFileName) + stateProofAccess, err := db.MakeAccessor(stateProofPathname, false, false) if err != nil { - log.Errorf("Cannot load compact cert data: %v", err) + log.Errorf("Cannot load state proof data: %v", err) return nil, err } - node.compactCert = compactcert.NewWorker(compactCertAccess, node.log, node.accountManager, node.ledger.Ledger, node.net, node) + node.stateProofWorker = stateproof.NewWorker(stateProofAccess, node.log, node.accountManager, node.ledger.Ledger, node.net, node) return node, err } @@ -353,7 +357,7 @@ func (node *AlgorandFullNode) Start() { node.blockService.Start() node.ledgerService.Start() node.txHandler.Start() - node.compactCert.Start() + node.stateProofWorker.Start() startNetwork() // start indexer if idx, err := node.Indexer(); err == nil { @@ -404,10 +408,8 @@ func (node *AlgorandFullNode) Stop() { defer func() { node.mu.Unlock() node.waitMonitoringRoutines() - // we want to shut down the compactCert last, since the oldKeyDeletionThread might depend on it when making the - // call to LatestSigsFromThisNode. - node.compactCert.Shutdown() - node.compactCert = nil + node.stateProofWorker.Shutdown() + node.stateProofWorker = nil }() node.net.ClearHandlers() @@ -1033,8 +1035,6 @@ func (node *AlgorandFullNode) oldKeyDeletionThread(done <-chan struct{}) { r := node.ledger.Latest() - // We need the latest header to determine the next compact cert - // round, if any. latestHdr, err := node.ledger.BlockHdr(r) if err != nil { switch err.(type) { diff --git a/protocol/hash.go b/protocol/hash.go index 272c538e16..2d7d48acdf 100644 --- a/protocol/hash.go +++ b/protocol/hash.go @@ -38,11 +38,8 @@ const ( AuctionParams HashID = "aP" AuctionSettlement HashID = "aS" - CompactCertCoin HashID = "ccc" - CompactCertPart HashID = "ccp" - CompactCertSig HashID = "ccs" - AgreementSelector HashID = "AS" + BlockHeader256 HashID = "B256" BlockHeader HashID = "BH" BalanceRecord HashID = "BR" Credential HashID = "CR" @@ -63,9 +60,15 @@ const ( Seed HashID = "SD" SpecialAddr HashID = "SpecialAddr" SignedTxnInBlock HashID = "STIB" - TestHashable HashID = "TE" - TxGroup HashID = "TG" - TxnMerkleLeaf HashID = "TL" - Transaction HashID = "TX" - Vote HashID = "VO" + + StateProofCoin HashID = "spc" + StateProofMessage HashID = "spm" + StateProofPart HashID = "spp" + StateProofSig HashID = "sps" + + TestHashable HashID = "TE" + TxGroup HashID = "TG" + TxnMerkleLeaf HashID = "TL" + Transaction HashID = "TX" + Vote HashID = "VO" ) diff --git a/protocol/msgp_gen.go b/protocol/msgp_gen.go index 59df20aa58..9d9a660bbd 100644 --- a/protocol/msgp_gen.go +++ b/protocol/msgp_gen.go @@ -7,14 +7,6 @@ import ( ) // The following msgp objects are implemented in this file: -// CompactCertType -// |-----> MarshalMsg -// |-----> CanMarshalMsg -// |-----> (*) UnmarshalMsg -// |-----> (*) CanUnmarshalMsg -// |-----> Msgsize -// |-----> MsgIsZero -// // ConsensusVersion // |-----> MarshalMsg // |-----> CanMarshalMsg @@ -47,6 +39,14 @@ import ( // |-----> Msgsize // |-----> MsgIsZero // +// StateProofType +// |-----> MarshalMsg +// |-----> CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) CanUnmarshalMsg +// |-----> Msgsize +// |-----> MsgIsZero +// // Tag // |-----> MarshalMsg // |-----> CanMarshalMsg @@ -64,52 +64,6 @@ import ( // |-----> MsgIsZero // -// MarshalMsg implements msgp.Marshaler -func (z CompactCertType) MarshalMsg(b []byte) (o []byte) { - o = msgp.Require(b, z.Msgsize()) - o = msgp.AppendUint64(o, uint64(z)) - return -} - -func (_ CompactCertType) CanMarshalMsg(z interface{}) bool { - _, ok := (z).(CompactCertType) - if !ok { - _, ok = (z).(*CompactCertType) - } - return ok -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *CompactCertType) UnmarshalMsg(bts []byte) (o []byte, err error) { - { - var zb0001 uint64 - zb0001, bts, err = msgp.ReadUint64Bytes(bts) - if err != nil { - err = msgp.WrapError(err) - return - } - (*z) = CompactCertType(zb0001) - } - o = bts - return -} - -func (_ *CompactCertType) CanUnmarshalMsg(z interface{}) bool { - _, ok := (z).(*CompactCertType) - return ok -} - -// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message -func (z CompactCertType) Msgsize() (s int) { - s = msgp.Uint64Size - return -} - -// MsgIsZero returns whether this is a zero value -func (z CompactCertType) MsgIsZero() bool { - return z == 0 -} - // MarshalMsg implements msgp.Marshaler func (z ConsensusVersion) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -294,6 +248,52 @@ func (z NetworkID) MsgIsZero() bool { return z == "" } +// MarshalMsg implements msgp.Marshaler +func (z StateProofType) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + o = msgp.AppendUint64(o, uint64(z)) + return +} + +func (_ StateProofType) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(StateProofType) + if !ok { + _, ok = (z).(*StateProofType) + } + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *StateProofType) UnmarshalMsg(bts []byte) (o []byte, err error) { + { + var zb0001 uint64 + zb0001, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + (*z) = StateProofType(zb0001) + } + o = bts + return +} + +func (_ *StateProofType) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*StateProofType) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z StateProofType) Msgsize() (s int) { + s = msgp.Uint64Size + return +} + +// MsgIsZero returns whether this is a zero value +func (z StateProofType) MsgIsZero() bool { + return z == 0 +} + // MarshalMsg implements msgp.Marshaler func (z Tag) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) diff --git a/protocol/compactcerts.go b/protocol/stateproof.go similarity index 52% rename from protocol/compactcerts.go rename to protocol/stateproof.go index 06e1e3b187..6031b97d41 100644 --- a/protocol/compactcerts.go +++ b/protocol/stateproof.go @@ -16,30 +16,29 @@ package protocol -// A single Algorand chain can support multiple types of compact certs, +// A single Algorand chain can support multiple types of stateproofs, // reflecting different hash functions, signature schemes, and frequency // parameters. -// CompactCertType identifies a particular configuration of compact certs. -type CompactCertType uint64 +// StateProofType identifies a particular configuration of state proofs. +type StateProofType uint64 const ( - // CompactCertBasic is our initial compact cert setup, using Ed25519 - // ephemeral-key signatures and SHA512/256 hashes. - CompactCertBasic CompactCertType = 0 + // StateProofBasic is our initial state proof setup. using falcon keys and subset-sum hash + StateProofBasic StateProofType = 0 - // NumCompactCertTypes is the max number of types of compact certs + // NumStateProofTypes is the max number of types of state proofs // that we support. This is used as an allocation bound for a map - // containing different compact cert types in msgpack encoding. - NumCompactCertTypes int = 1 + // containing different stateproof types in msgpack encoding. + NumStateProofTypes int = 1 ) -// SortCompactCertType implements sorting by CompactCertType keys for +// SortStateProofType implements sorting by StateProofType keys for // canonical encoding of maps in msgpack format. -//msgp:ignore SortCompactCertType -//msgp:sort CompactCertType SortCompactCertType -type SortCompactCertType []CompactCertType +//msgp:ignore SortStateProofType +//msgp:sort StateProofType SortStateProofType +type SortStateProofType []StateProofType -func (a SortCompactCertType) Len() int { return len(a) } -func (a SortCompactCertType) Less(i, j int) bool { return a[i] < a[j] } -func (a SortCompactCertType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortStateProofType) Len() int { return len(a) } +func (a SortStateProofType) Less(i, j int) bool { return a[i] < a[j] } +func (a SortStateProofType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/protocol/tags.go b/protocol/tags.go index 8ae6cfe564..f3bff635b5 100644 --- a/protocol/tags.go +++ b/protocol/tags.go @@ -27,13 +27,13 @@ type Tag string const ( UnknownMsgTag Tag = "??" AgreementVoteTag Tag = "AV" - CompactCertSigTag Tag = "CS" MsgOfInterestTag Tag = "MI" MsgDigestSkipTag Tag = "MS" NetPrioResponseTag Tag = "NP" PingTag Tag = "pi" PingReplyTag Tag = "pj" ProposalPayloadTag Tag = "PP" + StateProofSigTag Tag = "SP" TopicMsgRespTag Tag = "TS" TxnTag Tag = "TX" UniCatchupReqTag Tag = "UC" //Replaced by UniEnsBlockReqTag. Only for backward compatibility. diff --git a/protocol/txntype.go b/protocol/txntype.go index 1b50dab8ec..4342558962 100644 --- a/protocol/txntype.go +++ b/protocol/txntype.go @@ -41,8 +41,8 @@ const ( // ApplicationCallTx allows creating, deleting, and interacting with an application ApplicationCallTx TxType = "appl" - // CompactCertTx records a compact certificate - CompactCertTx TxType = "cert" + // StateProofTx records a state proof + StateProofTx TxType = "stpf" // UnknownTx signals an error UnknownTx TxType = "unknown" diff --git a/compactcert/abstractions.go b/stateproof/abstractions.go similarity index 78% rename from compactcert/abstractions.go rename to stateproof/abstractions.go index 9918b0f2e6..825b5090ea 100644 --- a/compactcert/abstractions.go +++ b/stateproof/abstractions.go @@ -14,11 +14,10 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "context" - "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" @@ -41,18 +40,24 @@ type Ledger interface { Wait(basics.Round) chan struct{} GenesisHash() crypto.Digest BlockHdr(basics.Round) (bookkeeping.BlockHeader, error) - CompactCertVoters(basics.Round) (*ledgercore.VotersForRound, error) + VotersForStateProof(basics.Round) (*ledgercore.VotersForRound, error) } // Network captures the aspects of the gossip network protocol that are // used by this package. type Network interface { - Broadcast(context.Context, protocol.Tag, []byte, bool, network.Peer) error + Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except network.Peer) error RegisterHandlers([]network.TaggedMessageHandler) } // Accounts captures the aspects of the AccountManager that are used by // this package. type Accounts interface { - StateProofKeys(basics.Round) []account.StateProofRecordForRound + StateProofKeys(basics.Round) []account.StateProofSecretsForRound + DeleteStateProofKey(id account.ParticipationID, round basics.Round) error +} + +// BlockHeaderFetcher captures the aspects of the Ledger that is used to fetch block headers +type BlockHeaderFetcher interface { + BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) } diff --git a/stateproof/builder.go b/stateproof/builder.go new file mode 100644 index 0000000000..fd800ebaf7 --- /dev/null +++ b/stateproof/builder.go @@ -0,0 +1,463 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "context" + "database/sql" + "encoding/binary" + "fmt" + "sort" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/stateproof/verify" +) + +// makeBuilderForRound not threadsafe, should be called in a lock environment +func (spw *Worker) makeBuilderForRound(rnd basics.Round) (builder, error) { + l := spw.ledger + hdr, err := l.BlockHdr(rnd) + if err != nil { + return builder{}, err + } + + hdrProto := config.Consensus[hdr.CurrentProtocol] + votersRnd := rnd.SubSaturate(basics.Round(hdrProto.StateProofInterval)) + votersHdr, err := l.BlockHdr(votersRnd) + if err != nil { + return builder{}, err + } + + lookback := votersRnd.SubSaturate(basics.Round(hdrProto.StateProofVotersLookback)) + voters, err := l.VotersForStateProof(lookback) + if err != nil { + return builder{}, err + } + + if voters == nil { + // Voters not tracked for that round. Might not be a valid + // state proof round; state proofs might not be enabled; etc. + return builder{}, fmt.Errorf("voters not tracked for lookback round %d", lookback) + } + + msg, err := GenerateStateProofMessage(l, uint64(votersHdr.Round), hdr) + if err != nil { + return builder{}, err + } + + provenWeight, err := verify.GetProvenWeight(&votersHdr, &hdr) + if err != nil { + return builder{}, err + } + + var res builder + res.votersHdr = votersHdr + res.voters = voters + res.message = msg + res.Builder, err = stateproof.MakeBuilder(msg.Hash(), + uint64(hdr.Round), + provenWeight, + voters.Participants, + voters.Tree, + config.Consensus[votersHdr.CurrentProtocol].StateProofStrengthTarget) + if err != nil { + return builder{}, err + } + + return res, nil +} + +func (spw *Worker) initBuilders() { + spw.mu.Lock() + defer spw.mu.Unlock() + + var roundSigs map[basics.Round][]pendingSig + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx) + return + }) + if err != nil { + spw.log.Warnf("initBuilders: getPendingSigs: %w", err) + return + } + + for rnd, sigs := range roundSigs { + if _, ok := spw.builders[rnd]; ok { + spw.log.Warnf("initBuilders: round %d already present", rnd) + continue + } + spw.addSigsToBuilder(sigs, rnd) + } +} + +func (spw *Worker) addSigsToBuilder(sigs []pendingSig, rnd basics.Round) { + builderForRound, err := spw.makeBuilderForRound(rnd) + if err != nil { + spw.log.Warnf("addSigsToBuilder: makeBuilderForRound(%d): %v", rnd, err) + return + } + spw.builders[rnd] = builderForRound + + for _, sig := range sigs { + pos, ok := builderForRound.voters.AddrToPos[sig.signer] + if !ok { + spw.log.Warnf("addSigsToBuilder: cannot find %v in round %d", sig.signer, rnd) + continue + } + + isPresent, err := builderForRound.Present(pos) + if err != nil { + spw.log.Warnf("addSigsToBuilder: failed to invoke builderForRound.Present on pos %d - %w ", pos, err) + continue + } + if isPresent { + spw.log.Warnf("addSigsToBuilder: cannot add %v in round %d: position %d already added", sig.signer, rnd, pos) + continue + } + + if err := builderForRound.IsValid(pos, &sig.sig, false); err != nil { + spw.log.Warnf("addSigsToBuilder: cannot add %v in round %d: %v", sig.signer, rnd, err) + continue + } + if err := builderForRound.Add(pos, sig.sig); err != nil { + spw.log.Warnf("addSigsToBuilder: error while adding sig. inner error: %w", err) + continue + } + } +} + +func (spw *Worker) handleSigMessage(msg network.IncomingMessage) network.OutgoingMessage { + var ssig sigFromAddr + err := protocol.Decode(msg.Data, &ssig) + if err != nil { + spw.log.Warnf("spw.handleSigMessage(): decode: %v", err) + return network.OutgoingMessage{Action: network.Disconnect} + } + + fwd, err := spw.handleSig(ssig, msg.Sender) + if err != nil { + spw.log.Warnf("spw.handleSigMessage(): %v", err) + } + + return network.OutgoingMessage{Action: fwd} +} + +// handleSig adds a signature to the pending in-memory state proof provers (builders). This function is +// also responsible for making sure that the signature is valid, and not duplicated. +// if a signature passes all verification it is written into the database. +func (spw *Worker) handleSig(sfa sigFromAddr, sender network.Peer) (network.ForwardingPolicy, error) { + spw.mu.Lock() + defer spw.mu.Unlock() + + builderForRound, ok := spw.builders[sfa.Round] + if !ok { + latest := spw.ledger.Latest() + latestHdr, err := spw.ledger.BlockHdr(latest) + if err != nil { + return network.Ignore, err + } + + if sfa.Round < latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound { + // Already have a complete state proof in ledger. + // Ignore this sig. + return network.Ignore, nil + } + + // The sig should be for a round which is a multiple of StateProofInterval + // using the latestHdr protocol, since changing StateProofInterval is not supported + proto := config.Consensus[latestHdr.CurrentProtocol] + + // proto.StateProofInterval is not expected to be 0 after passing StateProofNextRound + // checking anyway, otherwise will panic + if proto.StateProofInterval == 0 { + return network.Disconnect, fmt.Errorf("handleSig: StateProofInterval is 0 for round %d", latest) + } + + if uint64(sfa.Round)%proto.StateProofInterval != 0 { + // reject the sig for the round which is not a multiple of the interval + // Disconnect: should not be sending a sig for this round + return network.Disconnect, fmt.Errorf("handleSig: round %d is not a multiple of SP interval %d", + sfa.Round, proto.StateProofInterval) + } + + builderForRound, err = spw.makeBuilderForRound(sfa.Round) + if err != nil { + // Should not disconnect this peer, since this is a fault of the relay + // The peer could have other signatures what the relay is interested in + return network.Ignore, err + } + spw.builders[sfa.Round] = builderForRound + spw.log.Infof("spw.handleSig: starts gathering signatures for round %d", sfa.Round) + } + + pos, ok := builderForRound.voters.AddrToPos[sfa.SignerAddress] + if !ok { + return network.Disconnect, fmt.Errorf("handleSig: %v not in participants for %d", sfa.SignerAddress, sfa.Round) + } + + if isPresent, err := builderForRound.Present(pos); err != nil || isPresent { + // Signature already part of the builderForRound, ignore. + return network.Ignore, nil + } + + if err := builderForRound.IsValid(pos, &sfa.Sig, true); err != nil { + return network.Disconnect, err + } + + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return addPendingSig(tx, sfa.Round, pendingSig{ + signer: sfa.SignerAddress, + sig: sfa.Sig, + fromThisNode: sender == nil, + }) + }) + if err != nil { + return network.Ignore, err + } + // validated that we can add the sig previously. + if err := builderForRound.Add(pos, sfa.Sig); err != nil { + // only Present called from Add returns an error which is already + // passed in the call above. + return network.Ignore, err + } + return network.Broadcast, nil +} + +func (spw *Worker) builder(latest basics.Round) { + // We clock the building of state proofs based on new + // blocks. This is because the acceptable state proof + // size grows over time, so that we aim to construct an extremely + // small state proof upfront, but if that doesn't work out, we + // will settle for a larger proof. New blocks also tell us + // if a state proof has been committed, so that we can stop trying + // to build it. + for { + spw.tryBroadcast() + + nextrnd := latest + 1 + select { + case <-spw.ctx.Done(): + spw.wg.Done() + return + + case <-spw.ledger.Wait(nextrnd): + // Continue on + } + + // See if any new state proofs were formed, according to + // the new block, which would mean we can clean up some builders. + hdr, err := spw.ledger.BlockHdr(nextrnd) + if err != nil { + spw.log.Warnf("spw.builder: BlockHdr(%d): %v", nextrnd, err) + continue + } + + spw.deleteOldSigs(&hdr) + spw.deleteOldBuilders(&hdr) + + // Broadcast signatures based on the previous block(s) that + // were agreed upon. This ensures that, if we send a signature + // for block R, nodes will have already verified block R, because + // block R+1 has been formed. + proto := config.Consensus[hdr.CurrentProtocol] + newLatest := spw.ledger.Latest() + for r := latest; r < newLatest; r++ { + // Wait for the signer to catch up; mostly relevant in tests. + spw.waitForSignature(r) + + spw.broadcastSigs(r, proto) + } + latest = newLatest + } +} + +// broadcastSigs periodically broadcasts pending signatures for rounds +// that have not been able to form a state proof. +// +// Signature re-broadcasting happens in periods of proto.StateProofInterval +// rounds. +// +// In the first half of each such period, signers of a block broadcast their +// own signatures; this is the expected common path. +// +// In the second half of each such period, any signatures seen by this node +// are broadcast. +// +// The broadcast schedule is randomized by the address of the block signer, +// for load-balancing over time. +func (spw *Worker) broadcastSigs(brnd basics.Round, proto config.ConsensusParams) { + if proto.StateProofInterval == 0 { + return + } + + spw.mu.Lock() + defer spw.mu.Unlock() + + var roundSigs map[basics.Round][]pendingSig + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + if brnd%basics.Round(proto.StateProofInterval) < basics.Round(proto.StateProofInterval/2) { + roundSigs, err = getPendingSigsFromThisNode(tx) + } else { + roundSigs, err = getPendingSigs(tx) + } + return + }) + if err != nil { + spw.log.Warnf("broadcastSigs: getPendingSigs: %v", err) + return + } + + for rnd, sigs := range roundSigs { + if rnd > brnd { + // Signature is for later block than brnd. This could happen + // during catchup or testing. The caller's loop will eventually + // invoke this function with a suitably high brnd. + continue + } + + for _, sig := range sigs { + // Randomize which sigs get broadcast over time. + addr64 := binary.LittleEndian.Uint64(sig.signer[:]) + if addr64%(proto.StateProofInterval/2) != uint64(brnd)%(proto.StateProofInterval/2) { + continue + } + + sfa := sigFromAddr{ + SignerAddress: sig.signer, + Round: rnd, + Sig: sig.sig, + } + err = spw.net.Broadcast(context.Background(), protocol.StateProofSigTag, + protocol.Encode(&sfa), false, nil) + if err != nil { + spw.log.Warnf("broadcastSigs: Broadcast for %d: %v", rnd, err) + } + } + } +} + +func (spw *Worker) deleteOldSigs(currentHdr *bookkeeping.BlockHeader) { + oldestRoundToRemove := GetOldestExpectedStateProof(currentHdr) + + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return deletePendingSigsBeforeRound(tx, oldestRoundToRemove) + }) + if err != nil { + spw.log.Warnf("deletePendingSigsBeforeRound(%d): %v", oldestRoundToRemove, err) + } +} + +func (spw *Worker) deleteOldBuilders(currentHdr *bookkeeping.BlockHeader) { + oldestRoundToRemove := GetOldestExpectedStateProof(currentHdr) + + spw.mu.Lock() + defer spw.mu.Unlock() + + for rnd := range spw.builders { + if rnd < oldestRoundToRemove { + delete(spw.builders, rnd) + } + } +} + +func (spw *Worker) tryBroadcast() { + spw.mu.Lock() + defer spw.mu.Unlock() + + sortedRounds := make([]basics.Round, 0, len(spw.builders)) + for rnd := range spw.builders { + sortedRounds = append(sortedRounds, rnd) + } + sort.Slice(sortedRounds, func(i, j int) bool { return sortedRounds[i] < sortedRounds[j] }) + + for _, rnd := range sortedRounds { // Iterate over the builders in a sequential manner + b := spw.builders[rnd] + firstValid := spw.ledger.Latest() + acceptableWeight := verify.AcceptableStateProofWeight(&b.votersHdr, firstValid, logging.Base()) + if b.SignedWeight() < acceptableWeight { + // Haven't signed enough to build the state proof at this time.. + continue + } + + if !b.Ready() { + // Haven't gotten enough signatures to get past ProvenWeight + continue + } + + sp, err := b.Build() + if err != nil { + spw.log.Warnf("spw.tryBroadcast: building state proof for %d failed: %w", rnd, err) + continue + } + + spw.log.Infof("spw.tryBroadcast: building state proof transaction for round %d", rnd) + var stxn transactions.SignedTxn + stxn.Txn.Type = protocol.StateProofTx + stxn.Txn.Sender = transactions.StateProofSender + stxn.Txn.FirstValid = firstValid + stxn.Txn.LastValid = firstValid + basics.Round(b.voters.Proto.MaxTxnLife) + stxn.Txn.GenesisHash = spw.ledger.GenesisHash() + stxn.Txn.StateProofTxnFields.StateProofType = protocol.StateProofBasic + stxn.Txn.StateProofTxnFields.StateProof = *sp + stxn.Txn.StateProofTxnFields.Message = b.message + err = spw.txnSender.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{stxn}) + if err != nil { + spw.log.Warnf("spw.tryBroadcast: broadcasting state proof txn for %d: %v", rnd, err) + // if this StateProofTxn was rejected, the next one would be rejected as well since state proof should be added in + // a sequential order + break + } + } +} + +func (spw *Worker) invokeBuilder(r basics.Round) { + spw.mu.Lock() + spw.signed = r + spw.mu.Unlock() + + select { + case spw.signedCh <- struct{}{}: + default: + } +} + +func (spw *Worker) lastSignedBlock() basics.Round { + spw.mu.Lock() + defer spw.mu.Unlock() + return spw.signed +} + +func (spw *Worker) waitForSignature(r basics.Round) { + for { + if r <= spw.lastSignedBlock() { + return + } + + select { + case <-spw.ctx.Done(): + return + case <-spw.signedCh: + } + } +} diff --git a/compactcert/db.go b/stateproof/db.go similarity index 78% rename from compactcert/db.go rename to stateproof/db.go index 4d71ea5702..fa10c8c5ee 100644 --- a/compactcert/db.go +++ b/stateproof/db.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "database/sql" @@ -26,21 +26,21 @@ import ( ) var schema = []string{ - // sigs tracks signatures used to build a compact certificate, for - // rounds that have not formed a compact certificate yet. + // sigs tracks signatures used to build a state proofs, for + // rounds that have not formed a state proofs yet. // - // There can be at most one signature for a given (certrnd, signer): + // There can be at most one signature for a given (sprnd, signer): // that is, a signer (account address) can produce at most one signature - // for a given certrnd (the round of the block being signed). + // for a given sprnd (the round of the block being signed). // // Signatures produced by this node are special because we broadcast // them early; other signatures are retransmitted later on. `CREATE TABLE IF NOT EXISTS sigs ( - certrnd integer, + sprnd integer, signer blob, sig blob, from_this_node integer, - UNIQUE (certrnd, signer))`, + UNIQUE (sprnd, signer))`, `CREATE INDEX IF NOT EXISTS sigs_from_this_node ON sigs (from_this_node)`, } @@ -55,7 +55,7 @@ func initDB(tx *sql.Tx) error { for i, tableCreate := range schema { _, err := tx.Exec(tableCreate) if err != nil { - return fmt.Errorf("could not create compactcert table %d: %v", i, err) + return fmt.Errorf("could not state proof table %d: %v", i, err) } } @@ -63,7 +63,7 @@ func initDB(tx *sql.Tx) error { } func addPendingSig(tx *sql.Tx, rnd basics.Round, psig pendingSig) error { - _, err := tx.Exec("INSERT INTO sigs (certrnd, signer, sig, from_this_node) VALUES (?, ?, ?, ?)", + _, err := tx.Exec("INSERT INTO sigs (sprnd, signer, sig, from_this_node) VALUES (?, ?, ?, ?)", rnd, psig.signer[:], protocol.Encode(&psig.sig), @@ -72,12 +72,12 @@ func addPendingSig(tx *sql.Tx, rnd basics.Round, psig pendingSig) error { } func deletePendingSigsBeforeRound(tx *sql.Tx, rnd basics.Round) error { - _, err := tx.Exec("DELETE FROM sigs WHERE certrnd. -package compactcert +package stateproof import ( "context" diff --git a/compactcert/msgp_gen.go b/stateproof/msgp_gen.go similarity index 82% rename from compactcert/msgp_gen.go rename to stateproof/msgp_gen.go index 9099e8bb65..75abd4aa73 100644 --- a/compactcert/msgp_gen.go +++ b/stateproof/msgp_gen.go @@ -1,4 +1,4 @@ -package compactcert +package stateproof // Code generated by github.com/algorand/msgp DO NOT EDIT. @@ -22,15 +22,15 @@ func (z *sigFromAddr) MarshalMsg(b []byte) (o []byte) { // omitempty: check for empty values zb0001Len := uint32(3) var zb0001Mask uint8 /* 4 bits */ - if (*z).Round.MsgIsZero() { + if (*z).SignerAddress.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x2 } - if (*z).Sig.MsgIsZero() { + if (*z).Round.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x4 } - if (*z).Signer.MsgIsZero() { + if (*z).Sig.MsgIsZero() { zb0001Len-- zb0001Mask |= 0x8 } @@ -38,19 +38,19 @@ func (z *sigFromAddr) MarshalMsg(b []byte) (o []byte) { o = append(o, 0x80|uint8(zb0001Len)) if zb0001Len != 0 { if (zb0001Mask & 0x2) == 0 { // if not empty - // string "rnd" - o = append(o, 0xa3, 0x72, 0x6e, 0x64) - o = (*z).Round.MarshalMsg(o) + // string "a" + o = append(o, 0xa1, 0x61) + o = (*z).SignerAddress.MarshalMsg(o) } if (zb0001Mask & 0x4) == 0 { // if not empty - // string "sig" - o = append(o, 0xa3, 0x73, 0x69, 0x67) - o = (*z).Sig.MarshalMsg(o) + // string "r" + o = append(o, 0xa1, 0x72) + o = (*z).Round.MarshalMsg(o) } if (zb0001Mask & 0x8) == 0 { // if not empty - // string "signer" - o = append(o, 0xa6, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72) - o = (*z).Signer.MarshalMsg(o) + // string "s" + o = append(o, 0xa1, 0x73) + o = (*z).Sig.MarshalMsg(o) } } return @@ -76,9 +76,9 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) { } if zb0001 > 0 { zb0001-- - bts, err = (*z).Signer.UnmarshalMsg(bts) + bts, err = (*z).SignerAddress.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "struct-from-array", "Signer") + err = msgp.WrapError(err, "struct-from-array", "SignerAddress") return } } @@ -121,19 +121,19 @@ func (z *sigFromAddr) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch string(field) { - case "signer": - bts, err = (*z).Signer.UnmarshalMsg(bts) + case "a": + bts, err = (*z).SignerAddress.UnmarshalMsg(bts) if err != nil { - err = msgp.WrapError(err, "Signer") + err = msgp.WrapError(err, "SignerAddress") return } - case "rnd": + case "r": bts, err = (*z).Round.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "Round") return } - case "sig": + case "s": bts, err = (*z).Sig.UnmarshalMsg(bts) if err != nil { err = msgp.WrapError(err, "Sig") @@ -159,11 +159,11 @@ func (_ *sigFromAddr) CanUnmarshalMsg(z interface{}) bool { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *sigFromAddr) Msgsize() (s int) { - s = 1 + 7 + (*z).Signer.Msgsize() + 4 + (*z).Round.Msgsize() + 4 + (*z).Sig.Msgsize() + s = 1 + 2 + (*z).SignerAddress.Msgsize() + 2 + (*z).Round.Msgsize() + 2 + (*z).Sig.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *sigFromAddr) MsgIsZero() bool { - return ((*z).Signer.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).Sig.MsgIsZero()) + return ((*z).SignerAddress.MsgIsZero()) && ((*z).Round.MsgIsZero()) && ((*z).Sig.MsgIsZero()) } diff --git a/compactcert/msgp_gen_test.go b/stateproof/msgp_gen_test.go similarity index 98% rename from compactcert/msgp_gen_test.go rename to stateproof/msgp_gen_test.go index 0f206656e1..ed1bd8206c 100644 --- a/compactcert/msgp_gen_test.go +++ b/stateproof/msgp_gen_test.go @@ -1,7 +1,7 @@ //go:build !skip_msgp_testing // +build !skip_msgp_testing -package compactcert +package stateproof // Code generated by github.com/algorand/msgp DO NOT EDIT. diff --git a/stateproof/recovery.go b/stateproof/recovery.go new file mode 100644 index 0000000000..5902a603eb --- /dev/null +++ b/stateproof/recovery.go @@ -0,0 +1,42 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/protocol" +) + +// GetOldestExpectedStateProof returns the lowest round for which the node should create a state proof. +func GetOldestExpectedStateProof(latestHeader *bookkeeping.BlockHeader) basics.Round { + proto := config.Consensus[latestHeader.CurrentProtocol] + if proto.StateProofInterval == 0 { + return 0 + } + + recentRoundOnRecoveryPeriod := basics.Round(uint64(latestHeader.Round) - uint64(latestHeader.Round)%proto.StateProofInterval) + oldestRoundOnRecoveryPeriod := recentRoundOnRecoveryPeriod.SubSaturate(basics.Round(proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals))) + + nextStateproofRound := latestHeader.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + + if nextStateproofRound > oldestRoundOnRecoveryPeriod { + return nextStateproofRound + } + return oldestRoundOnRecoveryPeriod +} diff --git a/stateproof/signer.go b/stateproof/signer.go new file mode 100644 index 0000000000..697a56e9ee --- /dev/null +++ b/stateproof/signer.go @@ -0,0 +1,177 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "time" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/protocol" +) + +// sigFromAddr encapsulates a signature on a block header, which +// will eventually be used to form a state proof for that +// block. +type sigFromAddr struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + SignerAddress basics.Address `codec:"a"` + Round basics.Round `codec:"r"` + Sig merklesignature.Signature `codec:"s"` +} + +func (spw *Worker) signer(latest basics.Round) { + nextRnd := spw.nextStateProofRound(latest) + for { // Start signing StateProofs from nextRnd onwards + select { + case <-spw.ledger.Wait(nextRnd): + hdr, err := spw.ledger.BlockHdr(nextRnd) + if err != nil { + spw.log.Warnf("spw.signer(): BlockHdr(next %d): %v", nextRnd, err) + time.Sleep(1 * time.Second) + nextRnd = spw.nextStateProofRound(spw.ledger.Latest()) + continue + } + spw.signStateProof(hdr) + spw.invokeBuilder(nextRnd) + nextRnd++ + + case <-spw.ctx.Done(): + spw.wg.Done() + return + } + } +} + +func (spw *Worker) nextStateProofRound(latest basics.Round) basics.Round { + var nextrnd basics.Round + + for { + latestHdr, err := spw.ledger.BlockHdr(latest) + if err != nil { + spw.log.Warnf("spw.signer(): BlockHdr(latest %d): %v", latest, err) + time.Sleep(1 * time.Second) + latest = spw.ledger.Latest() + continue + } + + nextrnd = latestHdr.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + if nextrnd == 0 { + // State proofs are not enabled yet. Keep monitoring new blocks. + nextrnd = latest + 1 + } + break + } + + return nextrnd +} + +func (spw *Worker) signStateProof(hdr bookkeeping.BlockHeader) { + proto := config.Consensus[hdr.CurrentProtocol] + if proto.StateProofInterval == 0 { + return + } + + // Only sign blocks that are a multiple of StateProofInterval. + if hdr.Round%basics.Round(proto.StateProofInterval) != 0 { + return + } + + keys := spw.accts.StateProofKeys(hdr.Round) + if len(keys) == 0 { + // No keys, nothing to do. + return + } + + // votersRound is the round containing the merkle root commitment + // for the voters that are going to sign this block. + votersRound := hdr.Round.SubSaturate(basics.Round(proto.StateProofInterval)) + votersHdr, err := spw.ledger.BlockHdr(votersRound) + if err != nil { + spw.log.Warnf("spw.signBlock(%d): BlockHdr(%d): %v", hdr.Round, votersRound, err) + return + } + + if votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEmpty() { + // No voter commitment, perhaps because state proofs were + // just enabled. + return + } + + sigs := make([]sigFromAddr, 0, len(keys)) + ids := make([]account.ParticipationID, 0, len(keys)) + usedSigners := make([]*merklesignature.Signer, 0, len(keys)) + + stateproofMessage, err := GenerateStateProofMessage(spw.ledger, uint64(votersHdr.Round), hdr) + if err != nil { + spw.log.Warnf("spw.signBlock(%d): GenerateStateProofMessage: %v", hdr.Round, err) + return + } + hashedStateproofMessage := stateproofMessage.Hash() + + for _, key := range keys { + if key.FirstValid > hdr.Round || hdr.Round > key.LastValid { + continue + } + + if key.StateProofSecrets == nil { + spw.log.Warnf("spw.signBlock(%d): empty state proof secrets for round", hdr.Round) + continue + } + + sig, err := key.StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + if err != nil { + spw.log.Warnf("spw.signBlock(%d): StateProofSecrets.Sign: %v", hdr.Round, err) + continue + } + + sigs = append(sigs, sigFromAddr{ + SignerAddress: key.Account, + Round: hdr.Round, + Sig: sig, + }) + ids = append(ids, key.ParticipationID) + usedSigners = append(usedSigners, key.StateProofSecrets) + } + + // any error in handle sig indicates the signature wasn't stored in disk, thus we cannot delete the key. + for i, sfa := range sigs { + if _, err := spw.handleSig(sfa, nil); err != nil { + spw.log.Warnf("spw.signBlock(%d): handleSig: %v", hdr.Round, err) + continue + } + + spw.log.Infof("spw.signBlock(%d): sp message was signed with address %v", hdr.Round, sfa.SignerAddress) + firstRoundInKeyLifetime, err := usedSigners[i].FirstRoundInKeyLifetime() // Calculate first round of the key in order to delete all previous keys (and keep the current one for now) + if err != nil { + spw.log.Warnf("spw.signBlock(%d): Signer.FirstRoundInKeyLifetime: %v", hdr.Round, err) + continue + } + if firstRoundInKeyLifetime == 0 { + continue // No previous keys to delete (also underflows when subtracting 1) + } + + // Safe to delete key for sfa.Round because the signature is now stored in the disk. + if err := spw.accts.DeleteStateProofKey(ids[i], basics.Round(firstRoundInKeyLifetime-1)); err != nil { // Subtract 1 to delete all keys up to this one + spw.log.Warnf("spw.signBlock(%d): DeleteStateProofKey: %v", hdr.Round, err) + } + } +} diff --git a/stateproof/stateproofMessageGenerator.go b/stateproof/stateproofMessageGenerator.go new file mode 100644 index 0000000000..8befeb72f0 --- /dev/null +++ b/stateproof/stateproofMessageGenerator.go @@ -0,0 +1,146 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/protocol" +) + +var errInvalidParams = errors.New("provided parameters are invalid") +var errOutOfBound = errors.New("request pos is out of array bounds") +var errProvenWeightOverflow = errors.New("overflow computing provenWeight") + +// The Array implementation for block headers, required to build the merkle tree from them. +//msgp:ignore lightBlockHeaders +type lightBlockHeaders []bookkeeping.LightBlockHeader + +func (b lightBlockHeaders) Length() uint64 { + return uint64(len(b)) +} + +func (b lightBlockHeaders) Marshal(pos uint64) (crypto.Hashable, error) { + if pos >= b.Length() { + return nil, fmt.Errorf("%w: pos - %d, array length - %d", errOutOfBound, pos, b.Length()) + } + return &b[pos], nil +} + +// GenerateStateProofMessage returns a stateproof message that contains all the necessary data for proving on Algorand's state. +// In addition, it also includes the trusted data for the next stateproof verification +func GenerateStateProofMessage(l BlockHeaderFetcher, votersRound uint64, latestRoundHeader bookkeeping.BlockHeader) (stateproofmsg.Message, error) { + proto := config.Consensus[latestRoundHeader.CurrentProtocol] + commitment, err := createHeaderCommitment(l, &proto, &latestRoundHeader) + if err != nil { + return stateproofmsg.Message{}, err + } + + lnProvenWeight, err := calculateLnProvenWeight(&latestRoundHeader, &proto) + if err != nil { + return stateproofmsg.Message{}, err + } + + return stateproofmsg.Message{ + BlockHeadersCommitment: commitment.ToSlice(), + VotersCommitment: latestRoundHeader.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + LnProvenWeight: lnProvenWeight, + FirstAttestedRound: votersRound + 1, + LastAttestedRound: uint64(latestRoundHeader.Round), + }, nil +} + +func calculateLnProvenWeight(latestRoundInInterval *bookkeeping.BlockHeader, proto *config.ConsensusParams) (uint64, error) { + totalWeight := latestRoundInInterval.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.ToUint64() + provenWeight, overflowed := basics.Muldiv(totalWeight, uint64(proto.StateProofWeightThreshold), 1<<32) + if overflowed { + err := fmt.Errorf("calculateLnProvenWeight err: %w - %d %d * %d / (1<<32)", + errProvenWeightOverflow, latestRoundInInterval.Round, totalWeight, proto.StateProofWeightThreshold) + return 0, err + } + + lnProvenWeight, err := stateproof.LnIntApproximation(provenWeight) + if err != nil { + return 0, err + } + return lnProvenWeight, nil +} + +func createHeaderCommitment(l BlockHeaderFetcher, proto *config.ConsensusParams, latestRoundHeader *bookkeeping.BlockHeader) (crypto.GenericDigest, error) { + stateProofInterval := proto.StateProofInterval + + if latestRoundHeader.Round < basics.Round(stateProofInterval) { + return nil, fmt.Errorf("createHeaderCommitment stateProofRound must be >= than stateproofInterval (%w)", errInvalidParams) + } + + var lightHeaders lightBlockHeaders + lightHeaders, err := FetchLightHeaders(l, stateProofInterval, latestRoundHeader.Round) + if err != nil { + return crypto.GenericDigest{}, err + } + + // Build merkle tree from encoded headers + tree, err := merklearray.BuildVectorCommitmentTree( + lightHeaders, + crypto.HashFactory{HashType: crypto.Sha256}, + ) + if err != nil { + return nil, err + } + return tree.Root(), nil +} + +// FetchLightHeaders returns the headers of the blocks in the interval +func FetchLightHeaders(l BlockHeaderFetcher, stateProofInterval uint64, latestRound basics.Round) ([]bookkeeping.LightBlockHeader, error) { + blkHdrArr := make(lightBlockHeaders, stateProofInterval) + firstRound := latestRound - basics.Round(stateProofInterval) + 1 + + for i := uint64(0); i < stateProofInterval; i++ { + rnd := firstRound + basics.Round(i) + hdr, err := l.BlockHdr(rnd) + if err != nil { + return nil, err + } + blkHdrArr[i] = hdr.ToLightBlockHeader() + } + return blkHdrArr, nil +} + +// GenerateProofOfLightBlockHeaders sets up a tree over the blkHdrArr and returns merkle proof over one of the blocks. +func GenerateProofOfLightBlockHeaders(stateProofInterval uint64, blkHdrArr lightBlockHeaders, blockIndex uint64) (*merklearray.SingleLeafProof, error) { + if blkHdrArr.Length() != stateProofInterval { + return nil, fmt.Errorf("received wrong amount of block headers. err: %w - %d != %d", errInvalidParams, blkHdrArr.Length(), stateProofInterval) + } + + tree, err := merklearray.BuildVectorCommitmentTree( + blkHdrArr, + crypto.HashFactory{HashType: crypto.Sha256}, + ) + if err != nil { + return nil, err + } + + return tree.ProveSingleLeaf(blockIndex) +} diff --git a/stateproof/stateproofMessageGenerator_test.go b/stateproof/stateproofMessageGenerator_test.go new file mode 100644 index 0000000000..ab3399e51f --- /dev/null +++ b/stateproof/stateproofMessageGenerator_test.go @@ -0,0 +1,405 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-deadlock" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +type workerForStateProofMessageTests struct { + w *testWorkerStubs +} + +func (s *workerForStateProofMessageTests) StateProofKeys(round basics.Round) []account.StateProofSecretsForRound { + return s.w.StateProofKeys(round) +} + +func (s *workerForStateProofMessageTests) DeleteStateProofKey(id account.ParticipationID, round basics.Round) error { + return s.w.DeleteStateProofKey(id, round) +} + +func (s *workerForStateProofMessageTests) Latest() basics.Round { + return s.w.Latest() +} + +func (s *workerForStateProofMessageTests) Wait(round basics.Round) chan struct{} { + return s.w.Wait(round) +} + +func (s *workerForStateProofMessageTests) GenesisHash() crypto.Digest { + return s.w.GenesisHash() +} + +func (s *workerForStateProofMessageTests) BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) { + s.w.mu.Lock() + defer s.w.mu.Unlock() + + element, ok := s.w.blocks[round] + if !ok { + return bookkeeping.BlockHeader{}, ledgercore.ErrNoEntry{Round: round} + } + return element, nil +} + +func (s *workerForStateProofMessageTests) VotersForStateProof(round basics.Round) (*ledgercore.VotersForRound, error) { + voters := &ledgercore.VotersForRound{ + Proto: config.Consensus[protocol.ConsensusFuture], + AddrToPos: make(map[basics.Address]uint64), + } + + wt := uint64(0) + for i, k := range s.w.keysForVoters { + partWe := uint64((len(s.w.keysForVoters) + int(round) - i) * 10000) + voters.AddrToPos[k.Parent] = uint64(i) + voters.Participants = append(voters.Participants, basics.Participant{ + PK: *k.StateProofSecrets.GetVerifier(), + Weight: partWe, + }) + wt += partWe + } + + tree, err := merklearray.BuildVectorCommitmentTree(voters.Participants, crypto.HashFactory{HashType: stateproof.HashType}) + if err != nil { + return nil, err + } + + voters.Tree = tree + voters.TotalWeight = basics.MicroAlgos{Raw: wt} + return voters, nil +} + +func (s *workerForStateProofMessageTests) Broadcast(ctx context.Context, tag protocol.Tag, bytes []byte, b bool, peer network.Peer) error { + return s.w.Broadcast(ctx, tag, bytes, b, peer) +} + +func (s *workerForStateProofMessageTests) RegisterHandlers(handlers []network.TaggedMessageHandler) { + s.w.RegisterHandlers(handlers) +} + +func (s *workerForStateProofMessageTests) BroadcastInternalSignedTxGroup(txns []transactions.SignedTxn) error { + return s.w.BroadcastInternalSignedTxGroup(txns) +} + +func (s *workerForStateProofMessageTests) addBlockWithStateProofHeaders(ccNextRound basics.Round) { + + s.w.latest++ + + hdr := bookkeeping.BlockHeader{} + hdr.Round = s.w.latest + hdr.CurrentProtocol = protocol.ConsensusFuture + + var ccBasic = bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: make([]byte, stateproof.HashSize), + StateProofOnlineTotalWeight: basics.MicroAlgos{}, + StateProofNextRound: 0, + } + + if uint64(hdr.Round)%config.Consensus[hdr.CurrentProtocol].StateProofInterval == 0 { + voters, _ := s.VotersForStateProof(hdr.Round.SubSaturate(basics.Round(config.Consensus[hdr.CurrentProtocol].StateProofVotersLookback))) + ccBasic.StateProofVotersCommitment = voters.Tree.Root() + ccBasic.StateProofOnlineTotalWeight = voters.TotalWeight + + } + + ccBasic.StateProofNextRound = ccNextRound + hdr.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: ccBasic, + } + + s.w.blocks[s.w.latest] = hdr + if s.w.waiters[s.w.latest] != nil { + close(s.w.waiters[s.w.latest]) + } +} + +func newWorkerForStateProofMessageStubs(keys []account.Participation, totalWeight int) *workerForStateProofMessageTests { + s := &testWorkerStubs{ + t: nil, + mu: deadlock.Mutex{}, + latest: 0, + waiters: make(map[basics.Round]chan struct{}), + waitersCount: make(map[basics.Round]int), + blocks: make(map[basics.Round]bookkeeping.BlockHeader), + keys: keys, + keysForVoters: keys, + sigmsg: make(chan []byte, 1024), + txmsg: make(chan transactions.SignedTxn, 1024), + totalWeight: totalWeight, + deletedStateProofKeys: map[account.ParticipationID]basics.Round{}, + } + sm := workerForStateProofMessageTests{w: s} + return &sm +} + +func (s *workerForStateProofMessageTests) advanceLatest(delta uint64) { + s.w.mu.Lock() + defer s.w.mu.Unlock() + + for r := uint64(0); r < delta; r++ { + s.addBlockWithStateProofHeaders(s.w.blocks[s.w.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + } +} + +func TestStateProofMessage(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys, len(keys)) + dbs, _ := dbOpenTest(t, true) + w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) + + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + var lastMessage stateproofmsg.Message + + for iter := uint64(0); iter < 5; iter++ { + s.advanceLatest(proto.StateProofInterval) + + for { + tx, err := s.w.waitOnTxnWithTimeout(time.Second * 5) + a.NoError(err) + + a.Equal(tx.Txn.Type, protocol.StateProofTx) + + lastAttestedRound := basics.Round(tx.Txn.Message.LastAttestedRound) + if lastAttestedRound < basics.Round(iter+2)*basics.Round(proto.StateProofInterval) { + continue + } + + a.Equal(lastAttestedRound, basics.Round(iter+2)*basics.Round(proto.StateProofInterval)) + a.Equal(tx.Txn.Message.FirstAttestedRound, (iter+1)*proto.StateProofInterval+1) + + verifySha256BlockHeadersCommitments(a, tx.Txn.Message, s.w.blocks) + + if !lastMessage.MsgIsZero() { + verifier := stateproof.MkVerifierWithLnProvenWeight(lastMessage.VotersCommitment, lastMessage.LnProvenWeight, proto.StateProofStrengthTarget) + + err := verifier.Verify(uint64(lastAttestedRound), tx.Txn.Message.Hash(), &tx.Txn.StateProof) + a.NoError(err) + + } + + lastMessage = tx.Txn.Message + break + } + } +} + +func verifySha256BlockHeadersCommitments(a *require.Assertions, message stateproofmsg.Message, blocks map[basics.Round]bookkeeping.BlockHeader) { + blkHdrArr := make(lightBlockHeaders, message.LastAttestedRound-message.FirstAttestedRound+1) + for i := uint64(0); i < message.LastAttestedRound-message.FirstAttestedRound+1; i++ { + hdr := blocks[basics.Round(message.FirstAttestedRound+i)] + blkHdrArr[i] = hdr.ToLightBlockHeader() + } + + tree, err := merklearray.BuildVectorCommitmentTree(blkHdrArr, crypto.HashFactory{HashType: crypto.Sha256}) + a.NoError(err) + + a.Equal(tree.Root(), crypto.GenericDigest(message.BlockHeadersCommitment)) +} + +func TestGenerateStateProofMessageForSmallRound(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + _, err := GenerateStateProofMessage(s, 240, s.w.blocks[s.w.latest]) + a.ErrorIs(err, errInvalidParams) +} + +func TestMessageLnApproxError(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + s.advanceLatest(2*config.Consensus[protocol.ConsensusFuture].StateProofInterval + config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + tracking := s.w.blocks[512].StateProofTracking[protocol.StateProofBasic] + tracking.StateProofOnlineTotalWeight = basics.MicroAlgos{} + newtracking := tracking + s.w.blocks[512].StateProofTracking[protocol.StateProofBasic] = newtracking + + _, err := GenerateStateProofMessage(s, 256, s.w.blocks[512]) + a.ErrorIs(err, stateproof.ErrIllegalInputForLnApprox) +} + +func TestMessageMissingHeaderOnInterval(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + s.advanceLatest(2*config.Consensus[protocol.ConsensusFuture].StateProofInterval + config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + delete(s.w.blocks, 510) + + _, err := GenerateStateProofMessage(s, 256, s.w.blocks[512]) + a.ErrorIs(err, ledgercore.ErrNoEntry{Round: 510}) +} + +func TestGenerateBlockProof(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys, len(keys)) + dbs, _ := dbOpenTest(t, true) + w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) + + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + for iter := uint64(0); iter < 5; iter++ { + s.advanceLatest(proto.StateProofInterval) + + tx := <-s.w.txmsg + // we have a new tx. now attempt to fetch a block proof. + firstAttestedRound := tx.Txn.Message.FirstAttestedRound + lastAttestedRound := tx.Txn.Message.LastAttestedRound + + headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(lastAttestedRound)) + a.NoError(err) + a.Equal(proto.StateProofInterval, uint64(len(headers))) + + // attempting to get block proof for every block in the interval + for i := firstAttestedRound; i < lastAttestedRound; i++ { + headerIndex := i - firstAttestedRound + proof, err := GenerateProofOfLightBlockHeaders(proto.StateProofInterval, headers, headerIndex) + a.NoError(err) + a.NotNil(proof) + + lightheader := headers[headerIndex] + err = merklearray.VerifyVectorCommitment( + tx.Txn.Message.BlockHeadersCommitment, + map[uint64]crypto.Hashable{headerIndex: &lightheader}, + proof.ToProof()) + + a.NoError(err) + } + } +} + +func TestGenerateBlockProofOnSmallArray(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerForStateProofMessageStubs(keys, len(keys)) + s.w.latest-- + s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(2 * proto.StateProofInterval) + headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(2*proto.StateProofInterval)) + a.NoError(err) + headers = headers[1:] + + _, err = GenerateProofOfLightBlockHeaders(proto.StateProofInterval, headers, 1) + a.ErrorIs(err, errInvalidParams) +} diff --git a/stateproof/verify/stateproof.go b/stateproof/verify/stateproof.go new file mode 100644 index 0000000000..66c6f09d46 --- /dev/null +++ b/stateproof/verify/stateproof.go @@ -0,0 +1,179 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package verify + +import ( + "errors" + "fmt" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" +) + +var ( + errStateProofCrypto = errors.New("state proof crypto error") + errStateProofParamCreation = errors.New("state proof param creation error") + errStateProofNotEnabled = errors.New("state proofs are not enabled") + errNotAtRightMultiple = errors.New("state proof is not in a valid round multiple") + errInvalidVotersRound = errors.New("invalid voters round") + errInsufficientWeight = errors.New("insufficient state proof weight") +) + +// AcceptableStateProofWeight computes the acceptable signed weight +// of a state proof if it were to appear in a transaction with a +// particular firstValid round. Earlier rounds require a smaller proof. +// votersHdr specifies the block that contains the vector commitment of +// the voters for this state proof (and thus the state proof is for the interval +// (votersHdr.Round(), votersHdr.Round()+StateProofInterval]. +// +// logger must not be nil; use at least logging.Base() +func AcceptableStateProofWeight(votersHdr *bookkeeping.BlockHeader, firstValid basics.Round, logger logging.Logger) uint64 { + proto := config.Consensus[votersHdr.CurrentProtocol] + latestRoundInProof := votersHdr.Round + basics.Round(proto.StateProofInterval) + total := votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight + + // The acceptable weight depends on the elapsed time (in rounds) + // from the block we are trying to construct a proof for. + // Start by subtracting the latest round number in the state proof interval. + // If that round hasn't even passed yet, require 100% votes in proof. + offset := firstValid.SubSaturate(latestRoundInProof) + if offset == 0 { + return total.ToUint64() + } + + // During the first proto.StateProofInterval/2 blocks, the + // signatures are still being broadcast, so, continue requiring + // 100% votes. + offset = offset.SubSaturate(basics.Round(proto.StateProofInterval / 2)) + if offset == 0 { + return total.ToUint64() + } + + // In the next proto.StateProofInterval/2 blocks, linearly scale + // the acceptable weight from 100% to StateProofWeightThreshold. + // If we are outside of that window, accept any weight at or above + // StateProofWeightThreshold. + provenWeight, overflowed := basics.Muldiv(total.ToUint64(), uint64(proto.StateProofWeightThreshold), 1<<32) + if overflowed || provenWeight > total.ToUint64() { + // Shouldn't happen, but a safe fallback is to accept a larger proof. + logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight", + total, proto.StateProofInterval, latestRoundInProof, firstValid) + return 0 + } + + if offset >= basics.Round(proto.StateProofInterval/2) { + return provenWeight + } + + scaledWeight, overflowed := basics.Muldiv(total.ToUint64()-provenWeight, proto.StateProofInterval/2-uint64(offset), proto.StateProofInterval/2) + if overflowed { + // Shouldn't happen, but a safe fallback is to accept a larger state proof. + logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow scaledWeight", + total, proto.StateProofInterval, latestRoundInProof, firstValid) + return 0 + } + + w, overflowed := basics.OAdd(provenWeight, scaledWeight) + if overflowed { + // Shouldn't happen, but a safe fallback is to accept a larger state proof. + logger.Warnf("AcceptableStateProofWeight(%d, %d, %d, %d) overflow provenWeight (%d) + scaledWeight (%d)", + total, proto.StateProofInterval, latestRoundInProof, firstValid, provenWeight, scaledWeight) + return 0 + } + + return w +} + +// GetProvenWeight computes the parameters for building or verifying +// a state proof for the interval (votersHdr, latestRoundInProofHdr], using voters from block votersHdr. +func GetProvenWeight(votersHdr *bookkeeping.BlockHeader, latestRoundInProofHdr *bookkeeping.BlockHeader) (uint64, error) { + proto := config.Consensus[votersHdr.CurrentProtocol] + + if proto.StateProofInterval == 0 { + return 0, errStateProofNotEnabled + } + + if votersHdr.Round%basics.Round(proto.StateProofInterval) != 0 { + err := fmt.Errorf("votersHdr %d not a multiple of %d", + votersHdr.Round, proto.StateProofInterval) + return 0, err + } + + if latestRoundInProofHdr.Round != votersHdr.Round+basics.Round(proto.StateProofInterval) { + err := fmt.Errorf("certifying block %d not %d ahead of voters %d", + latestRoundInProofHdr.Round, proto.StateProofInterval, votersHdr.Round) + return 0, err + } + + totalWeight := votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.ToUint64() + provenWeight, overflowed := basics.Muldiv(totalWeight, uint64(proto.StateProofWeightThreshold), 1<<32) + if overflowed { + err := fmt.Errorf("overflow computing provenWeight[%d]: %d * %d / (1<<32)", + latestRoundInProofHdr.Round, totalWeight, proto.StateProofWeightThreshold) + return 0, err + } + + return provenWeight, nil +} + +// ValidateStateProof checks that a state proof is valid. +func ValidateStateProof(latestRoundInIntervalHdr *bookkeeping.BlockHeader, stateProof *stateproof.StateProof, votersHdr *bookkeeping.BlockHeader, atRound basics.Round, msg *stateproofmsg.Message) error { + proto := config.Consensus[latestRoundInIntervalHdr.CurrentProtocol] + + if proto.StateProofInterval == 0 { + return fmt.Errorf("rounds = %d: %w", proto.StateProofInterval, errStateProofNotEnabled) + } + + if latestRoundInIntervalHdr.Round%basics.Round(proto.StateProofInterval) != 0 { + return fmt.Errorf("state proof at %d for non-multiple of %d: %w", latestRoundInIntervalHdr.Round, proto.StateProofInterval, errNotAtRightMultiple) + } + + votersRound := latestRoundInIntervalHdr.Round.SubSaturate(basics.Round(proto.StateProofInterval)) + if votersRound != votersHdr.Round { + return fmt.Errorf("new state proof is for %d (voters %d), but votersHdr from %d: %w", + latestRoundInIntervalHdr.Round, votersRound, votersHdr.Round, errInvalidVotersRound) + } + + acceptableWeight := AcceptableStateProofWeight(votersHdr, atRound, logging.Base()) + if stateProof.SignedWeight < acceptableWeight { + return fmt.Errorf("insufficient weight at round %d: %d < %d: %w", + atRound, stateProof.SignedWeight, acceptableWeight, errInsufficientWeight) + } + + provenWeight, err := GetProvenWeight(votersHdr, latestRoundInIntervalHdr) + if err != nil { + return fmt.Errorf("%v: %w", err, errStateProofParamCreation) + } + + verifier, err := stateproof.MkVerifier(votersHdr.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, + provenWeight, + config.Consensus[votersHdr.CurrentProtocol].StateProofStrengthTarget) + if err != nil { + return err + } + + err = verifier.Verify(uint64(latestRoundInIntervalHdr.Round), msg.Hash(), stateProof) + if err != nil { + return fmt.Errorf("%v: %w", err, errStateProofCrypto) + } + return nil +} diff --git a/stateproof/verify/stateproof_test.go b/stateproof/verify/stateproof_test.go new file mode 100644 index 0000000000..38a76c2b80 --- /dev/null +++ b/stateproof/verify/stateproof_test.go @@ -0,0 +1,168 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package verify + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" +) + +func TestValidateStateProof(t *testing.T) { + partitiontest.PartitionTest(t) + + spHdr := &bookkeeping.BlockHeader{} + sp := &stateproof.StateProof{} + votersHdr := &bookkeeping.BlockHeader{} + var atRound basics.Round + msg := &stateproofmsg.Message{BlockHeadersCommitment: []byte("this is an arbitrary message")} + + // will definitely fail with nothing set up + err := ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errStateProofNotEnabled) + + spHdr.CurrentProtocol = "TestValidateStateProof" + spHdr.Round = 1 + proto := config.Consensus[spHdr.CurrentProtocol] + proto.StateProofInterval = 2 + proto.StateProofStrengthTarget = 256 + proto.StateProofWeightThreshold = (1 << 32) * 30 / 100 + config.Consensus[spHdr.CurrentProtocol] = proto + + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errNotAtRightMultiple) + + spHdr.Round = 4 + votersHdr.Round = 4 + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errInvalidVotersRound) + + votersHdr.Round = 2 + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errStateProofParamCreation) + + votersHdr.CurrentProtocol = spHdr.CurrentProtocol + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + // since proven weight is zero, we cann't create the verifier + require.ErrorIs(t, err, stateproof.ErrIllegalInputForLnApprox) + + votersHdr.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + cc := votersHdr.StateProofTracking[protocol.StateProofBasic] + cc.StateProofOnlineTotalWeight.Raw = 100 + votersHdr.StateProofTracking[protocol.StateProofBasic] = cc + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errInsufficientWeight) + + // Require 100% of the weight to be signed in order to accept stateproof before interval/2 rounds has passed from the latest round attested (optimal case) + sp.SignedWeight = 99 // suboptimal signed weight + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errInsufficientWeight) + + latestRoundInProof := votersHdr.Round + basics.Round(proto.StateProofInterval) + atRound = latestRoundInProof + basics.Round(proto.StateProofInterval/2) + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + require.ErrorIs(t, err, errInsufficientWeight) + + // This suboptimal signed weight should be enough for this round + atRound++ + err = ValidateStateProof(spHdr, sp, votersHdr, atRound, msg) + // still err, but a different err case to cover + require.ErrorIs(t, err, errStateProofCrypto) + + // Above cases leave validateStateProof() with 100% coverage. + // crypto/stateproof.Verify has its own tests +} + +func TestAcceptableStateProofWeight(t *testing.T) { + partitiontest.PartitionTest(t) + + var votersHdr bookkeeping.BlockHeader + var firstValid basics.Round + logger := logging.TestingLog(t) + + votersHdr.CurrentProtocol = "TestAcceptableStateProofWeight" + proto := config.Consensus[votersHdr.CurrentProtocol] + proto.StateProofInterval = 2 + config.Consensus[votersHdr.CurrentProtocol] = proto + out := AcceptableStateProofWeight(&votersHdr, firstValid, logger) + require.Equal(t, uint64(0), out) + + votersHdr.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + cc := votersHdr.StateProofTracking[protocol.StateProofBasic] + cc.StateProofOnlineTotalWeight.Raw = 100 + votersHdr.StateProofTracking[protocol.StateProofBasic] = cc + out = AcceptableStateProofWeight(&votersHdr, firstValid, logger) + require.Equal(t, uint64(100), out) + + // this should exercise the second return case + firstValid = basics.Round(3) + out = AcceptableStateProofWeight(&votersHdr, firstValid, logger) + require.Equal(t, uint64(100), out) + + firstValid = basics.Round(6) + proto.StateProofWeightThreshold = 999999999 + config.Consensus[votersHdr.CurrentProtocol] = proto + out = AcceptableStateProofWeight(&votersHdr, firstValid, logger) + require.Equal(t, uint64(0x17), out) + + proto.StateProofInterval = 10000 + votersHdr.Round = 10000 + firstValid = basics.Round(29000 - 2) + config.Consensus[votersHdr.CurrentProtocol] = proto + cc.StateProofOnlineTotalWeight.Raw = 0x7fffffffffffffff + votersHdr.StateProofTracking[protocol.StateProofBasic] = cc + proto.StateProofWeightThreshold = 0x7fffffff + config.Consensus[votersHdr.CurrentProtocol] = proto + out = AcceptableStateProofWeight(&votersHdr, firstValid, logger) + require.Equal(t, uint64(0x4cd35a85213a92a2), out) + + // Covers everything except "overflow that shouldn't happen" branches +} + +func TestStateProofParams(t *testing.T) { + partitiontest.PartitionTest(t) + + var votersHdr bookkeeping.BlockHeader + var hdr bookkeeping.BlockHeader + + _, err := GetProvenWeight(&votersHdr, &hdr) + require.Error(t, err) // not enabled + + votersHdr.CurrentProtocol = "TestStateProofParams" + proto := config.Consensus[votersHdr.CurrentProtocol] + proto.StateProofInterval = 2 + config.Consensus[votersHdr.CurrentProtocol] = proto + votersHdr.Round = 1 + _, err = GetProvenWeight(&votersHdr, &hdr) + require.Error(t, err) // wrong round + + votersHdr.Round = 2 + hdr.Round = 3 + _, err = GetProvenWeight(&votersHdr, &hdr) + require.Error(t, err) // wrong round + + // Covers all cases except overflow +} diff --git a/compactcert/worker.go b/stateproof/worker.go similarity index 78% rename from compactcert/worker.go rename to stateproof/worker.go index 38341d7ade..ca277dac8b 100644 --- a/compactcert/worker.go +++ b/stateproof/worker.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with go-algorand. If not, see . -package compactcert +package stateproof import ( "context" @@ -23,9 +23,10 @@ import ( "github.com/algorand/go-deadlock" - "github.com/algorand/go-algorand/crypto/compactcert" + "github.com/algorand/go-algorand/crypto/stateproof" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" @@ -34,16 +35,17 @@ import ( ) type builder struct { - *compactcert.Builder + *stateproof.Builder voters *ledgercore.VotersForRound votersHdr bookkeeping.BlockHeader + message stateproofmsg.Message } -// Worker builds compact certificates, by broadcasting +// Worker builds state proofs, by broadcasting // signatures using this node's participation keys, by collecting // signatures sent by others, and by sending out the resulting -// compact certs in a transaction. +// state proof in a transaction. type Worker struct { // The mutex serializes concurrent message handler invocations // from the network stack. @@ -86,34 +88,34 @@ func NewWorker(db db.Accessor, log logging.Logger, accts Accounts, ledger Ledger } // Start starts the goroutines for the worker. -func (ccw *Worker) Start() { - err := ccw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { +func (spw *Worker) Start() { + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { return initDB(tx) }) if err != nil { - ccw.log.Warnf("ccw.Start(): initDB: %v", err) + spw.log.Warnf("spw.Start(): initDB: %v", err) return } - ccw.initBuilders() + spw.initBuilders() handlers := []network.TaggedMessageHandler{ - {Tag: protocol.CompactCertSigTag, MessageHandler: network.HandlerFunc(ccw.handleSigMessage)}, + {Tag: protocol.StateProofSigTag, MessageHandler: network.HandlerFunc(spw.handleSigMessage)}, } - ccw.net.RegisterHandlers(handlers) + spw.net.RegisterHandlers(handlers) - latest := ccw.ledger.Latest() + latest := spw.ledger.Latest() - ccw.wg.Add(1) - go ccw.signer(latest) + spw.wg.Add(1) + go spw.signer(latest) - ccw.wg.Add(1) - go ccw.builder(latest) + spw.wg.Add(1) + go spw.builder(latest) } // Shutdown stops any goroutines associated with this worker. -func (ccw *Worker) Shutdown() { - ccw.shutdown() - ccw.wg.Wait() - ccw.db.Close() +func (spw *Worker) Shutdown() { + spw.shutdown() + spw.wg.Wait() + spw.db.Close() } diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go new file mode 100644 index 0000000000..0034b45b0f --- /dev/null +++ b/stateproof/worker_test.go @@ -0,0 +1,1290 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproof + +import ( + "context" + "database/sql" + "encoding/binary" + "fmt" + "io/ioutil" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + "github.com/algorand/go-algorand/crypto/merklesignature" + "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/ledger/ledgercore" + "github.com/algorand/go-algorand/logging" + "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/partitiontest" + "github.com/algorand/go-algorand/util/db" + "github.com/algorand/go-deadlock" +) + +type testWorkerStubs struct { + t testing.TB + mu deadlock.Mutex + latest basics.Round + waiters map[basics.Round]chan struct{} + waitersCount map[basics.Round]int + blocks map[basics.Round]bookkeeping.BlockHeader + keys []account.Participation + keysForVoters []account.Participation + sigmsg chan []byte + txmsg chan transactions.SignedTxn + totalWeight int + deletedStateProofKeys map[account.ParticipationID]basics.Round +} + +func newWorkerStubs(t testing.TB, keys []account.Participation, totalWeight int) *testWorkerStubs { + s := &testWorkerStubs{ + t: nil, + mu: deadlock.Mutex{}, + latest: 0, + waiters: make(map[basics.Round]chan struct{}), + waitersCount: make(map[basics.Round]int), + blocks: make(map[basics.Round]bookkeeping.BlockHeader), + keys: keys, + keysForVoters: keys, + sigmsg: make(chan []byte, 1024*1024), + txmsg: make(chan transactions.SignedTxn, 1024), + totalWeight: totalWeight, + deletedStateProofKeys: map[account.ParticipationID]basics.Round{}, + } + s.latest-- + s.addBlock(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + return s +} + +func (s *testWorkerStubs) addBlock(spNextRound basics.Round) { + s.latest++ + + hdr := bookkeeping.BlockHeader{} + hdr.Round = s.latest + hdr.CurrentProtocol = protocol.ConsensusFuture + + var stateProofBasic = bookkeeping.StateProofTrackingData{ + StateProofVotersCommitment: make([]byte, stateproof.HashSize), + StateProofOnlineTotalWeight: basics.MicroAlgos{}, + StateProofNextRound: 0, + } + stateProofBasic.StateProofOnlineTotalWeight.Raw = uint64(s.totalWeight) + + if hdr.Round > 0 { + // Just so it's not zero, since the signer logic checks for all-zeroes + stateProofBasic.StateProofVotersCommitment[1] = 0x12 + } + + stateProofBasic.StateProofNextRound = spNextRound + hdr.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ + protocol.StateProofBasic: stateProofBasic, + } + + s.blocks[s.latest] = hdr + + if s.waiters[s.latest] != nil { + close(s.waiters[s.latest]) + } +} + +func (s *testWorkerStubs) StateProofKeys(rnd basics.Round) (out []account.StateProofSecretsForRound) { + for _, part := range s.keys { + partRecord := account.ParticipationRecord{ + ParticipationID: part.ID(), + Account: part.Parent, + FirstValid: part.FirstValid, + LastValid: part.LastValid, + KeyDilution: part.KeyDilution, + LastVote: 0, + LastBlockProposal: 0, + LastStateProof: 0, + EffectiveFirst: 0, + EffectiveLast: 0, + VRF: part.VRF, + Voting: part.Voting, + } + signerInRound := part.StateProofSecrets.GetSigner(uint64(rnd)) + partRecordForRound := account.StateProofSecretsForRound{ + ParticipationRecord: partRecord, + StateProofSecrets: signerInRound, + } + out = append(out, partRecordForRound) + } + return +} + +func (s *testWorkerStubs) DeleteStateProofKey(id account.ParticipationID, round basics.Round) error { + s.mu.Lock() + s.deletedStateProofKeys[id] = round + s.mu.Unlock() + + return nil +} +func (s *testWorkerStubs) GetNumDeletedKeys() int { + s.mu.Lock() + numDeltedKeys := len(s.deletedStateProofKeys) + s.mu.Unlock() + + return numDeltedKeys +} + +func (s *testWorkerStubs) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { + s.mu.Lock() + defer s.mu.Unlock() + + hdr, ok := s.blocks[r] + if !ok { + return hdr, ledgercore.ErrNoEntry{ + Round: r, + Latest: s.latest, + Committed: s.latest, + } + } + + return hdr, nil +} + +func (s *testWorkerStubs) VotersForStateProof(r basics.Round) (*ledgercore.VotersForRound, error) { + voters := &ledgercore.VotersForRound{ + Proto: config.Consensus[protocol.ConsensusFuture], + AddrToPos: make(map[basics.Address]uint64), + TotalWeight: basics.MicroAlgos{Raw: uint64(s.totalWeight)}, + } + + for i, k := range s.keysForVoters { + voters.AddrToPos[k.Parent] = uint64(i) + voters.Participants = append(voters.Participants, basics.Participant{ + PK: *k.StateProofSecrets.GetVerifier(), + Weight: 1, + }) + } + + tree, err := merklearray.BuildVectorCommitmentTree(voters.Participants, crypto.HashFactory{HashType: stateproof.HashType}) + if err != nil { + return nil, err + } + + voters.Tree = tree + return voters, nil +} + +func (s *testWorkerStubs) GenesisHash() crypto.Digest { + return crypto.Digest{0x01, 0x02, 0x03, 0x04} +} + +func (s *testWorkerStubs) Latest() basics.Round { + s.mu.Lock() + defer s.mu.Unlock() + return s.latest +} + +func (s *testWorkerStubs) Wait(r basics.Round) chan struct{} { + s.mu.Lock() + defer s.mu.Unlock() + if s.waiters[r] == nil { + s.waiters[r] = make(chan struct{}) + s.waitersCount[r] = 0 + if r <= s.latest { + close(s.waiters[r]) + } + } + s.waitersCount[r] = s.waitersCount[r] + 1 + return s.waiters[r] +} + +func (s *testWorkerStubs) Broadcast(ctx context.Context, tag protocol.Tag, data []byte, wait bool, except network.Peer) error { + require.Equal(s.t, tag, protocol.StateProofSigTag) + s.sigmsg <- data + return nil +} + +func (s *testWorkerStubs) BroadcastInternalSignedTxGroup(tx []transactions.SignedTxn) error { + require.Equal(s.t, len(tx), 1) + s.txmsg <- tx[0] + return nil +} + +func (s *testWorkerStubs) RegisterHandlers([]network.TaggedMessageHandler) { +} + +func (s *testWorkerStubs) advanceLatest(delta uint64) { + s.mu.Lock() + defer s.mu.Unlock() + + for r := uint64(0); r < delta; r++ { + s.addBlock(s.blocks[s.latest].StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + } +} + +func (s *testWorkerStubs) waitOnSigWithTimeout(timeout time.Duration) ([]byte, error) { + select { + case sig := <-s.sigmsg: + return sig, nil + case <-time.After(timeout): + return nil, fmt.Errorf("timeout waiting on sigmsg") + } +} + +func (s *testWorkerStubs) waitOnTxnWithTimeout(timeout time.Duration) (transactions.SignedTxn, error) { + select { + case signedTx := <-s.txmsg: + return signedTx, nil + case <-time.After(timeout): + return transactions.SignedTxn{}, fmt.Errorf("timeout waiting on sigmsg") + } +} + +func newTestWorkerDB(t testing.TB, s *testWorkerStubs, dba db.Accessor) *Worker { + return NewWorker(dba, logging.TestingLog(t), s, s, s, s) +} + +func newTestWorker(t testing.TB, s *testWorkerStubs) *Worker { + dbs, _ := dbOpenTest(t, true) + return newTestWorkerDB(t, s, dbs.Wdb) +} + +// You must call defer part.Close() after calling this function, +// since it creates a DB accessor but the caller must close it (required for mss) +func newPartKey(t testing.TB, parent basics.Address) account.PersistedParticipation { + fn := fmt.Sprintf("%s.%d", strings.ReplaceAll(t.Name(), "/", "."), crypto.RandUint64()) + partDB, err := db.MakeAccessor(fn, false, true) + require.NoError(t, err) + + part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(15*config.Consensus[protocol.ConsensusFuture].StateProofInterval), config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution) + require.NoError(t, err) + + return part +} + +func TestWorkerAllSigs(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + // Go through several iterations, making sure that we get + // the signatures and certs broadcast at each round. + for iter := 0; iter < 5; iter++ { + s.advanceLatest(proto.StateProofInterval) + + for i := 0; i < len(keys); i++ { + // Expect all signatures to be broadcast. + _, err := s.waitOnSigWithTimeout(time.Second * 2) + require.NoError(t, err) + } + + // Expect a state proof to be formed. + for { + tx, err := s.waitOnTxnWithTimeout(time.Second * 5) + require.NoError(t, err) + + lastAttestedRound := basics.Round(tx.Txn.Message.LastAttestedRound) + require.Equal(t, tx.Txn.Type, protocol.StateProofTx) + if lastAttestedRound < basics.Round(iter+2)*basics.Round(proto.StateProofInterval) { + continue + } + + require.Equal(t, lastAttestedRound, basics.Round(iter+2)*basics.Round(proto.StateProofInterval)) + + stateProofLatestRound, err := s.BlockHdr(lastAttestedRound) + require.NoError(t, err) + + votersRound := lastAttestedRound.SubSaturate(basics.Round(proto.StateProofInterval)) + + msg, err := GenerateStateProofMessage(s, uint64(votersRound), stateProofLatestRound) + require.NoError(t, err) + require.Equal(t, msg, tx.Txn.Message) + + provenWeight, overflowed := basics.Muldiv(uint64(s.totalWeight), uint64(proto.StateProofWeightThreshold), 1<<32) + require.False(t, overflowed) + + voters, err := s.VotersForStateProof(lastAttestedRound - basics.Round(proto.StateProofInterval) - basics.Round(proto.StateProofVotersLookback)) + require.NoError(t, err) + + verif, err := stateproof.MkVerifier(voters.Tree.Root(), provenWeight, proto.StateProofStrengthTarget) + require.NoError(t, err) + + err = verif.Verify(uint64(lastAttestedRound), tx.Txn.Message.Hash(), &tx.Txn.StateProof) + require.NoError(t, err) + break + } + } +} + +func TestWorkerPartialSigs(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 7; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + s.advanceLatest(proto.StateProofInterval) + + for i := 0; i < len(keys); i++ { + // Expect all signatures to be broadcast. + _, err := s.waitOnSigWithTimeout(time.Second * 2) + require.NoError(t, err) + } + + // No state proof should be formed yet: not enough sigs for a stateproof this early. + select { + case <-s.txmsg: + t.Fatal("state proof formed too early") + case <-time.After(time.Second): + } + + // Expect a state proof to be formed in the next StateProofInterval/2. + s.advanceLatest(proto.StateProofInterval / 2) + + tx, err := s.waitOnTxnWithTimeout(time.Second * 5) + require.NoError(t, err) + + lastAttestedRound := basics.Round(tx.Txn.Message.LastAttestedRound) + require.Equal(t, tx.Txn.Type, protocol.StateProofTx) + require.Equal(t, lastAttestedRound, 2*basics.Round(proto.StateProofInterval)) + + stateProofLatestRound, err := s.BlockHdr(lastAttestedRound) + require.NoError(t, err) + + votersRound := lastAttestedRound.SubSaturate(basics.Round(proto.StateProofInterval)) + + msg, err := GenerateStateProofMessage(s, uint64(votersRound), stateProofLatestRound) + require.NoError(t, err) + require.Equal(t, msg, tx.Txn.Message) + + provenWeight, overflowed := basics.Muldiv(uint64(s.totalWeight), uint64(proto.StateProofWeightThreshold), 1<<32) + require.False(t, overflowed) + + voters, err := s.VotersForStateProof(lastAttestedRound - basics.Round(proto.StateProofInterval) - basics.Round(proto.StateProofVotersLookback)) + require.NoError(t, err) + + verif, err := stateproof.MkVerifier(voters.Tree.Root(), provenWeight, proto.StateProofStrengthTarget) + require.NoError(t, err) + err = verif.Verify(uint64(lastAttestedRound), msg.Hash(), &tx.Txn.StateProof) + require.NoError(t, err) +} + +func TestWorkerInsufficientSigs(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(3 * proto.StateProofInterval) + + for i := 0; i < len(keys); i++ { + // Expect all signatures to be broadcast. + _, err := s.waitOnSigWithTimeout(time.Second * 2) + require.NoError(t, err) + } + + // No state proof should be formed: not enough sigs. + select { + case <-s.txmsg: + t.Fatal("state proof formed without enough sigs") + case <-time.After(time.Second): + } +} + +func TestWorkerRestart(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(3*proto.StateProofInterval - 1) + + dbRand := crypto.RandUint64() + + formedAt := -1 + for i := 0; formedAt < 0 && i < len(keys); i++ { + // Give one key at a time to the worker, and then shut it down, + // to make sure that it will correctly save and restore these + // signatures across restart. + s.keys = keys[i : i+1] + dbs, _ := dbOpenTestRand(t, true, dbRand) + w := newTestWorkerDB(t, s, dbs.Wdb) + w.Start() + + // Check if the cert formed + select { + case <-s.txmsg: + formedAt = i + case <-time.After(time.Second): + } + + w.Shutdown() + } + + require.True(t, formedAt > 1) + require.True(t, formedAt < 5) +} + +func TestWorkerHandleSig(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(3 * proto.StateProofInterval) + + for i := 0; i < len(keys); i++ { + // Expect all signatures to be broadcast. + msg, err := s.waitOnSigWithTimeout(time.Second * 2) + require.NoError(t, err) + + res := w.handleSigMessage(network.IncomingMessage{ + Data: msg, + }) + + // This should be a dup signature, so should not be broadcast + // but also not disconnected. + require.Equal(t, res.Action, network.Ignore) + } +} + +func TestSignerDeletesUnneededStateProofKeys(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + nParticipants := 2 + for i := 0; i < nParticipants; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(3 * proto.StateProofInterval) + // Expect all signatures to be broadcast. + + require.Zero(t, s.GetNumDeletedKeys()) + w.signStateProof(s.blocks[basics.Round(proto.StateProofInterval)]) + require.Equal(t, s.GetNumDeletedKeys(), nParticipants) +} + +func TestSignerDoesntDeleteKeysWhenDBDoesntStoreSigs(t *testing.T) { + partitiontest.PartitionTest(t) + + var keys []account.Participation + for i := 0; i < 2; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, 10) + dbs, _ := dbOpenTest(t, true) + + logger := logging.NewLogger() + logger.SetOutput(ioutil.Discard) + + w := NewWorker(dbs.Wdb, logger, s, s, s, s) + + w.Start() + defer w.Shutdown() + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(3 * proto.StateProofInterval) + // Expect all signatures to be broadcast. + + require.NoError(t, w.db.Atomic( + func(ctx context.Context, tx *sql.Tx) error { + _, err := tx.Exec("DROP TABLE sigs") + return err + }), + ) + + w.signStateProof(s.blocks[3*basics.Round(proto.StateProofInterval)]) + require.Zero(t, s.GetNumDeletedKeys()) +} + +func TestWorkerRemoveBuildersAndSignatures(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + const expectedStateProofs = 8 + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + for iter := 0; iter < expectedStateProofs; iter++ { + s.advanceLatest(proto.StateProofInterval) + tx := <-s.txmsg + a.Equal(tx.Txn.Type, protocol.StateProofTx) + } + + err := waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + a.Equal(expectedStateProofs, len(w.builders)) + + var roundSigs map[basics.Round][]pendingSig + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx) + return + }) + + a.Equal(expectedStateProofs, len(roundSigs)) + + // add block that confirm a state proof for interval: expectedStateProofs - 1 + s.mu.Lock() + s.addBlock(basics.Round((expectedStateProofs - 1) * config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.mu.Unlock() + + err = waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + a.Equal(3, len(w.builders)) + + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx) + return + }) + + a.Equal(3, len(roundSigs)) +} + +func TestWorkerBuildersRecoveryLimit(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + proto := config.Consensus[protocol.ConsensusFuture] + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + for iter := uint64(0); iter < proto.StateProofMaxRecoveryIntervals+1; iter++ { + s.advanceLatest(proto.StateProofInterval) + tx := <-s.txmsg + a.Equal(tx.Txn.Type, protocol.StateProofTx) + } + + // since this test involves go routine, we would like to make sure that when + // we sample the builder it already processed our current round. + // in order to that, we wait for singer and the builder to wait. + // then we push one more round so the builder could process it (since the builder might skip rounds) + err := waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + s.mu.Lock() + s.addBlock(basics.Round(proto.StateProofInterval * 2)) + s.mu.Unlock() + err = waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + + // should not give up on rounds + a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(w.builders))) + + var roundSigs map[basics.Round][]pendingSig + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx) + return + }) + a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(roundSigs))) + + s.advanceLatest(proto.StateProofInterval) + tx := <-s.txmsg + a.Equal(tx.Txn.Type, protocol.StateProofTx) + + err = waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + s.mu.Lock() + s.addBlock(basics.Round(proto.StateProofInterval * 2)) + s.mu.Unlock() + err = waitForBuilderAndSignerToWaitOnRound(s) + a.NoError(err) + + // should not give up on rounds + a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(w.builders))) + + roundSigs = make(map[basics.Round][]pendingSig) + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) (err error) { + roundSigs, err = getPendingSigs(tx) + return + }) + a.Equal(proto.StateProofMaxRecoveryIntervals+1, uint64(len(roundSigs))) +} + +func waitForBuilderAndSignerToWaitOnRound(s *testWorkerStubs) error { + const maxRetries = 10000 + i := 0 + for { + s.mu.Lock() + r := s.latest + 1 + // in order to make sure the builder and the signer are waiting for a round we need to make sure + // that round r has c channel and r +1 doesn't have. + // we also want to make sure that the builder and the singer are waiting + isWaitingForRound := s.waiters[r] != nil && s.waiters[r+1] == nil + isWaitingForRound = isWaitingForRound && (s.waitersCount[r] == 2) + s.mu.Unlock() + if !isWaitingForRound { + if i == maxRetries { + return fmt.Errorf("timeout while waiting for round") + } + i++ + time.Sleep(time.Millisecond) + continue + } + return nil + } +} + +type sigOrigin int + +const ( + sigFromThisNode sigOrigin = iota + sigNotFromThisNode + sigAlternateOrigin +) + +// getSignaturesInDatabase sets up the db with signatures. This function supports creating up to StateProofInterval/2 address. +func getSignaturesInDatabase(t *testing.T, numAddresses int, sigFrom sigOrigin) ( + signatureBcasted map[basics.Address]int, fromThisNode map[basics.Address]bool, + tns *testWorkerStubs, spw *Worker) { + + // Some tests rely on having only one signature being broadcast at a single round. + // for that we need to make sure that addresses won't fall into the same broadcast round. + // For that same reason we can't have more than StateProofInterval / 2 address + require.LessOrEqual(t, uint64(numAddresses), config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + + // Prepare the addresses and the keys + signatureBcasted = make(map[basics.Address]int) + fromThisNode = make(map[basics.Address]bool) + var keys []account.Participation + for i := 0; i < numAddresses; i++ { + var parent basics.Address + binary.LittleEndian.PutUint64(parent[:], uint64(i)) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + signatureBcasted[parent] = 0 + } + + tns = newWorkerStubs(t, keys, len(keys)) + spw = newTestWorker(t, tns) + + // Prepare the database + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return initDB(tx) + }) + require.NoError(t, err) + + // All the keys are for round 255. This way, starting the period at 256, + // there will be no disqualified signatures from broadcasting because they are + // into the future. + round := basics.Round(255) + + // Sign the message + spRecords := tns.StateProofKeys(round) + sigs := make([]sigFromAddr, 0, len(keys)) + stateproofMessage := stateproofmsg.Message{} + hashedStateproofMessage := stateproofMessage.Hash() + for _, key := range spRecords { + sig, err := key.StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + require.NoError(t, err) + sigs = append(sigs, sigFromAddr{ + SignerAddress: key.Account, + Round: round, + Sig: sig, + }) + } + + // Add the signatures to the database + ftn := sigFrom == sigAlternateOrigin || sigFrom == sigFromThisNode + for _, sfa := range sigs { + err := spw.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return addPendingSig(tx, sfa.Round, pendingSig{ + signer: sfa.SignerAddress, + sig: sfa.Sig, + fromThisNode: ftn, + }) + }) + require.NoError(t, err) + fromThisNode[sfa.SignerAddress] = ftn + if sigFrom == sigAlternateOrigin { + // alternate the fromThisNode argument between addresses + ftn = !ftn + } + } + return +} + +// TestSigBroacastTwoPerSig checks if each signature is broadcasted twice per period +// It generates numAddresses and prepares a database with the account/signatures. +// Then, calls broadcastSigs with round numbers spanning periods and +// makes sure each account has 2 sigs sent per period if originated locally, and 1 sig +// if received from another relay. +func TestSigBroacastTwoPerSig(t *testing.T) { + partitiontest.PartitionTest(t) + signatureBcasted, fromThisNode, tns, spw := getSignaturesInDatabase(t, 10, sigAlternateOrigin) + + for periods := 1; periods < 10; periods += 3 { + sendReceiveCountMessages(t, tns, signatureBcasted, fromThisNode, spw, periods) + // reopen the channel + tns.sigmsg = make(chan []byte, 1024) + // reset the counters + for addr := range signatureBcasted { + signatureBcasted[addr] = 0 + } + } +} + +func sendReceiveCountMessages(t *testing.T, tns *testWorkerStubs, signatureBcasted map[basics.Address]int, + fromThisNode map[basics.Address]bool, spw *Worker, periods int) { + + proto := config.Consensus[protocol.ConsensusFuture] + + // Collect the broadcast messages + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for bMsg := range tns.sigmsg { + sfa := sigFromAddr{} + err := protocol.Decode(bMsg, &sfa) + require.NoError(t, err) + signatureBcasted[sfa.SignerAddress]++ + } + }() + + // Broadcast the messages + for brnd := 257; brnd < 257+int(proto.StateProofInterval)*periods; brnd++ { + spw.broadcastSigs(basics.Round(brnd), proto) + } + + close(tns.sigmsg) + wg.Wait() + + // Verify the number of times each signature was broadcast + for addr, sb := range signatureBcasted { + if fromThisNode[addr] { + require.Equal(t, 2*periods, sb) + } else { + require.Equal(t, periods, sb) + } + } +} + +func TestBuilderGeneratesValidStateProofTXN(t *testing.T) { + partitiontest.PartitionTest(t) + a := require.New(t) + + var keys []account.Participation + for i := 0; i < 10; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys, len(keys)) + w := newTestWorker(t, s) + w.Start() + defer w.Shutdown() + + proto := config.Consensus[protocol.ConsensusFuture] + s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) + + s.advanceLatest(proto.StateProofInterval) + + for i := 0; i < len(keys); i++ { + // Expect all signatures to be broadcast. + _, err := s.waitOnSigWithTimeout(time.Second * 2) + require.NoError(t, err) + } + + tx, err := s.waitOnTxnWithTimeout(time.Second * 5) + require.NoError(t, err) + + a.NoError(tx.Txn.WellFormed(transactions.SpecialAddresses{}, proto)) +} + +// TestForwardNotFromThisNodeSecondHalf tests that relays forward +// signatures from other nodes only in the second half of the period +func TestForwardNotFromThisNodeSecondHalf(t *testing.T) { + partitiontest.PartitionTest(t) + + _, _, tns, spw := getSignaturesInDatabase(t, 10, sigNotFromThisNode) + + proto := config.Consensus[protocol.ConsensusFuture] + for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { + spw.broadcastSigs(basics.Round(brnd), proto) + select { + case <-tns.sigmsg: + // The message is broadcast in the second half of the period + require.GreaterOrEqual(t, brnd%int(proto.StateProofInterval), int(proto.StateProofInterval)/2) + default: + } + } +} + +// TestForwardNotFromThisNodeFirstHalf tests that relays forward +// signatures in the first half of the period only if it is from this node +func TestForwardNotFromThisNodeFirstHalf(t *testing.T) { + partitiontest.PartitionTest(t) + + signatureBcasted, fromThisNode, tns, spw := getSignaturesInDatabase(t, 10, sigAlternateOrigin) + + proto := config.Consensus[protocol.ConsensusFuture] + for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { + spw.broadcastSigs(basics.Round(brnd), proto) + select { + case bMsg := <-tns.sigmsg: + sfa := sigFromAddr{} + err := protocol.Decode(bMsg, &sfa) + require.NoError(t, err) + + // If it is in the first half, then it must be from this node + if brnd%int(proto.StateProofInterval) < int(proto.StateProofInterval)/2 { + require.True(t, fromThisNode[sfa.SignerAddress]) + signatureBcasted[sfa.SignerAddress]++ + continue + } + + // The message is broadcast in the second half of the period, can be from this node or another node + require.GreaterOrEqual(t, brnd%int(proto.StateProofInterval), int(proto.StateProofInterval)/2) + if fromThisNode[sfa.SignerAddress] { + // It must have already been broadcasted once in the first period + require.Equal(t, brnd/int(proto.StateProofInterval), signatureBcasted[sfa.SignerAddress]) + } + default: + } + } +} + +func setBlocksAndMessage(t *testing.T, sigRound basics.Round) (s *testWorkerStubs, w *Worker, msg sigFromAddr, msgBytes []byte) { + proto := config.Consensus[protocol.ConsensusFuture] + + var address basics.Address + crypto.RandBytes(address[:]) + p := newPartKey(t, address) + defer p.Close() + + s = newWorkerStubs(t, []account.Participation{p.Participation}, 10) + w = newTestWorker(t, s) + + for r := 0; r < int(proto.StateProofInterval)*2; r++ { + s.addBlock(basics.Round(proto.StateProofInterval * 2)) + } + + msg = sigFromAddr{ + SignerAddress: address, + Round: sigRound, + Sig: merklesignature.Signature{}, + } + msgBytes = protocol.Encode(&msg) + return +} + +// relays reject signatures for old rounds (before stateproofNext) not disconnect +func TestWorkerHandleSigOldRounds(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + intervalRound := basics.Round(proto.StateProofInterval) + _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound) + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Ignore, fwd) + require.NoError(t, err) +} + +// relays reject signatures for a round not in ledger +func TestWorkerHandleSigRoundNotInLedger(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + intervalRound := basics.Round(proto.StateProofInterval) + _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*10) + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Ignore, fwd) + expected := ledgercore.ErrNoEntry{ + Round: msg.Round, + Latest: w.ledger.Latest(), + Committed: w.ledger.Latest(), + } + require.Equal(t, expected, err) +} + +// relays reject signatures for wrong message (sig verification fails) +func TestWorkerHandleSigWrongSignature(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + intervalRound := basics.Round(proto.StateProofInterval) + _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*2) + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Disconnect, fwd) + expected2 := fmt.Errorf("%w: %v", + merklesignature.ErrSignatureSchemeVerificationFailed, + merklearray.ErrRootMismatch) + require.Equal(t, expected2, err) +} + +// relays reject signatures for address not in top N +func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + proto.StateProofTopVoters = 2 + + addresses := make([]basics.Address, 0) + var keys []account.Participation + for i := 0; i < 4; i++ { + var parent basics.Address + crypto.RandBytes(parent[:]) + addresses = append(addresses, parent) + + p := newPartKey(t, parent) + defer p.Close() + keys = append(keys, p.Participation) + } + + s := newWorkerStubs(t, keys[0:proto.StateProofTopVoters], 10) + w := newTestWorker(t, s) + + for r := 0; r < int(proto.StateProofInterval)*2; r++ { + s.addBlock(basics.Round(r)) + } + + msg := sigFromAddr{ + SignerAddress: addresses[3], + Round: basics.Round(proto.StateProofInterval * 2), + Sig: merklesignature.Signature{}, + } + + msgBytes := protocol.Encode(&msg) + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Disconnect, fwd) + expected3 := fmt.Errorf("handleSig: %v not in participants for %d", + msg.SignerAddress, msg.Round) + require.Equal(t, expected3, err) +} + +// Signature already part of the builderForRound, ignore +func TestWorkerHandleSigAlreadyIn(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + lastRound := proto.StateProofInterval * 2 + s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) + + latestBlockHeader, err := w.ledger.BlockHdr(basics.Round(lastRound)) + require.NoError(t, err) + stateproofMessage, err := GenerateStateProofMessage(w.ledger, proto.StateProofInterval, latestBlockHeader) + require.NoError(t, err) + + hashedStateproofMessage := stateproofMessage.Hash() + spRecords := s.StateProofKeys(basics.Round(proto.StateProofInterval * 2)) + sig, err := spRecords[0].StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + require.NoError(t, err) + + msg.Sig = sig + // Create the database + err = w.db.Atomic(func(ctx context.Context, tx *sql.Tx) error { + return initDB(tx) + }) + require.NoError(t, err) + + msgBytes := protocol.Encode(&msg) + // First call to add the sig + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Broadcast}, reply) + + // The sig is already there. Shoud get error + reply = w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Ignore, fwd) + require.NoError(t, err) +} + +// Ignore on db internal error and report error +func TestWorkerHandleSigExceptionsDbError(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + lastRound := proto.StateProofInterval * 2 + s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) + latestBlockHeader, err := w.ledger.BlockHdr(basics.Round(lastRound)) + require.NoError(t, err) + stateproofMessage, err := GenerateStateProofMessage(w.ledger, proto.StateProofInterval, latestBlockHeader) + require.NoError(t, err) + + hashedStateproofMessage := stateproofMessage.Hash() + spRecords := s.StateProofKeys(basics.Round(proto.StateProofInterval * 2)) + sig, err := spRecords[0].StateProofSecrets.SignBytes(hashedStateproofMessage[:]) + require.NoError(t, err) + msg.Sig = sig + + msgBytes := protocol.Encode(&msg) + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Ignore, fwd) + require.Contains(t, "no such table: sigs", err.Error()) +} + +// relays reject signatures when could not makeBuilderForRound +func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + origProto := proto + defer func() { + config.Consensus[protocol.ConsensusFuture] = origProto + }() + proto.StateProofInterval = 512 + config.Consensus[protocol.ConsensusFuture] = proto + + var address basics.Address + crypto.RandBytes(address[:]) + p := newPartKey(t, address) + defer p.Close() + + s := newWorkerStubs(t, []account.Participation{p.Participation}, 10) + w := newTestWorker(t, s) + + for r := 0; r < int(proto.StateProofInterval)*2; r++ { + s.addBlock(basics.Round(512)) + } + // remove the first block from the ledger + delete(s.blocks, 0) + + msg := sigFromAddr{ + SignerAddress: address, + Round: basics.Round(proto.StateProofInterval), + Sig: merklesignature.Signature{}, + } + + msgBytes := protocol.Encode(&msg) + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Ignore}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Ignore, fwd) + expected := ledgercore.ErrNoEntry{ + Round: 0, + Latest: w.ledger.Latest(), + Committed: w.ledger.Latest(), + } + require.Equal(t, expected, err) +} + +// relays reject signiture for a round where StateProofInterval is 0 +func TestWorkerHandleSigIntervalZero(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + origProto := proto + defer func() { + config.Consensus[protocol.ConsensusFuture] = origProto + }() + proto.StateProofInterval = 0 + config.Consensus[protocol.ConsensusFuture] = proto + + intervalRound := basics.Round(proto.StateProofInterval) + _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*2) + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Disconnect, fwd) + expected := fmt.Errorf("handleSig: StateProofInterval is 0 for round %d", + uint64(msg.Round)) + require.Equal(t, expected, err) +} + +// relays reject signiture for a round not multiple of StateProofInterval +func TestWorkerHandleSigNotOnInterval(t *testing.T) { + partitiontest.PartitionTest(t) + + proto := config.Consensus[protocol.ConsensusFuture] + _, w, msg, msgBytes := setBlocksAndMessage(t, basics.Round(600)) + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) + + fwd, err := w.handleSig(msg, msg.SignerAddress) + require.Equal(t, network.Disconnect, fwd) + expected := fmt.Errorf("handleSig: round %d is not a multiple of SP interval %d", + msg.Round, proto.StateProofInterval) + require.Equal(t, expected, err) +} + +// relays handle corrupt message +func TestWorkerHandleSigCorrupt(t *testing.T) { + partitiontest.PartitionTest(t) + + var address basics.Address + crypto.RandBytes(address[:]) + p := newPartKey(t, address) + defer p.Close() + + s := newWorkerStubs(t, []account.Participation{p.Participation}, 10) + w := newTestWorker(t, s) + + msg := sigFromAddr{} + msgBytes := protocol.Encode(&msg) + msgBytes[0] = 55 // arbitrary value to fail protocol.Decode + + reply := w.handleSigMessage(network.IncomingMessage{ + Data: msgBytes, + }) + require.Equal(t, network.OutgoingMessage{Action: network.Disconnect}, reply) +} diff --git a/test/e2e-go/features/compactcert/compactcert_test.go b/test/e2e-go/features/compactcert/compactcert_test.go deleted file mode 100644 index 5c00286a5b..0000000000 --- a/test/e2e-go/features/compactcert/compactcert_test.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (C) 2019-2022 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package compactcert - -import ( - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/config" - "github.com/algorand/go-algorand/crypto/compactcert" - "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/protocol" - "github.com/algorand/go-algorand/rpcs" - "github.com/algorand/go-algorand/test/framework/fixtures" - "github.com/algorand/go-algorand/test/partitiontest" -) - -func TestCompactCerts(t *testing.T) { - partitiontest.PartitionTest(t) - defer fixtures.ShutdownSynchronizedTest(t) - - t.Skip("Disabling since they need work and shouldn't block releases") - t.Parallel() - r := require.New(fixtures.SynchronizedTest(t)) - - configurableConsensus := make(config.ConsensusProtocols) - consensusVersion := protocol.ConsensusVersion("test-fast-compactcert") - consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] - consensusParams.CompactCertRounds = 8 - consensusParams.CompactCertTopVoters = 1024 - consensusParams.CompactCertVotersLookback = 2 - consensusParams.CompactCertWeightThreshold = (1 << 32) * 30 / 100 - consensusParams.CompactCertSecKQ = 128 - consensusParams.EnableStateProofKeyregCheck = true - configurableConsensus[consensusVersion] = consensusParams - - tmp := config.Consensus[protocol.ConsensusFuture] - config.Consensus[protocol.ConsensusFuture] = consensusParams - defer func() { - config.Consensus[protocol.ConsensusFuture] = tmp - }() - - var fixture fixtures.RestClientFixture - fixture.SetConsensus(configurableConsensus) - fixture.Setup(t, filepath.Join("nettemplates", "CompactCert.json")) - defer fixture.Shutdown() - - restClient, err := fixture.NC.AlgodClient() - r.NoError(err) - - node0Client := fixture.GetLibGoalClientForNamedNode("Node0") - node0Wallet, err := node0Client.GetUnencryptedWalletHandle() - r.NoError(err) - node0AccountList, err := node0Client.ListAddresses(node0Wallet) - r.NoError(err) - node0Account := node0AccountList[0] - - node1Client := fixture.GetLibGoalClientForNamedNode("Node1") - node1Wallet, err := node1Client.GetUnencryptedWalletHandle() - r.NoError(err) - node1AccountList, err := node1Client.ListAddresses(node1Wallet) - r.NoError(err) - node1Account := node1AccountList[0] - - var lastCertBlock v1.Block - libgoal := fixture.LibGoalClient - for rnd := uint64(1); rnd <= consensusParams.CompactCertRounds*4; rnd++ { - // send a dummy payment transaction. - minTxnFee, _, err := fixture.CurrentMinFeeAndBalance() - r.NoError(err) - - _, err = node0Client.SendPaymentFromUnencryptedWallet(node0Account, node1Account, minTxnFee, rnd, nil) - r.NoError(err) - - err = fixture.WaitForRound(rnd, 30*time.Second) - r.NoError(err) - - blk, err := libgoal.Block(rnd) - r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) - - t.Logf("Round %d, block %v\n", rnd, blk) - - if (rnd % consensusParams.CompactCertRounds) == 0 { - // Must have a merkle commitment for participants - r.True(len(blk.CompactCertVoters) > 0) - r.True(blk.CompactCertVotersTotal != 0) - - // Special case: bootstrap validation with the first block - // that has a merkle root. - if lastCertBlock.Round == 0 { - lastCertBlock = blk - } - } - - for lastCertBlock.Round != 0 && lastCertBlock.Round+consensusParams.CompactCertRounds < blk.CompactCertNextRound { - nextCertRound := lastCertBlock.Round + consensusParams.CompactCertRounds - - // Find the cert transaction - res, err := restClient.TransactionsByAddr(transactions.CompactCertSender.String(), 0, rnd, 4) - r.NoError(err) - - var compactCert compactcert.Cert - compactCertFound := false - for _, txn := range res.Transactions { - r.Equal(txn.Type, string(protocol.CompactCertTx)) - r.True(txn.CompactCert != nil) - if txn.CompactCert.CertRound == nextCertRound { - err = protocol.Decode(txn.CompactCert.Cert, &compactCert) - r.NoError(err) - compactCertFound = true - } - } - r.True(compactCertFound) - - nextCertBlock, err := libgoal.Block(nextCertRound) - r.NoError(err) - - nextCertBlockRaw, err := libgoal.RawBlock(nextCertRound) - r.NoError(err) - - var nextCertBlockDecoded rpcs.EncodedBlockCert - err = protocol.Decode(nextCertBlockRaw, &nextCertBlockDecoded) - r.NoError(err) - - var votersRoot = make([]byte, compactcert.HashSize) - copy(votersRoot[:], lastCertBlock.CompactCertVoters) - - provenWeight, overflowed := basics.Muldiv(lastCertBlock.CompactCertVotersTotal, uint64(consensusParams.CompactCertWeightThreshold), 1<<32) - r.False(overflowed) - - ccparams := compactcert.Params{ - Msg: nextCertBlockDecoded.Block.BlockHeader, - ProvenWeight: provenWeight, - SigRound: basics.Round(nextCertBlock.Round), - SecKQ: consensusParams.CompactCertSecKQ, - } - verif := compactcert.MkVerifier(ccparams, votersRoot) - err = verif.Verify(&compactCert) - r.NoError(err) - - lastCertBlock = nextCertBlock - } - } - - r.Equalf(consensusParams.CompactCertRounds*3, lastCertBlock.Round, "the expected last certificate block wasn't the one that was observed") -} diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index a09b566a70..97ac14e006 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -291,8 +291,8 @@ func TestAccountGoesOnlineForShortPeriod(t *testing.T) { // we try to register online with a period in which we don't have stateproof keys partKeyFirstValid := uint64(1) - // TODO: Change consensus version when compact certs are deployed - partKeyLastValid := config.Consensus[protocol.ConsensusFuture].CompactCertRounds - 1 + // TODO: Change consensus version when state proofs are deployed + partKeyLastValid := config.Consensus[protocol.ConsensusFuture].StateProofInterval - 1 partkeyResponse, _, err := client.GenParticipationKeys(newAccount, partKeyFirstValid, partKeyLastValid, 1000) a.NoError(err, "rest client should be able to add participation key to new account") a.Equal(newAccount, partkeyResponse.Parent.String(), "partkey response should echo queried account") diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go new file mode 100644 index 0000000000..5ad6f49985 --- /dev/null +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -0,0 +1,1127 @@ +// Copyright (C) 2019-2022 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package stateproofs + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sync" + "sync/atomic" + "testing" + "time" + + "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/merklearray" + sp "github.com/algorand/go-algorand/crypto/stateproof" + "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" + "github.com/algorand/go-algorand/data/account" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/stateproofmsg" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/libgoal" + "github.com/algorand/go-algorand/nodecontrol" + "github.com/algorand/go-algorand/protocol" + "github.com/algorand/go-algorand/test/framework/fixtures" + "github.com/algorand/go-algorand/test/partitiontest" +) + +type accountFetcher struct { + nodeName string + accountNumber int +} + +func (a accountFetcher) getAccount(r *require.Assertions, f *fixtures.RestClientFixture) string { + node0Client := f.GetLibGoalClientForNamedNode(a.nodeName) + node0Wallet, err := node0Client.GetUnencryptedWalletHandle() + r.NoError(err) + node0AccountList, err := node0Client.ListAddresses(node0Wallet) + r.NoError(err) + return node0AccountList[a.accountNumber] +} + +func (a accountFetcher) getBalance(r *require.Assertions, f *fixtures.RestClientFixture) uint64 { + balance, _ := f.GetBalanceAndRound(a.getAccount(r, f)) + return balance +} + +func (a accountFetcher) goOffline(r *require.Assertions, f *fixtures.RestClientFixture, round uint64) { + account0 := a.getAccount(r, f) + + minTxnFee, _, err := f.CurrentMinFeeAndBalance() + r.NoError(err) + + client0 := f.GetLibGoalClientForNamedNode(a.nodeName) + txn, err := client0.MakeUnsignedGoOfflineTx(account0, round, round+1000, minTxnFee, [32]byte{}) + r.NoError(err) + wallet0, err := client0.GetUnencryptedWalletHandle() + r.NoError(err) + _, err = client0.SignAndBroadcastTransaction(wallet0, nil, txn) + r.NoError(err) +} + +type paymentSender struct { + from accountFetcher + to accountFetcher + amount uint64 +} + +func (p paymentSender) sendPayment(a *require.Assertions, f *fixtures.RestClientFixture, round uint64) { + account0 := p.from.getAccount(a, f) + account1 := p.to.getAccount(a, f) + + minTxnFee, _, err := f.CurrentMinFeeAndBalance() + a.NoError(err) + + client0 := f.GetLibGoalClientForNamedNode(p.from.nodeName) + _, err = client0.SendPaymentFromUnencryptedWallet(account0, account1, minTxnFee, p.amount, []byte{byte(round)}) + a.NoError(err) +} + +const timeoutUntilNextRound = 3 * time.Minute + +func TestStateProofs(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() + + verifyStateProofsCreation(t, &fixture, consensusParams) +} + +func TestStateProofsMultiWallets(t *testing.T) { + t.Skip("this test is heavy and should be run manually") + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + // Stateproof can be generated even if not all nodes function correctly. e.g node can be offline + // and stateproofs might still get generated. in order to make sure that all nodes work correctly + // we want the network to fail in generating stateproof if one node is not working correctly. + // For that we will increase the proven Weight to be close to 100%. However, this change might not be enough. + // if the signed Weight and the Proven Weight are very close to each other the number of reveals in the state proof + // will exceed the MAX_NUMBER_OF_REVEALS and proofs would not get generated + // for that reason we need to the decrease the StateProofStrengthTarget creating a "weak stateproof" + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 4 + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProofMultiWallets.json")) + defer fixture.Shutdown() + + verifyStateProofsCreation(t, &fixture, consensusParams) +} + +func verifyStateProofsCreation(t *testing.T, fixture *fixtures.RestClientFixture, consensusParams config.ConsensusParams) { + r := require.New(fixtures.SynchronizedTest(t)) + + var lastStateProofBlock bookkeeping.Block + var lastStateProofMessage stateproofmsg.Message + libgoal := fixture.LibGoalClient + + expectedNumberOfStateProofs := uint64(4) + // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs + for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + // send a dummy payment transaction to create non-empty blocks. + paymentSender{ + from: accountFetcher{nodeName: "Node0", accountNumber: 0}, + to: accountFetcher{nodeName: "Node1", accountNumber: 0}, + amount: 1, + }.sendPayment(r, fixture, rnd) + + err := fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoal.BookkeepingBlock(rnd) + r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + r.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) + r.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } else { + r.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) == 0) + r.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight == basics.MicroAlgos{}) + } + + for lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(r, fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams, expectedNumberOfStateProofs) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + } + + r.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") +} + +func TestStateProofOverlappingKeys(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + if testing.Short() { + t.Skip() + } + + r := require.New(fixtures.SynchronizedTest(t)) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 3 + consensusParams.AgreementFilterTimeout = 1000 * time.Millisecond + consensusParams.AgreementFilterTimeoutPeriod0 = 1000 * time.Millisecond + consensusParams.SeedLookback = 2 + consensusParams.SeedRefreshInterval = 8 + consensusParams.MaxBalLookback = 2 * consensusParams.SeedLookback * consensusParams.SeedRefreshInterval // 32 + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() + + // Get node libgoal clients in order to update their participation keys + var libgoalNodeClients [5]libgoal.Client + for i := 0; i < 5; i++ { + nodeName := fmt.Sprintf("Node%d", i) + c := fixture.GetLibGoalClientForNamedNode(nodeName) + libgoalNodeClients[i] = c + } + + // Get account address of each participating node + var accounts [5]string + for i, c := range libgoalNodeClients { + parts, err := c.GetParticipationKeys() // should have 1 participation per node + r.NoError(err) + accounts[i] = parts[0].Address + } + + var participations [5]account.Participation + var lastStateProofBlock bookkeeping.Block + var lastStateProofMessage stateproofmsg.Message + libgoalClient := fixture.LibGoalClient + + k, err := libgoalNodeClients[0].GetParticipationKeys() + r.NoError(err) + voteLastValid := k[0].Key.VoteLastValid + expectedNumberOfStateProofs := uint64(10) + for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + if rnd == voteLastValid-64 { // allow some buffer period before the voting keys are expired (for the keyreg to take effect) + // Generate participation keys (for the same accounts) + for i := 0; i < 5; i++ { + // Overlapping stateproof keys (the key for round 0 is valid up to 256) + _, part, err := installParticipationKey(t, libgoalNodeClients[i], accounts[i], 0, 200) + r.NoError(err) + participations[i] = part + } + // Register overlapping participation keys + for i := 0; i < 5; i++ { + registerParticipationAndWait(t, libgoalNodeClients[i], participations[i]) + } + } + + // send a dummy payment transaction. + paymentSender{ + from: accountFetcher{nodeName: "Node0", accountNumber: 0}, + to: accountFetcher{nodeName: "Node1", accountNumber: 0}, + amount: 1, + }.sendPayment(r, &fixture, rnd) + + err = fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoalClient.BookkeepingBlock(rnd) + r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + r.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) + r.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } + + for lastStateProofBlock.Round() != 0 && lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(r, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams, expectedNumberOfStateProofs) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + } + + r.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") +} + +func TestStateProofMessageCommitmentVerification(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + r := require.New(fixtures.SynchronizedTest(t)) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() + + libgoalClient := fixture.LibGoalClient + + var startRound = uint64(1) + var nextStateProofRound = uint64(0) + var firstStateProofRound = 2 * consensusParams.StateProofInterval + + for rnd := startRound; nextStateProofRound <= firstStateProofRound; rnd++ { + paymentSender{ + from: accountFetcher{nodeName: "Node0", accountNumber: 0}, + to: accountFetcher{nodeName: "Node1", accountNumber: 0}, + amount: 1, + }.sendPayment(r, &fixture, rnd) + + err := fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoalClient.BookkeepingBlock(rnd) + r.NoError(err) + + nextStateProofRound = uint64(blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound) + } + + _, stateProofMessage := getStateProofByLastRound(r, &fixture, firstStateProofRound, 1) + t.Logf("found first stateproof, attesting to rounds %d - %d. Verifying.\n", stateProofMessage.FirstAttestedRound, stateProofMessage.LastAttestedRound) + + for rnd := stateProofMessage.FirstAttestedRound; rnd <= stateProofMessage.LastAttestedRound; rnd++ { + proofResp, singleLeafProof, err := fixture.LightBlockHeaderProof(rnd) + r.NoError(err) + + blk, err := libgoalClient.BookkeepingBlock(rnd) + r.NoError(err) + + lightBlockHeader := blk.ToLightBlockHeader() + + elems := make(map[uint64]crypto.Hashable) + elems[proofResp.Index] = &lightBlockHeader + err = merklearray.VerifyVectorCommitment(stateProofMessage.BlockHeadersCommitment, elems, singleLeafProof.ToProof()) + r.NoError(err) + } +} + +func getDefaultStateProofConsensusParams() config.ConsensusParams { + consensusParams := config.Consensus[protocol.ConsensusCurrentVersion] + consensusParams.StateProofInterval = 16 + consensusParams.StateProofTopVoters = 1024 + consensusParams.StateProofVotersLookback = 2 + consensusParams.StateProofWeightThreshold = (1 << 32) * 30 / 100 + consensusParams.StateProofStrengthTarget = 256 + consensusParams.StateProofMaxRecoveryIntervals = 6 + consensusParams.EnableStateProofKeyregCheck = true + consensusParams.AgreementFilterTimeout = 1500 * time.Millisecond + consensusParams.AgreementFilterTimeoutPeriod0 = 1500 * time.Millisecond + + return consensusParams +} + +func getStateProofByLastRound(r *require.Assertions, fixture *fixtures.RestClientFixture, stateProofLatestRound uint64, expectedNumberOfStateProofs uint64) (sp.StateProof, stateproofmsg.Message) { + restClient, err := fixture.NC.AlgodClient() + r.NoError(err) + + curRound, err := fixture.LibGoalClient.CurrentRound() + r.NoError(err) + + res, err := restClient.TransactionsByAddr(transactions.StateProofSender.String(), 0, curRound, expectedNumberOfStateProofs+1) + r.NoError(err) + + var stateProof sp.StateProof + var stateProofMessage stateproofmsg.Message + for _, txn := range res.Transactions { + r.Equal(txn.Type, string(protocol.StateProofTx)) + r.True(txn.StateProof != nil) + err = protocol.Decode(txn.StateProof.StateProofMessage, &stateProofMessage) + r.NoError(err) + if stateProofMessage.LastAttestedRound == stateProofLatestRound { + err = protocol.Decode(txn.StateProof.StateProof, &stateProof) + r.NoError(err) + + return stateProof, stateProofMessage + } + } + + r.FailNow("no state proof with latest round %d found", stateProofLatestRound) + + // Should never get here + return sp.StateProof{}, stateproofmsg.Message{} +} + +func verifyStateProofForRound(r *require.Assertions, fixture *fixtures.RestClientFixture, nextStateProofRound uint64, prevStateProofMessage stateproofmsg.Message, lastStateProofBlock bookkeeping.Block, consensusParams config.ConsensusParams, expectedNumberOfStateProofs uint64) (stateproofmsg.Message, bookkeeping.Block) { + stateProof, stateProofMessage := getStateProofByLastRound(r, fixture, nextStateProofRound, expectedNumberOfStateProofs) + + nextStateProofBlock, err := fixture.LibGoalClient.BookkeepingBlock(nextStateProofRound) + + r.NoError(err) + + if !prevStateProofMessage.MsgIsZero() { + //if we have a previous stateproof message we can verify the current stateproof using data from it + verifier := sp.MkVerifierWithLnProvenWeight(prevStateProofMessage.VotersCommitment, prevStateProofMessage.LnProvenWeight, consensusParams.StateProofStrengthTarget) + err = verifier.Verify(uint64(nextStateProofBlock.Round()), stateProofMessage.Hash(), &stateProof) + r.NoError(err) + } + var votersRoot = make([]byte, sp.HashSize) + copy(votersRoot[:], lastStateProofBlock.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) + + provenWeight, overflowed := basics.Muldiv(lastStateProofBlock.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.Raw, uint64(consensusParams.StateProofWeightThreshold), 1<<32) + r.False(overflowed) + + verifier, err := sp.MkVerifier(votersRoot, provenWeight, consensusParams.StateProofStrengthTarget) + r.NoError(err) + + err = verifier.Verify(uint64(nextStateProofBlock.Round()), stateProofMessage.Hash(), &stateProof) + r.NoError(err) + return stateProofMessage, nextStateProofBlock +} + +// TestRecoverFromLaggingStateProofChain simulates a situation where the stateproof chain is lagging after the main chain. +// If the missing data is being accepted before StateProofMaxRecoveryIntervals * StateProofInterval rounds have passed, nodes should +// be able to produce stateproofs and continue as normal +func TestRecoverFromLaggingStateProofChain(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + r := require.New(fixtures.SynchronizedTest(t)) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + // Stateproof can be generated even if not all nodes function correctly. e.g node can be offline + // and stateproofs might still get generated. in order to make sure that all nodes work correctly + // we want the network to fail in generating stateproof if one node is not working correctly. + // For that we will increase the proven Weight to be close to 100%. However, this change might not be enough. + // if the signed Weight and the Proven Weight are very close to each other the number of reveals in the state proof + // will exceed the MAX_NUMBER_OF_REVEALS and proofs would not get generated + // for that reason we need to the decrease the StateProofStrengthTarget creating a "weak cert" + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 4 + consensusParams.StateProofMaxRecoveryIntervals = 4 + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() + + err := fixture.WaitForRound(1, timeoutUntilNextRound) + r.NoError(err) + + dir, err := fixture.GetNodeDir("Node4") + r.NoError(err) + + nc := nodecontrol.MakeNodeController(fixture.GetBinDir(), dir) + //Stop one of the nodes to prevent SP generation due to insufficient signatures. + nc.FullStop() + + var lastStateProofBlock bookkeeping.Block + var lastStateProofMessage stateproofmsg.Message + libgoal := fixture.LibGoalClient + + expectedNumberOfStateProofs := uint64(4) + // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs + for rnd := uint64(2); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + // Start the node in the last interval after which the SP will be abandoned if SPs are not generated. + if rnd == (consensusParams.StateProofMaxRecoveryIntervals)*consensusParams.StateProofInterval { + t.Logf("at round %d starting node\n", rnd) + dir, err = fixture.GetNodeDir("Node4") + r.NoError(err) + fixture.StartNode(dir) + } + + // send a dummy payment transaction to create non-empty blocks + paymentSender{ + from: accountFetcher{nodeName: "Node0", accountNumber: 0}, + to: accountFetcher{nodeName: "Node1", accountNumber: 0}, + amount: 1, + }.sendPayment(r, &fixture, rnd) + + err = fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoal.BookkeepingBlock(rnd) + r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + r.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) + r.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } + + // in case StateProofNextRound has changed (larger than the lastStateProofBlock ) we verify the new stateproof. + // since the stateproof chain is catching up there would be several proofs to check + for lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(r, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams, expectedNumberOfStateProofs) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + } + r.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") +} + +// TestUnableToRecoverFromLaggingStateProofChain simulates a situation where the stateproof chain is lagging after the main chain. +// unlike TestRecoverFromLaggingStateProofChain, in this test the node will start at a later round and the network will not be able to produce stateproofs/ +func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + r := require.New(fixtures.SynchronizedTest(t)) + + configurableConsensus := make(config.ConsensusProtocols) + consensusVersion := protocol.ConsensusVersion("test-fast-stateproofs") + consensusParams := getDefaultStateProofConsensusParams() + // Stateproof can be generated even if not all nodes function correctly. e.g node can be offline + // and stateproofs might still get generated. in order to make sure that all nodes work correctly + // we want the network to fail in generating stateproof if one node is not working correctly. + // For that we will increase the proven Weight to be close to 100%. However, this change might not be enough. + // if the signed Weight and the Proven Weight are very close to each other the number of reveals in the state proof + // will exceed the MAX_NUMBER_OF_REVEALS and proofs would not get generated + // for that reason we need to the decrease the StateProofStrengthTarget creating a "weak cert" + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 4 + consensusParams.StateProofMaxRecoveryIntervals = 4 + configurableConsensus[consensusVersion] = consensusParams + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + defer fixture.Shutdown() + + err := fixture.WaitForRound(1, timeoutUntilNextRound) + r.NoError(err) + + dir, err := fixture.GetNodeDir("Node4") + r.NoError(err) + nc := nodecontrol.MakeNodeController(fixture.GetBinDir(), dir) + nc.FullStop() + + var lastStateProofBlock bookkeeping.Block + libgoal := fixture.LibGoalClient + + expectedNumberOfStateProofs := uint64(4) + // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs + for rnd := uint64(2); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + if rnd == (consensusParams.StateProofMaxRecoveryIntervals+2)*consensusParams.StateProofInterval { + t.Logf("at round %d starting node\n", rnd) + dir, err = fixture.GetNodeDir("Node4") + r.NoError(err) + fixture.StartNode(dir) + } + + // send a dummy payment transaction to create non-empty blocks + paymentSender{ + from: accountFetcher{nodeName: "Node0", accountNumber: 0}, + to: accountFetcher{nodeName: "Node1", accountNumber: 0}, + amount: 1, + }.sendPayment(r, &fixture, rnd) + + err = fixture.WaitForRound(rnd, timeoutUntilNextRound) + r.NoError(err) + + blk, err := libgoal.BookkeepingBlock(rnd) + r.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + r.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) + r.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } + + if lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + r.FailNow("found a state proof at round %d", blk.Round()) + } + } +} + +// installParticipationKey generates a new key for a given account and installs it with the client. +func installParticipationKey(t *testing.T, client libgoal.Client, addr string, firstValid, lastValid uint64) (resp generated.PostParticipationResponse, part account.Participation, err error) { + dir, err := ioutil.TempDir("", "temporary_partkey_dir") + require.NoError(t, err) + defer os.RemoveAll(dir) + + // Install overlapping participation keys... + part, filePath, err := client.GenParticipationKeysTo(addr, firstValid, lastValid, 100, dir) + require.NoError(t, err) + require.NotNil(t, filePath) + require.Equal(t, addr, part.Parent.String()) + + resp, err = client.AddParticipationKey(filePath) + return +} + +func registerParticipationAndWait(t *testing.T, client libgoal.Client, part account.Participation) generated.NodeStatusResponse { + currentRnd, err := client.CurrentRound() + require.NoError(t, err) + sAccount := part.Address().String() + sWH, err := client.GetUnencryptedWalletHandle() + require.NoError(t, err) + goOnlineTx, err := client.MakeRegistrationTransactionWithGenesisID(part, 1000, currentRnd, uint64(part.LastValid), [32]byte{}, true) + assert.NoError(t, err) + require.Equal(t, sAccount, goOnlineTx.Src().String()) + onlineTxID, err := client.SignAndBroadcastTransaction(sWH, nil, goOnlineTx) + require.NoError(t, err) + require.NotEmpty(t, onlineTxID) + status, err := client.WaitForRound(currentRnd + 1) + require.NoError(t, err) + return status +} + +// In this test, we have five nodes, where we only need four to create a StateProof. +// After making the first Stateproof, we transfer three-quarters of the stake of the +// rich node to the poor node. For both cases, we assert different stakes, that is, to +// conclude whether the poor node is used to create the StateProof or the rich node. +func TestAttestorsChangeTest(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + + consensusParams := getDefaultStateProofConsensusParams() + // Stateproof can be generated even if not all nodes function correctly. e.g node can be offline + // and stateproofs might still get generated. in order to make sure that all nodes work correctly + // we want the network to fail in generating stateproof if one node is not working correctly. + // For that we will increase the proven Weight to be close to 100%. However, this change might not be enough. + // if the signed Weight and the Proven Weight are very close to each other the number of reveals in the state proof + // will exceed the MAX_NUMBER_OF_REVEALS and proofs would not get generated + // for that reason we need to the decrease the StateProofStrengthTarget creating a "weak cert" + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 4 + consensusParams.StateProofTopVoters = 4 + + configurableConsensus := config.ConsensusProtocols{ + protocol.ConsensusVersion("test-fast-stateproofs"): consensusParams, + } + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProof.json")) + defer fixture.Shutdown() + + var lastStateProofBlock bookkeeping.Block + var lastStateProofMessage stateproofmsg.Message + libgoal := fixture.LibGoalClient + + expectedNumberOfStateProofs := uint64(4) + // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs + + paymentMaker := paymentSender{ + from: accountFetcher{nodeName: "richNode", accountNumber: 0}, + to: accountFetcher{nodeName: "poorNode", accountNumber: 0}, + } + + for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + // Changing the amount to pay. This should transfer most of the money from the rich node to the poor node. + if consensusParams.StateProofInterval*2 == rnd { + balance := paymentMaker.from.getBalance(a, &fixture) + // ensuring that before the test, the rich node (from) has a significantly larger balance. + a.True(balance/2 > paymentMaker.to.getBalance(a, &fixture)) + + paymentMaker.amount = balance * 9 / 10 + paymentMaker.sendPayment(a, &fixture, rnd) + } + + // verifies that rich account transferred most of its money to the account that sits on poorNode. + if consensusParams.StateProofInterval*3 == rnd { + a.True(paymentMaker.to.getBalance(a, &fixture) > paymentMaker.from.getBalance(a, &fixture)) + } + + a.NoError(fixture.WaitForRound(rnd, timeoutUntilNextRound)) + blk, err := libgoal.BookkeepingBlock(rnd) + a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + a.True(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment) > 0) + a.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != basics.MicroAlgos{}) + + stake := blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.ToUint64() + + // the main part of the test (computing the total stake of the nodes): + sum := uint64(0) + for i := 1; i <= 3; i++ { + sum += accountFetcher{fmt.Sprintf("Node%d", i), 0}.getBalance(a, &fixture) + } + + richNodeStake := accountFetcher{"richNode", 0}.getBalance(a, &fixture) + poorNodeStake := accountFetcher{"poorNode", 0}.getBalance(a, &fixture) + sum = sum + richNodeStake + poorNodeStake + + a.Equal(sum, stake) + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } else { + a.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight == basics.MicroAlgos{}) + } + + for lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(a, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams, expectedNumberOfStateProofs) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + } + + a.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") +} + +func TestTotalWeightChanges(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + + consensusParams := getDefaultStateProofConsensusParams() + consensusParams.StateProofWeightThreshold = (1 << 32) * 90 / 100 + consensusParams.StateProofStrengthTarget = 4 + consensusParams.StateProofTopVoters = 4 + //consensusParams.StateProofInterval = 32 + + configurableConsensus := config.ConsensusProtocols{ + protocol.ConsensusVersion("test-fast-stateproofs"): consensusParams, + } + + var fixture fixtures.RestClientFixture + fixture.SetConsensus(configurableConsensus) + fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProof.json")) + defer fixture.Shutdown() + + var lastStateProofBlock bookkeeping.Block + var lastStateProofMessage stateproofmsg.Message + libgoal := fixture.LibGoalClient + + richNode := accountFetcher{nodeName: "richNode", accountNumber: 0} + + expectedNumberOfStateProofs := uint64(4) + // Loop through the rounds enough to check for expectedNumberOfStateProofs state proofs + + for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { + // Rich node goes offline + if consensusParams.StateProofInterval*2-8 == rnd { + // subtract 8 rounds since the total online stake is calculated prior to the actual state proof round (lookback) + richNode.goOffline(a, &fixture, rnd) + } + + a.NoError(fixture.WaitForRound(rnd, 30*time.Second)) + blk, err := libgoal.BookkeepingBlock(rnd) + a.NoErrorf(err, "failed to retrieve block from algod on round %d", rnd) + + if (rnd % consensusParams.StateProofInterval) == 0 { + // Must have a merkle commitment for participants + a.Greater(len(blk.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment), 0) + totalStake := blk.BlockHeader.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight.ToUint64() + a.NotEqual(basics.MicroAlgos{}, totalStake) + + if rnd <= consensusParams.StateProofInterval { + a.Equal(uint64(10000000000000000), totalStake) + } else { // richNode should be offline by now + a.Greater(uint64(10000000000000000), totalStake) + } + + // Special case: bootstrap validation with the first block + // that has a merkle root. + if lastStateProofBlock.Round() == 0 { + lastStateProofBlock = blk + } + } else { + a.True(blk.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight == basics.MicroAlgos{}) + } + + for lastStateProofBlock.Round()+basics.Round(consensusParams.StateProofInterval) < blk.StateProofTracking[protocol.StateProofBasic].StateProofNextRound && + lastStateProofBlock.Round() != 0 { + nextStateProofRound := uint64(lastStateProofBlock.Round()) + consensusParams.StateProofInterval + + t.Logf("found a state proof for round %d at round %d", nextStateProofRound, blk.Round()) + // Find the state proof transaction + stateProofMessage, nextStateProofBlock := verifyStateProofForRound(a, &fixture, nextStateProofRound, lastStateProofMessage, lastStateProofBlock, consensusParams, expectedNumberOfStateProofs) + lastStateProofMessage = stateProofMessage + lastStateProofBlock = nextStateProofBlock + } + } + + a.Equalf(int(consensusParams.StateProofInterval*expectedNumberOfStateProofs), int(lastStateProofBlock.Round()), "the expected last state proof block wasn't the one that was observed") +} + +// TestSPWithTXPoolFull makes sure a SP txn goes into the pool when the pool is full +func TestSPWithTXPoolFull(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + + var fixture fixtures.RestClientFixture + configurableConsensus := make(config.ConsensusProtocols) + consensusParams := getDefaultStateProofConsensusParams() + consensusParams.StateProofInterval = 4 + configurableConsensus[protocol.ConsensusFuture] = consensusParams + + fixture.SetConsensus(configurableConsensus) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "TwoNodes50EachFuture.json")) + + dir, err := fixture.GetNodeDir("Primary") + a.NoError(err) + + cfg, err := config.LoadConfigFromDisk(dir) + a.NoError(err) + cfg.TxPoolSize = 0 + cfg.SaveToDisk(dir) + + dir, err = fixture.GetNodeDir("Node") + a.NoError(err) + cfg.SaveToDisk(dir) + + fixture.Start() + defer fixture.Shutdown() + + relay := fixture.GetLibGoalClientForNamedNode("Primary") + + params, err := relay.SuggestedParams() + require.NoError(t, err) + + var genesisHash crypto.Digest + copy(genesisHash[:], params.GenesisHash) + + round := uint64(0) + for round < uint64(20) { + params, err = relay.SuggestedParams() + require.NoError(t, err) + + round = params.LastRound + err = fixture.WaitForRound(round+1, 6*time.Second) + require.NoError(t, err) + + b, err := relay.Block(round + 1) + require.NoError(t, err) + if len(b.Transactions.Transactions) == 0 { + continue + } + require.Equal(t, string(protocol.StateProofTx), b.Transactions.Transactions[0].Type) + var msg stateproofmsg.Message + err = protocol.Decode(b.Transactions.Transactions[0].StateProof.StateProofMessage, &msg) + require.NoError(t, err) + require.Equal(t, uint64(8), msg.LastAttestedRound) + break + } + require.Less(t, round, uint64(20)) +} + +// TestAtMostOneSPFullPool tests that there is at most one SP txn is admitted to the pool per roound +// when the pool is full. Note that the test sets TxPoolSize to 0 to simulate a full pool, which +// guarantees that no more than 1 SP txn get into a block. In normal configuration, it is +// possible to have multiple SPs getting into the same block when the pool is full. +func TestAtMostOneSPFullPool(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + + var fixture fixtures.RestClientFixture + configurableConsensus := make(config.ConsensusProtocols) + consensusParams := getDefaultStateProofConsensusParams() + consensusParams.StateProofInterval = 4 + configurableConsensus[protocol.ConsensusFuture] = consensusParams + + fixture.SetConsensus(configurableConsensus) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json")) + + dir, err := fixture.GetNodeDir("Primary") + a.NoError(err) + + cfg, err := config.LoadConfigFromDisk(dir) + a.NoError(err) + cfg.TxPoolSize = 0 + cfg.SaveToDisk(dir) + + fixture.Start() + defer fixture.Shutdown() + + relay := fixture.GetLibGoalClientForNamedNode("Primary") + + params, err := relay.SuggestedParams() + require.NoError(t, err) + + // Check that the first 2 stateproofs are added to the blockchain in different rounds + round := uint64(0) + expectedSPRound := consensusParams.StateProofInterval * 2 + for round < consensusParams.StateProofInterval*10 { + round = params.LastRound + + err := fixture.WaitForRound(round+1, 6*time.Second) + require.NoError(t, err) + + b, err := relay.Block(round + 1) + require.NoError(t, err) + + params, err = relay.SuggestedParams() + require.NoError(t, err) + if len(b.Transactions.Transactions) == 0 { + continue + } + tid := 0 + // Find a SP transaction in the block. The SP should be for StateProofIntervalLatestRound expectedSPRound + // Since the pool is full, only one additional SP transaction is allowed in. So only one SP can be added to be block + // break after finding it, and look for the next one in a subsequent block + // In case two SP transactions get into the same block, the following loop will not find the second one, and fail the test + for ; tid < len(b.Transactions.Transactions); tid++ { + if b.Transactions.Transactions[tid].Type == string(protocol.StateProofTx) { + require.Equal(t, string(protocol.StateProofTx), b.Transactions.Transactions[tid].Type) + + var msg stateproofmsg.Message + err = protocol.Decode(b.Transactions.Transactions[tid].StateProof.StateProofMessage, &msg) + require.NoError(t, err) + require.Equal(t, int(expectedSPRound), int(msg.LastAttestedRound)) + + expectedSPRound = expectedSPRound + consensusParams.StateProofInterval + break + } + } + if expectedSPRound == consensusParams.StateProofInterval*4 { + break + } + } + // If waited till round 20 and did not yet get the stateproof with last round 12, fail the test + require.Less(t, round, consensusParams.StateProofInterval*10) +} + +type specialAddr string + +func (a specialAddr) ToBeHashed() (protocol.HashID, []byte) { + return protocol.SpecialAddr, []byte(a) +} + +// TestSPWithCounterReset tests if the state proof transaction is getting into the pool and eventually +// at most one SP is getting into the block when the transaction pool is full. +// Bad SP and payment transaction traffic is added to increase the odds of getting SP txn into the pool +// in the same round. +func TestAtMostOneSPFullPoolWithLoad(t *testing.T) { + partitiontest.PartitionTest(t) + defer fixtures.ShutdownSynchronizedTest(t) + + a := require.New(fixtures.SynchronizedTest(t)) + + var fixture fixtures.RestClientFixture + configurableConsensus := make(config.ConsensusProtocols) + consensusParams := getDefaultStateProofConsensusParams() + consensusParams.StateProofInterval = 4 + configurableConsensus[protocol.ConsensusFuture] = consensusParams + + fixture.SetConsensus(configurableConsensus) + fixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNodeFuture.json")) + + dir, err := fixture.GetNodeDir("Primary") + a.NoError(err) + + cfg, err := config.LoadConfigFromDisk(dir) + a.NoError(err) + cfg.TxPoolSize = 0 + cfg.SaveToDisk(dir) + + fixture.Start() + defer fixture.Shutdown() + + relay := fixture.GetLibGoalClientForNamedNode("Primary") + + params, err := relay.SuggestedParams() + require.NoError(t, err) + + var genesisHash crypto.Digest + copy(genesisHash[:], params.GenesisHash) + + wg := sync.WaitGroup{} + var done uint32 + + defer func() { + atomic.StoreUint32(&done, uint32(1)) + wg.Wait() + }() + + stxn := getWellformedSPTransaction(params.LastRound+1, genesisHash, consensusParams, t) + + // Send well formed but bad stateproof transactions from two goroutines + for spSpam := 0; spSpam < 2; spSpam++ { + wg.Add(1) + go func() { + defer wg.Done() + for atomic.LoadUint32(&done) != 1 { + _, err := relay.BroadcastTransaction(stxn) + // The pool is full, and only one SP transaction will be admitted in per round. Otherwise, pool is full error will be returned + // However, if this is the lucky SP transaction to get into the pool, it will eventually be rejected by ValidateStateProof and a different + // error will be returned + require.Error(t, err) + time.Sleep(25 * time.Millisecond) + } + }() + } + + // Send payment transactions from two goroutines + for txnSpam := 0; txnSpam < 2; txnSpam++ { + wg.Add(1) + go func(amt uint64) { + defer wg.Done() + cntr := uint64(1) + params, err := relay.SuggestedParams() + require.NoError(t, err) + + ps := paymentSender{ + from: accountFetcher{nodeName: "Primary", accountNumber: 0}, + amount: amt, + } + account0 := ps.from.getAccount(a, &fixture) + + for atomic.LoadUint32(&done) != 1 { + ps.amount = cntr + cntr = cntr + 1 + // ignore the returned error (most of the time will be error) + _, err := relay.SendPaymentFromUnencryptedWallet(account0, account0, params.Fee, ps.amount, []byte{byte(params.LastRound)}) + require.Error(t, err) + require.Equal(t, "HTTP 400 Bad Request: TransactionPool.checkPendingQueueSize: transaction pool have reached capacity", err.Error()) + time.Sleep(25 * time.Millisecond) + } + }(uint64(txnSpam + 1)) + } + + // Check that the first 2 stateproofs are added to the blockchain + round := uint64(0) + expectedSPRound := consensusParams.StateProofInterval * 2 + for round < consensusParams.StateProofInterval*10 { + round = params.LastRound + + err := fixture.WaitForRound(round+1, 6*time.Second) + require.NoError(t, err) + + b, err := relay.Block(round + 1) + require.NoError(t, err) + + params, err = relay.SuggestedParams() + require.NoError(t, err) + if len(b.Transactions.Transactions) == 0 { + continue + } + tid := 0 + // Find a SP transaction in the block. The SP should be for StateProofIntervalLatestRound expectedSPRound + // Since the pool is full, only one additional SP transaction is allowed in. So only one SP can be added to be block + // break after finding it, and look for the next one in a subsequent block + // In case two SP transactions get into the same block, the following loop will not find the second one, and fail the test + for ; tid < len(b.Transactions.Transactions); tid++ { + if b.Transactions.Transactions[tid].Type == string(protocol.StateProofTx) { + require.Equal(t, string(protocol.StateProofTx), b.Transactions.Transactions[tid].Type) + + var msg stateproofmsg.Message + err = protocol.Decode(b.Transactions.Transactions[tid].StateProof.StateProofMessage, &msg) + require.NoError(t, err) + require.Equal(t, int(expectedSPRound), int(msg.LastAttestedRound)) + + expectedSPRound = expectedSPRound + consensusParams.StateProofInterval + break + } + } + if expectedSPRound == consensusParams.StateProofInterval*4 { + break + } + } + // Do not check if the SPs were added to the block. TestAtMostOneSPFullPool checks it. + // In some environments (ARM) the high load may prevent it. +} + +func getWellformedSPTransaction(round uint64, genesisHash crypto.Digest, consensusParams config.ConsensusParams, t *testing.T) (stxn transactions.SignedTxn) { + + msg := stateproofmsg.Message{} + proof := &sp.StateProof{} + proto := consensusParams + + stxn.Txn.Type = protocol.StateProofTx + stxn.Txn.Sender = transactions.StateProofSender + stxn.Txn.FirstValid = basics.Round(round) + stxn.Txn.LastValid = basics.Round(round + 1000) + stxn.Txn.GenesisHash = genesisHash + stxn.Txn.StateProofType = protocol.StateProofBasic + stxn.Txn.StateProof = *proof + stxn.Txn.Message = msg + + err := stxn.Txn.WellFormed(transactions.SpecialAddresses{}, proto) + require.NoError(t, err) + + return stxn +} diff --git a/test/e2e-go/features/transactions/proof_test.go b/test/e2e-go/features/transactions/proof_test.go index 9cbc151078..8de0dcb02c 100644 --- a/test/e2e-go/features/transactions/proof_test.go +++ b/test/e2e-go/features/transactions/proof_test.go @@ -23,7 +23,6 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklearray" - "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/framework/fixtures" "github.com/algorand/go-algorand/test/partitiontest" @@ -99,36 +98,19 @@ func TestTxnMerkleProof(t *testing.T) { blk, err := client.BookkeepingBlock(confirmedTx.ConfirmedRound) a.NoError(err) - proofresp, err := client.TxnProof(txid.String(), confirmedTx.ConfirmedRound, crypto.Sha512_256) + proofresp, proof, err := fixture.TransactionProof(txid.String(), confirmedTx.ConfirmedRound, crypto.Sha512_256) a.NoError(err) - proofrespSHA256, err := client.TxnProof(txid.String(), confirmedTx.ConfirmedRound, crypto.Sha256) + proofrespSHA256, proofSHA256, err := fixture.TransactionProof(txid.String(), confirmedTx.ConfirmedRound, crypto.Sha256) a.NoError(err) - generateProof := func(h crypto.HashType, prfRsp generated.ProofResponse) (p merklearray.Proof) { - p.HashFactory = crypto.HashFactory{HashType: h} - p.TreeDepth = uint8(prfRsp.Treedepth) - a.NotEqual(p.TreeDepth, 0) - proofconcat := prfRsp.Proof - for len(proofconcat) > 0 { - var d crypto.Digest - copy(d[:], proofconcat) - p.Path = append(p.Path, d[:]) - proofconcat = proofconcat[len(d):] - } - return - } - - proof := generateProof(crypto.Sha512_256, proofresp) - proofSHA256 := generateProof(crypto.Sha256, proofrespSHA256) - element := TxnMerkleElemRaw{Txn: crypto.Digest(txid)} copy(element.Stib[:], proofresp.Stibhash[:]) elems := make(map[uint64]crypto.Hashable) elems[proofresp.Idx] = &element - err = merklearray.Verify(blk.TxnCommitments.NativeSha512_256Commitment.ToSlice(), elems, &proof) + err = merklearray.Verify(blk.TxnCommitments.NativeSha512_256Commitment.ToSlice(), elems, proof.ToProof()) if err != nil { t.Logf("blk.TxnCommitments : %v \nproof path %v \ndepth: %d \nStibhash %v\nIndex: %d", blk.TxnCommitments.NativeSha512_256Commitment.ToSlice(), proof.Path, proof.TreeDepth, proofresp.Stibhash, proofresp.Idx) a.NoError(err) @@ -140,7 +122,7 @@ func TestTxnMerkleProof(t *testing.T) { elems = make(map[uint64]crypto.Hashable) elems[proofrespSHA256.Idx] = &element - err = merklearray.VerifyVectorCommitment(blk.TxnCommitments.Sha256Commitment.ToSlice(), elems, &proofSHA256) + err = merklearray.VerifyVectorCommitment(blk.TxnCommitments.Sha256Commitment.ToSlice(), elems, proofSHA256.ToProof()) if err != nil { t.Logf("blk.TxnCommitments : %v \nproof path %v \ndepth: %d \nStibhash %v\nIndex: %d", blk.TxnCommitments.Sha256Commitment.ToSlice(), proofSHA256.Path, proofSHA256.TreeDepth, proofrespSHA256.Stibhash, proofrespSHA256.Idx) a.NoError(err) diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index dee64d399e..3506b8b660 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -554,7 +554,8 @@ func TestAccountParticipationInfo(t *testing.T) { lastRound := basics.Round(params.LastRound + 1000) dilution := uint64(100) var stateproof merklesignature.Verifier - stateproof[0] = 1 // change some byte so the stateproof is not considered empty (required since consensus v31) + stateproof.KeyLifetime = merklesignature.KeyLifetimeDefault + stateproof.Commitment[0] = 1 // change some byte so the stateproof is not considered empty (required since consensus v31) randomVotePKStr := randomString(32) var votePK crypto.OneTimeSignatureVerifier @@ -579,7 +580,7 @@ func TestAccountParticipationInfo(t *testing.T) { VoteKeyDilution: dilution, VoteFirst: firstRound, VoteLast: lastRound, - StateProofPK: stateproof, + StateProofPK: stateproof.Commitment, }, } txID, err := testClient.SignAndBroadcastTransaction(wh, nil, tx) @@ -1160,7 +1161,7 @@ func TestStateProofParticipationKeysAPI(t *testing.T) { actual := [merklesignature.MerkleSignatureSchemeRootSize]byte{} a.NotNil(pRoot[0].Key.StateProofKey) copy(actual[:], *pRoot[0].Key.StateProofKey) - a.Equal(partkey.StateProofSecrets.GetVerifier()[:], actual[:]) + a.Equal(partkey.StateProofSecrets.GetVerifier().Commitment[:], actual[:]) } func TestNilStateProofInParticipationInfo(t *testing.T) { diff --git a/test/e2e-go/upgrades/stateproof_test.go b/test/e2e-go/upgrades/stateproof_participation_test.go similarity index 100% rename from test/e2e-go/upgrades/stateproof_test.go rename to test/e2e-go/upgrades/stateproof_participation_test.go diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index e6e90b47c5..34c8276ede 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -31,6 +31,9 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/merklearray" + generatedV2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/gen" "github.com/algorand/go-algorand/libgoal" @@ -499,3 +502,34 @@ func (f *LibGoalFixture) MinFeeAndBalance(round uint64) (minFee, minBalance uint } return params.MinTxnFee, minBalance, nil } + +// TransactionProof returns a proof for usage in merkle array verification for the provided transaction. +func (f *LibGoalFixture) TransactionProof(txid string, round uint64, hashType crypto.HashType) (generatedV2.ProofResponse, merklearray.SingleLeafProof, error) { + proofResp, err := f.LibGoalClient.TransactionProof(txid, round, hashType) + if err != nil { + return generatedV2.ProofResponse{}, merklearray.SingleLeafProof{}, err + } + + proof, err := merklearray.ProofDataToSingleLeafProof(proofResp.Hashtype, proofResp.Treedepth, proofResp.Proof) + if err != nil { + return generatedV2.ProofResponse{}, merklearray.SingleLeafProof{}, err + } + + return proofResp, proof, nil +} + +// LightBlockHeaderProof returns a proof for usage in merkle array verification for the provided block's light block header. +func (f *LibGoalFixture) LightBlockHeaderProof(round uint64) (generatedV2.LightBlockHeaderProofResponse, merklearray.SingleLeafProof, error) { + proofResp, err := f.LibGoalClient.LightBlockHeaderProof(round) + + if err != nil { + return generatedV2.LightBlockHeaderProofResponse{}, merklearray.SingleLeafProof{}, err + } + + proof, err := merklearray.ProofDataToSingleLeafProof(crypto.Sha256.String(), proofResp.Treedepth, proofResp.Proof) + if err != nil { + return generatedV2.LightBlockHeaderProofResponse{}, merklearray.SingleLeafProof{}, err + } + + return proofResp, proof, nil +} diff --git a/test/testdata/nettemplates/CompactCert.json b/test/testdata/nettemplates/CompactCert.json deleted file mode 100644 index f4f86e2612..0000000000 --- a/test/testdata/nettemplates/CompactCert.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "Genesis": { - "NetworkName": "tbd", - "ConsensusProtocol": "test-fast-compactcert", - "LastPartKeyRound": 3000, - "Wallets": [ - { "Name": "Wallet0", "Stake": 10, "Online": true }, - { "Name": "Wallet1", "Stake": 10, "Online": true }, - { "Name": "Wallet2", "Stake": 10, "Online": true }, - { "Name": "Wallet3", "Stake": 10, "Online": true }, - { "Name": "Wallet4", "Stake": 10, "Online": true }, - { "Name": "Wallet5", "Stake": 10, "Online": true }, - { "Name": "Wallet6", "Stake": 10, "Online": true }, - { "Name": "Wallet7", "Stake": 10, "Online": true }, - { "Name": "Wallet8", "Stake": 10, "Online": true }, - { "Name": "Wallet9", "Stake": 10, "Online": true } - ] - }, - "Nodes": [ - { - "Name": "Relay0", - "IsRelay": true, - "Wallets": [] - }, - { - "Name": "Relay1", - "IsRelay": true, - "Wallets": [] - }, - { "Name": "Node0", "Wallets": [ { "Name": "Wallet0", "ParticipationOnly": false } ] }, - { "Name": "Node1", "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } ] }, - { "Name": "Node2", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } ] }, - { "Name": "Node3", "Wallets": [ { "Name": "Wallet3", "ParticipationOnly": false } ] }, - { "Name": "Node4", "Wallets": [ { "Name": "Wallet4", "ParticipationOnly": false } ] }, - { "Name": "Node5", "Wallets": [ { "Name": "Wallet5", "ParticipationOnly": false } ] }, - { "Name": "Node6", "Wallets": [ { "Name": "Wallet6", "ParticipationOnly": false } ] }, - { "Name": "Node7", "Wallets": [ { "Name": "Wallet7", "ParticipationOnly": false } ] }, - { "Name": "Node8", "Wallets": [ { "Name": "Wallet8", "ParticipationOnly": false } ] }, - { "Name": "Node9", "Wallets": [ { "Name": "Wallet9", "ParticipationOnly": false } ] } - ] -} diff --git a/test/testdata/nettemplates/RichAccountStateProof.json b/test/testdata/nettemplates/RichAccountStateProof.json new file mode 100644 index 0000000000..e908ec807d --- /dev/null +++ b/test/testdata/nettemplates/RichAccountStateProof.json @@ -0,0 +1,31 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "test-fast-stateproofs", + "LastPartKeyRound": 100, + "Wallets": [ + { "Name": "richWallet", "Stake": 39, "Online": true }, + { "Name": "Wallet1", "Stake": 20, "Online": true }, + { "Name": "Wallet2", "Stake": 20, "Online": true }, + { "Name": "Wallet3", "Stake": 20, "Online": true }, + { "Name": "poorWallet", "Stake": 1, "Online": true } + ] + }, + "Nodes": [ + { + "Name": "Relay0", + "IsRelay": true, + "Wallets": [] + }, + { + "Name": "Relay1", + "IsRelay": true, + "Wallets": [] + }, + { "Name": "richNode", "Wallets": [ { "Name": "richWallet", "ParticipationOnly": false } ] }, + { "Name": "Node1", "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } ] }, + { "Name": "Node2", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } ] }, + { "Name": "Node3", "Wallets": [ { "Name": "Wallet3", "ParticipationOnly": false } ] }, + { "Name": "poorNode", "Wallets": [ { "Name": "poorWallet", "ParticipationOnly": false } ] } + ] +} diff --git a/test/testdata/nettemplates/StateProof.json b/test/testdata/nettemplates/StateProof.json new file mode 100644 index 0000000000..1194af6434 --- /dev/null +++ b/test/testdata/nettemplates/StateProof.json @@ -0,0 +1,31 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "test-fast-stateproofs", + "LastPartKeyRound": 100, + "Wallets": [ + { "Name": "Wallet0", "Stake": 20, "Online": true }, + { "Name": "Wallet1", "Stake": 20, "Online": true }, + { "Name": "Wallet2", "Stake": 20, "Online": true }, + { "Name": "Wallet3", "Stake": 20, "Online": true }, + { "Name": "Wallet4", "Stake": 20, "Online": true } + ] + }, + "Nodes": [ + { + "Name": "Relay0", + "IsRelay": true, + "Wallets": [] + }, + { + "Name": "Relay1", + "IsRelay": true, + "Wallets": [] + }, + { "Name": "Node0", "Wallets": [ { "Name": "Wallet0", "ParticipationOnly": false } ] }, + { "Name": "Node1", "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } ] }, + { "Name": "Node2", "Wallets": [ { "Name": "Wallet2", "ParticipationOnly": false } ] }, + { "Name": "Node3", "Wallets": [ { "Name": "Wallet3", "ParticipationOnly": false } ] }, + { "Name": "Node4", "Wallets": [ { "Name": "Wallet4", "ParticipationOnly": false } ] } + ] +} diff --git a/test/testdata/nettemplates/StateProofMultiWallets.json b/test/testdata/nettemplates/StateProofMultiWallets.json new file mode 100644 index 0000000000..e71bd14917 --- /dev/null +++ b/test/testdata/nettemplates/StateProofMultiWallets.json @@ -0,0 +1,63 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "test-fast-stateproofs", + "LastPartKeyRound": 3000, + "Wallets": [ + { "Name": "Wallet0", "Stake": 5, "Online": true }, + { "Name": "Wallet1", "Stake": 5, "Online": true }, + { "Name": "Wallet2", "Stake": 5, "Online": true }, + { "Name": "Wallet3", "Stake": 5, "Online": true }, + { "Name": "Wallet4", "Stake": 5, "Online": true }, + { "Name": "Wallet5", "Stake": 5, "Online": true }, + { "Name": "Wallet6", "Stake": 5, "Online": true }, + { "Name": "Wallet7", "Stake": 5, "Online": true }, + { "Name": "Wallet8", "Stake": 5, "Online": true }, + { "Name": "Wallet9", "Stake": 5, "Online": true }, + { "Name": "Wallet10", "Stake": 5, "Online": true }, + { "Name": "Wallet11", "Stake": 5, "Online": true }, + { "Name": "Wallet12", "Stake": 5, "Online": true }, + { "Name": "Wallet13", "Stake": 5, "Online": true }, + { "Name": "Wallet14", "Stake": 5, "Online": true }, + { "Name": "Wallet15", "Stake": 5, "Online": true }, + { "Name": "Wallet16", "Stake": 5, "Online": true }, + { "Name": "Wallet17", "Stake": 5, "Online": true }, + { "Name": "Wallet18", "Stake": 5, "Online": true }, + { "Name": "Wallet19", "Stake": 5, "Online": true } + ] + }, + "Nodes": [ + { + "Name": "Relay0", + "IsRelay": true, + "Wallets": [] + }, + { + "Name": "Relay1", + "IsRelay": true, + "Wallets": [] + }, + { "Name": "Node0", "Wallets": [ + { "Name": "Wallet0", "ParticipationOnly": false }, + { "Name": "Wallet1", "ParticipationOnly": false }, + { "Name": "Wallet2", "ParticipationOnly": false }, + { "Name": "Wallet3", "ParticipationOnly": false }, + { "Name": "Wallet4", "ParticipationOnly": false }, + { "Name": "Wallet5", "ParticipationOnly": false }, + { "Name": "Wallet6", "ParticipationOnly": false }, + { "Name": "Wallet7", "ParticipationOnly": false }, + { "Name": "Wallet8", "ParticipationOnly": false }, + { "Name": "Wallet9", "ParticipationOnly": false } ] }, + { "Name": "Node1", "Wallets": [ + { "Name": "Wallet10", "ParticipationOnly": false }, + { "Name": "Wallet11", "ParticipationOnly": false }, + { "Name": "Wallet12", "ParticipationOnly": false }, + { "Name": "Wallet13", "ParticipationOnly": false }, + { "Name": "Wallet14", "ParticipationOnly": false }, + { "Name": "Wallet15", "ParticipationOnly": false }, + { "Name": "Wallet16", "ParticipationOnly": false }, + { "Name": "Wallet17", "ParticipationOnly": false }, + { "Name": "Wallet18", "ParticipationOnly": false }, + { "Name": "Wallet19", "ParticipationOnly": false }] } + ] +} diff --git a/tools/debug/algodump/main.go b/tools/debug/algodump/main.go index b5f95a1be4..e3ebba9223 100644 --- a/tools/debug/algodump/main.go +++ b/tools/debug/algodump/main.go @@ -140,7 +140,7 @@ func setDumpHandlers(n network.GossipNode) { h := []network.TaggedMessageHandler{ {Tag: protocol.AgreementVoteTag, MessageHandler: &dh}, - {Tag: protocol.CompactCertSigTag, MessageHandler: &dh}, + {Tag: protocol.StateProofSigTag, MessageHandler: &dh}, {Tag: protocol.MsgOfInterestTag, MessageHandler: &dh}, {Tag: protocol.MsgDigestSkipTag, MessageHandler: &dh}, {Tag: protocol.NetPrioResponseTag, MessageHandler: &dh}, From 566be49a6e76816b392058a7f27c4ffca296a877 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 9 Aug 2022 11:52:08 -0400 Subject: [PATCH 03/24] AVM: Make `txn FirstValidTime` and `block` opcode available in logicsigs (#4371) --- cmd/goal/clerk.go | 5 +- cmd/tealdbg/debugger_test.go | 1 + cmd/tealdbg/local.go | 1 + daemon/algod/api/server/v2/dryrun.go | 1 + data/transactions/logic/assembler.go | 2 +- data/transactions/logic/eval.go | 40 +++++++++-- data/transactions/logic/evalStateful_test.go | 24 +++++-- data/transactions/logic/eval_test.go | 12 ++-- data/transactions/verify/txn.go | 18 +++-- data/transactions/verify/txn_test.go | 22 +++--- .../verify/verifiedTxnCache_test.go | 12 ++-- data/txHandler.go | 4 +- ledger/applications_test.go | 67 +++++++++++++++++++ ledger/internal/eval.go | 4 +- node/node.go | 2 +- test/scripts/e2e_subs/hdr-access-logicsig.sh | 63 +++++++++++++++++ test/scripts/e2e_subs/hdr-access.py | 41 ++++++------ 17 files changed, 250 insertions(+), 69 deletions(-) create mode 100755 test/scripts/e2e_subs/hdr-access-logicsig.sh diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 75dc372c2c..856662147b 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -424,7 +424,7 @@ var sendCmd = &cobra.Command{ CurrentProtocol: proto, }, } - groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, blockHeader) + groupCtx, err := verify.PrepareGroupContext([]transactions.SignedTxn{uncheckedTxn}, blockHeader, nil) if err == nil { err = verify.LogicSigSanityCheck(&uncheckedTxn, 0, groupCtx) } @@ -825,7 +825,7 @@ var signCmd = &cobra.Command{ } var groupCtx *verify.GroupContext if lsig.Logic != nil { - groupCtx, err = verify.PrepareGroupContext(txnGroup, contextHdr) + groupCtx, err = verify.PrepareGroupContext(txnGroup, contextHdr, nil) if err != nil { // this error has to be unsupported protocol reportErrorf("%s: %v", txFilename, err) @@ -1162,6 +1162,7 @@ var dryrunCmd = &cobra.Command{ reportErrorf("program size too large: %d > %d", len(txn.Lsig.Logic), params.LogicSigMaxSize) } ep := logic.NewEvalParams(txgroup, ¶ms, nil) + ep.SigLedger = logic.NoHeaderLedger{} err := logic.CheckSignature(i, ep) if err != nil { reportErrorf("program failed Check: %s", err) diff --git a/cmd/tealdbg/debugger_test.go b/cmd/tealdbg/debugger_test.go index 4a390d461d..4f4b35ea20 100644 --- a/cmd/tealdbg/debugger_test.go +++ b/cmd/tealdbg/debugger_test.go @@ -103,6 +103,7 @@ func TestDebuggerSimple(t *testing.T) { ep := logic.NewEvalParams(make([]transactions.SignedTxnWithAD, 1), &proto, nil) ep.Debugger = debugger + ep.SigLedger = logic.NoHeaderLedger{} source := `int 0 int 1 diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go index 8e92f66fd6..c9cba4de39 100644 --- a/cmd/tealdbg/local.go +++ b/cmd/tealdbg/local.go @@ -545,6 +545,7 @@ func (r *LocalRunner) RunAll() error { start := time.Now() ep := logic.NewEvalParams(txngroup, &r.proto, &transactions.SpecialAddresses{}) + ep.SigLedger = logic.NoHeaderLedger{} configureDebugger(ep) var last error diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 4a27470ce2..3079a71aa1 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -418,6 +418,7 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) { if len(stxn.Lsig.Logic) > 0 { var debug dryrunDebugReceiver ep.Debugger = &debug + ep.SigLedger = &dl pass, err := logic.EvalSignature(ti, ep) var messages []string result.Disassembly = debug.lines // Keep backwards compat diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index fcd56d2c7c..e175a17034 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2269,7 +2269,7 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) { } if strings.HasPrefix(spec.Name, "bytec_") { b := spec.Name[len(spec.Name)-1] - byte('0') - if int(b) < len(dis.intc) { + if int(b) < len(dis.bytec) { out += fmt.Sprintf(" // %s", guessByteFormat(dis.bytec[b])) } } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 7334485d9f..b648ff7781 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -195,6 +195,23 @@ func ComputeMinAvmVersion(group []transactions.SignedTxnWithAD) uint64 { return minVersion } +// LedgerForSignature represents the parts of Ledger that LogicSigs can see. It +// only exposes things that consensus has already agreed upon, so it is +// "stateless" for signature purposes. +type LedgerForSignature interface { + BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) +} + +// NoHeaderLedger is intended for debugging situations in which it is reasonable +// to preclude the use of `block` and `txn LastValidTime` +type NoHeaderLedger struct { +} + +// BlockHdrCached always errors +func (NoHeaderLedger) BlockHdrCached(basics.Round) (bookkeeping.BlockHeader, error) { + return bookkeeping.BlockHeader{}, fmt.Errorf("no block header access") +} + // LedgerForLogic represents ledger API for Stateful TEAL program type LedgerForLogic interface { AccountData(addr basics.Address) (ledgercore.AccountData, error) @@ -239,7 +256,8 @@ type EvalParams struct { logger logging.Logger - Ledger LedgerForLogic + SigLedger LedgerForSignature + Ledger LedgerForLogic // optional debugger Debugger DebuggerHook @@ -387,6 +405,7 @@ func NewInnerEvalParams(txg []transactions.SignedTxnWithAD, caller *EvalContext) Specials: caller.Specials, PooledApplicationBudget: caller.PooledApplicationBudget, pooledAllowedInners: caller.pooledAllowedInners, + SigLedger: caller.SigLedger, Ledger: caller.Ledger, created: caller.created, appAddrCache: caller.appAddrCache, @@ -610,6 +629,9 @@ func EvalContract(program []byte, gi int, aid basics.AppIndex, params *EvalParam if params.Ledger == nil { return false, nil, errors.New("no ledger in contract eval") } + if params.SigLedger == nil { + params.SigLedger = params.Ledger + } if aid == 0 { return false, nil, errors.New("0 appId in contract eval") } @@ -652,6 +674,9 @@ func EvalApp(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (b // EvalSignature evaluates the logicsig of the ith transaction in params. // A program passes successfully if it finishes with one int element on the stack that is non-zero. func EvalSignature(gi int, params *EvalParams) (pass bool, err error) { + if params.SigLedger == nil { + return false, errors.New("no sig ledger in signature eval") + } cx := EvalContext{ EvalParams: params, runModeFlags: modeSig, @@ -2304,7 +2329,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t if err != nil { return sv, err } - hdr, err := cx.Ledger.BlockHdrCached(rnd) + hdr, err := cx.SigLedger.BlockHdrCached(rnd) if err != nil { return sv, err } @@ -4848,10 +4873,13 @@ func (cx *EvalContext) availableRound(r uint64) (basics.Round, error) { if firstAvail > cx.txn.Txn.LastValid || firstAvail == 0 { // early in chain's life firstAvail = 1 } - current := cx.Ledger.Round() + lastAvail := cx.txn.Txn.FirstValid - 1 + if lastAvail > cx.txn.Txn.FirstValid { // txn had a 0 in FirstValid + lastAvail = 0 // So nothing will be available + } round := basics.Round(r) - if round < firstAvail || round >= current { - return 0, fmt.Errorf("round %d is not available. It's outside [%d-%d]", r, firstAvail, current-1) + if firstAvail > round || round > lastAvail { + return 0, fmt.Errorf("round %d is not available. It's outside [%d-%d]", r, firstAvail, lastAvail) } return round, nil } @@ -4868,7 +4896,7 @@ func opBlock(cx *EvalContext) error { return fmt.Errorf("invalid block field %s", f) } - hdr, err := cx.Ledger.BlockHdrCached(round) + hdr, err := cx.SigLedger.BlockHdrCached(round) if err != nil { return err } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 820f52ff49..3e051fc6be 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -52,6 +52,7 @@ func makeSampleEnvWithVersion(version uint64) (*EvalParams, *transactions.Transa ep := defaultEvalParamsWithVersion(nil, version) ep.TxnGroup = transactions.WrapSignedTxnsWithAD(makeSampleTxnGroup(makeSampleTxn())) ledger := MakeLedger(map[basics.Address]uint64{}) + ep.SigLedger = ledger ep.Ledger = ledger return ep, &ep.TxnGroup[0].Txn, ledger } @@ -2372,7 +2373,7 @@ func TestReturnTypes(t *testing.T) { "base64_decode": `: byte "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdEncoding`, "json_ref": `: byte "{\"k\": 7}"; byte "k"; json_ref JSONUint64`, - "block": ": int 4294967200; block BlkSeed", + "block": "block BlkSeed", } /* Make sure the specialCmd tests the opcode in question */ @@ -2537,19 +2538,23 @@ func TestLatestTimestamp(t *testing.T) { func TestBlockSeed(t *testing.T) { ep, txn, l := makeSampleEnv() - // makeSampleENv creates txns with fv, lv that don't actually fit the round - // in l. Nothing in most tests cares. But the rule for `block` is - // related to lv and the current round, so we set the fv,lv more - // realistically. + // makeSampleEnv creates txns with fv, lv that don't actually fit the round + // in l. Nothing in most tests cares. But the rule for `block` is related + // to lv and fv, so we set the fv,lv more realistically. txn.FirstValid = l.round() - 10 txn.LastValid = l.round() + 10 + // Keep in mind that proto.MaxTxnLife is 1500 in the test proto + // l.round() is 0xffffffff+5 = 4294967300 in test ledger - testApp(t, "int 4294967299; block BlkSeed; len; int 32; ==", ep) // current - 1 + + // These first two tests show that current-1 is not available now, though a + // resonable extension is to allow such access for apps (not sigs). + testApp(t, "int 4294967299; block BlkSeed; len; int 32; ==", ep, + "not available") // current - 1 testApp(t, "int 4294967300; block BlkSeed; len; int 32; ==", ep, "not available") // can't get current round's blockseed - // proto.MaxTxnLife is 1500 in test. testApp(t, "int 4294967300; int 1500; -; block BlkSeed; len; int 32; ==", ep, "not available") // 1500 back from current is more than 1500 back from lv testApp(t, "int 4294967310; int 1500; -; block BlkSeed; len; int 32; ==", ep) // 1500 back from lv is legal @@ -2560,6 +2565,11 @@ func TestBlockSeed(t *testing.T) { // A little silly, as it only tests the test ledger: ensure samenes and differentness testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff0; block BlkSeed; ==", ep) testApp(t, "int 0xfffffff0; block BlkSeed; int 0xfffffff1; block BlkSeed; !=", ep) + + // `block` should also work in LogicSigs, to drive home the point, blot out + // the normal Ledger + ep.Ledger = nil + testLogic(t, "int 0xfffffff0; block BlkTimestamp", randomnessVersion, ep) } func TestCurrentApplicationID(t *testing.T) { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 6ad3340a85..734f4ba6c9 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -131,6 +131,7 @@ func defaultEvalParamsWithVersion(txn *transactions.SignedTxn, version uint64) * Specials: &transactions.SpecialAddresses{}, Trace: &strings.Builder{}, FeeCredit: &zero, + SigLedger: MakeLedger(nil), } if txn != nil { ep.TxnGroup[0].SignedTxn = *txn @@ -252,16 +253,18 @@ func TestTxnFirstValidTime(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - // txn FirstValidTime is unusual. It's not really a field of a txn, but - // since it looks at the past of the blockchain, it is "stateless", in the - // sense that the value can not change, so it is available in logicsigs - ep, tx, ledger := makeSampleEnv() // By default, test ledger uses an oddball round, ask it what round it's // going to use and prep fv, lv accordingly. current := ledger.Round() + // txn FirstValidTime is unusual. It's not really a field of a txn, but + // since it looks at the past of the blockchain, it is "stateless" + + // Kill off ep.Ledger, to confirm it's not being used + ep.Ledger = nil + tx.FirstValid = current - 10 tx.LastValid = current + 10 testLogic(t, "txn FirstValidTime", 7, ep) @@ -2689,6 +2692,7 @@ int 1`, Proto: makeTestProto(), TxnGroup: txgroup, pastScratch: make([]*scratchSpace, 2), + SigLedger: MakeLedger(nil), } switch failCase.runMode { diff --git a/data/transactions/verify/txn.go b/data/transactions/verify/txn.go index 05d54caa39..1d947d31ae 100644 --- a/data/transactions/verify/txn.go +++ b/data/transactions/verify/txn.go @@ -66,11 +66,12 @@ type GroupContext struct { consensusParams config.ConsensusParams minAvmVersion uint64 signedGroupTxns []transactions.SignedTxn + ledger logic.LedgerForSignature } // PrepareGroupContext prepares a verification group parameter object for a given transaction // group. -func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader) (*GroupContext, error) { +func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, ledger logic.LedgerForSignature) (*GroupContext, error) { if len(group) == 0 { return nil, nil } @@ -87,6 +88,7 @@ func PrepareGroupContext(group []transactions.SignedTxn, contextHdr bookkeeping. consensusParams: consensusParams, minAvmVersion: logic.ComputeMinAvmVersion(transactions.WrapSignedTxnsWithAD(group)), signedGroupTxns: group, + ledger: ledger, }, nil } @@ -132,10 +134,10 @@ func TxnBatchVerify(s *transactions.SignedTxn, txnIdx int, groupCtx *GroupContex } // TxnGroup verifies a []SignedTxn as being signed and having no obviously inconsistent data. -func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache) (groupCtx *GroupContext, err error) { +func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (groupCtx *GroupContext, err error) { batchVerifier := crypto.MakeBatchVerifier() - if groupCtx, err = TxnGroupBatchVerify(stxs, contextHdr, cache, batchVerifier); err != nil { + if groupCtx, err = TxnGroupBatchVerify(stxs, contextHdr, cache, ledger, batchVerifier); err != nil { return nil, err } @@ -152,8 +154,8 @@ func TxnGroup(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, // TxnGroupBatchVerify verifies a []SignedTxn having no obviously inconsistent data. // it is the caller responsibility to call batchVerifier.verify() -func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) { - groupCtx, err = PrepareGroupContext(stxs, contextHdr) +func TxnGroupBatchVerify(stxs []transactions.SignedTxn, contextHdr bookkeeping.BlockHeader, cache VerifiedTransactionCache, ledger logic.LedgerForSignature, verifier *crypto.BatchVerifier) (groupCtx *GroupContext, err error) { + groupCtx, err = PrepareGroupContext(stxs, contextHdr, ledger) if err != nil { return nil, err } @@ -293,6 +295,7 @@ func LogicSigSanityCheckBatchVerify(txn *transactions.SignedTxn, groupIndex int, Proto: &groupCtx.consensusParams, TxnGroup: txngroup, MinAvmVersion: &groupCtx.minAvmVersion, + SigLedger: groupCtx.ledger, // won't be needed for CheckSignature } err := logic.CheckSignature(groupIndex, &ep) if err != nil { @@ -348,6 +351,7 @@ func logicSigBatchVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx * Proto: &groupCtx.consensusParams, TxnGroup: transactions.WrapSignedTxnsWithAD(groupCtx.signedGroupTxns), MinAvmVersion: &groupCtx.minAvmVersion, + SigLedger: groupCtx.ledger, } pass, err := logic.EvalSignature(groupIndex, &ep) if err != nil { @@ -369,7 +373,7 @@ func logicSigBatchVerify(txn *transactions.SignedTxn, groupIndex int, groupCtx * // a PaysetGroups may be well-formed, but a payset might contain an overspend. // // This version of verify is performing the verification over the provided execution pool. -func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHeader bookkeeping.BlockHeader, verificationPool execpool.BacklogPool, cache VerifiedTransactionCache) (err error) { +func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHeader bookkeeping.BlockHeader, verificationPool execpool.BacklogPool, cache VerifiedTransactionCache, ledger logic.LedgerForSignature) (err error) { if len(payset) == 0 { return nil } @@ -406,7 +410,7 @@ func PaysetGroups(ctx context.Context, payset [][]transactions.SignedTxn, blkHea batchVerifier := crypto.MakeBatchVerifierWithHint(len(payset)) for i, signTxnsGrp := range txnGroups { - groupCtxs[i], grpErr = TxnGroupBatchVerify(signTxnsGrp, blkHeader, nil, batchVerifier) + groupCtxs[i], grpErr = TxnGroupBatchVerify(signTxnsGrp, blkHeader, nil, ledger, batchVerifier) // abort only if it's a non-cache error. if grpErr != nil { return grpErr diff --git a/data/transactions/verify/txn_test.go b/data/transactions/verify/txn_test.go index 52cf298743..3998352114 100644 --- a/data/transactions/verify/txn_test.go +++ b/data/transactions/verify/txn_test.go @@ -114,7 +114,7 @@ func TestSignedPayment(t *testing.T) { payments, stxns, secrets, addrs := generateTestObjects(1, 1, 0) payment, stxn, secret, addr := payments[0], stxns[0], secrets[0], addrs[0] - groupCtx, err := PrepareGroupContext(stxns, blockHeader) + groupCtx, err := PrepareGroupContext(stxns, blockHeader, nil) require.NoError(t, err) require.NoError(t, payment.WellFormed(spec, proto), "generateTestObjects generated an invalid payment") require.NoError(t, Txn(&stxn, 0, groupCtx), "generateTestObjects generated a bad signedtxn") @@ -135,7 +135,7 @@ func TestTxnValidationEncodeDecode(t *testing.T) { _, signed, _, _ := generateTestObjects(100, 50, 0) for _, txn := range signed { - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) require.NoError(t, err) if Txn(&txn, 0, groupCtx) != nil { t.Errorf("signed transaction %#v did not verify", txn) @@ -157,7 +157,7 @@ func TestTxnValidationEmptySig(t *testing.T) { _, signed, _, _ := generateTestObjects(100, 50, 0) for _, txn := range signed { - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{txn}, blockHeader, nil) require.NoError(t, err) if Txn(&txn, 0, groupCtx) != nil { t.Errorf("signed transaction %#v did not verify", txn) @@ -202,7 +202,7 @@ func TestTxnValidationStateProof(t *testing.T) { }, } - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{stxn}, blockHeader, nil) require.NoError(t, err) err = Txn(&stxn, 0, groupCtx) @@ -256,7 +256,7 @@ func TestDecodeNil(t *testing.T) { err := protocol.Decode(nilEncoding, &st) if err == nil { // This used to panic when run on a zero value of SignedTxn. - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{st}, blockHeader, nil) require.NoError(t, err) Txn(&st, 0, groupCtx) } @@ -285,17 +285,17 @@ func TestPaysetGroups(t *testing.T) { txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) startPaysetGroupsTime := time.Now() - err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000)) + err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil) require.NoError(t, err) paysetGroupDuration := time.Now().Sub(startPaysetGroupsTime) // break the signature and see if it fails. txnGroups[0][0].Sig[0] = txnGroups[0][0].Sig[0] + 1 - err = PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000)) + err = PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil) require.Error(t, err) // ensure the rest are fine - err = PaysetGroups(context.Background(), txnGroups[1:], blkHdr, verificationPool, MakeVerifiedTransactionCache(50000)) + err = PaysetGroups(context.Background(), txnGroups[1:], blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil) require.NoError(t, err) // test the context cancelation: @@ -312,7 +312,7 @@ func TestPaysetGroups(t *testing.T) { go func() { defer close(waitCh) cache := MakeVerifiedTransactionCache(50000) - waitCh <- PaysetGroups(ctx, txnGroups, blkHdr, verificationPool, cache) + waitCh <- PaysetGroups(ctx, txnGroups, blkHdr, verificationPool, cache, nil) }() startPaysetGroupsTime = time.Now() select { @@ -366,7 +366,7 @@ func BenchmarkPaysetGroups(b *testing.B) { cache := MakeVerifiedTransactionCache(50000) b.ResetTimer() - err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, cache) + err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, cache, nil) require.NoError(b, err) b.StopTimer() } @@ -422,7 +422,7 @@ func BenchmarkTxn(b *testing.B) { b.ResetTimer() for _, txnGroup := range txnGroups { - groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader) + groupCtx, err := PrepareGroupContext(txnGroup, blk.BlockHeader, nil) require.NoError(b, err) for i, txn := range txnGroup { err := Txn(&txn, i, groupCtx) diff --git a/data/transactions/verify/verifiedTxnCache_test.go b/data/transactions/verify/verifiedTxnCache_test.go index af8e36b421..35d958e354 100644 --- a/data/transactions/verify/verifiedTxnCache_test.go +++ b/data/transactions/verify/verifiedTxnCache_test.go @@ -34,7 +34,7 @@ func TestAddingToCache(t *testing.T) { impl := icache.(*verifiedTransactionCache) _, signedTxn, secrets, addrs := generateTestObjects(10, 5, 50) txnGroups := generateTransactionGroups(signedTxn, secrets, addrs) - groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader) + groupCtx, err := PrepareGroupContext(txnGroups[0], blockHeader, nil) require.NoError(t, err) impl.Add(txnGroups[0], groupCtx) // make it was added. @@ -55,7 +55,7 @@ func TestBucketCycling(t *testing.T) { _, signedTxn, _, _ := generateTestObjects(entriesPerBucket*bucketCount*2, bucketCount, 0) require.Equal(t, entriesPerBucket*bucketCount*2, len(signedTxn)) - groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader) + groupCtx, err := PrepareGroupContext([]transactions.SignedTxn{signedTxn[0]}, blockHeader, nil) require.NoError(t, err) // fill up the cache with entries. @@ -92,7 +92,7 @@ func TestGetUnverifiedTranscationGroups50(t *testing.T) { if i%2 == 0 { expectedUnverifiedGroups = append(expectedUnverifiedGroups, txnGroups[i]) } else { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) impl.Add(txnGroups[i], groupCtx) } } @@ -116,7 +116,7 @@ func BenchmarkGetUnverifiedTranscationGroups50(b *testing.B) { if i%2 == 1 { queryTxnGroups = append(queryTxnGroups, txnGroups[i]) } else { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) impl.Add(txnGroups[i], groupCtx) } } @@ -145,7 +145,7 @@ func TestUpdatePinned(t *testing.T) { // insert some entries. for i := 0; i < len(txnGroups); i++ { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) impl.Add(txnGroups[i], groupCtx) } @@ -174,7 +174,7 @@ func TestPinningTransactions(t *testing.T) { // insert half of the entries. for i := 0; i < len(txnGroups)/2; i++ { - groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader) + groupCtx, _ := PrepareGroupContext(txnGroups[i], blockHeader, nil) impl.Add(txnGroups[i], groupCtx) } diff --git a/data/txHandler.go b/data/txHandler.go index 1d4d1a5000..9bc91203ad 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -203,7 +203,7 @@ func (handler *TxHandler) asyncVerifySignature(arg interface{}) interface{} { logging.Base().Warnf("Could not get header for previous block %d: %v", latest, err) } else { // we can't use PaysetGroups here since it's using a execpool like this go-routine and we don't want to deadlock. - _, tx.verificationErr = verify.TxnGroup(tx.unverifiedTxGroup, latestHdr, handler.ledger.VerifiedTransactionCache()) + _, tx.verificationErr = verify.TxnGroup(tx.unverifiedTxGroup, latestHdr, handler.ledger.VerifiedTransactionCache(), handler.ledger) } select { @@ -295,7 +295,7 @@ func (handler *TxHandler) processDecoded(unverifiedTxGroup []transactions.Signed } unverifiedTxnGroups := bookkeeping.SignedTxnsToGroups(unverifiedTxGroup) - err = verify.PaysetGroups(context.Background(), unverifiedTxnGroups, latestHdr, handler.txVerificationPool, handler.ledger.VerifiedTransactionCache()) + err = verify.PaysetGroups(context.Background(), unverifiedTxnGroups, latestHdr, handler.txVerificationPool, handler.ledger.VerifiedTransactionCache(), handler.ledger) if err != nil { // transaction is invalid logging.Base().Warnf("One or more transactions were malformed: %v", err) diff --git a/ledger/applications_test.go b/ledger/applications_test.go index 79ef7d6cd4..b8fc6ad362 100644 --- a/ledger/applications_test.go +++ b/ledger/applications_test.go @@ -1235,3 +1235,70 @@ int 1 }) } } + +// TestLogicSigValidation tests that LogicSig-signed transactions can be validated properly. +func TestLogicSigValidation(t *testing.T) { + partitiontest.PartitionTest(t) + + source := `#pragma version 6 +int 1 +` + + a := require.New(t) + ops, err := logic.AssembleString(source) + a.NoError(err) + a.Greater(len(ops.Program), 1) + program := ops.Program + pd := logic.HashProgram(program) + lsigAddr := basics.Address(pd) + + funder, err := basics.UnmarshalChecksumAddress("3LN5DBFC2UTPD265LQDP3LMTLGZCQ5M3JV7XTVTGRH5CKSVNQVDFPN6FG4") + a.NoError(err) + + proto := config.Consensus[protocol.ConsensusCurrentVersion] + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 100) + a.Contains(genesisInitState.Accounts, funder) + + cfg := config.GetDefaultLocal() + l, err := OpenLedger(logging.Base(), t.Name(), true, genesisInitState, cfg) + a.NoError(err) + defer l.Close() + + genesisID := t.Name() + txHeader := transactions.Header{ + Sender: funder, + Fee: basics.MicroAlgos{Raw: proto.MinTxnFee}, + FirstValid: l.Latest() + 1, + LastValid: l.Latest() + 10, + GenesisID: genesisID, + GenesisHash: genesisInitState.GenesisHash, + } + + // fund lsig account + fundingPayment := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txHeader, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: lsigAddr, + Amount: basics.MicroAlgos{Raw: proto.MinBalance + proto.MinTxnFee}, + }, + } + err = l.appendUnvalidatedTx(t, genesisInitState.Accounts, initKeys, fundingPayment, transactions.ApplyData{}) + a.NoError(err) + + // send 0 Algos from lsig account to self + txHeader.Sender = lsigAddr + lsigPayment := transactions.Transaction{ + Type: protocol.PaymentTx, + Header: txHeader, + PaymentTxnFields: transactions.PaymentTxnFields{ + Receiver: lsigAddr, + }, + } + signedLsigPayment := transactions.SignedTxn{ + Lsig: transactions.LogicSig{Logic: program}, + Txn: lsigPayment, + } + err = l.appendUnvalidatedSignedTx(t, genesisInitState.Accounts, signedLsigPayment, transactions.ApplyData{}) + a.NoError(err) +} diff --git a/ledger/internal/eval.go b/ledger/internal/eval.go index 93429f2e0c..1944c4a69a 100644 --- a/ledger/internal/eval.go +++ b/ledger/internal/eval.go @@ -1447,6 +1447,7 @@ type evalTxValidator struct { txcache verify.VerifiedTransactionCache block bookkeeping.Block verificationPool execpool.BacklogPool + ledger logic.LedgerForSignature ctx context.Context txgroups [][]transactions.SignedTxnWithAD @@ -1477,7 +1478,7 @@ func (validator *evalTxValidator) run() { unverifiedTxnGroups = validator.txcache.GetUnverifiedTranscationGroups(unverifiedTxnGroups, specialAddresses, validator.block.BlockHeader.CurrentProtocol) - err := verify.PaysetGroups(validator.ctx, unverifiedTxnGroups, validator.block.BlockHeader, validator.verificationPool, validator.txcache) + err := verify.PaysetGroups(validator.ctx, unverifiedTxnGroups, validator.block.BlockHeader, validator.verificationPool, validator.txcache, validator.ledger) if err != nil { validator.done <- err } @@ -1532,6 +1533,7 @@ func Eval(ctx context.Context, l LedgerForEvaluator, blk bookkeeping.Block, vali txvalidator.txcache = txcache txvalidator.block = blk txvalidator.verificationPool = executionPool + txvalidator.ledger = l txvalidator.ctx = validationCtx txvalidator.txgroups = paysetgroups diff --git a/node/node.go b/node/node.go index bea64849f1..7f0df81400 100644 --- a/node/node.go +++ b/node/node.go @@ -499,7 +499,7 @@ func (node *AlgorandFullNode) broadcastSignedTxGroup(txgroup []transactions.Sign return err } - _, err = verify.TxnGroup(txgroup, b, node.ledger.VerifiedTransactionCache()) + _, err = verify.TxnGroup(txgroup, b, node.ledger.VerifiedTransactionCache(), node.ledger) if err != nil { node.log.Warnf("malformed transaction: %v", err) return err diff --git a/test/scripts/e2e_subs/hdr-access-logicsig.sh b/test/scripts/e2e_subs/hdr-access-logicsig.sh new file mode 100755 index 0000000000..32c36d7b28 --- /dev/null +++ b/test/scripts/e2e_subs/hdr-access-logicsig.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +filename=$(basename "$0") +scriptname="${filename%.*}" +date "+${scriptname} start %Y%m%d_%H%M%S" + +set -e +set -x +set -o pipefail + +WALLET=$1 + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +cat >${TEMPDIR}/hdr.teal< +assert + +int 6 +< +EOF + +${gcmd} clerk compile -o ${TEMPDIR}/hdr.lsig -s -a ${ACCOUNT} ${TEMPDIR}/hdr.teal + +SIGACCOUNT=$(${gcmd} clerk compile -n ${TEMPDIR}/hdr.teal|awk '{ print $2 }') + +# Avoid rewards by giving less than an algo +${gcmd} clerk send --amount 900000 --from ${ACCOUNT} --to ${SIGACCOUNT} + +function balance { + acct=$1; shift + goal account balance -a "$acct" | awk '{print $1}' +} + +[ "$(balance "$SIGACCOUNT")" = 900000 ] + +# Don't let goal set lastvalid so far in the future, that prevents `block` access +${gcmd} clerk send --amount 10 --from ${SIGACCOUNT} --to ${ACCOUNT} --lastvalid 100 -o ${TEMPDIR}/hdr.tx + +${gcmd} clerk sign -i ${TEMPDIR}/hdr.tx -o ${TEMPDIR}/hdr.stx --program ${TEMPDIR}/hdr.teal + +${gcmd} clerk rawsend -f ${TEMPDIR}/hdr.stx + +# remove min fee + 10 +[ "$(balance "$SIGACCOUNT")" = 898990 ] diff --git a/test/scripts/e2e_subs/hdr-access.py b/test/scripts/e2e_subs/hdr-access.py index 067c80523e..4da7856a34 100755 --- a/test/scripts/e2e_subs/hdr-access.py +++ b/test/scripts/e2e_subs/hdr-access.py @@ -32,19 +32,35 @@ assert "invalid txn field FirstValidTime" in str(err), err -# Test that the block timestamp from two blocks ago is between 2 and 5 -# (inclusive) seconds before the previous block timestamp. devMode -# might mess this test up. This works because FirstValid is set to -# the last committed block by SDK, not the next coming one. +# Can't access two behind FirstValid because LastValid is 1000 after teal = """ #pragma version 7 txn FirstValid + int 2 + - block BlkTimestamp +""" +txinfo, err = goal.app_create(joe, goal.assemble(teal)) +assert "not available" in str(err), err +# We want to manipulate lastvalid, so we need to turn off autosend +goal.autosend = False + +# We will be able to access two blocks, by setting lv explcitly. So we +# test that the block timestamp from two blocks ago is between 2 and 5 +# (inclusive) seconds before the previous block timestamp. devMode +# might mess this test up. +teal = """ +#pragma version 7 txn FirstValid int 1 - block BlkTimestamp + + txn FirstValid + int 2 + - + block BlkTimestamp // last two times are on stack - dup @@ -58,23 +74,6 @@ < """ checktimes = goal.assemble(teal) -txinfo, err = goal.app_create(joe, checktimes) -assert not err, err - -# Can't access two behind FirstValid because LastValid is 1000 after -teal = """ -#pragma version 7 - txn FirstValid - int 2 - - - block BlkTimestamp -""" -txinfo, err = goal.app_create(joe, goal.assemble(teal)) -assert "not available" in str(err), err - -# We want to manipulate lastvalid, so we need to turn off autosend -goal.autosend = False - tx = goal.app_create(joe, goal.assemble(teal)) tx.last_valid_round = tx.last_valid_round - 800 txinfo, err = goal.send(tx) From 9a03260c8bf1824dbe8a9ca62d93e751939cb95e Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 9 Aug 2022 12:19:10 -0400 Subject: [PATCH 04/24] tests: fix TestAssetValidRounds (#4351) * The test validates first/last valid against possible expired value of lastRound. Make ComputeValidityRounds returning lastValid to check validity range against it. * Fix some linter warnings * Modify all usages of ComputeValidityRounds * expect: fix global vars usage in TCL when dumping nodes logs on error --- cmd/goal/account.go | 4 +- cmd/goal/application.go | 16 ++++---- cmd/goal/asset.go | 12 +++--- cmd/goal/clerk.go | 2 +- cmd/goal/interact.go | 2 +- libgoal/libgoal.go | 9 +++-- .../cli/goal/expect/goalExpectCommon.exp | 14 +++---- .../features/transactions/application_test.go | 5 ++- .../features/transactions/asset_test.go | 38 ++++++++++--------- .../transactions/onlineStatusChange_test.go | 3 ++ .../features/transactions/sendReceive_test.go | 2 + 11 files changed, 58 insertions(+), 49 deletions(-) diff --git a/cmd/goal/account.go b/cmd/goal/account.go index 45cfafada5..6cd3b9a87e 100644 --- a/cmd/goal/account.go +++ b/cmd/goal/account.go @@ -819,7 +819,7 @@ var changeOnlineCmd = &cobra.Command{ } } - firstTxRound, lastTxRound, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstTxRound, lastTxRound, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf(err.Error()) } @@ -1430,7 +1430,7 @@ var markNonparticipatingCmd = &cobra.Command{ dataDir := ensureSingleDataDir() client := ensureFullClient(dataDir) - firstTxRound, lastTxRound, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstTxRound, lastTxRound, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf(errorConstructingTX, err) } diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 66532647d6..884c3d6e68 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -461,7 +461,7 @@ var createAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -536,7 +536,7 @@ var updateAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -606,7 +606,7 @@ var optInAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -676,7 +676,7 @@ var closeOutAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -746,7 +746,7 @@ var clearAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -816,7 +816,7 @@ var callAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -886,7 +886,7 @@ var deleteAppCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -1307,7 +1307,7 @@ var methodAppCmd = &cobra.Command{ appCallTxn.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } diff --git a/cmd/goal/asset.go b/cmd/goal/asset.go index acfb0a732b..5826fd0837 100644 --- a/cmd/goal/asset.go +++ b/cmd/goal/asset.go @@ -283,7 +283,7 @@ var createAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -362,7 +362,7 @@ var destroyAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -455,7 +455,7 @@ var configAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -540,7 +540,7 @@ var sendAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -611,7 +611,7 @@ var freezeAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } @@ -698,7 +698,7 @@ var optinAssetCmd = &cobra.Command{ tx.Note = parseNoteField(cmd) tx.Lease = parseLease(cmd) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 856662147b..2be5ff3322 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -384,7 +384,7 @@ var sendCmd = &cobra.Command{ } } client := ensureFullClient(dataDir) - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf(err.Error()) } diff --git a/cmd/goal/interact.go b/cmd/goal/interact.go index ffa66b5caf..45fb5bf8a9 100644 --- a/cmd/goal/interact.go +++ b/cmd/goal/interact.go @@ -596,7 +596,7 @@ var appExecuteCmd = &cobra.Command{ tx.Lease = parseLease(cmd) // Fill in rounds, fee, etc. - fv, lv, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) + fv, lv, _, err := client.ComputeValidityRounds(firstValid, lastValid, numValidRounds) if err != nil { reportErrorf("Cannot determine last valid round: %s", err) } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 6aed7f0754..562ec0fe1b 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -513,17 +513,18 @@ func (c *Client) signAndBroadcastTransactionWithWallet(walletHandle, pw []byte, // M | 0 | first + validRounds - 1 // M | M | error // -func (c *Client) ComputeValidityRounds(firstValid, lastValid, validRounds uint64) (uint64, uint64, error) { +func (c *Client) ComputeValidityRounds(firstValid, lastValid, validRounds uint64) (first, last, latest uint64, err error) { params, err := c.SuggestedParams() if err != nil { - return 0, 0, err + return 0, 0, 0, err } cparams, ok := c.consensus[protocol.ConsensusVersion(params.ConsensusVersion)] if !ok { - return 0, 0, fmt.Errorf("cannot construct transaction: unknown consensus protocol %s", params.ConsensusVersion) + return 0, 0, 0, fmt.Errorf("cannot construct transaction: unknown consensus protocol %s", params.ConsensusVersion) } - return computeValidityRounds(firstValid, lastValid, validRounds, params.LastRound, cparams.MaxTxnLife) + first, last, err = computeValidityRounds(firstValid, lastValid, validRounds, params.LastRound, cparams.MaxTxnLife) + return first, last, params.LastRound, err } func computeValidityRounds(firstValid, lastValid, validRounds, lastRound, maxTxnLife uint64) (uint64, uint64, error) { diff --git a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp index 976ab73d09..f528dabb19 100644 --- a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp +++ b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp @@ -52,7 +52,7 @@ proc ::AlgorandGoal::Abort { ERROR } { puts "GLOBAL_NETWORK_NAME $::GLOBAL_NETWORK_NAME" log_user 1 - set NODE_DATA_DIR $GLOBAL_TEST_ROOT_DIR/Primary + set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Primary if { [info exists ::NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] puts "$NODE_DATA_DIR/algod-out.log :\r\n$outLog" @@ -61,7 +61,7 @@ proc ::AlgorandGoal::Abort { ERROR } { set nodeLog [exec -- tail -n 30 $NODE_DATA_DIR/node.log] puts "$NODE_DATA_DIR/node.log :\r\n$nodeLog" } - set NODE_DATA_DIR $GLOBAL_TEST_ROOT_DIR/Node + set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Node if { [info exists ::NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] puts "$NODE_DATA_DIR/algod-out.log :\r\n$outLog" @@ -78,12 +78,12 @@ proc ::AlgorandGoal::Abort { ERROR } { puts "GLOBAL_TEST_ALGO_DIR $::GLOBAL_TEST_ALGO_DIR" log_user 1 - set outLog [exec cat $GLOBAL_TEST_ALGO_DIR/algod-out.log] - puts "$GLOBAL_TEST_ALGO_DIR/algod-out.log :\r\n$outLog" - set errLog [exec cat $GLOBAL_TEST_ALGO_DIR/algod-err.log] + set outLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-out.log] + puts "$::GLOBAL_TEST_ALGO_DIR/algod-out.log :\r\n$outLog" + set errLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-err.log] puts "$NODE_DATA_DIR/algod-err.log :\r\n$errLog" - set nodeLog [exec -- tail -n 30 $GLOBAL_TEST_ALGO_DIR/node.log] - puts "$GLOBAL_TEST_ALGO_DIR/node.log :\r\n$nodeLog" + set nodeLog [exec -- tail -n 30 $::GLOBAL_TEST_ALGO_DIR/node.log] + puts "$::GLOBAL_TEST_ALGO_DIR/node.log :\r\n$nodeLog" ::AlgorandGoal::StopNode $::GLOBAL_TEST_ALGO_DIR } diff --git a/test/e2e-go/features/transactions/application_test.go b/test/e2e-go/features/transactions/application_test.go index c9a4ac1315..a5786cc4f3 100644 --- a/test/e2e-go/features/transactions/application_test.go +++ b/test/e2e-go/features/transactions/application_test.go @@ -66,7 +66,7 @@ func TestApplication(t *testing.T) { a.NoError(err) creator := accountList[0].Address - wh, err := client.GetUnencryptedWalletHandle() + _, err = client.GetUnencryptedWalletHandle() a.NoError(err) fee := uint64(1000) @@ -101,7 +101,7 @@ log a.NoError(err) tx, err = client.FillUnsignedTxTemplate(creator, 0, 0, fee, tx) a.NoError(err) - wh, err = client.GetUnencryptedWalletHandle() + wh, err := client.GetUnencryptedWalletHandle() a.NoError(err) signedTxn, err := client.SignTransactionWithWallet(wh, nil, tx) a.NoError(err) @@ -122,6 +122,7 @@ log logs[31] = "c" b, err := client.BookkeepingBlock(round) + a.NoError(err) for _, ps := range b.Payset { ed := ps.ApplyData.EvalDelta ok = checkEqual(logs, ed.Logs) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index fd21364b21..520f8af0e0 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -67,10 +67,7 @@ func TestAssetValidRounds(t *testing.T) { client := fixture.LibGoalClient // First, test valid rounds to last valid conversion - var firstValid, lastValid, validRounds uint64 - firstValid = 0 - lastValid = 0 - validRounds = 0 + var firstValid, lastValid, lastRound, validRounds uint64 params, err := client.SuggestedParams() a.NoError(err) @@ -80,29 +77,29 @@ func TestAssetValidRounds(t *testing.T) { firstValid = 0 lastValid = 0 validRounds = cparams.MaxTxnLife + 1 - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + firstValid, lastValid, lastRound, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) - a.Equal(params.LastRound+1, firstValid) + a.Equal(lastRound+1, firstValid) a.Equal(firstValid+cparams.MaxTxnLife, lastValid) firstValid = 0 lastValid = 0 validRounds = cparams.MaxTxnLife + 2 - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + _, _, _, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.Error(err) a.True(strings.Contains(err.Error(), "cannot construct transaction: txn validity period")) firstValid = 0 lastValid = 0 validRounds = 1 - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) a.Equal(firstValid, lastValid) firstValid = 1 lastValid = 0 validRounds = 1 - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) a.Equal(uint64(1), firstValid) a.Equal(firstValid, lastValid) @@ -110,7 +107,7 @@ func TestAssetValidRounds(t *testing.T) { firstValid = 1 lastValid = 0 validRounds = cparams.MaxTxnLife - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) a.Equal(uint64(1), firstValid) a.Equal(cparams.MaxTxnLife, lastValid) @@ -118,7 +115,7 @@ func TestAssetValidRounds(t *testing.T) { firstValid = 100 lastValid = 0 validRounds = cparams.MaxTxnLife - firstValid, lastValid, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) + firstValid, lastValid, _, err = client.ComputeValidityRounds(firstValid, lastValid, validRounds) a.NoError(err) a.Equal(uint64(100), firstValid) a.Equal(firstValid+cparams.MaxTxnLife-1, lastValid) @@ -255,7 +252,6 @@ func TestAssetConfig(t *testing.T) { a.NoError(err) confirmed := fixture.WaitForAllTxnsToConfirm(status.LastRound+20, txids) a.True(confirmed, "creating max number of assets") - txids = make(map[string]string) // re-generate wh, since this test takes a while and sometimes // the wallet handle expires. @@ -265,7 +261,7 @@ func TestAssetConfig(t *testing.T) { var tx transactions.Transaction if config.Consensus[protocol.ConsensusFuture].MaxAssetsPerAccount != 0 { // Creating more assets should return an error - tx, err = client.MakeUnsignedAssetCreateTx(1, false, manager, reserve, freeze, clawback, fmt.Sprintf("toomany"), fmt.Sprintf("toomany"), assetURL, assetMetadataHash, 0) + tx, err = client.MakeUnsignedAssetCreateTx(1, false, manager, reserve, freeze, clawback, "toomany", "toomany", assetURL, assetMetadataHash, 0) _, err = helperFillSignBroadcast(client, wh, account0, tx, err) a.Error(err) a.True(strings.Contains(err.Error(), "too many assets in account:")) @@ -336,7 +332,6 @@ func TestAssetConfig(t *testing.T) { a.NoError(err) confirmed = fixture.WaitForAllTxnsToConfirm(status.LastRound+20, txids) a.True(confirmed, "changing keys") - txids = make(map[string]string) info, err = client.AccountInformation(account0) a.NoError(err) @@ -672,7 +667,7 @@ func TestAssetGroupCreateSendDestroy(t *testing.T) { a.Error(err) // sending it should fail txSend, err = client1.MakeUnsignedAssetSendTx(assetID3, 0, account1, "", "") - txid, err = helperFillSignBroadcast(client1, wh1, account1, txSend, err) + _, err = helperFillSignBroadcast(client1, wh1, account1, txSend, err) a.Error(err) } @@ -750,11 +745,11 @@ func TestAssetSend(t *testing.T) { // An account with no algos should not be able to accept assets tx, err = client.MakeUnsignedAssetSendTx(nonFrozenIdx, 0, extra, "", "") - txid, err = helperFillSignBroadcast(client, wh, account0, tx, err) + _, err = helperFillSignBroadcast(client, wh, account0, tx, err) a.NoError(err) tx, err = client.MakeUnsignedAssetSendTx(nonFrozenIdx, 0, extra, "", "") - txid, err = helperFillSignBroadcast(client, wh, extra, tx, err) + _, err = helperFillSignBroadcast(client, wh, extra, tx, err) a.Error(err) a.True(strings.Contains(err.Error(), "overspend")) a.True(strings.Contains(err.Error(), "tried to spend")) @@ -977,6 +972,7 @@ func TestAssetCreateWaitRestartDelete(t *testing.T) { // Destroy the asset tx, err := client.MakeUnsignedAssetDestroyTx(assetIndex) + a.NoError(err) submitAndWaitForTransaction(manager, tx, "destroying assets", client, fixture, a) // Check again that asset is destroyed @@ -986,6 +982,7 @@ func TestAssetCreateWaitRestartDelete(t *testing.T) { // Should be able to close now wh, err := client.GetUnencryptedWalletHandle() + a.NoError(err) _, err = client.SendPaymentFromWallet(wh, nil, account0, "", 0, 0, nil, reserve, 0, 0) a.NoError(err) } @@ -1047,6 +1044,7 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { _, curRound := fixture.GetBalanceAndRound(account0) nodeStatus, _ := client.Status() consParams, err := client.ConsensusParams(nodeStatus.LastRound) + a.NoError(err) err = fixture.WaitForRoundWithTimeout(curRound + consParams.MaxBalLookback + 1) a.NoError(err) @@ -1063,6 +1061,7 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { // Destroy the asset tx, err := client.MakeUnsignedAssetDestroyTx(assetIndex) + a.NoError(err) submitAndWaitForTransaction(manager, tx, "destroying assets", client, fixture, a) // Check again that asset is destroyed @@ -1072,6 +1071,7 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { // Should be able to close now wh, err := client.GetUnencryptedWalletHandle() + a.NoError(err) _, err = client.SendPaymentFromWallet(wh, nil, account0, "", 0, 0, nil, reserve, 0, 0) a.NoError(err) } @@ -1084,7 +1084,7 @@ func setupTestAndNetwork(t *testing.T, networkTemplate string, consensus config. t.Parallel() asser := require.New(fixtures.SynchronizedTest(t)) - if 0 == len(networkTemplate) { + if len(networkTemplate) == 0 { // If the networkTemplate is not specified, used the default one networkTemplate = "TwoNodes50Each.json" } @@ -1113,6 +1113,7 @@ func createAsset(assetName, account0, manager, reserve, freeze, clawback string, // Create two assets: one with default-freeze, and one without default-freeze txids := make(map[string]string) wh, err := client.GetUnencryptedWalletHandle() + asser.NoError(err) tx, err := client.MakeUnsignedAssetCreateTx(100, false, manager, reserve, freeze, clawback, assetName, "testunit", assetURL, assetMetadataHash, 0) txid, err := helperFillSignBroadcast(*client, wh, account0, tx, err) asser.NoError(err) @@ -1128,6 +1129,7 @@ func setupActors(account0 string, client *libgoal.Client, asser *require.Asserti // Setup the actors wh, err := client.GetUnencryptedWalletHandle() + asser.NoError(err) manager, err = client.GenerateAddress(wh) asser.NoError(err) reserve, err = client.GenerateAddress(wh) diff --git a/test/e2e-go/features/transactions/onlineStatusChange_test.go b/test/e2e-go/features/transactions/onlineStatusChange_test.go index e954b95840..fdda7eea67 100644 --- a/test/e2e-go/features/transactions/onlineStatusChange_test.go +++ b/test/e2e-go/features/transactions/onlineStatusChange_test.go @@ -101,6 +101,7 @@ func testAccountsCanChangeOnlineState(t *testing.T, templatePath string) { goOfflineUTx, err := client.MakeUnsignedGoOfflineTx(initiallyOnline, curRound, curRound+transactionValidityPeriod, transactionFee, [32]byte{}) a.NoError(err, "should be able to make go offline tx") wh, err = client.GetUnencryptedWalletHandle() + a.NoError(err) offlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, goOfflineUTx) a.NoError(err, "should be no errors when going offline") @@ -112,6 +113,7 @@ func testAccountsCanChangeOnlineState(t *testing.T, templatePath string) { becomeNonparticpatingUTx, err := client.MakeUnsignedBecomeNonparticipatingTx(becomesNonparticipating, curRound, curRound+transactionValidityPeriod, transactionFee) a.NoError(err, "should be able to make become-nonparticipating tx") wh, err = client.GetUnencryptedWalletHandle() + a.NoError(err) nonparticipatingTxID, err = client.SignAndBroadcastTransaction(wh, nil, becomeNonparticpatingUTx) a.NoError(err, "should be no errors when marking nonparticipating") } @@ -170,6 +172,7 @@ func TestCloseOnError(t *testing.T) { var partkeyFile string _, partkeyFile, err = client.GenParticipationKeysTo(initiallyOffline, 0, curRound+1000, 0, t.TempDir()) + a.NoError(err) // make a participation key for initiallyOffline _, err = client.AddParticipationKey(partkeyFile) diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index b21ec529f8..693ed8f2a3 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -93,7 +93,9 @@ func testAccountsCanSendMoney(t *testing.T, templatePath string, numberOfSends i } pingBalance, err := c.GetBalance(pingAccount) + a.NoError(err) pongBalance, err := c.GetBalance(pongAccount) + a.NoError(err) a.Equal(pingBalance, pongBalance, "both accounts should start with same balance") a.NotEqual(pingAccount, pongAccount, "accounts under study should be different") From 60fb4f6d323fc67254d6ac24fa4ae4438ae113e4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Tue, 9 Aug 2022 13:28:27 -0400 Subject: [PATCH 05/24] consensus: introduce v33, v34 (#4334) * State proofs * TEAL v7 * 5 MiB blocks * 3.4s FilterTimeout --- agreement/service_test.go | 2 +- catchup/service_test.go | 7 +- config/consensus.go | 62 +++++++++++------ crypto/merklesignature/kats_test.go | 2 +- .../algod/api/server/v2/test/handlers_test.go | 2 +- data/account/participationRegistry_test.go | 5 +- data/bookkeeping/txn_merkle_test.go | 2 +- data/pools/transactionPool_test.go | 10 +-- data/transactions/logic/assembler_test.go | 1 + data/transactions/logic/eval_test.go | 8 +++ data/transactions/logic/langspec.json | 2 +- data/transactions/logic/opcodes.go | 2 +- data/transactions/transaction_test.go | 2 +- ledger/acctonline_test.go | 2 +- ledger/blockHeaderCache_test.go | 3 +- ledger/internal/apptxn_test.go | 12 ++-- ledger/internal/eval_blackbox_test.go | 14 ++-- ledger/ledger_test.go | 16 ++--- ledger/voters_test.go | 50 +++++++------- protocol/consensus.go | 13 +++- stateproof/stateproofMessageGenerator_test.go | 30 +++++---- stateproof/worker_test.go | 67 ++++++++++--------- 22 files changed, 181 insertions(+), 133 deletions(-) diff --git a/agreement/service_test.go b/agreement/service_test.go index 453403eb35..b5ec2ce613 100644 --- a/agreement/service_test.go +++ b/agreement/service_test.go @@ -1968,7 +1968,7 @@ func TestAgreementSlowPayloadsPostDeadline(t *testing.T) { activityMonitor.waitForQuiet() zeroes = expectNoNewPeriod(clocks, zeroes) - triggerGlobalTimeout(FilterTimeout(0, version), clocks, activityMonitor) + triggerGlobalTimeout(FilterTimeout(1, version), clocks, activityMonitor) zeroes = expectNewPeriod(clocks, zeroes) } diff --git a/catchup/service_test.go b/catchup/service_test.go index 972370180b..679ee3239e 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -977,10 +977,11 @@ func TestSynchronizingTime(t *testing.T) { func TestDownloadBlocksToSupportStateProofs(t *testing.T) { partitiontest.PartitionTest(t) + // make sure we download enough blocks to verify state proof 512 topBlk := bookkeeping.Block{} topBlk.BlockHeader.Round = 1500 - topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusCurrentVersion trackingData := bookkeeping.StateProofTrackingData{StateProofNextRound: 512} topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData @@ -993,7 +994,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { // instead, we will download blocks to confirm only the recovery period lookback. topBlk = bookkeeping.Block{} topBlk.BlockHeader.Round = 8000 - topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusCurrentVersion trackingData = bookkeeping.StateProofTrackingData{StateProofNextRound: 512} topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData @@ -1001,7 +1002,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { lookback = lookbackForStateproofsSupport(&topBlk) oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) - lowestRoundToRetain := 8000 - (8000 % 256) - (config.Consensus[protocol.ConsensusFuture].StateProofInterval * (config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 1)) + lowestRoundToRetain := 8000 - (8000 % 256) - (config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval * (config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals + 1)) assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) topBlk = bookkeeping.Block{} diff --git a/config/consensus.go b/config/consensus.go index 5896f97d99..af0b50e53c 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -1150,37 +1150,59 @@ func initConsensusProtocols() { // v31 can be upgraded to v32, with an update delay of 7 days ( see calculation above ) v31.ApprovedUpgrades[protocol.ConsensusV32] = 140000 - // ConsensusFuture is used to test features that are implemented - // but not yet released in a production protocol version. - vFuture := v32 - vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} + v33 := v32 + v33.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} // Make the accounts snapshot for round X at X-CatchpointLookback - vFuture.CatchpointLookback = 320 + // order to guarantee all nodes produce catchpoint at the same round. + v33.CatchpointLookback = 320 // Require MaxTxnLife + X blocks and headers preserved by a node - vFuture.DeeperBlockHeaderHistory = 1 + v33.DeeperBlockHeaderHistory = 1 + + v33.MaxTxnBytesPerBlock = 5 * 1024 * 1024 + + Consensus[protocol.ConsensusV33] = v33 + + // v32 can be upgraded to v33, with an update delay of 7 days ( see calculation above ) + v32.ApprovedUpgrades[protocol.ConsensusV33] = 140000 + + v34 := v33 + v34.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} // Enable state proofs. - vFuture.StateProofInterval = 256 - vFuture.StateProofTopVoters = 1024 - vFuture.StateProofVotersLookback = 16 - vFuture.StateProofWeightThreshold = (1 << 32) * 30 / 100 - vFuture.StateProofStrengthTarget = 256 - vFuture.StateProofMaxRecoveryIntervals = 10 + v34.StateProofInterval = 256 + v34.StateProofTopVoters = 1024 + v34.StateProofVotersLookback = 16 + v34.StateProofWeightThreshold = (1 << 32) * 30 / 100 + v34.StateProofStrengthTarget = 256 + v34.StateProofMaxRecoveryIntervals = 10 - vFuture.LogicSigVersion = 7 // When moving this to a release, put a new higher LogicSigVersion here - vFuture.MinInnerApplVersion = 4 + v34.LogicSigVersion = 7 + v34.MinInnerApplVersion = 4 - vFuture.UnifyInnerTxIDs = true + v34.UnifyInnerTxIDs = true - vFuture.EnableSHA256TxnCommitmentHeader = true - vFuture.EnableOnlineAccountCatchpoints = true + v34.EnableSHA256TxnCommitmentHeader = true + v34.EnableOnlineAccountCatchpoints = true - vFuture.UnfundedSenders = true + v34.UnfundedSenders = true + + v34.AgreementFilterTimeoutPeriod0 = 3400 * time.Millisecond + + Consensus[protocol.ConsensusV34] = v34 + + // v33 can be upgraded to v34, with an update delay of 12h: + // 10046 = (12 * 60 * 60 / 4.3) + // for the sake of future manual calculations, we'll round that down a bit : + v33.ApprovedUpgrades[protocol.ConsensusV34] = 10000 + + // ConsensusFuture is used to test features that are implemented + // but not yet released in a production protocol version. + vFuture := v34 + vFuture.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{} - vFuture.AgreementFilterTimeoutPeriod0 = 3400 * time.Millisecond - vFuture.MaxTxnBytesPerBlock = 5 * 1024 * 1024 + vFuture.LogicSigVersion = 8 // When moving this to a release, put a new higher LogicSigVersion here Consensus[protocol.ConsensusFuture] = vFuture } diff --git a/crypto/merklesignature/kats_test.go b/crypto/merklesignature/kats_test.go index 8a48d699bc..bc61ec47b2 100644 --- a/crypto/merklesignature/kats_test.go +++ b/crypto/merklesignature/kats_test.go @@ -60,7 +60,7 @@ func generateMssKat(startRound, atRound, numOfKeys uint64, messageToSign []byte) return mssKat{}, fmt.Errorf("error: Signature round cann't be smaller then start round") } - interval := config.Consensus[protocol.ConsensusFuture].StateProofInterval + interval := config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval stateProofSecrets, err := New(startRound, startRound+(interval*numOfKeys)-1, interval) if err != nil { return mssKat{}, fmt.Errorf("error: %w", err) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index 10e89deaf9..f8b7e4eb5b 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -1030,7 +1030,7 @@ func insertRounds(a *require.Assertions, h v2.Handlers, numRounds int) { for i := 0; i < numRounds; i++ { blk := newEmptyBlock(a, lastBlk, genBlk, ledger) blk = addStateProofIfNeeded(blk) - blk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + blk.BlockHeader.CurrentProtocol = protocol.ConsensusCurrentVersion a.NoError(ledger.(*data.Ledger).AddBlock(blk, agreement.Certificate{})) lastBlk = blk } diff --git a/data/account/participationRegistry_test.go b/data/account/participationRegistry_test.go index 61b78adac7..8e02ff4e43 100644 --- a/data/account/participationRegistry_test.go +++ b/data/account/participationRegistry_test.go @@ -47,8 +47,7 @@ import ( "github.com/algorand/go-algorand/util/db" ) -// TODO: change to ConsensusCurrentVersion when updated -var stateProofIntervalForTests = config.Consensus[protocol.ConsensusFuture].StateProofInterval +var stateProofIntervalForTests = config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval func getRegistry(t testing.TB) (registry *participationDB, dbfile string) { return getRegistryImpl(t, true, false) @@ -1222,7 +1221,7 @@ func TestParticipationDB_Locking(t *testing.T) { const targetFlushes = 5 go func() { for i := 0; i < 25; i++ { - registry.DeleteExpired(basics.Round(i), config.Consensus[protocol.ConsensusFuture]) + registry.DeleteExpired(basics.Round(i), config.Consensus[protocol.ConsensusCurrentVersion]) registry.Flush(defaultTimeout) if atomic.LoadInt32(&flushCount) < targetFlushes { atomic.AddInt32(&flushCount, 1) diff --git a/data/bookkeeping/txn_merkle_test.go b/data/bookkeeping/txn_merkle_test.go index 977af620d5..30a34ab7f2 100644 --- a/data/bookkeeping/txn_merkle_test.go +++ b/data/bookkeeping/txn_merkle_test.go @@ -94,7 +94,7 @@ func TestBlock_TxnMerkleTreeSHA256(t *testing.T) { for ntxn := uint64(0); ntxn < 128; ntxn++ { var b Block - b.CurrentProtocol = protocol.ConsensusFuture + b.CurrentProtocol = protocol.ConsensusCurrentVersion crypto.RandBytes(b.BlockHeader.GenesisHash[:]) var elems []txnMerkleElem diff --git a/data/pools/transactionPool_test.go b/data/pools/transactionPool_test.go index d4c863d440..7dcc4c6ba7 100644 --- a/data/pools/transactionPool_test.go +++ b/data/pools/transactionPool_test.go @@ -1281,10 +1281,10 @@ func TestTxPoolSizeLimits(t *testing.T) { } } -func TestTStateProofLogging(t *testing.T) { +func TestStateProofLogging(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] cfg := config.GetDefaultLocal() cfg.TxPoolSize = testPoolSize @@ -1331,7 +1331,7 @@ func TestTStateProofLogging(t *testing.T) { logger.SetOutput(&buf) // Set the ledger and the transaction pool - mockLedger := makeMockLedgerFuture(t, initAccounts) + mockLedger := makeMockLedger(t, initAccounts) transactionPool := MakeTransactionPool(mockLedger, cfg, logger) transactionPool.logAssembleStats = true @@ -1339,7 +1339,7 @@ func TestTStateProofLogging(t *testing.T) { var b bookkeeping.Block b.BlockHeader.GenesisID = "pooltest" b.BlockHeader.GenesisHash = mockLedger.GenesisHash() - b.CurrentProtocol = protocol.ConsensusFuture + b.CurrentProtocol = protocol.ConsensusCurrentVersion b.BlockHeader.Round = 1 phdr, err := mockLedger.BlockHdr(0) @@ -1472,7 +1472,7 @@ func generateProofForTesting( } // Prepare the builder - stateProofStrengthTargetForTests := config.Consensus[protocol.ConsensusFuture].StateProofStrengthTarget + stateProofStrengthTargetForTests := config.Consensus[protocol.ConsensusCurrentVersion].StateProofStrengthTarget b, err := cryptostateproof.MakeBuilder(data, round, provenWeight, partArray, partTree, stateProofStrengthTargetForTests) require.NoError(t, err) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 9bd1fc91fd..24d9dffd07 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -423,6 +423,7 @@ var compiled = map[uint64]string{ 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03", 6: "06" + v6Compiled, 7: "07" + v7Compiled, + 8: "08" + v8Compiled, } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 734f4ba6c9..ba7df73e9c 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1064,6 +1064,10 @@ const globalV7TestProgram = globalV6TestProgram + ` // No new globals in v7 ` +const globalV8TestProgram = globalV7TestProgram + ` +// No new globals in v7 +` + func TestGlobal(t *testing.T) { partitiontest.PartitionTest(t) @@ -1082,6 +1086,7 @@ func TestGlobal(t *testing.T) { 5: {GroupID, globalV5TestProgram}, 6: {CallerApplicationAddress, globalV6TestProgram}, 7: {CallerApplicationAddress, globalV7TestProgram}, + 8: {CallerApplicationAddress, globalV8TestProgram}, } // tests keys are versions so they must be in a range 1..AssemblerMaxVersion plus zero version require.LessOrEqual(t, len(tests), AssemblerMaxVersion+1) @@ -1558,6 +1563,8 @@ assert int 1 ` +const testTxnProgramTextV8 = testTxnProgramTextV7 + func makeSampleTxn() transactions.SignedTxn { var txn transactions.SignedTxn copy(txn.Txn.Sender[:], []byte("aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00")) @@ -1660,6 +1667,7 @@ func TestTxn(t *testing.T) { 5: testTxnProgramTextV5, 6: testTxnProgramTextV6, 7: testTxnProgramTextV7, + 8: testTxnProgramTextV8, } for i, txnField := range TxnFieldNames { diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 0095a6e25f..4e29b9a88b 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -1,6 +1,6 @@ { "EvalMaxVersion": 7, - "LogicSigVersion": 6, + "LogicSigVersion": 7, "Ops": [ { "Opcode": 0, diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 5b31e5d868..dc5627422e 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -24,7 +24,7 @@ import ( ) // LogicVersion defines default assembler and max eval versions -const LogicVersion = 7 +const LogicVersion = 8 // rekeyingEnabledVersion is the version of TEAL where RekeyTo functionality // was enabled. This is important to remember so that old TEAL accounts cannot diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 45fd1ca495..bac43c22c9 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -614,7 +614,7 @@ func TestWellFormedKeyRegistrationTx(t *testing.T) { 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.ConsensusFuture].MaxKeyregValidPeriod // TODO: change to curProto.MaxKeyregValidPeriod + maxValidPeriod := config.Consensus[protocol.ConsensusCurrentVersion].MaxKeyregValidPeriod runTestCase := func(testCase keyRegTestCase) error { diff --git a/ledger/acctonline_test.go b/ledger/acctonline_test.go index f4c4fc0b1e..c1d14b43ee 100644 --- a/ledger/acctonline_test.go +++ b/ledger/acctonline_test.go @@ -1135,7 +1135,7 @@ func TestAcctOnlineVotersLongerHistory(t *testing.T) { addSinkAndPoolAccounts(genesisAccts) testProtocolVersion := protocol.ConsensusVersion("test-protocol-TestAcctOnlineCacheDBSync") - protoParams := config.Consensus[protocol.ConsensusFuture] + protoParams := config.Consensus[protocol.ConsensusCurrentVersion] protoParams.MaxBalLookback = maxBalLookback protoParams.SeedLookback = seedLookback protoParams.SeedRefreshInterval = seedInteval diff --git a/ledger/blockHeaderCache_test.go b/ledger/blockHeaderCache_test.go index 6c89734357..a5fac5ae61 100644 --- a/ledger/blockHeaderCache_test.go +++ b/ledger/blockHeaderCache_test.go @@ -90,6 +90,5 @@ func TestCacheSizeConsensus(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - // TODO Stateproof: change to CurrentVersion when feature is enabled - a.Equal(uint64(latestHeaderCacheSize), config.Consensus[protocol.ConsensusFuture].StateProofInterval*2) + a.Equal(uint64(latestHeaderCacheSize), config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*2) } diff --git a/ledger/internal/apptxn_test.go b/ledger/internal/apptxn_test.go index 5f6d4b26a6..fdbc64aca0 100644 --- a/ledger/internal/apptxn_test.go +++ b/ledger/internal/apptxn_test.go @@ -1890,7 +1890,7 @@ func TestInnerAppVersionCalling(t *testing.T) { genBalances, addrs, _ := ledgertesting.NewTestGenesis() - // 31 allowed inner appls. v33 lowered proto.MinInnerApplVersion + // 31 allowed inner appls. v34 lowered proto.MinInnerApplVersion testConsensusRange(t, 31, 0, func(t *testing.T, ver int) { dl := NewDoubleLedger(t, genBalances, consensusByNumber[ver]) defer dl.Close() @@ -1977,7 +1977,7 @@ itxn_begin itxn_submit`, } - if ver <= 32 { + if ver <= 33 { dl.txn(&call, "inner app call with version v5 < v6") call.ForeignApps[0] = v6id dl.txn(&call, "overspend") // it tried to execute, but test doesn't bother funding @@ -1986,7 +1986,7 @@ itxn_submit`, createAndOptin.ApplicationArgs = [][]byte{three.Program, three.Program} dl.txn(&createAndOptin, "inner app call with version v3 < v6") - // nor v5 in proto ver 32 + // nor v5 in proto ver 33 createAndOptin.ApplicationArgs = [][]byte{five.Program, five.Program} dl.txn(&createAndOptin, "inner app call with version v5 < v6") @@ -1994,7 +1994,7 @@ itxn_submit`, createAndOptin.ApplicationArgs = [][]byte{six.Program, six.Program} dl.txn(&createAndOptin, "overspend") // passed the checks, but is an overspend } else { - // after 32 proto.MinInnerApplVersion is lowered to 4, so calls and optins to v5 are ok + // after 33 proto.MinInnerApplVersion is lowered to 4, so calls and optins to v5 are ok dl.txn(&call, "overspend") // it tried to execute, but test doesn't bother funding dl.txn(&optin, "overspend") // it tried to execute, but test doesn't bother funding optin.ForeignApps[0] = v5withv3csp // but we can't optin to a v5 if it has an old csp @@ -2152,7 +2152,7 @@ func TestAppDowngrade(t *testing.T) { // Downgrade (allowed for pre 6 programs until MinInnerApplVersion was lowered) update.ClearStateProgram = four.Program - if ver <= 32 { + if ver <= 33 { dl.fullBlock(update.Noted("actually a repeat of first upgrade")) } else { dl.txn(update.Noted("actually a repeat of first upgrade"), "clearstate program version downgrade") @@ -3146,7 +3146,7 @@ itxn_submit } dl.beginBlock() - if ver <= 32 { + if ver <= 33 { dl.txgroup("invalid Account reference", &fund0, &fund1, &callTx) dl.endBlock() return diff --git a/ledger/internal/eval_blackbox_test.go b/ledger/internal/eval_blackbox_test.go index a296f65396..5a533ba8f7 100644 --- a/ledger/internal/eval_blackbox_test.go +++ b/ledger/internal/eval_blackbox_test.go @@ -898,6 +898,8 @@ var consensusByNumber = []protocol.ConsensusVersion{ protocol.ConsensusV30, // AVM v5 (inner txs) protocol.ConsensusV31, // AVM v6 (inner txs with appls) protocol.ConsensusV32, // unlimited assets and apps + protocol.ConsensusV33, // 320 rounds + protocol.ConsensusV34, // AVM v7, stateproofs protocol.ConsensusFuture, } @@ -969,8 +971,8 @@ func TestHeaderAccess(t *testing.T) { t.Parallel() genBalances, addrs, _ := ledgertesting.NewTestGenesis() - // Added in v33 - testConsensusRange(t, 33, 0, func(t *testing.T, ver int) { + // Added in v34 + testConsensusRange(t, 34, 0, func(t *testing.T, ver int) { cv := consensusByNumber[ver] dl := NewDoubleLedger(t, genBalances, cv) defer dl.Close() @@ -1177,9 +1179,9 @@ func TestUnfundedSenders(t *testing.T) { }, } - // v33 is the likely version for UnfundedSenders. Change if that doesn't happen. + // v34 is the likely version for UnfundedSenders. Change if that doesn't happen. var problem string - if ver < 33 { + if ver < 34 { // In the old days, balances.Move would try to increase the rewardsState on the unfunded account problem = "balance 0 below min" } @@ -1232,9 +1234,9 @@ func TestAppCallAppDuringInit(t *testing.T) { ForeignApps: []basics.AppIndex{approveID}, Fee: 2000, // Enough to have the inner fee paid for } - // v33 is the likely version for UnfundedSenders. Change if that doesn't happen. + // v34 is the likely version for UnfundedSenders. Change if that doesn't happen. var problem string - if ver < 33 { + if ver < 34 { // In the old days, balances.Move would try to increase the rewardsState on the unfunded account problem = "balance 0 below min" } diff --git a/ledger/ledger_test.go b/ledger/ledger_test.go index d925cec61b..114057f503 100644 --- a/ledger/ledger_test.go +++ b/ledger/ledger_test.go @@ -1659,9 +1659,9 @@ func TestLedgerKeepsOldBlocksForStateProof(t *testing.T) { // since the first state proof is expected to happen on stateproofInterval*2 we would start give-up on state proofs we would // give up on old state proofs only after stateproofInterval*3 - maxBlocks := int((config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 2) * config.Consensus[protocol.ConsensusFuture].StateProofInterval) + maxBlocks := int((config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals + 2) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) - genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusFuture, 10000000000) + genesisInitState, initKeys := ledgertesting.GenerateInitState(t, protocol.ConsensusCurrentVersion, 10000000000) // place real values on the participation period, so we would create a commitment with some stake. accountsWithValid := make(map[basics.Address]basics.AccountData) @@ -2656,10 +2656,10 @@ func verifyVotersContent(t *testing.T, expected map[basics.Round]*ledgercore.Vot func TestVotersReloadFromDisk(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() - genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = false @@ -2702,11 +2702,11 @@ func TestVotersReloadFromDisk(t *testing.T) { func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() - genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = false @@ -2761,11 +2761,11 @@ func TestVotersReloadFromDiskAfterOneStateProofCommitted(t *testing.T) { func TestVotersReloadFromDiskPassRecoveryPeriod(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] dbName := fmt.Sprintf("%s.%d", t.Name(), crypto.RandUint64()) genesisInitState := getInitState() - genesisInitState.Block.CurrentProtocol = protocol.ConsensusFuture + genesisInitState.Block.CurrentProtocol = protocol.ConsensusCurrentVersion const inMem = true cfg := config.GetDefaultLocal() cfg.Archival = false diff --git a/ledger/voters_test.go b/ledger/voters_test.go index 9c65e73668..b13b11d971 100644 --- a/ledger/voters_test.go +++ b/ledger/voters_test.go @@ -40,9 +40,9 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval - numOfIntervals := config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals - 1 - lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + intervalForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval + numOfIntervals := config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals - 1 + lookbackForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} @@ -56,7 +56,7 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { sinkdata.Status = basics.NotParticipating accts[0][testSinkAddr] = sinkdata - ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, accts) defer ml.Close() conf := config.GetDefaultLocal() @@ -68,7 +68,7 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { // adding blocks to the voterstracker (in order to pass the numOfIntervals*stateproofInterval we add 1) for ; i < (numOfIntervals*intervalForTest)+1; i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } @@ -77,7 +77,7 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { block := randomBlock(basics.Round(i)) i++ - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion // committing stateproof that confirm the (numOfIntervals - 1)th interval var stateTracking bookkeeping.StateProofTrackingData @@ -94,7 +94,7 @@ func TestVoterTrackerDeleteVotersAfterStateproofConfirmed(t *testing.T) { a.Equal(basics.Round((numOfIntervals-2)*intervalForTest-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) block = randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion stateTracking.StateProofNextRound = basics.Round(numOfIntervals * intervalForTest) block.block.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) block.block.BlockHeader.StateProofTracking[protocol.StateProofBasic] = stateTracking @@ -108,9 +108,9 @@ func TestLimitVoterTracker(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval - recoveryIntervalForTests := config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals - lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + intervalForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval + recoveryIntervalForTests := config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals + lookbackForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} @@ -124,7 +124,7 @@ func TestLimitVoterTracker(t *testing.T) { sinkdata.Status = basics.NotParticipating accts[0][testSinkAddr] = sinkdata - ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, accts) defer ml.Close() conf := config.GetDefaultLocal() @@ -140,7 +140,7 @@ func TestLimitVoterTracker(t *testing.T) { // should not give up on any state proof for ; i < intervalForTest*(recoveryIntervalForTests+2); i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } @@ -149,52 +149,52 @@ func TestLimitVoterTracker(t *testing.T) { // + 1 - since votersForRoundCache would contain the votersForRound for the next state proof to come // + 1 - in order to confirm recoveryIntervalForTests number of state proofs we need recoveryIntervalForTests + 1 headers (for the commitment) a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) - a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters for ; i < intervalForTest*(recoveryIntervalForTests+3)+1; i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) - a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*2-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*2-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) // after adding the round intervalForTest*(recoveryIntervalForTests+3)+1 we expect the voter tracker to remove voters for ; i < intervalForTest*(recoveryIntervalForTests+4)+1; i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) - a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) // if the last round of the intervalForTest has not been added to the ledger the votersTracker would // retain one more element for ; i < intervalForTest*(recoveryIntervalForTests+5); i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } a.Equal(recoveryIntervalForTests+3, uint64(len(ao.voters.votersForRoundCache))) - a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*3-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) for ; i < intervalForTest*(recoveryIntervalForTests+5)+1; i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } a.Equal(recoveryIntervalForTests+2, uint64(len(ao.voters.votersForRoundCache))) - a.Equal(basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval*4-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) + a.Equal(basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*4-lookbackForTest), ao.voters.lowestRound(basics.Round(i))) } func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - intervalForTest := config.Consensus[protocol.ConsensusFuture].StateProofInterval - lookbackForTest := config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + intervalForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval + lookbackForTest := config.Consensus[protocol.ConsensusCurrentVersion].StateProofVotersLookback accts := []map[basics.Address]basics.AccountData{ledgertesting.RandomAccounts(20, true)} @@ -208,7 +208,7 @@ func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { sinkdata.Status = basics.NotParticipating accts[0][testSinkAddr] = sinkdata - ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusFuture, accts) + ml := makeMockLedgerForTracker(t, true, 1, protocol.ConsensusCurrentVersion, accts) defer ml.Close() conf := config.GetDefaultLocal() @@ -219,7 +219,7 @@ func TestTopNAccountsThatHaveNoMssKeys(t *testing.T) { i := uint64(1) for ; i < (intervalForTest)+1; i++ { block := randomBlock(basics.Round(i)) - block.block.CurrentProtocol = protocol.ConsensusFuture + block.block.CurrentProtocol = protocol.ConsensusCurrentVersion addBlockToAccountsUpdate(block.block, ao) } diff --git a/protocol/consensus.go b/protocol/consensus.go index 14017385b9..11a5c5f07c 100644 --- a/protocol/consensus.go +++ b/protocol/consensus.go @@ -176,6 +176,17 @@ const ConsensusV32 = ConsensusVersion( "https://github.com/algorandfoundation/specs/tree/d5ac876d7ede07367dbaa26e149aa42589aac1f7", ) +// ConsensusV33 enables large blocks, the deeper block history for TEAL +// and catchpoint generation round after lowering in-memory deltas size (320 -> 4). +const ConsensusV33 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/830a4e673148498cc7230a0d1ba1ed0a5471acc6", +) + +// ConsensusV34 enables the TEAL v7 opcodes, stateproofs, shorter lambda. +const ConsensusV34 = ConsensusVersion( + "https://github.com/algorandfoundation/specs/tree/2dd5435993f6f6d65691140f592ebca5ef19ffbd", +) + // ConsensusFuture is a protocol that should not appear in any production // network, but is used to test features before they are released. const ConsensusFuture = ConsensusVersion( @@ -188,7 +199,7 @@ const ConsensusFuture = ConsensusVersion( // ConsensusCurrentVersion is the latest version and should be used // when a specific version is not provided. -const ConsensusCurrentVersion = ConsensusV32 +const ConsensusCurrentVersion = ConsensusV34 // Error is used to indicate that an unsupported protocol has been detected. type Error ConsensusVersion diff --git a/stateproof/stateproofMessageGenerator_test.go b/stateproof/stateproofMessageGenerator_test.go index ab3399e51f..1b909d775f 100644 --- a/stateproof/stateproofMessageGenerator_test.go +++ b/stateproof/stateproofMessageGenerator_test.go @@ -78,7 +78,7 @@ func (s *workerForStateProofMessageTests) BlockHdr(round basics.Round) (bookkeep func (s *workerForStateProofMessageTests) VotersForStateProof(round basics.Round) (*ledgercore.VotersForRound, error) { voters := &ledgercore.VotersForRound{ - Proto: config.Consensus[protocol.ConsensusFuture], + Proto: config.Consensus[protocol.ConsensusCurrentVersion], AddrToPos: make(map[basics.Address]uint64), } @@ -121,7 +121,7 @@ func (s *workerForStateProofMessageTests) addBlockWithStateProofHeaders(ccNextRo hdr := bookkeeping.BlockHeader{} hdr.Round = s.w.latest - hdr.CurrentProtocol = protocol.ConsensusFuture + hdr.CurrentProtocol = protocol.ConsensusCurrentVersion var ccBasic = bookkeeping.StateProofTrackingData{ StateProofVotersCommitment: make([]byte, stateproof.HashSize), @@ -192,13 +192,13 @@ func TestStateProofMessage(t *testing.T) { dbs, _ := dbOpenTest(t, true) w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) var lastMessage stateproofmsg.Message @@ -263,8 +263,9 @@ func TestGenerateStateProofMessageForSmallRound(t *testing.T) { } s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) _, err := GenerateStateProofMessage(s, 240, s.w.blocks[s.w.latest]) a.ErrorIs(err, errInvalidParams) @@ -284,10 +285,11 @@ func TestMessageLnApproxError(t *testing.T) { } s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) - s.advanceLatest(2*config.Consensus[protocol.ConsensusFuture].StateProofInterval + config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + s.advanceLatest(2*proto.StateProofInterval + proto.StateProofInterval/2) tracking := s.w.blocks[512].StateProofTracking[protocol.StateProofBasic] tracking.StateProofOnlineTotalWeight = basics.MicroAlgos{} newtracking := tracking @@ -312,9 +314,10 @@ func TestMessageMissingHeaderOnInterval(t *testing.T) { s := newWorkerForStateProofMessageStubs(keys[:], len(keys)) s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) - s.advanceLatest(2*config.Consensus[protocol.ConsensusFuture].StateProofInterval + config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + s.advanceLatest(2*proto.StateProofInterval + proto.StateProofInterval/2) delete(s.w.blocks, 510) _, err := GenerateStateProofMessage(s, 256, s.w.blocks[512]) @@ -338,13 +341,13 @@ func TestGenerateBlockProof(t *testing.T) { dbs, _ := dbOpenTest(t, true) w := NewWorker(dbs.Wdb, logging.TestingLog(t), s, s, s, s) + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) for iter := uint64(0); iter < 5; iter++ { @@ -391,10 +394,11 @@ func TestGenerateBlockProofOnSmallArray(t *testing.T) { } s := newWorkerForStateProofMessageStubs(keys, len(keys)) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + s.w.latest-- - s.addBlockWithStateProofHeaders(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlockWithStateProofHeaders(2 * basics.Round(proto.StateProofInterval)) - proto := config.Consensus[protocol.ConsensusFuture] s.advanceLatest(2 * proto.StateProofInterval) headers, err := FetchLightHeaders(s, proto.StateProofInterval, basics.Round(2*proto.StateProofInterval)) a.NoError(err) diff --git a/stateproof/worker_test.go b/stateproof/worker_test.go index 0034b45b0f..4c35c8c693 100644 --- a/stateproof/worker_test.go +++ b/stateproof/worker_test.go @@ -79,7 +79,7 @@ func newWorkerStubs(t testing.TB, keys []account.Participation, totalWeight int) deletedStateProofKeys: map[account.ParticipationID]basics.Round{}, } s.latest-- - s.addBlock(2 * basics.Round(config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlock(2 * basics.Round(config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) return s } @@ -88,7 +88,7 @@ func (s *testWorkerStubs) addBlock(spNextRound basics.Round) { hdr := bookkeeping.BlockHeader{} hdr.Round = s.latest - hdr.CurrentProtocol = protocol.ConsensusFuture + hdr.CurrentProtocol = protocol.ConsensusCurrentVersion var stateProofBasic = bookkeeping.StateProofTrackingData{ StateProofVotersCommitment: make([]byte, stateproof.HashSize), @@ -173,7 +173,7 @@ func (s *testWorkerStubs) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, err func (s *testWorkerStubs) VotersForStateProof(r basics.Round) (*ledgercore.VotersForRound, error) { voters := &ledgercore.VotersForRound{ - Proto: config.Consensus[protocol.ConsensusFuture], + Proto: config.Consensus[protocol.ConsensusCurrentVersion], AddrToPos: make(map[basics.Address]uint64), TotalWeight: basics.MicroAlgos{Raw: uint64(s.totalWeight)}, } @@ -277,7 +277,8 @@ func newPartKey(t testing.TB, parent basics.Address) account.PersistedParticipat partDB, err := db.MakeAccessor(fn, false, true) require.NoError(t, err) - part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(15*config.Consensus[protocol.ConsensusFuture].StateProofInterval), config.Consensus[protocol.ConsensusFuture].DefaultKeyDilution) + proto := config.Consensus[protocol.ConsensusCurrentVersion] + part, err := account.FillDBWithParticipationKeys(partDB, parent, 0, basics.Round(15*proto.StateProofInterval), proto.DefaultKeyDilution) require.NoError(t, err) return part @@ -300,7 +301,7 @@ func TestWorkerAllSigs(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) // Go through several iterations, making sure that we get @@ -369,7 +370,7 @@ func TestWorkerPartialSigs(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) s.advanceLatest(proto.StateProofInterval) @@ -434,7 +435,7 @@ func TestWorkerInsufficientSigs(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(3 * proto.StateProofInterval) for i := 0; i < len(keys); i++ { @@ -465,7 +466,7 @@ func TestWorkerRestart(t *testing.T) { s := newWorkerStubs(t, keys, 10) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(3*proto.StateProofInterval - 1) dbRand := crypto.RandUint64() @@ -511,7 +512,7 @@ func TestWorkerHandleSig(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(3 * proto.StateProofInterval) for i := 0; i < len(keys); i++ { @@ -547,7 +548,7 @@ func TestSignerDeletesUnneededStateProofKeys(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(3 * proto.StateProofInterval) // Expect all signatures to be broadcast. @@ -578,7 +579,7 @@ func TestSignerDoesntDeleteKeysWhenDBDoesntStoreSigs(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(3 * proto.StateProofInterval) // Expect all signatures to be broadcast. @@ -612,7 +613,7 @@ func TestWorkerRemoveBuildersAndSignatures(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) for iter := 0; iter < expectedStateProofs; iter++ { @@ -635,7 +636,7 @@ func TestWorkerRemoveBuildersAndSignatures(t *testing.T) { // add block that confirm a state proof for interval: expectedStateProofs - 1 s.mu.Lock() - s.addBlock(basics.Round((expectedStateProofs - 1) * config.Consensus[protocol.ConsensusFuture].StateProofInterval)) + s.addBlock(basics.Round((expectedStateProofs - 1) * config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval)) s.mu.Unlock() err = waitForBuilderAndSignerToWaitOnRound(s) @@ -654,7 +655,7 @@ func TestWorkerBuildersRecoveryLimit(t *testing.T) { partitiontest.PartitionTest(t) a := require.New(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] var keys []account.Participation for i := 0; i < 10; i++ { var parent basics.Address @@ -762,7 +763,7 @@ func getSignaturesInDatabase(t *testing.T, numAddresses int, sigFrom sigOrigin) // Some tests rely on having only one signature being broadcast at a single round. // for that we need to make sure that addresses won't fall into the same broadcast round. // For that same reason we can't have more than StateProofInterval / 2 address - require.LessOrEqual(t, uint64(numAddresses), config.Consensus[protocol.ConsensusFuture].StateProofInterval/2) + require.LessOrEqual(t, uint64(numAddresses), config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval/2) // Prepare the addresses and the keys signatureBcasted = make(map[basics.Address]int) @@ -849,7 +850,7 @@ func TestSigBroacastTwoPerSig(t *testing.T) { func sendReceiveCountMessages(t *testing.T, tns *testWorkerStubs, signatureBcasted map[basics.Address]int, fromThisNode map[basics.Address]bool, spw *Worker, periods int) { - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] // Collect the broadcast messages var wg sync.WaitGroup @@ -900,7 +901,7 @@ func TestBuilderGeneratesValidStateProofTXN(t *testing.T) { w.Start() defer w.Shutdown() - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] s.advanceLatest(proto.StateProofInterval + proto.StateProofInterval/2) s.advanceLatest(proto.StateProofInterval) @@ -924,7 +925,7 @@ func TestForwardNotFromThisNodeSecondHalf(t *testing.T) { _, _, tns, spw := getSignaturesInDatabase(t, 10, sigNotFromThisNode) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { spw.broadcastSigs(basics.Round(brnd), proto) select { @@ -943,7 +944,7 @@ func TestForwardNotFromThisNodeFirstHalf(t *testing.T) { signatureBcasted, fromThisNode, tns, spw := getSignaturesInDatabase(t, 10, sigAlternateOrigin) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] for brnd := 0; brnd < int(proto.StateProofInterval*10); brnd++ { spw.broadcastSigs(basics.Round(brnd), proto) select { @@ -971,7 +972,7 @@ func TestForwardNotFromThisNodeFirstHalf(t *testing.T) { } func setBlocksAndMessage(t *testing.T, sigRound basics.Round) (s *testWorkerStubs, w *Worker, msg sigFromAddr, msgBytes []byte) { - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] var address basics.Address crypto.RandBytes(address[:]) @@ -998,7 +999,7 @@ func setBlocksAndMessage(t *testing.T, sigRound basics.Round) (s *testWorkerStub func TestWorkerHandleSigOldRounds(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] intervalRound := basics.Round(proto.StateProofInterval) _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound) @@ -1016,7 +1017,7 @@ func TestWorkerHandleSigOldRounds(t *testing.T) { func TestWorkerHandleSigRoundNotInLedger(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] intervalRound := basics.Round(proto.StateProofInterval) _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*10) @@ -1039,7 +1040,7 @@ func TestWorkerHandleSigRoundNotInLedger(t *testing.T) { func TestWorkerHandleSigWrongSignature(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] intervalRound := basics.Round(proto.StateProofInterval) _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*2) @@ -1060,7 +1061,7 @@ func TestWorkerHandleSigWrongSignature(t *testing.T) { func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] proto.StateProofTopVoters = 2 addresses := make([]basics.Address, 0) @@ -1105,7 +1106,7 @@ func TestWorkerHandleSigAddrsNotInTopN(t *testing.T) { func TestWorkerHandleSigAlreadyIn(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] lastRound := proto.StateProofInterval * 2 s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) @@ -1148,7 +1149,7 @@ func TestWorkerHandleSigAlreadyIn(t *testing.T) { func TestWorkerHandleSigExceptionsDbError(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] lastRound := proto.StateProofInterval * 2 s, w, msg, _ := setBlocksAndMessage(t, basics.Round(lastRound)) latestBlockHeader, err := w.ledger.BlockHdr(basics.Round(lastRound)) @@ -1177,13 +1178,13 @@ func TestWorkerHandleSigExceptionsDbError(t *testing.T) { func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] origProto := proto defer func() { - config.Consensus[protocol.ConsensusFuture] = origProto + config.Consensus[protocol.ConsensusCurrentVersion] = origProto }() proto.StateProofInterval = 512 - config.Consensus[protocol.ConsensusFuture] = proto + config.Consensus[protocol.ConsensusCurrentVersion] = proto var address basics.Address crypto.RandBytes(address[:]) @@ -1225,13 +1226,13 @@ func TestWorkerHandleSigCantMakeBuilder(t *testing.T) { func TestWorkerHandleSigIntervalZero(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] origProto := proto defer func() { - config.Consensus[protocol.ConsensusFuture] = origProto + config.Consensus[protocol.ConsensusCurrentVersion] = origProto }() proto.StateProofInterval = 0 - config.Consensus[protocol.ConsensusFuture] = proto + config.Consensus[protocol.ConsensusCurrentVersion] = proto intervalRound := basics.Round(proto.StateProofInterval) _, w, msg, msgBytes := setBlocksAndMessage(t, intervalRound*2) @@ -1252,7 +1253,7 @@ func TestWorkerHandleSigIntervalZero(t *testing.T) { func TestWorkerHandleSigNotOnInterval(t *testing.T) { partitiontest.PartitionTest(t) - proto := config.Consensus[protocol.ConsensusFuture] + proto := config.Consensus[protocol.ConsensusCurrentVersion] _, w, msg, msgBytes := setBlocksAndMessage(t, basics.Round(600)) reply := w.handleSigMessage(network.IncomingMessage{ From 3d5d9034fa00fd395dc752ed02dbf2fdaf8acdff Mon Sep 17 00:00:00 2001 From: Brian Olson Date: Tue, 9 Aug 2022 15:49:40 -0400 Subject: [PATCH 06/24] un-shadow privateKeys var (#4380) --- cmd/loadgenerator/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/loadgenerator/main.go b/cmd/loadgenerator/main.go index cfa1ea66af..25026f7f90 100644 --- a/cmd/loadgenerator/main.go +++ b/cmd/loadgenerator/main.go @@ -137,7 +137,7 @@ func main() { } } else if len(algodDir) > 0 { // get test cluster local unlocked wallet - privateKeys := findRootKeys(algodDir) + privateKeys = findRootKeys(algodDir) if len(privateKeys) == 0 { fmt.Fprintf(os.Stderr, "%s: found no root keys\n", algodDir) os.Exit(1) From a83eac70257db374f1d4ebb027dd35bff755b755 Mon Sep 17 00:00:00 2001 From: nicholasguoalgorand <67928479+nicholasguoalgorand@users.noreply.github.com> Date: Tue, 9 Aug 2022 13:13:40 -0700 Subject: [PATCH 07/24] tests: localize failure place in TestAccountsCanSendMoney (#4379) * the test did not throw an error when not all txns are confirmed within the allotted time. --- .../features/transactions/sendReceive_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/e2e-go/features/transactions/sendReceive_test.go b/test/e2e-go/features/transactions/sendReceive_test.go index 693ed8f2a3..d17bd7dd2b 100644 --- a/test/e2e-go/features/transactions/sendReceive_test.go +++ b/test/e2e-go/features/transactions/sendReceive_test.go @@ -141,9 +141,13 @@ func testAccountsCanSendMoney(t *testing.T, templatePath string, numberOfSends i curStatus, _ := pongClient.Status() curRound := curStatus.LastRound + confirmed := true + fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pongClient.DataDir())) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) + confirmed = fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) + a.True(confirmed, "failed to see confirmed ping transaction by round %v", curRound+uint64(5)) + confirmed = fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) + a.True(confirmed, "failed to see confirmed pong transaction by round %v", curRound+uint64(5)) pingBalance, err = pongClient.GetBalance(pingAccount) a.NoError(err) @@ -153,8 +157,10 @@ func testAccountsCanSendMoney(t *testing.T, templatePath string, numberOfSends i a.True(expectedPongBalance <= pongBalance, "pong balance is different than expected.") fixture.AlgodClient = fixture.GetAlgodClientForController(fixture.GetNodeControllerForDataDir(pingClient.DataDir())) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) - fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) + confirmed = fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pingTxidsToAddresses) + a.True(confirmed, "failed to see confirmed ping transaction by round %v", curRound+uint64(5)) + confirmed = fixture.WaitForAllTxnsToConfirm(curRound+uint64(5), pongTxidsToAddresses) + a.True(confirmed, "failed to see confirmed pong transaction by round %v", curRound+uint64(5)) pingBalance, err = pingClient.GetBalance(pingAccount) a.NoError(err) From 2d510704f08245e14d9c761a4c022f82e220c5d9 Mon Sep 17 00:00:00 2001 From: Brian Olson Date: Tue, 9 Aug 2022 16:33:32 -0400 Subject: [PATCH 08/24] make txBacklogSize responsive to block size (#4377) --- data/txHandler.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/txHandler.go b/data/txHandler.go index 9bc91203ad..46248b4edb 100644 --- a/data/txHandler.go +++ b/data/txHandler.go @@ -23,6 +23,7 @@ import ( "io" "sync" + "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/pools" @@ -38,7 +39,8 @@ import ( // The size txBacklogSize used to determine the size of the backlog that is used to store incoming transaction messages before starting dropping them. // It should be configured to be higher then the number of CPU cores, so that the execution pool get saturated, but not too high to avoid lockout of the // execution pool for a long duration of time. -const txBacklogSize = 1000 +// Set backlog at 'approximately one block' by dividing block size by a typical transaction size. +var txBacklogSize = config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnBytesPerBlock / 200 var transactionMessagesHandled = metrics.MakeCounter(metrics.TransactionMessagesHandled) var transactionMessagesDroppedFromBacklog = metrics.MakeCounter(metrics.TransactionMessagesDroppedFromBacklog) From 42f449f01fc6205bac96c2cd21cf3e9b26c7cb2c Mon Sep 17 00:00:00 2001 From: Or Aharonee <17099688+Aharonee@users.noreply.github.com> Date: Tue, 9 Aug 2022 23:35:03 +0300 Subject: [PATCH 09/24] Algod: rename some API operations for clarity (#4376) * Rename GetProof to GetTransactionProof to avoid ambiguity * Rename GetProofForLightBlockHeader to GetLightBlockHeaderProof --- daemon/algod/api/algod.oas2.json | 53 ++- daemon/algod/api/algod.oas3.yml | 146 +++--- daemon/algod/api/client/restClient.go | 2 +- .../api/server/v2/generated/private/routes.go | 309 ++++++------ .../api/server/v2/generated/private/types.go | 62 ++- .../algod/api/server/v2/generated/routes.go | 439 +++++++++--------- daemon/algod/api/server/v2/generated/types.go | 66 ++- daemon/algod/api/server/v2/handlers.go | 17 +- .../algod/api/server/v2/test/handlers_test.go | 14 +- libgoal/libgoal.go | 2 +- test/framework/fixtures/libgoalFixture.go | 6 +- 11 files changed, 612 insertions(+), 504 deletions(-) diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index 47aa60bb41..c95c0dbd18 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -531,8 +531,8 @@ "schemes": [ "http" ], - "summary": "Get a Merkle proof for a transaction in a block.", - "operationId": "GetProof", + "summary": "Get a proof for a transaction in a block.", + "operationId": "GetTransactionProof", "parameters": [ { "type": "integer", @@ -566,7 +566,7 @@ ], "responses": { "200": { - "$ref": "#/responses/ProofResponse" + "$ref": "#/responses/TransactionProofResponse" }, "400": { "description": "Malformed round number or transaction ID", @@ -587,7 +587,7 @@ } }, "500": { - "description": "Internal error, including protocol not supporting Merkle proofs.", + "description": "Internal error, including protocol not supporting proofs.", "schema": { "$ref": "#/definitions/ErrorResponse" } @@ -1317,7 +1317,7 @@ "http" ], "summary": "Gets a proof for a given light block header inside a state proof commitment", - "operationId": "GetProofForLightBlockHeader", + "operationId": "GetLightBlockHeaderProof", "parameters": [ { "type": "integer", @@ -2683,9 +2683,42 @@ ], "properties": { "Message": { - "description": "The encoded message.", - "type": "string", - "format": "byte" + "description": "Represents the message that the state proofs are attesting to.", + "type": "object", + "required": [ + "BlockHeadersCommitment", + "VotersCommitment", + "LnProvenWeight", + "FirstAttestedRound", + "LastAttestedRound" + ], + "properties": { + "BlockHeadersCommitment": { + "description": "The vector commitment root on all light block headers within a state proof interval.", + "type": "string", + "format": "byte" + }, + "VotersCommitment": { + "description": "The vector commitment root of the top N accounts to sign the next StateProof.", + "type": "string", + "format": "byte" + }, + "LnProvenWeight": { + "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "FirstAttestedRound": { + "description": "The first round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LastAttestedRound": { + "description": "The last round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + } + } }, "StateProof": { "description": "The encoded StateProof for the message.", @@ -2976,7 +3009,7 @@ } } }, - "ProofResponse": { + "TransactionProofResponse": { "description": "Proof of transaction in a block.", "schema": { "type": "object", @@ -2989,7 +3022,7 @@ ], "properties": { "proof": { - "description": "Merkle proof of transaction membership.", + "description": "Proof of transaction membership.", "type": "string", "format": "byte" }, diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 969134bcb3..44d4d1b955 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -630,53 +630,6 @@ }, "description": "Transaction ID of the submission." }, - "ProofResponse": { - "content": { - "application/json": { - "schema": { - "properties": { - "hashtype": { - "description": "The type of hash function used to create the proof, must be one of: \n* sha512_256 \n* sha256", - "enum": [ - "sha512_256", - "sha256" - ], - "type": "string" - }, - "idx": { - "description": "Index of the transaction in the block's payset.", - "type": "integer" - }, - "proof": { - "description": "Merkle proof of transaction membership.", - "format": "byte", - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - "type": "string" - }, - "stibhash": { - "description": "Hash of SignedTxnInBlock for verifying proof.", - "format": "byte", - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - "type": "string" - }, - "treedepth": { - "description": "Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root.", - "type": "integer" - } - }, - "required": [ - "hashtype", - "idx", - "proof", - "stibhash", - "treedepth" - ], - "type": "object" - } - } - }, - "description": "Proof of transaction in a block." - }, "StateProofResponse": { "content": { "application/json": { @@ -765,6 +718,53 @@ }, "description": "TransactionParams contains the parameters that help a client construct a new transaction." }, + "TransactionProofResponse": { + "content": { + "application/json": { + "schema": { + "properties": { + "hashtype": { + "description": "The type of hash function used to create the proof, must be one of: \n* sha512_256 \n* sha256", + "enum": [ + "sha512_256", + "sha256" + ], + "type": "string" + }, + "idx": { + "description": "Index of the transaction in the block's payset.", + "type": "integer" + }, + "proof": { + "description": "Proof of transaction membership.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "stibhash": { + "description": "Hash of SignedTxnInBlock for verifying proof.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "treedepth": { + "description": "Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root.", + "type": "integer" + } + }, + "required": [ + "hashtype", + "idx", + "proof", + "stibhash", + "treedepth" + ], + "type": "object" + } + } + }, + "description": "Proof of transaction in a block." + }, "VersionsResponse": { "content": { "application/json": { @@ -1623,10 +1623,44 @@ "description": "Represents a state proof and its corresponding message", "properties": { "Message": { - "description": "The encoded message.", - "format": "byte", - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - "type": "string" + "description": "Represents the message that the state proofs are attesting to.", + "properties": { + "BlockHeadersCommitment": { + "description": "The vector commitment root on all light block headers within a state proof interval.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "FirstAttestedRound": { + "description": "The first round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LastAttestedRound": { + "description": "The last round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LnProvenWeight": { + "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "VotersCommitment": { + "description": "The vector commitment root of the top N accounts to sign the next StateProof.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + } + }, + "required": [ + "BlockHeadersCommitment", + "FirstAttestedRound", + "LastAttestedRound", + "LnProvenWeight", + "VotersCommitment" + ], + "type": "object" }, "StateProof": { "description": "The encoded StateProof for the message.", @@ -2666,7 +2700,7 @@ }, "/v2/blocks/{round}/lightheader/proof": { "get": { - "operationId": "GetProofForLightBlockHeader", + "operationId": "GetLightBlockHeaderProof", "parameters": [ { "description": "The round to which the light block header belongs.", @@ -2740,7 +2774,7 @@ }, "/v2/blocks/{round}/transactions/{txid}/proof": { "get": { - "operationId": "GetProof", + "operationId": "GetTransactionProof", "parameters": [ { "description": "The round in which the transaction appears.", @@ -2805,7 +2839,7 @@ "type": "integer" }, "proof": { - "description": "Merkle proof of transaction membership.", + "description": "Proof of transaction membership.", "format": "byte", "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", "type": "string" @@ -2872,14 +2906,14 @@ } } }, - "description": "Internal error, including protocol not supporting Merkle proofs." + "description": "Internal error, including protocol not supporting proofs." }, "default": { "content": {}, "description": "Unknown error" } }, - "summary": "Get a Merkle proof for a transaction in a block." + "summary": "Get a proof for a transaction in a block." } }, "/v2/catchup/{catchpoint}": { diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index ac4a78ff32..b5c3097565 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -661,7 +661,7 @@ func (client RestClient) LightBlockHeaderProof(round uint64) (response generated } // TransactionProof gets a Merkle proof for a transaction in a block. -func (client RestClient) TransactionProof(txid string, round uint64, hashType crypto.HashType) (response generatedV2.ProofResponse, err error) { +func (client RestClient) TransactionProof(txid string, round uint64, hashType crypto.HashType) (response generatedV2.TransactionProofResponse, err error) { txid = stripTransaction(txid) err = client.get(&response, fmt.Sprintf("/v2/blocks/%d/transactions/%s/proof", round, txid), proofParams{HashType: hashType.String()}) return diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index 9367f54efd..2ea4c40331 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -311,159 +311,162 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "///X/FsWRbgAAA==", + "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", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index 83d9ea4020..8221e77310 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -459,8 +459,24 @@ type StateDelta []EvalDeltaKeyValue // StateProof defines model for StateProof. type StateProof struct { - // The encoded message. - Message []byte `json:"Message"` + // Represents the message that the state proofs are attesting to. + Message struct { + + // The vector commitment root on all light block headers within a state proof interval. + BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` + + // The first round the message attests to. + FirstAttestedRound uint64 `json:"FirstAttestedRound"` + + // The last round the message attests to. + LastAttestedRound uint64 `json:"LastAttestedRound"` + + // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. + LnProvenWeight uint64 `json:"LnProvenWeight"` + + // The vector commitment root of the top N accounts to sign the next StateProof. + VotersCommitment []byte `json:"VotersCommitment"` + } `json:"Message"` // The encoded StateProof for the message. StateProof []byte `json:"StateProof"` @@ -742,27 +758,6 @@ type PostTransactionsResponse struct { TxId string `json:"txId"` } -// ProofResponse defines model for ProofResponse. -type ProofResponse struct { - - // The type of hash function used to create the proof, must be one of: - // * sha512_256 - // * sha256 - Hashtype string `json:"hashtype"` - - // Index of the transaction in the block's payset. - Idx uint64 `json:"idx"` - - // Merkle proof of transaction membership. - Proof []byte `json:"proof"` - - // Hash of SignedTxnInBlock for verifying proof. - Stibhash []byte `json:"stibhash"` - - // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. - Treedepth uint64 `json:"treedepth"` -} - // StateProofResponse defines model for StateProofResponse. type StateProofResponse StateProof @@ -806,6 +801,27 @@ type TransactionParametersResponse struct { MinFee uint64 `json:"min-fee"` } +// TransactionProofResponse defines model for TransactionProofResponse. +type TransactionProofResponse struct { + + // The type of hash function used to create the proof, must be one of: + // * sha512_256 + // * sha256 + Hashtype string `json:"hashtype"` + + // Index of the transaction in the block's payset. + Idx uint64 `json:"idx"` + + // Proof of transaction membership. + Proof []byte `json:"proof"` + + // Hash of SignedTxnInBlock for verifying proof. + Stibhash []byte `json:"stibhash"` + + // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. + Treedepth uint64 `json:"treedepth"` +} + // VersionsResponse defines model for VersionsResponse. type VersionsResponse Version diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index 3539f8f9a3..185fbbca1b 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -40,10 +40,10 @@ type ServerInterface interface { GetBlock(ctx echo.Context, round uint64, params GetBlockParams) error // Gets a proof for a given light block header inside a state proof commitment // (GET /v2/blocks/{round}/lightheader/proof) - GetProofForLightBlockHeader(ctx echo.Context, round uint64) error - // Get a Merkle proof for a transaction in a block. + GetLightBlockHeaderProof(ctx echo.Context, round uint64) error + // Get a proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) - GetProof(ctx echo.Context, round uint64, txid string, params GetProofParams) error + GetTransactionProof(ctx echo.Context, round uint64, txid string, params GetTransactionProofParams) error // Get the current supply reported by the ledger. // (GET /v2/ledger/supply) GetSupply(ctx echo.Context) error @@ -397,8 +397,8 @@ func (w *ServerInterfaceWrapper) GetBlock(ctx echo.Context) error { return err } -// GetProofForLightBlockHeader converts echo context to params. -func (w *ServerInterfaceWrapper) GetProofForLightBlockHeader(ctx echo.Context) error { +// GetLightBlockHeaderProof converts echo context to params. +func (w *ServerInterfaceWrapper) GetLightBlockHeaderProof(ctx echo.Context) error { validQueryParams := map[string]bool{ "pretty": true, @@ -423,12 +423,12 @@ func (w *ServerInterfaceWrapper) GetProofForLightBlockHeader(ctx echo.Context) e ctx.Set("api_key.Scopes", []string{""}) // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetProofForLightBlockHeader(ctx, round) + err = w.Handler.GetLightBlockHeaderProof(ctx, round) return err } -// GetProof converts echo context to params. -func (w *ServerInterfaceWrapper) GetProof(ctx echo.Context) error { +// GetTransactionProof converts echo context to params. +func (w *ServerInterfaceWrapper) GetTransactionProof(ctx echo.Context) error { validQueryParams := map[string]bool{ "pretty": true, @@ -463,7 +463,7 @@ func (w *ServerInterfaceWrapper) GetProof(ctx echo.Context) error { ctx.Set("api_key.Scopes", []string{""}) // Parameter object where we will unmarshal all parameters from the context - var params GetProofParams + var params GetTransactionProofParams // ------------- Optional query parameter "hashtype" ------------- if paramValue := ctx.QueryParam("hashtype"); paramValue != "" { @@ -485,7 +485,7 @@ func (w *ServerInterfaceWrapper) GetProof(ctx echo.Context) error { } // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetProof(ctx, round, txid, params) + err = w.Handler.GetTransactionProof(ctx, round, txid, params) return err } @@ -837,8 +837,8 @@ func RegisterHandlers(router interface { router.GET("/v2/applications/:application-id", wrapper.GetApplicationByID, m...) router.GET("/v2/assets/:asset-id", wrapper.GetAssetByID, m...) router.GET("/v2/blocks/:round", wrapper.GetBlock, m...) - router.GET("/v2/blocks/:round/lightheader/proof", wrapper.GetProofForLightBlockHeader, m...) - router.GET("/v2/blocks/:round/transactions/:txid/proof", wrapper.GetProof, m...) + router.GET("/v2/blocks/:round/lightheader/proof", wrapper.GetLightBlockHeaderProof, m...) + router.GET("/v2/blocks/:round/transactions/:txid/proof", wrapper.GetTransactionProof, m...) router.GET("/v2/ledger/supply", wrapper.GetSupply, m...) router.GET("/v2/stateproofs/:round", wrapper.GetStateProof, m...) router.GET("/v2/status", wrapper.GetStatus, m...) @@ -856,212 +856,215 @@ func RegisterHandlers(router interface { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "+/j/AwAA//92ZqolGSgBAA==", + "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", + "ljPEDCwBAA==", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index ec2587265b..db3bd074eb 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -459,8 +459,24 @@ type StateDelta []EvalDeltaKeyValue // StateProof defines model for StateProof. type StateProof struct { - // The encoded message. - Message []byte `json:"Message"` + // Represents the message that the state proofs are attesting to. + Message struct { + + // The vector commitment root on all light block headers within a state proof interval. + BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` + + // The first round the message attests to. + FirstAttestedRound uint64 `json:"FirstAttestedRound"` + + // The last round the message attests to. + LastAttestedRound uint64 `json:"LastAttestedRound"` + + // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. + LnProvenWeight uint64 `json:"LnProvenWeight"` + + // The vector commitment root of the top N accounts to sign the next StateProof. + VotersCommitment []byte `json:"VotersCommitment"` + } `json:"Message"` // The encoded StateProof for the message. StateProof []byte `json:"StateProof"` @@ -742,27 +758,6 @@ type PostTransactionsResponse struct { TxId string `json:"txId"` } -// ProofResponse defines model for ProofResponse. -type ProofResponse struct { - - // The type of hash function used to create the proof, must be one of: - // * sha512_256 - // * sha256 - Hashtype string `json:"hashtype"` - - // Index of the transaction in the block's payset. - Idx uint64 `json:"idx"` - - // Merkle proof of transaction membership. - Proof []byte `json:"proof"` - - // Hash of SignedTxnInBlock for verifying proof. - Stibhash []byte `json:"stibhash"` - - // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. - Treedepth uint64 `json:"treedepth"` -} - // StateProofResponse defines model for StateProofResponse. type StateProofResponse StateProof @@ -806,6 +801,27 @@ type TransactionParametersResponse struct { MinFee uint64 `json:"min-fee"` } +// TransactionProofResponse defines model for TransactionProofResponse. +type TransactionProofResponse struct { + + // The type of hash function used to create the proof, must be one of: + // * sha512_256 + // * sha256 + Hashtype string `json:"hashtype"` + + // Index of the transaction in the block's payset. + Idx uint64 `json:"idx"` + + // Proof of transaction membership. + Proof []byte `json:"proof"` + + // Hash of SignedTxnInBlock for verifying proof. + Stibhash []byte `json:"stibhash"` + + // Represents the depth of the tree that is being proven, i.e. the number of edges from a leaf to the root. + Treedepth uint64 `json:"treedepth"` +} + // VersionsResponse defines model for VersionsResponse. type VersionsResponse Version @@ -850,8 +866,8 @@ type GetBlockParams struct { Format *string `json:"format,omitempty"` } -// GetProofParams defines parameters for GetProof. -type GetProofParams struct { +// GetTransactionProofParams defines parameters for GetTransactionProof. +type GetTransactionProofParams struct { // The type of hash function used to create the proof, must be one of: // * sha512_256 diff --git a/daemon/algod/api/server/v2/handlers.go b/daemon/algod/api/server/v2/handlers.go index 141f71882f..918440f19b 100644 --- a/daemon/algod/api/server/v2/handlers.go +++ b/daemon/algod/api/server/v2/handlers.go @@ -593,9 +593,9 @@ func (v2 *Handlers) GetBlock(ctx echo.Context, round uint64, params generated.Ge return ctx.Blob(http.StatusOK, contentType, data) } -// GetProof generates a Merkle proof for a transaction in a block. +// GetTransactionProof generates a Merkle proof for a transaction in a block. // (GET /v2/blocks/{round}/transactions/{txid}/proof) -func (v2 *Handlers) GetProof(ctx echo.Context, round uint64, txid string, params generated.GetProofParams) error { +func (v2 *Handlers) GetTransactionProof(ctx echo.Context, round uint64, txid string, params generated.GetTransactionProofParams) error { var txID transactions.Txid err := txID.UnmarshalText([]byte(txid)) if err != nil { @@ -660,7 +660,7 @@ func (v2 *Handlers) GetProof(ctx echo.Context, round uint64, txid string, params return internalError(ctx, err, "generating proof", v2.Log) } - response := generated.ProofResponse{ + response := generated.TransactionProofResponse{ Proof: proof.GetConcatenatedProof(), Stibhash: stibhash[:], Idx: uint64(idx), @@ -1254,16 +1254,21 @@ func (v2 *Handlers) GetStateProof(ctx echo.Context, round uint64) error { } response := generated.StateProofResponse{ - Message: protocol.Encode(&tx.Message), StateProof: protocol.Encode(&tx.StateProof), } + response.Message.BlockHeadersCommitment = tx.Message.BlockHeadersCommitment + response.Message.VotersCommitment = tx.Message.VotersCommitment + response.Message.LnProvenWeight = tx.Message.LnProvenWeight + response.Message.FirstAttestedRound = tx.Message.FirstAttestedRound + response.Message.LastAttestedRound = tx.Message.LastAttestedRound + return ctx.JSON(http.StatusOK, response) } -// GetProofForLightBlockHeader Gets a proof of a light block header for a given round +// GetLightBlockHeaderProof Gets a proof of a light block header for a given round // (GET /v2/blocks/{round}/lightheader/proof) -func (v2 *Handlers) GetProofForLightBlockHeader(ctx echo.Context, round uint64) error { +func (v2 *Handlers) GetLightBlockHeaderProof(ctx echo.Context, round uint64) error { ledger := v2.Node.LedgerForAPI() if ledger.Latest() < basics.Round(round) { return internalError(ctx, errors.New(errRoundGreaterThanTheLatest), errRoundGreaterThanTheLatest, v2.Log) diff --git a/daemon/algod/api/server/v2/test/handlers_test.go b/daemon/algod/api/server/v2/test/handlers_test.go index f8b7e4eb5b..789097fda5 100644 --- a/daemon/algod/api/server/v2/test/handlers_test.go +++ b/daemon/algod/api/server/v2/test/handlers_test.go @@ -934,10 +934,10 @@ func TestGetProofDefault(t *testing.T) { defer releasefunc() txid := stx.ID() - err := handler.GetProof(c, 1, txid.String(), generated.GetProofParams{}) + err := handler.GetTransactionProof(c, 1, txid.String(), generated.GetTransactionProofParams{}) a.NoError(err) - var resp generatedV2.ProofResponse + var resp generatedV2.TransactionProofResponse err = json.Unmarshal(rec.Body.Bytes(), &resp) a.NoError(err) a.Equal("sha512_256", resp.Hashtype) @@ -1075,9 +1075,7 @@ func TestStateProof200(t *testing.T) { stprfResp := generated.StateProofResponse{} a.NoError(json.Unmarshal(responseRecorder.Body.Bytes(), &stprfResp)) - msg := stateproofmsg.Message{} - a.NoError(protocol.Decode(stprfResp.Message, &msg)) - a.Equal([]byte{0x0, 0x1, 0x2}, msg.BlockHeadersCommitment) + a.Equal([]byte{0x0, 0x1, 0x2}, stprfResp.Message.BlockHeadersCommitment) } func TestHeaderProofRoundTooHigh(t *testing.T) { @@ -1087,7 +1085,7 @@ func TestHeaderProofRoundTooHigh(t *testing.T) { handler, ctx, responseRecorder, _, _, releasefunc := setupTestForMethodGet(t) defer releasefunc() - a.NoError(handler.GetProofForLightBlockHeader(ctx, 2)) + a.NoError(handler.GetLightBlockHeaderProof(ctx, 2)) a.Equal(500, responseRecorder.Code) } @@ -1100,7 +1098,7 @@ func TestHeaderProofStateProofNotFound(t *testing.T) { insertRounds(a, handler, 700) - a.NoError(handler.GetProofForLightBlockHeader(ctx, 650)) + a.NoError(handler.GetLightBlockHeaderProof(ctx, 650)) a.Equal(404, responseRecorder.Code) } @@ -1113,7 +1111,7 @@ func TestGetBlockProof200(t *testing.T) { insertRounds(a, handler, 1000) - a.NoError(handler.GetProofForLightBlockHeader(ctx, stateProofIntervalForHandlerTests*2+2)) + a.NoError(handler.GetLightBlockHeaderProof(ctx, stateProofIntervalForHandlerTests*2+2)) a.Equal(200, responseRecorder.Code) blkHdrArr, err := stateproof.FetchLightHeaders(handler.Node.LedgerForAPI(), stateProofIntervalForHandlerTests, basics.Round(stateProofIntervalForHandlerTests*3)) diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 562ec0fe1b..b19d379a31 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1212,7 +1212,7 @@ func (c *Client) Dryrun(data []byte) (resp generatedV2.DryrunResponse, err error } // TransactionProof returns a Merkle proof for a transaction in a block. -func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp generatedV2.ProofResponse, err error) { +func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp generatedV2.TransactionProofResponse, err error) { algod, err := c.ensureAlgodClient() if err == nil { return algod.TransactionProof(txid, round, hashType) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index 34c8276ede..af84e4d2e1 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -504,15 +504,15 @@ func (f *LibGoalFixture) MinFeeAndBalance(round uint64) (minFee, minBalance uint } // TransactionProof returns a proof for usage in merkle array verification for the provided transaction. -func (f *LibGoalFixture) TransactionProof(txid string, round uint64, hashType crypto.HashType) (generatedV2.ProofResponse, merklearray.SingleLeafProof, error) { +func (f *LibGoalFixture) TransactionProof(txid string, round uint64, hashType crypto.HashType) (generatedV2.TransactionProofResponse, merklearray.SingleLeafProof, error) { proofResp, err := f.LibGoalClient.TransactionProof(txid, round, hashType) if err != nil { - return generatedV2.ProofResponse{}, merklearray.SingleLeafProof{}, err + return generatedV2.TransactionProofResponse{}, merklearray.SingleLeafProof{}, err } proof, err := merklearray.ProofDataToSingleLeafProof(proofResp.Hashtype, proofResp.Treedepth, proofResp.Proof) if err != nil { - return generatedV2.ProofResponse{}, merklearray.SingleLeafProof{}, err + return generatedV2.TransactionProofResponse{}, merklearray.SingleLeafProof{}, err } return proofResp, proof, nil From 701d431782c0cc3ddbd386e3db8640c943bcc070 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Wed, 10 Aug 2022 08:23:25 -0400 Subject: [PATCH 10/24] test: wait until new block is committed to `BlockQueue` (#4381) --- ledger/internal/eval_blackbox_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ledger/internal/eval_blackbox_test.go b/ledger/internal/eval_blackbox_test.go index 5a533ba8f7..11c51f9aa9 100644 --- a/ledger/internal/eval_blackbox_test.go +++ b/ledger/internal/eval_blackbox_test.go @@ -533,6 +533,16 @@ func endBlock(t testing.TB, ledger *ledger.Ledger, eval *internal.BlockEvaluator require.NoError(t, err) err = ledger.AddValidatedBlock(*validatedBlock, agreement.Certificate{}) require.NoError(t, err) + // `rndBQ` gives the latest known block round added to the ledger + // we should wait until `rndBQ` block to be committed to blockQueue, + // in case there is a data race, noted in + // https://github.com/algorand/go-algorand/issues/4349 + // where writing to `callTxnGroup` after `dl.fullBlock` caused data race, + // because the underlying async goroutine `go bq.syncer()` is reading `callTxnGroup`. + // A solution here would be wait until all new added blocks are committed, + // then we return the result and continue the execution. + rndBQ := ledger.Latest() + ledger.WaitForCommit(rndBQ) return validatedBlock } From e3ffb42db1c2954330284d73d2cdb1450964ca10 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Thu, 11 Aug 2022 19:47:58 +0300 Subject: [PATCH 11/24] download StateProofVotersLookback more amount of blocks if needed (#4392) A fast-caught up node should be able to be part of state proofs creation and must download all necessary blocks. Currently, if the state proof chain is lagging the node download lowestStateProofRound - stateproofInterval rounds back on fast catchup. This is not accurate since the balances comes from lowestStateProofRound - stateproofInterval - StateProofVotersLookback round. --- catchup/catchpointService.go | 4 +++- catchup/service_test.go | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 5d01fa9608..e2a9fcc387 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -473,8 +473,10 @@ func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { return 0 } lowestStateProofRound := stateproof.GetOldestExpectedStateProof(&topBlock.BlockHeader) - // in order to be able to confirm lowestStateProofRound we need to have round number: (lowestStateProofRound - stateproofInterval) + // in order to be able to confirm/build lowestStateProofRound we would need to reconstruct + // the corresponding voterForRound which is (lowestStateProofRound - stateproofInterval - VotersLookback) lowestStateProofRound = lowestStateProofRound.SubSaturate(basics.Round(proto.StateProofInterval)) + lowestStateProofRound = lowestStateProofRound.SubSaturate(basics.Round(proto.StateProofVotersLookback)) return uint64(topBlock.Round().SubSaturate(lowestStateProofRound)) } diff --git a/catchup/service_test.go b/catchup/service_test.go index 679ee3239e..676a283bad 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -988,7 +988,7 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { lookback := lookbackForStateproofsSupport(&topBlk) oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) - assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval) + assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval-config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback) // the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512. // instead, we will download blocks to confirm only the recovery period lookback. @@ -1002,7 +1002,9 @@ func TestDownloadBlocksToSupportStateProofs(t *testing.T) { lookback = lookbackForStateproofsSupport(&topBlk) oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) - lowestRoundToRetain := 8000 - (8000 % 256) - (config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval * (config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals + 1)) + lowestRoundToRetain := 8000 - (8000 % config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval) - + config.Consensus[protocol.ConsensusCurrentVersion].StateProofInterval*(config.Consensus[protocol.ConsensusCurrentVersion].StateProofMaxRecoveryIntervals+1) - config.Consensus[protocol.ConsensusFuture].StateProofVotersLookback + assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) topBlk = bookkeeping.Block{} From 22a47906e7eae9cb64d0cacd781d457d23c6e3ed Mon Sep 17 00:00:00 2001 From: iten-alg <85889519+iten-alg@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:58:55 -0500 Subject: [PATCH 12/24] AVM: Add Semicolon Parsing (#4363) Allow semicolons to separate "statements" in TEAL. Co-authored-by: John Jannotti --- data/transactions/logic/assembler.go | 202 ++++---- data/transactions/logic/assembler_test.go | 453 +++++++----------- .../transactions/logic/backwardCompat_test.go | 6 +- data/transactions/logic/evalStateful_test.go | 8 +- data/transactions/logic/eval_test.go | 129 +++-- data/transactions/logic/fields_test.go | 2 +- 6 files changed, 374 insertions(+), 426 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index e175a17034..f99c480834 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -545,7 +545,7 @@ func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func base32DecdodeAnyPadding(x string) (val []byte, err error) { +func base32DecodeAnyPadding(x string) (val []byte, err error) { val, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(x) if err != nil { // try again with standard padding @@ -567,7 +567,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) { err = errors.New("byte base32 arg lacks close paren") return } - val, err = base32DecdodeAnyPadding(arg[open+1 : close]) + val, err = base32DecodeAnyPadding(arg[open+1 : close]) if err != nil { return } @@ -595,7 +595,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) { err = fmt.Errorf("need literal after 'byte %s'", arg) return } - val, err = base32DecdodeAnyPadding(args[1]) + val, err = base32DecodeAnyPadding(args[1]) if err != nil { return } @@ -1399,25 +1399,29 @@ func typecheck(expected, got StackType) bool { return expected == got } -var spaces = [256]uint8{'\t': 1, ' ': 1} +// newline not included since handled in scanner +var tokenSeparators = [256]bool{'\t': true, ' ': true, ';': true} -func fieldsFromLine(line string) []string { - var fields []string +func tokensFromLine(line string) []string { + var tokens []string i := 0 - for i < len(line) && spaces[line[i]] != 0 { + for i < len(line) && tokenSeparators[line[i]] { + if line[i] == ';' { + tokens = append(tokens, ";") + } i++ } start := i - inString := false - inBase64 := false + inString := false // tracked to allow spaces and comments inside + inBase64 := false // tracked to allow '//' inside for i < len(line) { - if spaces[line[i]] == 0 { // if not space + if !tokenSeparators[line[i]] { // if not space switch line[i] { case '"': // is a string literal? if !inString { - if i == 0 || i > 0 && spaces[line[i-1]] != 0 { + if i == 0 || i > 0 && tokenSeparators[line[i-1]] { inString = true } } else { @@ -1428,9 +1432,9 @@ func fieldsFromLine(line string) []string { case '/': // is a comment? if i < len(line)-1 && line[i+1] == '/' && !inBase64 && !inString { if start != i { // if a comment without whitespace - fields = append(fields, line[start:i]) + tokens = append(tokens, line[start:i]) } - return fields + return tokens } case '(': // is base64( seq? prefix := line[start:i] @@ -1446,19 +1450,29 @@ func fieldsFromLine(line string) []string { i++ continue } + + // we've hit a space, end last token unless inString + if !inString { - field := line[start:i] - fields = append(fields, field) - if field == "base64" || field == "b64" { - inBase64 = true - } else if inBase64 { + token := line[start:i] + tokens = append(tokens, token) + if line[i] == ';' { + tokens = append(tokens, ";") + } + if inBase64 { inBase64 = false + } else if token == "base64" || token == "b64" { + inBase64 = true } } i++ + // gobble up consecutive whitespace (but notice semis) if !inString { - for i < len(line) && spaces[line[i]] != 0 { + for i < len(line) && tokenSeparators[line[i]] { + if line[i] == ';' { + tokens = append(tokens, ";") + } i++ } start = i @@ -1467,10 +1481,10 @@ func fieldsFromLine(line string) []string { // add rest of the string if any if start < len(line) { - fields = append(fields, line[start:i]) + tokens = append(tokens, line[start:i]) } - return fields + return tokens } func (ops *OpStream) trace(format string, args ...interface{}) { @@ -1531,6 +1545,16 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction } } +// splitTokens breaks tokens into two slices at the first semicolon. +func splitTokens(tokens []string) (current, rest []string) { + for i, token := range tokens { + if token == ";" { + return tokens[:i], tokens[i+1:] + } + } + return tokens, nil +} + // assemble reads text from an input and accumulates the program func (ops *OpStream) assemble(text string) error { fin := strings.NewReader(text) @@ -1541,73 +1565,80 @@ func (ops *OpStream) assemble(text string) error { for scanner.Scan() { ops.sourceLine++ line := scanner.Text() - line = strings.TrimSpace(line) - if len(line) == 0 { - ops.trace("%3d: 0 line\n", ops.sourceLine) - continue - } - if strings.HasPrefix(line, "//") { - ops.trace("%3d: // line\n", ops.sourceLine) - continue - } - if strings.HasPrefix(line, "#pragma") { - ops.trace("%3d: #pragma line\n", ops.sourceLine) - ops.pragma(line) - continue - } - fields := fieldsFromLine(line) - if len(fields) == 0 { - ops.trace("%3d: no fields\n", ops.sourceLine) - continue - } - // we're about to begin processing opcodes, so settle the Version - if ops.Version == assemblerNoVersion { - ops.Version = AssemblerDefaultVersion - } - if ops.versionedPseudoOps == nil { - ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version) - } - opstring := fields[0] - if opstring[len(opstring)-1] == ':' { - ops.createLabel(opstring[:len(opstring)-1]) - fields = fields[1:] - if len(fields) == 0 { - ops.trace("%3d: label only\n", ops.sourceLine) + tokens := tokensFromLine(line) + if len(tokens) > 0 { + if first := tokens[0]; first[0] == '#' { + directive := first[1:] + switch directive { + case "pragma": + ops.pragma(tokens) + ops.trace("%3d: #pragma line\n", ops.sourceLine) + default: + ops.errorf("Unknown directive: %s", directive) + } continue } - opstring = fields[0] } - spec, expandedName, ok := getSpec(ops, opstring, fields[1:]) - if ok { - ops.trace("%3d: %s\t", ops.sourceLine, opstring) - ops.recordSourceLine() - if spec.Modes == modeApp { - ops.HasStatefulOps = true + for current, next := splitTokens(tokens); len(current) > 0 || len(next) > 0; current, next = splitTokens(next) { + if len(current) == 0 { + continue } - args, returns := spec.Arg.Types, spec.Return.Types - if spec.refine != nil { - nargs, nreturns := spec.refine(&ops.known, fields[1:]) - if nargs != nil { - args = nargs - } - if nreturns != nil { - returns = nreturns - } + // we're about to begin processing opcodes, so settle the Version + if ops.Version == assemblerNoVersion { + ops.Version = AssemblerDefaultVersion } - ops.trackStack(args, returns, append([]string{expandedName}, fields[1:]...)) - spec.asm(ops, &spec, fields[1:]) - if spec.deadens() { // An unconditional branch deadens the following code - ops.known.deaden() + if ops.versionedPseudoOps == nil { + ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version) } - if spec.Name == "callsub" { - // since retsub comes back to the callsub, it is an entry point like a label - ops.known.label() + opstring := current[0] + if opstring[len(opstring)-1] == ':' { + ops.createLabel(opstring[:len(opstring)-1]) + current = current[1:] + if len(current) == 0 { + ops.trace("%3d: label only\n", ops.sourceLine) + continue + } + opstring = current[0] + } + spec, expandedName, ok := getSpec(ops, opstring, current[1:]) + if ok { + ops.trace("%3d: %s\t", ops.sourceLine, opstring) + ops.recordSourceLine() + if spec.Modes == modeApp { + ops.HasStatefulOps = true + } + args, returns := spec.Arg.Types, spec.Return.Types + if spec.refine != nil { + nargs, nreturns := spec.refine(&ops.known, current[1:]) + if nargs != nil { + args = nargs + } + if nreturns != nil { + returns = nreturns + } + } + ops.trackStack(args, returns, append([]string{expandedName}, current[1:]...)) + spec.asm(ops, &spec, current[1:]) + if spec.deadens() { // An unconditional branch deadens the following code + ops.known.deaden() + } + if spec.Name == "callsub" { + // since retsub comes back to the callsub, it is an entry point like a label + ops.known.label() + } } ops.trace("\n") continue } } + if err := scanner.Err(); err != nil { + if errors.Is(err, bufio.ErrTooLong) { + err = errors.New("line too long") + } + ops.error(err) + } + // backward compatibility: do not allow jumps behind last instruction in v1 if ops.Version <= 1 { for label, dest := range ops.labels { @@ -1635,21 +1666,20 @@ func (ops *OpStream) assemble(text string) error { return nil } -func (ops *OpStream) pragma(line string) error { - fields := strings.Split(line, " ") - if fields[0] != "#pragma" { - return ops.errorf("invalid syntax: %s", fields[0]) +func (ops *OpStream) pragma(tokens []string) error { + if tokens[0] != "#pragma" { + return ops.errorf("invalid syntax: %s", tokens[0]) } - if len(fields) < 2 { + if len(tokens) < 2 { return ops.error("empty pragma") } - key := fields[1] + key := tokens[1] switch key { case "version": - if len(fields) < 3 { + if len(tokens) < 3 { return ops.error("no version value") } - value := fields[2] + value := tokens[2] var ver uint64 if ops.pending.Len() > 0 { return ops.error("#pragma version is only allowed before instructions") @@ -1674,10 +1704,10 @@ func (ops *OpStream) pragma(line string) error { } return nil case "typetrack": - if len(fields) < 3 { + if len(tokens) < 3 { return ops.error("no typetrack value") } - value := fields[2] + value := tokens[2] on, err := strconv.ParseBool(value) if err != nil { return ops.errorf("bad #pragma typetrack: %#v", value) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 24d9dffd07..f1afb9cc25 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -552,8 +552,7 @@ func summarize(trace *strings.Builder) string { func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpStream { t.Helper() - program := strings.ReplaceAll(source, ";", "\n") - ops, err := assembleWithTrace(program, ver) + ops, err := assembleWithTrace(source, ver) if len(expected) == 0 { if len(ops.Errors) > 0 || err != nil || ops == nil || ops.Program == nil { t.Log(summarize(ops.Trace)) @@ -567,13 +566,13 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt require.NotNil(t, ops.Program) // It should always be possible to Disassemble dis, err := Disassemble(ops.Program) - require.NoError(t, err, program) + require.NoError(t, err, source) // And, while the disassembly may not match input // exactly, the assembly of the disassembly should // give the same bytecode ops2, err := AssembleStringWithVersion(notrack(dis), ver) if len(ops2.Errors) > 0 || err != nil || ops2 == nil || ops2.Program == nil { - t.Log(program) + t.Log(source) t.Log(dis) } require.Empty(t, ops2.Errors) @@ -581,7 +580,7 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt require.Equal(t, ops.Program, ops2.Program) } else { if err == nil { - t.Log(program) + t.Log(source) } require.Error(t, err) errors := ops.Errors @@ -701,9 +700,9 @@ func TestAssembleGlobal(t *testing.T) { testProg(t, "global MinTxnFee; int 2; +", AssemblerMaxVersion) testProg(t, "global ZeroAddress; byte 0x12; concat; len", AssemblerMaxVersion) testProg(t, "global MinTxnFee; byte 0x12; concat", AssemblerMaxVersion, - Expect{3, "concat arg 0 wanted type []byte..."}) + Expect{1, "concat arg 0 wanted type []byte..."}) testProg(t, "int 2; global ZeroAddress; +", AssemblerMaxVersion, - Expect{3, "+ arg 1 wanted type uint64..."}) + Expect{1, "+ arg 1 wanted type uint64..."}) } func TestAssembleDefault(t *testing.T) { @@ -1111,209 +1110,91 @@ func TestFieldsFromLine(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - line := "op arg" - fields := fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "arg", fields[1]) - - line = "op arg // test" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "arg", fields[1]) - - line = "op base64 ABC//==" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op base64 ABC/==" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - - line = "op base64 ABC/== /" - fields = fieldsFromLine(line) - require.Equal(t, 4, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - require.Equal(t, "/", fields[3]) - - line = "op base64 ABC/== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - - line = "op base64 ABC//== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op b64 ABC//== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op b64(ABC//==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64(ABC//==)", fields[1]) - - line = "op base64(ABC//==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64(ABC//==)", fields[1]) - - line = "op b64(ABC/==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64(ABC/==)", fields[1]) - - line = "op base64(ABC/==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64(ABC/==)", fields[1]) - - line = "base64(ABC//==)" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "base64(ABC//==)", fields[0]) - - line = "b(ABC//==)" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - - line = "b(ABC//==) //" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - - line = "b(ABC ==) //" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - require.Equal(t, "==)", fields[1]) - - line = "op base64 ABC)" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC)", fields[2]) - - line = "op base64 ABC) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC)", fields[2]) - - line = "op base64 ABC//) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//)", fields[2]) - - line = `op "test"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test"`, fields[1]) - - line = `op "test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2 // not a comment"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2 // not a comment" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2 // not a comment" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2" //` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2"//` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2` // non-terminated string literal - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2`, fields[1]) - - line = `op "test1 test2\"` // non-terminated string literal - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2\"`, fields[1]) - - line = `op \"test1 test2\"` // not a string literal - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `\"test1`, fields[1]) - require.Equal(t, `test2\"`, fields[2]) - - line = `"test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, `"test1 test2"`, fields[0]) - - line = `\"test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, `\"test1`, fields[0]) - require.Equal(t, `test2"`, fields[1]) - - line = `"" // test` - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, `""`, fields[0]) + check := func(line string, tokens ...string) { + t.Helper() + assert.Equal(t, tokensFromLine(line), tokens) + } + + check("op arg", "op", "arg") + check("op arg // test", "op", "arg") + check("op base64 ABC//==", "op", "base64", "ABC//==") + check("op base64 base64", "op", "base64", "base64") + check("op base64 base64 //comment", "op", "base64", "base64") + check("op base64 base64; op2 //done", "op", "base64", "base64", ";", "op2") + check("op base64 ABC/==", "op", "base64", "ABC/==") + check("op base64 ABC/== /", "op", "base64", "ABC/==", "/") + check("op base64 ABC/== //", "op", "base64", "ABC/==") + check("op base64 ABC//== //", "op", "base64", "ABC//==") + check("op b64 ABC//== //", "op", "b64", "ABC//==") + check("op b64(ABC//==) // comment", "op", "b64(ABC//==)") + check("op base64(ABC//==) // comment", "op", "base64(ABC//==)") + check("op b64(ABC/==) // comment", "op", "b64(ABC/==)") + check("op base64(ABC/==) // comment", "op", "base64(ABC/==)") + check("base64(ABC//==)", "base64(ABC//==)") + check("b(ABC//==)", "b(ABC") + check("b(ABC//==) //", "b(ABC") + check("b(ABC ==) //", "b(ABC", "==)") + check("op base64 ABC)", "op", "base64", "ABC)") + check("op base64 ABC) // comment", "op", "base64", "ABC)") + check("op base64 ABC//) // comment", "op", "base64", "ABC//)") + check(`op "test"`, "op", `"test"`) + check(`op "test1 test2"`, "op", `"test1 test2"`) + check(`op "test1 test2" // comment`, "op", `"test1 test2"`) + check(`op "test1 test2 // not a comment"`, "op", `"test1 test2 // not a comment"`) + check(`op "test1 test2 // not a comment" // comment`, "op", `"test1 test2 // not a comment"`) + check(`op "test1 test2" //`, "op", `"test1 test2"`) + check(`op "test1 test2"//`, "op", `"test1 test2"`) + check(`op "test1 test2`, "op", `"test1 test2`) // non-terminated string literal + check(`op "test1 test2\"`, "op", `"test1 test2\"`) // non-terminated string literal + check(`op \"test1 test2\"`, "op", `\"test1`, `test2\"`) // not a string literal + check(`"test1 test2"`, `"test1 test2"`) + check(`\"test1 test2"`, `\"test1`, `test2"`) + check(`"" // test`, `""`) + check("int 1; int 2", "int", "1", ";", "int", "2") + check("int 1;;;int 2", "int", "1", ";", ";", ";", "int", "2") + check("int 1; ;int 2;; ; ;; ", "int", "1", ";", ";", "int", "2", ";", ";", ";", ";", ";") + check(";", ";") + check("; ; ;;;;", ";", ";", ";", ";", ";", ";") + check(" ;", ";") + check(" ; ", ";") +} + +func TestSplitTokens(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + check := func(tokens []string, left []string, right []string) { + t.Helper() + current, next := splitTokens(tokens) + assert.Equal(t, left, current) + assert.Equal(t, right, next) + } + + check([]string{"hey,", "how's", ";", ";", "it", "going", ";"}, + []string{"hey,", "how's"}, + []string{";", "it", "going", ";"}, + ) + + check([]string{";"}, + []string{}, + []string{}, + ) + + check([]string{";", "it", "going"}, + []string{}, + []string{"it", "going"}, + ) + + check([]string{"hey,", "how's"}, + []string{"hey,", "how's"}, + nil, + ) + + check([]string{`"hey in quotes;"`, "getting", `";"`, ";", "tricky"}, + []string{`"hey in quotes;"`, "getting", `";"`}, + []string{"tricky"}, + ) + } func TestAssembleRejectNegJump(t *testing.T) { @@ -1798,22 +1679,22 @@ func TestAssembleAsset(t *testing.T) { testProg(t, "asset_holding_get ABC 1", v, Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) testProg(t, "int 1; asset_holding_get ABC 1", v, - Expect{2, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, - Expect{3, "asset_holding_get expects 1 immediate argument"}) + Expect{1, "asset_holding_get expects 1 immediate argument"}) testProg(t, "int 1; int 1; asset_holding_get ABC", v, - Expect{3, "asset_holding_get unknown field: \"ABC\""}) + Expect{1, "asset_holding_get unknown field: \"ABC\""}) testProg(t, "byte 0x1234; asset_params_get ABC 1", v, - Expect{2, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) + Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) // Test that AssetUnitName is known to return bytes testProg(t, "int 1; asset_params_get AssetUnitName; pop; int 1; +", v, - Expect{5, "+ arg 0 wanted type uint64..."}) + Expect{1, "+ arg 0 wanted type uint64..."}) // Test that AssetTotal is known to return uint64 testProg(t, "int 1; asset_params_get AssetTotal; pop; byte 0x12; concat", v, - Expect{5, "concat arg 0 wanted type []byte..."}) + Expect{1, "concat arg 0 wanted type []byte..."}) testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects 1 immediate argument") testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") @@ -2422,29 +2303,29 @@ func TestSwapTypeCheck(t *testing.T) { t.Parallel() /* reconfirm that we detect this type error */ - testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{3, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) /* despite swap, we track types */ - testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) - testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) } func TestDigAsm(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{2, "dig expects 1 immediate..."}) - testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{2, "dig unable to parse..."}) + testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{1, "dig expects 1 immediate..."}) + testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{1, "dig unable to parse..."}) testProg(t, "int 1; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion, - Expect{5, "+ arg 1..."}) + Expect{1, "+ arg 1..."}) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 3; +", AssemblerMaxVersion, - Expect{4, "dig 3 expects 4..."}) + Expect{1, "dig 3 expects 4..."}) testProg(t, "int 1; byte 0x1234; int 2; dig 12; +", AssemblerMaxVersion, - Expect{4, "dig 12 expects 13..."}) + Expect{1, "dig 12 expects 13..."}) // Confirm that digging something out does not ruin our knowledge about the types in the middle testProg(t, "int 1; byte 0x1234; byte 0x1234; dig 2; dig 3; +; pop; +", AssemblerMaxVersion, - Expect{8, "+ arg 1..."}) + Expect{1, "+ arg 1..."}) testProg(t, "int 3; pushbytes \"123456\"; int 1; dig 2; substring3", AssemblerMaxVersion) } @@ -2452,39 +2333,39 @@ func TestDigAsm(t *testing.T) { func TestEqualsTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."}) - testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."}) - testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."}) - testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."}) + testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) + testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) + testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) + testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) } func TestDupTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) + testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) testProg(t, "byte 0x1234; int 1; dup; +", AssemblerMaxVersion) - testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."}) + testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) - testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) - testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestSelectTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestSetBitTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestScratchTypeCheck(t *testing.T) { @@ -2493,13 +2374,13 @@ func TestScratchTypeCheck(t *testing.T) { // All scratch slots should start as uint64 testProg(t, "load 0; int 1; +", AssemblerMaxVersion) // Check load and store accurately using the scratch space - testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{5, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // Loads should know the type it's loading if all the slots are the same type - testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{3, "btoi arg 0..."}) + testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) // Loads doesn't know the type when slot types vary testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion) // Stores should only set slots to StackAny if they are not the same type as what is being stored - testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{8, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // ScratchSpace should reset after hitting label in deadcode testProg(t, "byte 0x01; store 0; b label1; label1:; load 0; int 1; +", AssemblerMaxVersion) // But it should reset to StackAny not uint64 @@ -2507,7 +2388,7 @@ func TestScratchTypeCheck(t *testing.T) { // Callsubs should also reset the scratch space testProg(t, "callsub A; load 0; btoi; return; A: byte 0x01; store 0; retsub", AssemblerMaxVersion) // But the scratchspace should still be tracked after the callsub - testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{5, "btoi arg 0..."}) + testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) } func TestCoverAsm(t *testing.T) { @@ -2515,9 +2396,9 @@ func TestCoverAsm(t *testing.T) { t.Parallel() testProg(t, `int 4; byte "john"; int 5; cover 2; pop; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; cover 1; pop; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."}) + testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) - testProg(t, `int 4; cover junk`, AssemblerMaxVersion, Expect{2, "cover unable to parse n ..."}) + testProg(t, `int 4; cover junk`, AssemblerMaxVersion, Expect{1, "cover unable to parse n ..."}) } func TestUncoverAsm(t *testing.T) { @@ -2526,38 +2407,38 @@ func TestUncoverAsm(t *testing.T) { testProg(t, `int 4; byte "john"; int 5; uncover 2; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; uncover 1; pop; +`, AssemblerMaxVersion) testProg(t, `int 1; byte "jj"; byte "ayush"; byte "john"; int 5; uncover 4; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."}) + testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) } func TestTxTypes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{2, "itxn_field Sender expects 1 stack argument..."}) - testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{3, "...wanted type []byte got uint64"}) + testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{1, "itxn_field Sender expects 1 stack argument..."}) + testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{1, "...wanted type []byte got uint64"}) testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5) - testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{2, "itxn_field Amount expects 1 stack argument..."}) - testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{3, "...wanted type uint64 got []byte"}) + testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{1, "itxn_field Amount expects 1 stack argument..."}) + testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{1, "...wanted type uint64 got []byte"}) testProg(t, "itxn_begin; int 1; itxn_field Amount", 5) } func TestBadInnerFields(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{4, "...is not allowed."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{3, "...Note field was introduced in v6..."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 5, Expect{3, "...VotePK field was introduced in v6..."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{4, "...is not allowed."}) - - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{4, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{1, "...Note field was introduced in v6..."}) + testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 5, Expect{1, "...VotePK field was introduced in v6..."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{1, "...is not allowed."}) + + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 6) testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 6) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{4, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{1, "...is not allowed."}) } func TestTypeTracking(t *testing.T) { @@ -2573,7 +2454,7 @@ func TestTypeTracking(t *testing.T) { // but we do want to ensure we're not just treating the code after callsub as dead testProg(t, "callsub A; int 1; concat; return; A: int 1; int 2; retsub", LogicVersion, - Expect{3, "concat arg 1 wanted..."}) + Expect{1, "concat arg 1 wanted..."}) // retsub deadens code, like any unconditional branch testProg(t, "callsub A; +; return; A: int 1; int 2; retsub; concat", LogicVersion) @@ -2697,7 +2578,7 @@ func TestAddPseudoDocTags(t *testing.T) { delete(opDocByName, "any") }() - pseudoOps["tests"] = map[int]OpSpec{2: OpSpec{Name: "multiple"}, 1: OpSpec{Name: "single"}, 0: OpSpec{Name: "none"}, anyImmediates: OpSpec{Name: "any"}} + pseudoOps["tests"] = map[int]OpSpec{2: {Name: "multiple"}, 1: {Name: "single"}, 0: {Name: "none"}, anyImmediates: {Name: "any"}} addPseudoDocTags() require.Equal(t, "`multiple` can be called using `tests` with 2 immediates.", opDocByName["multiple"]) require.Equal(t, "`single` can be called using `tests` with 1 immediate.", opDocByName["single"]) @@ -2711,7 +2592,45 @@ func TestReplacePseudo(t *testing.T) { for v := uint64(replaceVersion); v <= AssemblerMaxVersion; v++ { testProg(t, "byte 0x0000; byte 0x1234; replace 0", v) testProg(t, "byte 0x0000; int 0; byte 0x1234; replace", v) - testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{3, "replace without immediates expects 3 stack arguments but stack height is 2"}) - testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{4, "replace 0 arg 0 wanted type []byte got uint64"}) + testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{1, "replace without immediates expects 3 stack arguments but stack height is 2"}) + testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{1, "replace 0 arg 0 wanted type []byte got uint64"}) + } +} + +func checkSame(t *testing.T, version uint64, first string, compares ...string) { + t.Helper() + if version == 0 { + version = assemblerNoVersion + } + ops, err := AssembleStringWithVersion(first, version) + require.NoError(t, err, first) + for _, compare := range compares { + other, err := AssembleStringWithVersion(compare, version) + assert.NoError(t, err, compare) + assert.Equal(t, other.Program, ops.Program, "%s unlike %s", first, compare) } } + +func TestSemiColon(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + checkSame(t, AssemblerMaxVersion, + "pushint 0 ; pushint 1 ; +; int 3 ; *", + "pushint 0\npushint 1\n+\nint 3\n*", + "pushint 0; pushint 1; +; int 3; *; // comment; int 2", + "pushint 0; ; ; pushint 1 ; +; int 3 ; *//check", + ) + + checkSame(t, 0, + "#pragma version 7\nint 1", + "// junk;\n#pragma version 7\nint 1", + "// junk;\n #pragma version 7\nint 1", + ) + + checkSame(t, AssemblerMaxVersion, + `byte "test;this"; pop;`, + `byte "test;this"; ; pop;`, + `byte "test;this";;;pop;`, + ) +} diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 086741dcde..13a19ddaa7 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -467,15 +467,15 @@ func TestBackwardCompatAssemble(t *testing.T) { source := "int 1; int 1; bnz done; done:" t.Run("v=default", func(t *testing.T) { - testProg(t, source, assemblerNoVersion, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, assemblerNoVersion, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { - testProg(t, source, 0, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, 0, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { - testProg(t, source, 1, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, 1, Expect{1, "label \"done\" is too far away"}) }) for v := uint64(2); v <= AssemblerMaxVersion; v++ { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 3e051fc6be..bb2f1e0f1d 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -314,7 +314,7 @@ func TestBalance(t *testing.T) { text = `txn Accounts 1; balance; int 177; ==;` // won't assemble in old version teal - testProg(t, text, directRefEnabledVersion-1, Expect{2, "balance arg 0 wanted type uint64..."}) + testProg(t, text, directRefEnabledVersion-1, Expect{1, "balance arg 0 wanted type uint64..."}) // but legal after that testApp(t, text, ep) @@ -475,7 +475,7 @@ func TestMinBalance(t *testing.T) { testApp(t, "int 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion-1, - Expect{2, "min_balance arg 0 wanted type uint64..."}) + Expect{1, "min_balance arg 0 wanted type uint64..."}) testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion) testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] // Receiver opts in @@ -528,7 +528,7 @@ func TestAppCheckOptedIn(t *testing.T) { testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id testApp(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", now) testProg(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", directRefEnabledVersion-1, - Expect{3, "app_opted_in arg 0 wanted type uint64..."}) + Expect{1, "app_opted_in arg 0 wanted type uint64..."}) // Receiver opts into 888, the current app in testApp ledger.NewLocals(txn.Txn.Receiver, 888) @@ -939,7 +939,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) // it wasn't legal to use a direct ref for account testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, - directRefEnabledVersion-1, Expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) + directRefEnabledVersion-1, Expect{1, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) // but it is now (empty asset yields 0,0 on stack) testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now) // This is receiver, who is in Assets array diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index ba7df73e9c..45a8ceeb22 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -340,12 +340,12 @@ func TestSimpleMath(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testAccepts(t, "int 2; int 3; + ;int 5;==", 1) - testAccepts(t, "int 22; int 3; - ;int 19;==", 1) - testAccepts(t, "int 8; int 7; * ;int 56;==", 1) - testAccepts(t, "int 21; int 7; / ;int 3;==", 1) + testAccepts(t, "int 2; int 3; + ; int 5; ==", 1) + testAccepts(t, "int 22; int 3; - ; int 19; ==", 1) + testAccepts(t, "int 8; int 7; * ; int 56; ==", 1) + testAccepts(t, "int 21; int 7; / ; int 3; ==", 1) - testPanics(t, "int 1; int 2; - ;int 0; ==", 1) + testPanics(t, "int 1; int 2; - ; int 0; ==", 1) } func TestSha256EqArg(t *testing.T) { @@ -962,7 +962,7 @@ func TestArg(t *testing.T) { t.Parallel() for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - source := "arg 0; arg 1; ==; arg 2; arg 3; !=; &&; arg 4; len; int 9; <; &&;" + source := "arg 0; arg 1; ==; arg 2; arg 3; !=; &&; arg 4; len; int 9; <; &&; " if v >= 5 { source += "int 0; args; int 1; args; ==; assert; int 2; args; int 3; args; !=; assert" } @@ -2836,16 +2836,16 @@ func TestSlowLogic(t *testing.T) { t.Parallel() fragment := `byte 0x666E6F7264; keccak256 - byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567; ==;` + byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567; ==; ` // Sanity check. Running a short sequence of these fragments passes in all versions. - source := fragment + strings.Repeat(fragment+"&&;", 5) + source := fragment + strings.Repeat(fragment+"&&; ", 5) testAccepts(t, source, 1) // in v1, each repeat costs 30 - v1overspend := fragment + strings.Repeat(fragment+"&&;", 20000/30) + v1overspend := fragment + strings.Repeat(fragment+"&&; ", 20000/30) // in v2,v3 each repeat costs 134 - v2overspend := fragment + strings.Repeat(fragment+"&&;", 20000/134) + v2overspend := fragment + strings.Repeat(fragment+"&&; ", 20000/134) // v1overspend fails (on v1) ops := testProg(t, v1overspend, 1) @@ -3546,8 +3546,7 @@ func benchmarkOperation(b *testing.B, prefix string, operation string, suffix st b.Helper() runs := 1 + b.N/2000 inst := strings.Count(operation, ";") + strings.Count(operation, "\n") - source := prefix + ";" + strings.Repeat(operation+";", 2000) + ";" + suffix - source = strings.ReplaceAll(source, ";", "\n") + source := prefix + ";" + strings.Repeat(operation+"\n", 2000) + ";" + suffix ops := testProg(b, source, AssemblerMaxVersion) evalLoop(b, runs, ops.Program) b.ReportMetric(float64(inst), "extra/op") @@ -3860,9 +3859,9 @@ func TestStackOverflow(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := "int 1; int 2;" + source := "int 1; int 2; " for i := 1; i < maxStackDepth/2; i++ { - source += "dup2;" + source += "dup2; " } testAccepts(t, source+"return", 2) testPanics(t, source+"dup2; return", 2) @@ -4266,11 +4265,11 @@ func TestAssert(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testAccepts(t, "int 1;assert;int 1", 3) - testRejects(t, "int 1;assert;int 0", 3) - testPanics(t, "int 0;assert;int 1", 3) - testPanics(t, notrack("assert;int 1"), 3) - testPanics(t, notrack(`byte "john";assert;int 1`), 3) + testAccepts(t, "int 1; assert; int 1", 3) + testRejects(t, "int 1; assert; int 0", 3) + testPanics(t, "int 0; assert; int 1", 3) + testPanics(t, notrack("assert; int 1"), 3) + testPanics(t, notrack(`byte "john"; assert; int 1`), 3) } func TestBits(t *testing.T) { @@ -4766,7 +4765,7 @@ func TestLog(t *testing.T) { loglen: 2, }, { - source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log;`, maxLogCalls)), + source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)), loglen: maxLogCalls, }, { @@ -4811,7 +4810,7 @@ func TestLog(t *testing.T) { runMode: modeApp, }, { - source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log;`, maxLogCalls+1)), + source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)), errContains: "too many log calls", runMode: modeApp, }, @@ -5123,7 +5122,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5131,7 +5130,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; //max uint64 value ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5139,7 +5138,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"\\u0061\\u006C\\u0067\\u006F\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5147,7 +5146,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5159,7 +5158,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 int 10 ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5169,7 +5168,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "teal" ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"\\"teal\\"\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5179,7 +5178,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte ""teal"" // quotes match ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \" teal \", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5189,7 +5188,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " teal " // spaces match ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"10\"}}, \"key5\": 18446744073709551615 }"; @@ -5200,7 +5199,7 @@ func TestOpJSONRef(t *testing.T) { byte "{\"key40\": 10, \"key40\": \"10\"}" == `, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5208,7 +5207,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5216,7 +5215,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5226,7 +5225,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " based64url_encoded_client_data"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"\\u0072\\u0061\\u0077\\u0049\\u0044\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5234,7 +5233,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "responseId" ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // JavaScript MAX_SAFE_INTEGER { @@ -5243,7 +5242,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 9007199254740991; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // maximum uint64 { @@ -5252,7 +5251,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // larger-than-uint64s are allowed if not requested { @@ -5261,7 +5260,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, } @@ -5301,52 +5300,52 @@ func TestOpJSONRef(t *testing.T) { { source: `byte "{\"key0\": 1 }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal number into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [1] }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal array into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal object into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": \"1\" }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal string into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [\"1\"] }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal array into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal object into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [1]}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal array into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": \"1\"}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal string into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]} }"; byte "key3"; json_ref JSONString;`, error: "key key3 not found in JSON text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]}}"; @@ -5356,52 +5355,52 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "key key5 not found in JSON text", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": -0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -0 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1e10,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1e10 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0.2e-2,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 0.2e-2 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1.0 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key1"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 2.5 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key2"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -3 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 18446744073709551616}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1, \"key0\": \"3\"}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"should fail!\"}}}"; @@ -5413,7 +5412,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}, {13, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}, {7, "unknown opcode: json_ref"}}, }, { source: `byte "[1,2,3]"; @@ -5421,7 +5420,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "2"; @@ -5429,7 +5428,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "null"; @@ -5437,7 +5436,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "true"; @@ -5445,7 +5444,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "\"sometext\""; @@ -5453,7 +5452,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{noquotes: \"shouldn't work\"}"; @@ -5462,7 +5461,7 @@ func TestOpJSONRef(t *testing.T) { byte "shouldn't work"; ==`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // max uint64 + 1 should fail { @@ -5472,7 +5471,7 @@ func TestOpJSONRef(t *testing.T) { int 1; return`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, } diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 0a29288134..2b5008f5c8 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -225,7 +225,7 @@ func TestAssetParamsFieldsVersions(t *testing.T) { ep, _, _ := makeSampleEnv() ep.Proto.LogicSigVersion = v if field.version > v { - testProg(t, text, v, Expect{3, "...was introduced in..."}) + testProg(t, text, v, Expect{1, "...was introduced in..."}) ops := testProg(t, text, field.version) // assemble in the future ops.Program[0] = byte(v) testAppBytes(t, ops.Program, ep, "invalid asset_params_get field") From e79064390b0a8672e3f602d0993d45f4844a80f7 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:16:35 -0400 Subject: [PATCH 13/24] tests: add debug output to e2e_basic_start_stop (#4396) --- test/scripts/e2e_basic_start_stop.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/test/scripts/e2e_basic_start_stop.sh b/test/scripts/e2e_basic_start_stop.sh index 8d9da35c9a..1bb9b0bf13 100755 --- a/test/scripts/e2e_basic_start_stop.sh +++ b/test/scripts/e2e_basic_start_stop.sh @@ -28,6 +28,8 @@ function verify_at_least_one_running() { } function verify_none_running() { + local datadir=$1 + # Shutting down can take some time, so wait at least 5 seconds for TRIES in 1 2 3 4 5; do update_running_count @@ -37,6 +39,15 @@ function verify_none_running() { sleep 1.4 done echo "algod not expected to be running but it is" + if [ -n "$datadir" ]; then + echo "last 20 lines of node.log:" + tail -20 "$datadir/node.log" + echo "================================" + echo "stdout and stdin:" + cat "$datadir/algod-out.log" + echo "================================" + cat "$datadir/algod-err.log" + fi exit 1 } @@ -64,7 +75,7 @@ verify_at_least_one_running echo Verifying we can stop it using goal goal node stop -d ${DATADIR} -verify_none_running +verify_none_running ${DATADIR} #---------------------- # Test that we can start a generic node straight with no overrides @@ -72,7 +83,7 @@ echo Verifying a generic node will start directly algod -d ${DATADIR} & verify_at_least_one_running pkill -u $(whoami) -x algod || true -verify_none_running +verify_none_running ${DATADIR} #---------------------- # Test that we can start a generic node against the datadir @@ -85,7 +96,7 @@ verify_at_least_one_running # one should still be running verify_one_running # in fact, exactly one should still be running # clean up pkill -u $(whoami) -x algod || true -verify_none_running +verify_none_running ${DATADIR} echo "----------------------------------------------------------------------" echo " DONE: e2e_basic_start_stop" From 8d9e8684c46401b084287628aba294b5768615a6 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:47:37 -0400 Subject: [PATCH 14/24] tests: fix TestBasicCatchpointCatchup (#4390) * After the protocol upgrade nodes required to have MaxTxnLife + DeeperBlockHeaderHistory blocks that violated the test's expectations. * Fixed a rare case when a block exists in a local ledger but peer's stat is still updated even if no communications were made. --- catchup/catchpointService.go | 13 ++- .../catchpointCatchupWebProxy/webproxy.go | 6 +- .../catchup/catchpointCatchup_test.go | 79 +++++++++++++------ test/framework/fixtures/webProxyFixture.go | 18 +++-- 4 files changed, 78 insertions(+), 38 deletions(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index e2a9fcc387..99031a0c1d 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -19,10 +19,11 @@ package catchup import ( "context" "fmt" - "github.com/algorand/go-algorand/stateproof" "sync" "time" + "github.com/algorand/go-algorand/stateproof" + "github.com/algorand/go-deadlock" "github.com/algorand/go-algorand/config" @@ -499,6 +500,7 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { if lookback < lookbackForStateProofSupport { lookback = lookbackForStateProofSupport } + // in case the effective lookback is going before our rounds count, trim it there. // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) if lookback >= uint64(topBlock.Round()) { @@ -588,9 +590,12 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { return cs.abort(fmt.Errorf("processStageBlocksDownload: downloaded block content does not match downloaded block header")) } - cs.updateBlockRetrievalStatistics(0, 1) - peerRank := cs.blocksDownloadPeerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration) - cs.blocksDownloadPeerSelector.rankPeer(psp, peerRank) + if psp != nil { + // the block might have been retrieved from the local ledger, nothing to rank + cs.updateBlockRetrievalStatistics(0, 1) + peerRank := cs.blocksDownloadPeerSelector.peerDownloadDurationToRank(psp, blockDownloadDuration) + cs.blocksDownloadPeerSelector.rankPeer(psp, peerRank) + } // all good, persist and move on. err = cs.ledgerAccessor.StoreBlock(cs.ctx, blk) diff --git a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go index 335d5bafb2..8c574c4b1e 100644 --- a/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go +++ b/test/e2e-go/cli/goal/expect/catchpointCatchupWebProxy/webproxy.go @@ -28,6 +28,7 @@ import ( "github.com/algorand/go-deadlock" + "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/test/framework/fixtures" ) @@ -49,14 +50,15 @@ func main() { return } var mu deadlock.Mutex - wp, err := fixtures.MakeWebProxy(*webProxyDestination, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { + log := logging.Base() + wp, err := fixtures.MakeWebProxy(*webProxyDestination, log, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { mu.Lock() time.Sleep(time.Duration(*webProxyRequestDelay) * time.Millisecond) mu.Unlock() // prevent requests for block #2 to go through. if strings.HasSuffix(request.URL.String(), "/block/2") { - response.Write([]byte("webProxy prevents block 2 from serving")) response.WriteHeader(http.StatusBadRequest) + response.Write([]byte("webProxy prevents block 2 from serving")) return } if *webProxyLogFile != "" { diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index 98efc0b266..e5eba0e97c 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -18,7 +18,6 @@ package catchup import ( "fmt" - generatedV2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" "net/http" "os/exec" "path/filepath" @@ -32,6 +31,9 @@ import ( "github.com/algorand/go-algorand/config" algodclient "github.com/algorand/go-algorand/daemon/algod/api/client" + generatedV2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/protocol" @@ -116,11 +118,11 @@ func TestBasicCatchpointCatchup(t *testing.T) { // Overview of this test: // Start a two-node network (primary has 100%, secondary has 0%) - // Nodes are having a consensus allowing balances history of 32 rounds and transaction history of 33 rounds. - // Let it run for 37 rounds. - // create a web proxy, and connect it to the primary node, blocking all requests for round #1. ( and allowing everything else ) - // start a secondary node, and instuct it to catchpoint catchup from the proxy. ( which would be for round 36 ) - // wait until the clone node cought up, skipping the "impossible" hole of round #1. + // Nodes are having a consensus allowing balances history of 8 rounds and transaction history of 13 rounds. + // Let it run for 21 rounds. + // create a web proxy, and connect it to the primary node, blocking all requests for round #2. ( and allowing everything else ) + // start a secondary node, and instuct it to catchpoint catchup from the proxy. ( which would be for round 20 ) + // wait until the clone node cought up, skipping the "impossible" hole of round #2. consensus := make(config.ConsensusProtocols) const consensusCatchpointCatchupTestProtocol = protocol.ConsensusVersion("catchpointtestingprotocol") @@ -129,9 +131,9 @@ func TestBasicCatchpointCatchup(t *testing.T) { // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 8 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 32 - catchpointCatchupProtocol.MaxTxnLife = 33 + catchpointCatchupProtocol.SeedRefreshInterval = 2 + catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 8 + catchpointCatchupProtocol.MaxTxnLife = 13 catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true @@ -161,13 +163,16 @@ func TestBasicCatchpointCatchup(t *testing.T) { // prepare it's configuration file to set it to generate a catchpoint every 4 rounds. cfg, err := config.LoadConfigFromDisk(primaryNode.GetDataDir()) a.NoError(err) - cfg.CatchpointInterval = 4 + const catchpointInterval = 4 + cfg.CatchpointInterval = catchpointInterval cfg.MaxAcctLookback = 2 cfg.SaveToDisk(primaryNode.GetDataDir()) cfg.Archival = false + cfg.CatchpointInterval = 0 cfg.NetAddress = "" cfg.EnableLedgerService = false cfg.EnableBlockService = false + cfg.BaseLoggerDebugLevel = uint32(logging.Debug) cfg.SaveToDisk(secondNode.GetDataDir()) // start the primary node @@ -180,10 +185,15 @@ func TestBasicCatchpointCatchup(t *testing.T) { ExitErrorCallback: errorsCollector.nodeExitWithError, }) a.NoError(err) + defer primaryNode.StopAlgod() // Let the network make some progress currentRound := uint64(1) - const targetRound = uint64(37) + expectedBlocksToDownload := catchpointCatchupProtocol.MaxTxnLife + catchpointCatchupProtocol.DeeperBlockHeaderHistory + const restrictedBlock = 2 // block number that is rejected to be downloaded to ensure fast catchup and not regular catchup is running + // calculate the target round: this is the next round after catchpoint that is greater than expectedBlocksToDownload before the restrictedBlock block number + targetCatchpointRound := (basics.Round(expectedBlocksToDownload+restrictedBlock)/catchpointInterval + 1) * catchpointInterval + targetRound := uint64(targetCatchpointRound) + 1 // 21 primaryNodeRestClient := fixture.GetAlgodClientForController(primaryNode) primaryNodeRestClient.SetAPIVersionAffinity(algodclient.APIVersionV2) log.Infof("Building ledger history..") @@ -194,16 +204,17 @@ func TestBasicCatchpointCatchup(t *testing.T) { break } currentRound++ - } log.Infof("done building!\n") + primaryListeningAddress, err := primaryNode.GetListeningAddress() a.NoError(err) - wp, err := fixtures.MakeWebProxy(primaryListeningAddress, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { + wp, err := fixtures.MakeWebProxy(primaryListeningAddress, log, func(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) { // prevent requests for block #2 to go through. if request.URL.String() == "/v1/test-v1/block/2" { response.WriteHeader(http.StatusBadRequest) + response.Write([]byte("webProxy prevents block 2 from serving")) return } next(response, request) @@ -222,6 +233,7 @@ func TestBasicCatchpointCatchup(t *testing.T) { ExitErrorCallback: errorsCollector.nodeExitWithError, }) a.NoError(err) + defer secondNode.StopAlgod() // wait until node is caught up. secondNodeRestClient := fixture.GetAlgodClientForController(secondNode) @@ -240,10 +252,32 @@ func TestBasicCatchpointCatchup(t *testing.T) { } log.Infof(" - done catching up!\n") - status, err := awaitCatchpointCreation(primaryNodeRestClient, &fixture, 3) - a.NoError(err) + // ensure the catchpoint is created for targetCatchpointRound + var status generatedV2.NodeStatusResponse + timer := time.NewTimer(10 * time.Second) +outer: + for { + status, err = primaryNodeRestClient.Status() + a.NoError(err) - log.Infof("primary node latest catchpoint - %s!\n", status.LastCatchpoint) + var round basics.Round + if status.LastCatchpoint != nil && len(*status.LastCatchpoint) > 0 { + round, _, err = ledgercore.ParseCatchpointLabel(*status.LastCatchpoint) + a.NoError(err) + if round >= targetCatchpointRound { + break + } + } + select { + case <-timer.C: + a.Failf("timeout waiting a catchpoint", "target: %d, got %d", targetCatchpointRound, round) + break outer + default: + time.Sleep(250 * time.Millisecond) + } + } + + log.Infof("primary node latest catchpoint - %s!\n", *status.LastCatchpoint) _, err = secondNodeRestClient.Catchup(*status.LastCatchpoint) a.NoError(err) @@ -260,9 +294,6 @@ func TestBasicCatchpointCatchup(t *testing.T) { currentRound++ } log.Infof("done catching up!\n") - - secondNode.StopAlgod() - primaryNode.StopAlgod() } func TestCatchpointLabelGeneration(t *testing.T) { @@ -295,9 +326,9 @@ func TestCatchpointLabelGeneration(t *testing.T) { // MaxBalLookback = 2 x SeedRefreshInterval x SeedLookback // ref. https://github.com/algorandfoundation/specs/blob/master/dev/abft.md catchpointCatchupProtocol.SeedLookback = 2 - catchpointCatchupProtocol.SeedRefreshInterval = 8 - catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 32 - catchpointCatchupProtocol.MaxTxnLife = 33 + catchpointCatchupProtocol.SeedRefreshInterval = 2 + catchpointCatchupProtocol.MaxBalLookback = 2 * catchpointCatchupProtocol.SeedLookback * catchpointCatchupProtocol.SeedRefreshInterval // 8 + catchpointCatchupProtocol.MaxTxnLife = 13 catchpointCatchupProtocol.CatchpointLookback = catchpointCatchupProtocol.MaxBalLookback catchpointCatchupProtocol.EnableOnlineAccountCatchpoints = true @@ -338,10 +369,11 @@ func TestCatchpointLabelGeneration(t *testing.T) { ExitErrorCallback: errorsCollector.nodeExitWithError, }) a.NoError(err) + defer primaryNode.StopAlgod() // Let the network make some progress currentRound := uint64(1) - targetRound := uint64(41) + targetRound := uint64(21) primaryNodeRestClient := fixture.GetAlgodClientForController(primaryNode) primaryNodeRestClient.SetAPIVersionAffinity(algodclient.APIVersionV2) log.Infof("Building ledger history..") @@ -364,7 +396,6 @@ func TestCatchpointLabelGeneration(t *testing.T) { } else { a.Empty(*primaryNodeStatus.LastCatchpoint) } - primaryNode.StopAlgod() }) } } diff --git a/test/framework/fixtures/webProxyFixture.go b/test/framework/fixtures/webProxyFixture.go index 94cee4ce7c..0b9e12d936 100644 --- a/test/framework/fixtures/webProxyFixture.go +++ b/test/framework/fixtures/webProxyFixture.go @@ -17,10 +17,11 @@ package fixtures import ( - "fmt" "net" "net/http" "strings" + + "github.com/algorand/go-algorand/logging" ) // WebProxyInterceptFunc expose the web proxy intercept function @@ -32,16 +33,16 @@ type WebProxy struct { listener net.Listener destination string intercept WebProxyInterceptFunc + log logging.Logger } // MakeWebProxy creates an instance of the web proxy -func MakeWebProxy(destination string, intercept WebProxyInterceptFunc) (wp *WebProxy, err error) { - if strings.HasPrefix(destination, "http://") { - destination = destination[7:] - } +func MakeWebProxy(destination string, log logging.Logger, intercept WebProxyInterceptFunc) (wp *WebProxy, err error) { + destination = strings.TrimPrefix(destination, "http://") wp = &WebProxy{ destination: destination, intercept: intercept, + log: log, } wp.server = &http.Server{ Handler: wp, @@ -63,6 +64,7 @@ func (wp *WebProxy) GetListenAddress() string { // Close release the web proxy resources func (wp *WebProxy) Close() { + wp.log.Debugln("webproxy: quiting") // we can't use shutdown, since we have tunneled websocket, which is a hijacked connection // that http.Server doens't know how to handle. wp.server.Close() @@ -70,7 +72,7 @@ func (wp *WebProxy) Close() { // ServeHTTP serves a single HTTP request func (wp *WebProxy) ServeHTTP(response http.ResponseWriter, request *http.Request) { - //fmt.Printf("incoming request for %v\n", request.URL) + wp.log.Debugf("webproxy: incoming request for %v", request.URL) if wp.intercept == nil { wp.Passthrough(response, request) return @@ -86,7 +88,7 @@ func (wp *WebProxy) Passthrough(response http.ResponseWriter, request *http.Requ clientRequestURL.Host = wp.destination clientRequest, err := http.NewRequest(request.Method, clientRequestURL.String(), request.Body) if err != nil { - fmt.Printf("Passthrough request assembly error %v (%#v)\n", err, clientRequestURL) + wp.log.Debugf("Passthrough request assembly error %v (%#v)", err, clientRequestURL) response.WriteHeader(http.StatusInternalServerError) return } @@ -99,7 +101,7 @@ func (wp *WebProxy) Passthrough(response http.ResponseWriter, request *http.Requ } clientResponse, err := client.Do(clientRequest) if err != nil { - fmt.Printf("Passthrough request error %v (%v)\n", err, request.URL.String()) + wp.log.Debugf("Passthrough request error %v (%v)", err, request.URL.String()) response.WriteHeader(http.StatusInternalServerError) return } From 16e4165af37ce07a82ef1f08b571e34bd658d87e Mon Sep 17 00:00:00 2001 From: Gary <982483+gmalouf@users.noreply.github.com> Date: Thu, 11 Aug 2022 16:27:06 -0400 Subject: [PATCH 15/24] Build: Remove mac_amd64 from our default triggered builds. (#4397) * Remove mac_amd64 from our default triggered builds. * Remove mc_amd64 from test verification job. --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5cfc869fc6..e04193f4a0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,7 +70,7 @@ workflows: name: << matrix.platform >>_build matrix: &matrix-default parameters: - platform: ["amd64", "arm64", "mac_amd64"] + platform: ["amd64", "arm64"] filters: &filters-default branches: ignore: @@ -155,7 +155,7 @@ workflows: name: << matrix.platform >>_<< matrix.job_type >>_verification matrix: parameters: - platform: ["amd64", "arm64", "mac_amd64"] + platform: ["amd64", "arm64"] job_type: ["test", "integration", "e2e_expect"] requires: - << matrix.platform >>_<< matrix.job_type >> From 7c70e38a7000723d6d313ed01abf7e4c9a376417 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 11 Aug 2022 16:30:22 -0400 Subject: [PATCH 16/24] Four missing partiontest calls (#4394) * Some minor TEAL doc update --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 2 +- data/transactions/logic/assembler_test.go | 3 +++ data/transactions/logic/doc.go | 2 +- data/transactions/logic/evalCrypto_test.go | 3 +++ data/transactions/logic/evalStateful_test.go | 3 +++ data/transactions/logic/eval_test.go | 3 +++ data/transactions/logic/langspec.json | 2 +- 8 files changed, 16 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 0494971b3b..6c68a8a7c9 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -615,7 +615,7 @@ Account fields used in the `acct_params_get` opcode. | `app_params_get f` | X is field F from app A. Y is 1 if A exists, else 0 | | `acct_params_get f` | X is field F from account A. Y is 1 if A owns positive algos, else 0 | | `log` | write A to log state of the current application | -| `block f` | field F of block A. Fail unless A falls between txn.LastValid-1002 and the current round (exclusive) | +| `block f` | field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) | ### Inner Transactions diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 5fbd310d2a..6c5d679108 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -1401,7 +1401,7 @@ The notation A,B indicates that A and B are interpreted as a uint128 value, with - Opcode: 0xd1 {uint8 block field} - Stack: ..., A: uint64 → ..., any -- field F of block A. Fail unless A falls between txn.LastValid-1002 and the current round (exclusive) +- field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) - Availability: v7 `block` Fields: diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index f1afb9cc25..d0bcff2954 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -479,6 +479,9 @@ var experiments = []uint64{pairingVersion} // intended to release the opcodes, they should have been removed from // `experiments`. func TestExperimental(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + futureV := config.Consensus[protocol.ConsensusFuture].LogicSigVersion for _, v := range experiments { // Allows less, so we can push something out, even before vFuture has been updated. diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 1a43995c21..003c60a611 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -192,7 +192,7 @@ var opDocByName = map[string]string{ "itxn_submit": "execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.", "vrf_verify": "Verify the proof B of message A against pubkey C. Returns vrf output and verification flag.", - "block": "field F of block A. Fail unless A falls between txn.LastValid-1002 and the current round (exclusive)", + "block": "field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)", } // OpDoc returns a description of the op diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index b2c6bec0e5..773330fab3 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -109,6 +109,9 @@ byte 0x%s } func TestVrfVerify(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + ep, _, _ := makeSampleEnv() testApp(t, notrack("int 1; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 0 wanted") testApp(t, notrack("byte 0x1122; int 2; int 3; vrf_verify VrfAlgorand"), ep, "arg 1 wanted") diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index bb2f1e0f1d..e513172519 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2536,6 +2536,9 @@ func TestLatestTimestamp(t *testing.T) { } func TestBlockSeed(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + ep, txn, l := makeSampleEnv() // makeSampleEnv creates txns with fv, lv that don't actually fit the round diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 45a8ceeb22..19970acfb3 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5509,6 +5509,9 @@ func TestOpJSONRef(t *testing.T) { } func TestTypeComplaints(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + testProg(t, "err; store 0", AssemblerMaxVersion) testProg(t, "int 1; return; store 0", AssemblerMaxVersion) } diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json index 4e29b9a88b..ff1a681667 100644 --- a/data/transactions/logic/langspec.json +++ b/data/transactions/logic/langspec.json @@ -2297,7 +2297,7 @@ "Args": "U", "Returns": ".", "Size": 2, - "Doc": "field F of block A. Fail unless A falls between txn.LastValid-1002 and the current round (exclusive)", + "Doc": "field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive)", "ImmediateNote": "{uint8 block field}", "Groups": [ "State Access" From 5a852ba81aec41187c406d079734df559a22a2b0 Mon Sep 17 00:00:00 2001 From: nicholasguoalgorand <67928479+nicholasguoalgorand@users.noreply.github.com> Date: Thu, 11 Aug 2022 17:34:42 -0700 Subject: [PATCH 17/24] tests: add logging to libgoal fixture on failure (#4384) --- test/framework/fixtures/libgoalFixture.go | 23 ++++++++++++++++++++ test/framework/fixtures/restClientFixture.go | 16 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/test/framework/fixtures/libgoalFixture.go b/test/framework/fixtures/libgoalFixture.go index af84e4d2e1..e6ad44e060 100644 --- a/test/framework/fixtures/libgoalFixture.go +++ b/test/framework/fixtures/libgoalFixture.go @@ -17,6 +17,7 @@ package fixtures import ( + "bufio" "fmt" "io/ioutil" "os" @@ -311,6 +312,10 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { f.NC.StopKMD() if preserveData { f.network.Stop(f.binDir) + f.dumpLogs(filepath.Join(f.PrimaryDataDir(), "node.log")) + for _, nodeDir := range f.NodeDataDirs() { + f.dumpLogs(filepath.Join(nodeDir, "node.log")) + } } else { f.network.Delete(f.binDir) @@ -324,6 +329,24 @@ func (f *LibGoalFixture) ShutdownImpl(preserveData bool) { } } +// dumpLogs prints out log files for the running nodes +func (f *LibGoalFixture) dumpLogs(filePath string) { + file, err := os.Open(filePath) + if err != nil { + f.t.Logf("could not open %s", filePath) + return + } + defer file.Close() + + f.t.Log("=================================\n") + parts := strings.Split(filePath, "/") + f.t.Logf("%s/%s:", parts[len(parts)-2], parts[len(parts)-1]) // Primary/node.log + scanner := bufio.NewScanner(file) + for scanner.Scan() { + f.t.Logf(scanner.Text()) + } +} + // intercept baseFixture.failOnError so we can clean up any algods that are still alive func (f *LibGoalFixture) failOnError(err error, message string) { if err != nil { diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 7265c560cb..0e6f6ec5c8 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -265,6 +265,22 @@ func (f *RestClientFixture) WaitForAllTxnsToConfirm(roundTimeout uint64, txidsAn for txid, addr := range txidsAndAddresses { _, err := f.WaitForConfirmedTxn(roundTimeout, addr, txid) if err != nil { + f.t.Logf("txn failed to confirm: ", addr, txid) + pendingTxns, err := f.AlgodClient.GetPendingTransactions(0) + if err == nil { + pendingTxids := make([]string, 0, pendingTxns.TotalTxns) + for _, txn := range pendingTxns.TruncatedTxns.Transactions { + pendingTxids = append(pendingTxids, txn.TxID) + } + f.t.Logf("pending txids: ", pendingTxids) + } else { + f.t.Logf("unable to log pending txns, ", err) + } + allTxids := make([]string, 0, len(txidsAndAddresses)) + for txID := range txidsAndAddresses { + allTxids = append(allTxids, txID) + } + f.t.Logf("all txids: ", allTxids) return false } } From 3e048ee34ac0d3d07557c3879c9a5e87fd9baa20 Mon Sep 17 00:00:00 2001 From: Almog Tal <107349997+almog-t@users.noreply.github.com> Date: Fri, 12 Aug 2022 15:57:24 +0300 Subject: [PATCH 18/24] changed state proof message to no longer be embedded (#4395) --- daemon/algod/api/algod.oas2.json | 75 ++++---- daemon/algod/api/algod.oas3.yml | 79 +++++---- daemon/algod/api/algod2.oas2.json | 0 .../api/server/v2/generated/private/routes.go | 60 +++---- .../api/server/v2/generated/private/types.go | 31 ++-- .../algod/api/server/v2/generated/routes.go | 166 +++++++++--------- daemon/algod/api/server/v2/generated/types.go | 31 ++-- 7 files changed, 227 insertions(+), 215 deletions(-) create mode 100644 daemon/algod/api/algod2.oas2.json diff --git a/daemon/algod/api/algod.oas2.json b/daemon/algod/api/algod.oas2.json index c95c0dbd18..9af8a2aefe 100644 --- a/daemon/algod/api/algod.oas2.json +++ b/daemon/algod/api/algod.oas2.json @@ -2683,42 +2683,7 @@ ], "properties": { "Message": { - "description": "Represents the message that the state proofs are attesting to.", - "type": "object", - "required": [ - "BlockHeadersCommitment", - "VotersCommitment", - "LnProvenWeight", - "FirstAttestedRound", - "LastAttestedRound" - ], - "properties": { - "BlockHeadersCommitment": { - "description": "The vector commitment root on all light block headers within a state proof interval.", - "type": "string", - "format": "byte" - }, - "VotersCommitment": { - "description": "The vector commitment root of the top N accounts to sign the next StateProof.", - "type": "string", - "format": "byte" - }, - "LnProvenWeight": { - "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", - "type": "integer", - "x-algorand-format": "uint64" - }, - "FirstAttestedRound": { - "description": "The first round the message attests to.", - "type": "integer", - "x-algorand-format": "uint64" - }, - "LastAttestedRound": { - "description": "The last round the message attests to.", - "type": "integer", - "x-algorand-format": "uint64" - } - } + "$ref": "#/definitions/StateProofMessage" }, "StateProof": { "description": "The encoded StateProof for the message.", @@ -2750,6 +2715,44 @@ "format": "byte" } } + }, + "StateProofMessage": { + "description": "Represents the message that the state proofs are attesting to.", + "type": "object", + "required": [ + "BlockHeadersCommitment", + "VotersCommitment", + "LnProvenWeight", + "FirstAttestedRound", + "LastAttestedRound" + ], + "properties": { + "BlockHeadersCommitment": { + "description": "The vector commitment root on all light block headers within a state proof interval.", + "type": "string", + "format": "byte" + }, + "VotersCommitment": { + "description": "The vector commitment root of the top N accounts to sign the next StateProof.", + "type": "string", + "format": "byte" + }, + "LnProvenWeight": { + "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "FirstAttestedRound": { + "description": "The first round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LastAttestedRound": { + "description": "The last round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + } + } } }, "parameters": { diff --git a/daemon/algod/api/algod.oas3.yml b/daemon/algod/api/algod.oas3.yml index 44d4d1b955..404da3a2ea 100644 --- a/daemon/algod/api/algod.oas3.yml +++ b/daemon/algod/api/algod.oas3.yml @@ -1623,44 +1623,7 @@ "description": "Represents a state proof and its corresponding message", "properties": { "Message": { - "description": "Represents the message that the state proofs are attesting to.", - "properties": { - "BlockHeadersCommitment": { - "description": "The vector commitment root on all light block headers within a state proof interval.", - "format": "byte", - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - "type": "string" - }, - "FirstAttestedRound": { - "description": "The first round the message attests to.", - "type": "integer", - "x-algorand-format": "uint64" - }, - "LastAttestedRound": { - "description": "The last round the message attests to.", - "type": "integer", - "x-algorand-format": "uint64" - }, - "LnProvenWeight": { - "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", - "type": "integer", - "x-algorand-format": "uint64" - }, - "VotersCommitment": { - "description": "The vector commitment root of the top N accounts to sign the next StateProof.", - "format": "byte", - "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", - "type": "string" - } - }, - "required": [ - "BlockHeadersCommitment", - "FirstAttestedRound", - "LastAttestedRound", - "LnProvenWeight", - "VotersCommitment" - ], - "type": "object" + "$ref": "#/components/schemas/StateProofMessage" }, "StateProof": { "description": "The encoded StateProof for the message.", @@ -1675,6 +1638,46 @@ ], "type": "object" }, + "StateProofMessage": { + "description": "Represents the message that the state proofs are attesting to.", + "properties": { + "BlockHeadersCommitment": { + "description": "The vector commitment root on all light block headers within a state proof interval.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + }, + "FirstAttestedRound": { + "description": "The first round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LastAttestedRound": { + "description": "The last round the message attests to.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "LnProvenWeight": { + "description": "An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof.", + "type": "integer", + "x-algorand-format": "uint64" + }, + "VotersCommitment": { + "description": "The vector commitment root of the top N accounts to sign the next StateProof.", + "format": "byte", + "pattern": "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$", + "type": "string" + } + }, + "required": [ + "BlockHeadersCommitment", + "FirstAttestedRound", + "LastAttestedRound", + "LnProvenWeight", + "VotersCommitment" + ], + "type": "object" + }, "TealKeyValue": { "description": "Represents a key-value pair in an application store.", "properties": { diff --git a/daemon/algod/api/algod2.oas2.json b/daemon/algod/api/algod2.oas2.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/daemon/algod/api/server/v2/generated/private/routes.go b/daemon/algod/api/server/v2/generated/private/routes.go index 2ea4c40331..921f69d943 100644 --- a/daemon/algod/api/server/v2/generated/private/routes.go +++ b/daemon/algod/api/server/v2/generated/private/routes.go @@ -437,36 +437,36 @@ var swaggerSpec = []string{ "sJlb+TtdU97U/GkfaytC2TUEKbSd3b5Xg0Bc98lXdgGre4Hzz1Sqp5NSiDwZMBcf96sYdM/AOUvPISPm", "7vAxJAOVwMlXaKWs/YGX643P2i9L4JA9nhFi1PKi1BvvGmwX5OxMzh/pbfNf4axZZQuLOH1/9pHHw5+w", "5Ie8I3/zw2znagoM87vjVHaQHWUCrgYqKEh6GamLP/atxoizrlurvCEqC0VMSrllzuio893X+SOkHxTr", - "3q79hCnlPuszFdKajlBa8gadrvDy49Ajhx290T+ZWZ+zYEqFEbWGKShbAEv0haTAKKBe17p2fPP7Kjnm", - "QwuONSL6qrxC0xjWWQwRYehMXtD84dVxTJQ/RHy411ziCw31uRDJFpXqdt73t3TU3IHudn9T8xM0H/wT", - "zB5FbZpuKGcXrAuQ+7ppWA+I5iQXzUMEOCS5xDGtEXT/JVm4CMtSQsoU6wSfX/oScLX6ghVRm0d+tutL", - "u9b5i9B3IGMn8IqSvGvKSWmB/K6BsDn7f3Ig3cDJjVJ5jPp6ZBHB3yD3PdltcgueJfCyjiPmPxlxP9Ys", - "N1hKbKVhUucORn/esgPbQoSdOAwh4Z7twYED+ob24H666tjl4TpwQysF/XWOvmdbuI1csc3axjoz+sgd", - "9kHoxRgfRLxomumOThCLEKw4SBBU8tv+b0TCEkuKC/LkCU7w5MnUNf3tafuzYVxPnkQFsAdzf7Re/nXz", - "xijml6G4PRubNhAi2tmPiuXZLsJoBfw25f0xpPVXFxr9pzww8Ku1hPaPqivNfBPHa3cTEDGRtbYmD6YK", - "QnlHRPG6brPo28wK0koyvcGMbW84Y79GK+F8V9vana+mzvFzt7wW51Dn/DeW+Up5OeI7Yd9yLow0jG5v", - "jW89vbmiRZmDOyhfP1r8HZ7943m292z/74t/7L3YS+H5i1d7e/TVc7r/6tk+PP3Hi+d7sL98+WrxNHv6", - "/Oni+dPnL1+8Sp893188f/nq748MHzIgW0AnPj9o8j/xFY7k8OQ4OTPANjihJaufeDNk7AuA0xRPIhSU", - "5ZMD/9N/9ydsloqiGd7/OnHpB5O11qU6mM8vLy9nYZf5Ck1xiRZVup77efpPa50c16HRNqUVd9RGvRpS", - "wE11pHCI396/OT0jhyfHs4ZgJgeTvdnebB8fzimB05JNDibP8Cc8PWvc97kjtsnB5+vpZL4GmqPnyvxR", - "gJYs9Z/UJV2tQM5cJXTz08XTuRea5p+dGfJ627d5WFRw/rllrc129MS6a/PPPp14e+tWvq6zUgcdRkIx", - "PKV9PHb+GYX2wd/nqB9Zcpx770K8ZQvgz/qKZdfdHu65xvnn5v3Ua3tec4j5EmxQPQ2eW50SpgldCIkp", - "tTpdmyPqc/mYaj+3W9PbcWbozPR6Xb8lG5QxOvjQUy3sQMSPhIfSUFxzZlozNWxRywrCyjo102+1b1j/", - "h73k1afP+9P9veu/Gdbu/nzx7HqkU/B18xTtac23Rzb8hIlwaN7Eo/R0b+8OLy0d8vBdXNyk4EGv6PPY", - "VZkUQyYDt1WdgUiNjB0JO53hBx7jfH7DFW81wrRCcCIPJ3xDM+LzTHDu/Yeb+5ijS9awWGKvkOvp5MVD", - "rv6YG5KnOcGWQQZ2f+t/5udcXHLf0tz3VVFQufHHWLWYgn8hGm8VulJokpPsgmqYfEKbbywgcYC5KE1v", - "wVxOTa8vzOWhmAtu0n0wl/ZA98xcnt7wgP/1V/yFnf7V2OmpZXfj2akT5Wwq49w+7thIeL1C/yuI5lRi", - "diPd9mpzl8N+B7r3CPXkjizmT3uP+v/vc/J87/nDQdCuUv0DbMg7ocm3aEz+i57ZccdnmyTU0YyyrEfk", - "lv2D0t+IbLMFQ4ValS79KCKXLBg3IPdvl/6zh71Hos9hQ2xskPcBc5FBTx66viMP+Mu+Z/2Fh3zhIdJO", - "/+zhpj8FecFSIGdQlEJSyfIN+ZnXyeO3V+uyLBp33T76PZ5mtJFUZLACnjiGlSxEtvGFA1sDnoM1YvcE", - "lfnndvVvaygbNEsd4e/1o4R9oBcbcnzUk2Bsty6n/WaDTTsaY0Qn7IK4VTPs8qIBZWwbmZuFrIQmFguZ", - "W9QXxvOF8dxJeBl9eGLyS1Sb8Iac7p089VVUYnWGqO5PPUbn+FOP671sdF+fiekvNj4dMhJ8sIlUXTR/", - "YQlfWMLdWMJ3EDmMeGodk4gQ3W0svX0GgaG4WfcNHQx08M2rnEqiYKyZ4hBHdMaJh+ASD62kRXFldTTK", - "CVwxG7UZ2bD71du+sLgvLO4v5LXazWjagsiNNZ1z2BS0rPUbta50Ji5t9cEoV8TC/DR3VXwxorSO2dCC", - "+AGajFfyk6sWkG8wjJZlRozTrAAjUtW8znT2eQxNgLcZoXlMecU4ToCsAmex5appkEumIBXcPj3a8bU5", - "yN5ZnTDGZH+vADmaw42DcTJtOVvcNkaKQ99Z/ur7Rq632NKRKmzsez8eo35ctPX3/JIynSyFdHmmiL5+", - "Zw00n7siXJ1fm4ISvS9YJSP4MQjsiP86rx9TiH7sBq/EvrqIEd+oiU4Lo71wg+s4rw+fzD5hLV63903w", - "0sF8jslZa6H0fHI9/dwJbAo/fqq35nN9Lbstuv50/X8DAAD//89W3bJFvAAA", + "3q79hCnlPuszFdKajlBa8gadrvDyY2MRGlc23HfYAV6oFAeFwz03cuD8yTFCP9ZICZYySAmt5e/Ss/0T", + "ozVfCrZIYQSyWaayBcNEX6gMjCjqdW2biOO5b8LA/HHBsaZG3/Sh0JSIdSlDwjHnUl7Q/OHNF1hY4BDx", + "4V6/iS801H9DJFtUqttFK7ylo+YOdN37m5qfoLnln2D2KGoDdkM5O2pdsN3XmcP6STQnuWgebsAhySWO", + "aY3G+y/JwkWklhJSplgnWP/Sl8yr1T2sINs8irRdv9y1zl+EvgMZOwVBlORdU35LC7wfGgibI/onM5WB", + "kxul8hj19cgigr8YjwpTQ3dcF+cta7ItZ9iJ5hAS7tmqHLixb2hV7ie9jl0ergMvnUpBf52jb+sWbiMX", + "dbO2sS6RPnKHPRl6McaTES+9ZrqjK8UiBOsWEgSV/Lb/G5GwxMLkgjx5ghM8eTJ1TX972v5sjvOTJ1Ex", + "7sGcKK33g928MYr5ZSj6z0a4DQSadvajYnm2izBaYcPNIwEYGPurC7D+U54p+NXaU/tH1RV4von7trsJ", + "iJjIWluTB1MFAcEjYoFdt1n0hWcFaSWZ3mDetze/sV+j9XS+qy32zuNTZwq6u0+Lc6grBzT2/Ur52/U7", + "YV+ELoxMjc5zjS9GvbmiRZmDOyhfP1r8HZ7943m292z/74t/7L3YS+H5i1d7e/TVc7r/6tk+PP3Hi+d7", + "sL98+WrxNHv6/Oni+dPnL1+8Sp893188f/nq748MHzIgW0AnPsto8j/xLY/k8OQ4OTPANjihJasfijNk", + "7MuI0xRPIhSU5ZMD/9N/9ydsloqiGd7/OnFJDJO11qU6mM8vLy9nYZf5Cg16iRZVup77efoPdJ0c1wHW", + "NjEWd9TGzhpSwE11pHCI396/OT0jhyfHs4ZgJgeTvdnebB+f3ymB05JNDibP8Cc8PWvc97kjtsnB5+vp", + "ZL4GmqP/y/xRgJYs9Z/UJV2tQM5cPXXz08XTuRcl5p+dMfN627d5WJpw/rll88129MTqbfPPPil5e+tW", + "1q+zdQcdRkIxPKV9gnb+GUXZwd/nqDVYcpx7H0W8ZQvgz/qKZdfdHu7Rx/nn5hXWa3tec4h5JGxoPg0e", + "bZ0SpgldCImJuTpdmyPqMwKZaj/aW9PbcWbozPR6Xb9IGxRDOvjQE7jtQMSPhIfSUFxzZlozNWxRywrC", + "+jw102+1b1j/h73k1afP+9P9veu/Gdbu/nzx7Hqka/F186Dtac23Rzb8hOl0aCTFo/R0b+8O7zUd8vB1", + "Xdyk4Fmw6CPbVZkUQ4q026rOQKRGxo60n87wA096Pr/hireaclqBPJHnF76hGfHZKjj3/sPNfczRsWtY", + "LLFXyPV08uIhV3/MDcnTnGDLII+7v/U/83MuLrlvae77qiio3PhjrFpMwb8zjbcKXSk07El2QTVMPqHl", + "OBbWOMBclKa3YC6nptcX5vJQzAU36T6YS3uge2YuT294wP/6K/7CTv9q7PTUsrvx7NSJcjYhcm6fiGwk", + "vN5zASuIZmZijiTd9vZzl8N+B7r3lPXkjizmT3vV+v/vc/J87/nDQdCudf0DbMg7ocm3aGL9i57Zccdn", + "myTU0YyyrEfklv2D0t+IbLMFQ4ValS6JKSKXLBg3IPdvl/7jib2nps9hQ2yEkfckc5FBTx66viMP+Mu+", + "iv2Fh3zhIdJO/+zhpj8FecFSIGdQlEJSyfIN+ZnXKei3V+uyLBq93T76PZ5mtJFUZLACnjiGlSxEtvHl", + "B1sDnoM1YvcElfnndg1xaygbNEsd4e/104Z9oBcbcnzUk2Bsty6n/WaDTTsaY0Qn7IK4VTPs8qIBZWwb", + "mZuFrIQmFguZW9QXxvOF8dxJeBl9eGLyS1Sb8Iac7p089bVYYtWKqO5PPUbn+FOP671sdF+fiekvNsod", + "MhJ8sOlYXTR/YQlfWMLdWMJ3EDmMeGodk4gQ3W0svX0GgQG9WfclHgx08M2rnEqiYKyZ4hBHdMaJh+AS", + "D62kRXFldTTKCVwxG8sY2bD71du+sLgvLO4v5LXazWjagsiNNZ1z2BS0rPUbta50Ji5tDcMoV8Ty/jR3", + "tYAxzrKO2dCC+AGavFnyk6s5kG8wuJRlRozTrAAjUtW8znT22RBN2LMZoXmSecU4ToCsAmexRa9pkJGm", + "IBXcPmDa8bU5yN5ZnTDGZH+vADmaw42DcTJtOVvcNkZKTN9Z/ur7Rq632NKRKmxEeD8eo36itPX3/JIy", + "nSyFdNmqiL5+Zw00n7tSXp1fm7IUvS9YayP4MQjsiP86r59kiH7sBq/EvrqIEd+oiU4Lo71wg+s4rw+f", + "zD5hRV+3903w0sF8jilea6H0fHI9/dwJbAo/fqq35nN9Lbstuv50/X8DAAD//50wp92LvAAA", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/private/types.go b/daemon/algod/api/server/v2/generated/private/types.go index 8221e77310..1eaa4b4c83 100644 --- a/daemon/algod/api/server/v2/generated/private/types.go +++ b/daemon/algod/api/server/v2/generated/private/types.go @@ -460,26 +460,29 @@ type StateDelta []EvalDeltaKeyValue type StateProof struct { // Represents the message that the state proofs are attesting to. - Message struct { + Message StateProofMessage `json:"Message"` - // The vector commitment root on all light block headers within a state proof interval. - BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` + // The encoded StateProof for the message. + StateProof []byte `json:"StateProof"` +} - // The first round the message attests to. - FirstAttestedRound uint64 `json:"FirstAttestedRound"` +// StateProofMessage defines model for StateProofMessage. +type StateProofMessage struct { - // The last round the message attests to. - LastAttestedRound uint64 `json:"LastAttestedRound"` + // The vector commitment root on all light block headers within a state proof interval. + BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` - // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. - LnProvenWeight uint64 `json:"LnProvenWeight"` + // The first round the message attests to. + FirstAttestedRound uint64 `json:"FirstAttestedRound"` - // The vector commitment root of the top N accounts to sign the next StateProof. - VotersCommitment []byte `json:"VotersCommitment"` - } `json:"Message"` + // The last round the message attests to. + LastAttestedRound uint64 `json:"LastAttestedRound"` - // The encoded StateProof for the message. - StateProof []byte `json:"StateProof"` + // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. + LnProvenWeight uint64 `json:"LnProvenWeight"` + + // The vector commitment root of the top N accounts to sign the next StateProof. + VotersCommitment []byte `json:"VotersCommitment"` } // TealKeyValue defines model for TealKeyValue. diff --git a/daemon/algod/api/server/v2/generated/routes.go b/daemon/algod/api/server/v2/generated/routes.go index 185fbbca1b..f8fc805146 100644 --- a/daemon/algod/api/server/v2/generated/routes.go +++ b/daemon/algod/api/server/v2/generated/routes.go @@ -982,89 +982,89 @@ var swaggerSpec = []string{ "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", - "ljPEDCwBAA==", + "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", + "7uP/DwAA///T+v88UiwBAA==", } // GetSwagger returns the Swagger specification corresponding to the generated code diff --git a/daemon/algod/api/server/v2/generated/types.go b/daemon/algod/api/server/v2/generated/types.go index db3bd074eb..c3d71a7aa0 100644 --- a/daemon/algod/api/server/v2/generated/types.go +++ b/daemon/algod/api/server/v2/generated/types.go @@ -460,26 +460,29 @@ type StateDelta []EvalDeltaKeyValue type StateProof struct { // Represents the message that the state proofs are attesting to. - Message struct { + Message StateProofMessage `json:"Message"` - // The vector commitment root on all light block headers within a state proof interval. - BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` + // The encoded StateProof for the message. + StateProof []byte `json:"StateProof"` +} - // The first round the message attests to. - FirstAttestedRound uint64 `json:"FirstAttestedRound"` +// StateProofMessage defines model for StateProofMessage. +type StateProofMessage struct { - // The last round the message attests to. - LastAttestedRound uint64 `json:"LastAttestedRound"` + // The vector commitment root on all light block headers within a state proof interval. + BlockHeadersCommitment []byte `json:"BlockHeadersCommitment"` - // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. - LnProvenWeight uint64 `json:"LnProvenWeight"` + // The first round the message attests to. + FirstAttestedRound uint64 `json:"FirstAttestedRound"` - // The vector commitment root of the top N accounts to sign the next StateProof. - VotersCommitment []byte `json:"VotersCommitment"` - } `json:"Message"` + // The last round the message attests to. + LastAttestedRound uint64 `json:"LastAttestedRound"` - // The encoded StateProof for the message. - StateProof []byte `json:"StateProof"` + // An integer value representing the natural log of the proven weight with 16 bits of precision. This value would be used to verify the next state proof. + LnProvenWeight uint64 `json:"LnProvenWeight"` + + // The vector commitment root of the top N accounts to sign the next StateProof. + VotersCommitment []byte `json:"VotersCommitment"` } // TealKeyValue defines model for TealKeyValue. From bc957abe8c1d70c408061ceb1e02ac49493852c0 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 12 Aug 2022 10:09:51 -0400 Subject: [PATCH 19/24] tests: stabilize TestAssetCreateWaitRestartDelete (#4400) * Ensure manager account is funded before running any operations on it * Shave 2 minutes from TestAssetCreateWaitBalLookbackDelete by introducing faster rounds on a custom proto that is already in place --- .../features/transactions/asset_test.go | 17 +++++++++++ test/framework/fixtures/restClientFixture.go | 30 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 520f8af0e0..287747b656 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -21,6 +21,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/stretchr/testify/require" @@ -970,6 +971,13 @@ func TestAssetCreateWaitRestartDelete(t *testing.T) { verifyAssetParameters(asset, "test", "testunit", manager, reserve, freeze, clawback, assetMetadataHash, assetURL, a) + // Ensure manager is funded before submitting any transactions + currentRound, err := client.CurrentRound() + a.NoError(err) + + err = fixture.WaitForAccountFunded(currentRound+5, manager) + a.NoError(err) + // Destroy the asset tx, err := client.MakeUnsignedAssetDestroyTx(assetIndex) a.NoError(err) @@ -1009,6 +1017,8 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { consensusParams.SeedLookback = 2 consensusParams.SeedRefreshInterval = 8 consensusParams.MaxBalLookback = 2 * consensusParams.SeedLookback * consensusParams.SeedRefreshInterval // 32 + consensusParams.AgreementFilterTimeoutPeriod0 = 400 * time.Millisecond + consensusParams.AgreementFilterTimeout = 400 * time.Millisecond configurableConsensus[consensusVersion] = consensusParams @@ -1059,6 +1069,13 @@ func TestAssetCreateWaitBalLookbackDelete(t *testing.T) { verifyAssetParameters(asset, "test", "testunit", manager, reserve, freeze, clawback, assetMetadataHash, assetURL, a) + // Ensure manager is funded before submitting any transactions + currentRound, err := client.CurrentRound() + a.NoError(err) + + err = fixture.WaitForAccountFunded(currentRound+5, manager) + a.NoError(err) + // Destroy the asset tx, err := client.MakeUnsignedAssetDestroyTx(assetIndex) a.NoError(err) diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 0e6f6ec5c8..f77e786c87 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -25,7 +25,7 @@ import ( "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/daemon/algod/api/client" - "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" + v1 "github.com/algorand/go-algorand/daemon/algod/api/spec/v1" "github.com/algorand/go-algorand/libgoal" "github.com/algorand/go-algorand/nodecontrol" "github.com/algorand/go-algorand/test/e2e-go/globals" @@ -287,6 +287,34 @@ func (f *RestClientFixture) WaitForAllTxnsToConfirm(roundTimeout uint64, txidsAn return true } +// WaitForAccountFunded waits until either the passed account gets non-empty balance +// or until the passed roundTimeout passes +// or until waiting for a round to pass times out +func (f *RestClientFixture) WaitForAccountFunded(roundTimeout uint64, accountAddress string) (err error) { + client := f.AlgodClient + for { + // Get current round information + curStatus, statusErr := client.Status() + require.NoError(f.t, statusErr, "fixture should be able to get node status") + curRound := curStatus.LastRound + + // Check if we know about the transaction yet + acct, acctErr := client.AccountInformation(accountAddress) + require.NoError(f.t, acctErr, "fixture should be able to get account info") + if acct.Amount > 0 { + return nil + } + + // Check if we should wait a round + if curRound > roundTimeout { + return fmt.Errorf("failed to see confirmed transaction by round %v", roundTimeout) + } + // Wait a round + err = f.WaitForRoundWithTimeout(curRound + 1) + require.NoError(f.t, err, "fixture should be able to wait for one round to pass") + } +} + // SendMoneyAndWait uses the rest client to send money and WaitForTxnConfirmation to wait for the send to confirm // it adds some extra error checking as well func (f *RestClientFixture) SendMoneyAndWait(curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string, closeToAccount string) (txn v1.Transaction) { From 07341cdc5eb2086c45223b94d0da252ff7367fb4 Mon Sep 17 00:00:00 2001 From: Shant Karakashian <55754073+algonautshant@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:07:29 -0400 Subject: [PATCH 20/24] tests: make SP e2e tests easier for arm (#4402) --- .../features/stateproofs/stateproofs_test.go | 51 +++++++++++++++---- .../RichAccountStateProofSmall.json | 20 ++++++++ .../nettemplates/StateProofSmall.json | 20 ++++++++ 3 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 test/testdata/nettemplates/RichAccountStateProofSmall.json create mode 100644 test/testdata/nettemplates/StateProofSmall.json diff --git a/test/e2e-go/features/stateproofs/stateproofs_test.go b/test/e2e-go/features/stateproofs/stateproofs_test.go index 5ad6f49985..8fdcfe31f6 100644 --- a/test/e2e-go/features/stateproofs/stateproofs_test.go +++ b/test/e2e-go/features/stateproofs/stateproofs_test.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "sync" "sync/atomic" "testing" @@ -111,7 +112,11 @@ func TestStateProofs(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) + } else { + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + } defer fixture.Shutdown() verifyStateProofsCreation(t, &fixture, consensusParams) @@ -219,27 +224,33 @@ func TestStateProofOverlappingKeys(t *testing.T) { configurableConsensus[consensusVersion] = consensusParams var fixture fixtures.RestClientFixture + pNodes := 5 fixture.SetConsensus(configurableConsensus) - fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) + pNodes = 2 + } else { + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + } defer fixture.Shutdown() // Get node libgoal clients in order to update their participation keys - var libgoalNodeClients [5]libgoal.Client - for i := 0; i < 5; i++ { + libgoalNodeClients := make([]libgoal.Client, pNodes, pNodes) + for i := 0; i < pNodes; i++ { nodeName := fmt.Sprintf("Node%d", i) c := fixture.GetLibGoalClientForNamedNode(nodeName) libgoalNodeClients[i] = c } // Get account address of each participating node - var accounts [5]string + accounts := make([]string, pNodes, pNodes) for i, c := range libgoalNodeClients { parts, err := c.GetParticipationKeys() // should have 1 participation per node r.NoError(err) accounts[i] = parts[0].Address } - var participations [5]account.Participation + participations := make([]account.Participation, pNodes, pNodes) var lastStateProofBlock bookkeeping.Block var lastStateProofMessage stateproofmsg.Message libgoalClient := fixture.LibGoalClient @@ -251,14 +262,14 @@ func TestStateProofOverlappingKeys(t *testing.T) { for rnd := uint64(1); rnd <= consensusParams.StateProofInterval*(expectedNumberOfStateProofs+1); rnd++ { if rnd == voteLastValid-64 { // allow some buffer period before the voting keys are expired (for the keyreg to take effect) // Generate participation keys (for the same accounts) - for i := 0; i < 5; i++ { + for i := 0; i < pNodes; i++ { // Overlapping stateproof keys (the key for round 0 is valid up to 256) _, part, err := installParticipationKey(t, libgoalNodeClients[i], accounts[i], 0, 200) r.NoError(err) participations[i] = part } // Register overlapping participation keys - for i := 0; i < 5; i++ { + for i := 0; i < pNodes; i++ { registerParticipationAndWait(t, libgoalNodeClients[i], participations[i]) } } @@ -315,7 +326,11 @@ func TestStateProofMessageCommitmentVerification(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + fixture.Setup(t, filepath.Join("nettemplates", "StateProofSmall.json")) + } else { + fixture.Setup(t, filepath.Join("nettemplates", "StateProof.json")) + } defer fixture.Shutdown() libgoalClient := fixture.LibGoalClient @@ -439,6 +454,10 @@ func TestRecoverFromLaggingStateProofChain(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + t.Skip("This test is difficult for ARM") + } + r := require.New(fixtures.SynchronizedTest(t)) configurableConsensus := make(config.ConsensusProtocols) @@ -533,6 +552,10 @@ func TestUnableToRecoverFromLaggingStateProofChain(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + t.Skip("This test is difficult for ARM") + } + r := require.New(fixtures.SynchronizedTest(t)) configurableConsensus := make(config.ConsensusProtocols) @@ -649,6 +672,10 @@ func TestAttestorsChangeTest(t *testing.T) { partitiontest.PartitionTest(t) defer fixtures.ShutdownSynchronizedTest(t) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + t.Skip("This test is difficult for ARM") + } + a := require.New(fixtures.SynchronizedTest(t)) consensusParams := getDefaultStateProofConsensusParams() @@ -765,7 +792,11 @@ func TestTotalWeightChanges(t *testing.T) { var fixture fixtures.RestClientFixture fixture.SetConsensus(configurableConsensus) - fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProof.json")) + if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { + fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProofSmall.json")) + } else { + fixture.Setup(t, filepath.Join("nettemplates", "RichAccountStateProof.json")) + } defer fixture.Shutdown() var lastStateProofBlock bookkeeping.Block diff --git a/test/testdata/nettemplates/RichAccountStateProofSmall.json b/test/testdata/nettemplates/RichAccountStateProofSmall.json new file mode 100644 index 0000000000..bd2fd8ee5b --- /dev/null +++ b/test/testdata/nettemplates/RichAccountStateProofSmall.json @@ -0,0 +1,20 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "test-fast-stateproofs", + "LastPartKeyRound": 100, + "Wallets": [ + { "Name": "richWallet", "Stake": 99, "Online": true }, + { "Name": "poorWallet", "Stake": 1, "Online": true } + ] + }, + "Nodes": [ + { + "Name": "Relay0", + "IsRelay": true, + "Wallets": [] + }, + { "Name": "richNode", "Wallets": [ { "Name": "richWallet", "ParticipationOnly": false } ] }, + { "Name": "poorNode", "Wallets": [ { "Name": "poorWallet", "ParticipationOnly": false } ] } + ] +} diff --git a/test/testdata/nettemplates/StateProofSmall.json b/test/testdata/nettemplates/StateProofSmall.json new file mode 100644 index 0000000000..cfe7d6936d --- /dev/null +++ b/test/testdata/nettemplates/StateProofSmall.json @@ -0,0 +1,20 @@ +{ + "Genesis": { + "NetworkName": "tbd", + "ConsensusProtocol": "test-fast-stateproofs", + "LastPartKeyRound": 100, + "Wallets": [ + { "Name": "Wallet0", "Stake": 50, "Online": true }, + { "Name": "Wallet1", "Stake": 50, "Online": true } + ] + }, + "Nodes": [ + { + "Name": "Relay0", + "IsRelay": true, + "Wallets": [] + }, + { "Name": "Node0", "Wallets": [ { "Name": "Wallet0", "ParticipationOnly": false } ] }, + { "Name": "Node1", "Wallets": [ { "Name": "Wallet1", "ParticipationOnly": false } ] } + ] +} From ee302b0ef84dd5c1015acea45286d1e89d4a5bc4 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy <65323360+algorandskiy@users.noreply.github.com> Date: Fri, 12 Aug 2022 16:09:05 -0400 Subject: [PATCH 21/24] tests: improve logging in expect tests (#4405) --- .../cli/goal/expect/goalExpectCommon.exp | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp index f528dabb19..0e3c8fc619 100644 --- a/test/e2e-go/cli/goal/expect/goalExpectCommon.exp +++ b/test/e2e-go/cli/goal/expect/goalExpectCommon.exp @@ -45,6 +45,7 @@ package require Tcl 8.0 # Utility method to abort out of this script proc ::AlgorandGoal::Abort { ERROR } { puts "Aborting with Error: $ERROR" + set LOGS_COLLECTED 0 if { [info exists ::GLOBAL_TEST_ROOT_DIR] } { # terminate child algod processes, if there are active child processes the test will hang on a test failure @@ -53,22 +54,25 @@ proc ::AlgorandGoal::Abort { ERROR } { log_user 1 set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Primary - if { [info exists ::NODE_DATA_DIR] } { + if { [file exists $NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] - puts "$NODE_DATA_DIR/algod-out.log :\r\n$outLog" + puts "\n$NODE_DATA_DIR/algod-out.log:\r\n$outLog" set errLog [exec cat $NODE_DATA_DIR/algod-err.log] - puts "$NODE_DATA_DIR/algod-err.log :\r\n$errLog" - set nodeLog [exec -- tail -n 30 $NODE_DATA_DIR/node.log] - puts "$NODE_DATA_DIR/node.log :\r\n$nodeLog" + puts "\n$NODE_DATA_DIR/algod-err.log:\r\n$errLog" + set nodeLog [exec -- tail -n 50 $NODE_DATA_DIR/node.log] + puts "\n$NODE_DATA_DIR/node.log:\r\n$nodeLog" + set LOGS_COLLECTED 1 } set NODE_DATA_DIR $::GLOBAL_TEST_ROOT_DIR/Node - if { [info exists ::NODE_DATA_DIR] } { + puts "Node path $NODE_DATA_DIR" + if { [file exists $NODE_DATA_DIR] } { set outLog [exec cat $NODE_DATA_DIR/algod-out.log] - puts "$NODE_DATA_DIR/algod-out.log :\r\n$outLog" + puts "\n$NODE_DATA_DIR/algod-out.log:\r\n$outLog" set errLog [exec cat $NODE_DATA_DIR/algod-err.log] - puts "$NODE_DATA_DIR/algod-err.log :\r\n$errLog" - set nodeLog [exec -- tail -n 30 $NODE_DATA_DIR/node.log] - puts "$NODE_DATA_DIR/node.log :\r\n$nodeLog" + puts "\n$NODE_DATA_DIR/algod-err.log:\r\n$errLog" + set nodeLog [exec -- tail -n 50 $NODE_DATA_DIR/node.log] + puts "\n$NODE_DATA_DIR/node.log:\r\n$nodeLog" + set LOGS_COLLECTED 1 } ::AlgorandGoal::StopNetwork $::GLOBAL_NETWORK_NAME $::GLOBAL_TEST_ROOT_DIR @@ -77,13 +81,15 @@ proc ::AlgorandGoal::Abort { ERROR } { if { [info exists ::GLOBAL_TEST_ALGO_DIR] } { puts "GLOBAL_TEST_ALGO_DIR $::GLOBAL_TEST_ALGO_DIR" - log_user 1 - set outLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-out.log] - puts "$::GLOBAL_TEST_ALGO_DIR/algod-out.log :\r\n$outLog" - set errLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-err.log] - puts "$NODE_DATA_DIR/algod-err.log :\r\n$errLog" - set nodeLog [exec -- tail -n 30 $::GLOBAL_TEST_ALGO_DIR/node.log] - puts "$::GLOBAL_TEST_ALGO_DIR/node.log :\r\n$nodeLog" + if { $LOGS_COLLECTED == 0 } { + log_user 1 + set outLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-out.log] + puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-out.log:\r\n$outLog" + set errLog [exec cat $::GLOBAL_TEST_ALGO_DIR/algod-err.log] + puts "\n$::GLOBAL_TEST_ALGO_DIR/algod-err.log:\r\n$errLog" + set nodeLog [exec -- tail -n 50 $::GLOBAL_TEST_ALGO_DIR/node.log] + puts "\n$::GLOBAL_TEST_ALGO_DIR/node.log:\r\n$nodeLog" + } ::AlgorandGoal::StopNode $::GLOBAL_TEST_ALGO_DIR } @@ -967,7 +973,7 @@ proc ::AlgorandGoal::WaitForRound { WAIT_FOR_ROUND_NUMBER NODE_DATA_DIR } { eof { catch wait result; if { [lindex $result 3] != 0 } { - ::AlgorandGoal::Abort "failed to wait for round : error code [lindex $result 3]" + ::AlgorandGoal::Abort "failed to wait for round : error code [lindex $result 3], output: $expect_out(buffer)" } } } From 6d858e60a6c7a0e273611202446f14050eca3de8 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 15 Aug 2022 08:16:57 -0400 Subject: [PATCH 22/24] Acquire locks when logging start and stop. (#4408) --- test/scripts/e2e_client_runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/scripts/e2e_client_runner.py b/test/scripts/e2e_client_runner.py index 6fa5b4ffba..53a0e484ef 100755 --- a/test/scripts/e2e_client_runner.py +++ b/test/scripts/e2e_client_runner.py @@ -246,10 +246,10 @@ def get_pub_wallet(self): return self.pubw, self.maxpubaddr def start(self, scriptname, timeout): - self.event_log("run", scriptname) t = threading.Thread(target=script_thread, args=(self, scriptname, timeout)) t.start() with self.lock: + self.event_log("run", scriptname) self.threads[scriptname] = t def running(self, scriptname, p): @@ -257,8 +257,8 @@ def running(self, scriptname, p): self.procs[scriptname] = p def done(self, scriptname, ok, seconds): - self.event_log("pass" if ok else "fail", scriptname, seconds) with self.lock: + self.event_log("pass" if ok else "fail", scriptname, seconds) self.statuses.append( {'script':scriptname, 'ok':ok, 'seconds':seconds} ) if not ok: self.errors.append('{} failed'.format(scriptname)) From d530f3e55feecb8c84f49a37fe997a3ea6b88e78 Mon Sep 17 00:00:00 2001 From: nicholasguoalgorand <67928479+nicholasguoalgorand@users.noreply.github.com> Date: Mon, 15 Aug 2022 06:13:14 -0700 Subject: [PATCH 23/24] tests: remove TestAsyncRecord as obsolete (#4407) --- node/node_test.go | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/node/node_test.go b/node/node_test.go index f440810f6b..dcf2bb6a20 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -502,51 +502,6 @@ func TestMismatchingGenesisDirectoryPermissions(t *testing.T) { require.NoError(t, os.RemoveAll(testDirectroy)) } -func TestAsyncRecord(t *testing.T) { - partitiontest.PartitionTest(t) - - testDirectroy := t.TempDir() - - genesis := bookkeeping.Genesis{ - SchemaID: "go-test-node-record-async", - Proto: protocol.ConsensusCurrentVersion, - Network: config.Devtestnet, - FeeSink: sinkAddr.String(), - RewardsPool: poolAddr.String(), - } - - cfg := config.GetDefaultLocal() - cfg.DisableNetworking = true - node, err := MakeFull(logging.TestingLog(t), testDirectroy, config.GetDefaultLocal(), []string{}, genesis) - require.NoError(t, err) - node.Start() - defer node.Stop() - - var addr basics.Address - addr[0] = 1 - - p := account.Participation{ - Parent: addr, - FirstValid: 0, - LastValid: 1000000, - Voting: &crypto.OneTimeSignatureSecrets{}, - VRF: &crypto.VRFSecrets{}, - } - id, err := node.accountManager.Registry().Insert(p) - require.NoError(t, err) - err = node.accountManager.Registry().Register(id, 0) - require.NoError(t, err) - - node.Record(addr, 10000, account.Vote) - node.Record(addr, 20000, account.BlockProposal) - - time.Sleep(5000 * time.Millisecond) - records := node.accountManager.Registry().GetAll() - require.Len(t, records, 1) - require.Equal(t, 10000, int(records[0].LastVote)) - require.Equal(t, 20000, int(records[0].LastBlockProposal)) -} - // TestOfflineOnlineClosedBitStatus a test that validates that the correct bits are being set func TestOfflineOnlineClosedBitStatus(t *testing.T) { partitiontest.PartitionTest(t) From fd488f806dcbc2586f585155eea0180c30287f70 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Mon, 15 Aug 2022 11:00:12 -0400 Subject: [PATCH 24/24] Fix variable application in verifyAssetParameters (#4410) --- test/e2e-go/features/transactions/asset_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e-go/features/transactions/asset_test.go b/test/e2e-go/features/transactions/asset_test.go index 287747b656..9616f3cb05 100644 --- a/test/e2e-go/features/transactions/asset_test.go +++ b/test/e2e-go/features/transactions/asset_test.go @@ -1186,8 +1186,8 @@ func verifyAssetParameters(asset v1.AssetParams, unitName, assetName, manager, reserve, freeze, clawback string, metadataHash []byte, assetURL string, asser *require.Assertions) { - asser.Equal(asset.UnitName, "test") - asser.Equal(asset.AssetName, "testunit") + asser.Equal(asset.UnitName, unitName) + asser.Equal(asset.AssetName, assetName) asser.Equal(asset.ManagerAddr, manager) asser.Equal(asset.ReserveAddr, reserve) asser.Equal(asset.FreezeAddr, freeze)