diff --git a/.circleci/config.yml b/.circleci/config.yml index 61535ae0..8eecf262 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ workflows: name: 'test_go_<< matrix.go_version >>' matrix: parameters: - go_version: ['1.15', '1.16', '1.17'] + go_version: ['1.16', '1.17'] jobs: test: diff --git a/.test-env b/.test-env new file mode 100644 index 00000000..3b17bb19 --- /dev/null +++ b/.test-env @@ -0,0 +1,14 @@ +# Configs for testing repo download: +SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" +SDK_TESTING_BRANCH="master" +SDK_TESTING_HARNESS="test-harness" + +VERBOSE_HARNESS=0 + +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +REMOVE_LOCAL_FEATURES=0 + +# WARNING: Be careful when turning on the next variable. +# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` +OVERWRITE_TESTING_ENVIRONMENT=0 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd74f8c9..d110e831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +# 1.20.0 +## What's Changed +### Bugfixes +* Bug-Fix: passthru verbosity by @tzaffi in https://github.com/algorand/go-algorand-sdk/pull/371 +* BugFix: Src map type assert fix by @barnjamin in https://github.com/algorand/go-algorand-sdk/pull/370 +### New Features +* StateProof: State proof support by @Eric-Warehime in https://github.com/algorand/go-algorand-sdk/pull/374 +* StateProof: State Proof Verification additions by @almog-t in https://github.com/algorand/go-algorand-sdk/pull/377 +* State Proofs: added compute leaf function for light block header to sdk by @almog-t in https://github.com/algorand/go-algorand-sdk/pull/382 +* State Proofs: renamed light block header hash func by @almog-t in https://github.com/algorand/go-algorand-sdk/pull/383 +### Enhancements +* Enhancement: Use Sandbox for Testing by @tzaffi in https://github.com/algorand/go-algorand-sdk/pull/360 +* Enhancement: Deprecating use of langspec by @ahangsu in https://github.com/algorand/go-algorand-sdk/pull/366 +* State Proofs: Use generic type for StateProof txn field. by @winder in https://github.com/algorand/go-algorand-sdk/pull/378 +* Improvement: Better SourceMap decoding by @barnjamin in https://github.com/algorand/go-algorand-sdk/pull/380 +* tests: Enable stpf cucumber unit tests by @Eric-Warehime in https://github.com/algorand/go-algorand-sdk/pull/386 + +## New Contributors +* @tzaffi made their first contribution in https://github.com/algorand/go-algorand-sdk/pull/360 +* @almog-t made their first contribution in https://github.com/algorand/go-algorand-sdk/pull/377 + +**Full Changelog**: https://github.com/algorand/go-algorand-sdk/compare/v1.19.0...v1.20.0 + # 1.19.0 ## Enhancements * AVM: Consolidate TEAL and AVM versions ([#345](https://github.com/algorand/go-algorand-sdk/pull/345)) diff --git a/Makefile b/Makefile index 0d881e7e..e3e2c90a 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,9 @@ SRCPATH := $(shell pwd) TEST_SOURCES := $(shell cd $(SRCPATH) && go list ./...) TEST_SOURCES_NO_CUCUMBER := $(shell cd $(SRCPATH) && go list ./... | grep -v test) +UNIT_TAGS := "$(shell awk '{print $2}' test/unit.tags | paste -s -d, -)" +INTEGRATIONS_TAGS := "$(shell awk '{print $2}' test/integration.tags | paste -s -d, -)" +GO_IMAGE := golang:$(subst go,,$(shell go version | cut -d' ' -f 3 | cut -d'.' -f 1,2))-stretch lint: golint `go list ./... | grep -v /vendor/` @@ -19,13 +22,27 @@ test: unit: go test $(TEST_SOURCES_NO_CUCUMBER) - cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags="@unit.sourcemap,@unit.offline,@unit.algod,@unit.indexer,@unit.transactions.keyreg,@unit.rekey,@unit.tealsign,@unit.dryrun,@unit.responses,@unit.applications,@unit.transactions,@unit.indexer.rekey,@unit.responses.messagepack,@unit.responses.231,@unit.responses.messagepack.231,@unit.responses.genesis,@unit.feetest,@unit.indexer.logs,@unit.abijson,@unit.abijson.byname,@unit.transactions.payment,@unit.atomic_transaction_composer,@unit.responses.unlimited_assets,@unit.indexer.ledger_refactoring,@unit.algod.ledger_refactoring,@unit.dryrun.trace.application" --test.v . + cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags=$(UNIT_TAGS) --test.v . integration: go test $(TEST_SOURCES_NO_CUCUMBER) - cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags="@algod,@assets,@auction,@kmd,@send,@indexer,@rekey_v1,@send.keyregtxn,@dryrun,@compile,@applications.verified,@indexer.applications,@indexer.231,@abi,@c2c,@compile.sourcemap" --test.v . + cd test && go test -timeout 0s --godog.strict=true --godog.format=pretty --godog.tags=$(INTEGRATIONS_TAGS) --test.v . + +display-all-go-steps: + find test -name "*.go" | xargs grep "github.com/cucumber/godog" 2>/dev/null | cut -d: -f1 | sort | uniq | xargs grep -Eo "Step[(].[^\`]+" | awk '{sub(/:Step\(./,":")} 1' | sed -E 's/", [a-zA-Z0-9]+\)//g' + +harness: + ./test-harness.sh + +docker-gosdk-build: + echo "Building docker image from base $(GO_IMAGE)" + docker build -t go-sdk-testing --build-arg GO_IMAGE="$(GO_IMAGE)" -f test/docker/Dockerfile $(shell pwd) + +docker-gosdk-run: + docker ps -a + docker run -it --network host go-sdk-testing:latest + +docker-test: harness docker-gosdk-build docker-gosdk-run -docker-test: - ./test/docker/run_docker.sh .PHONY: test fmt diff --git a/README.md b/README.md index c224d501..17ff328c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ In `client/v2` the `indexer` package contains a client for the Algorand Indexer # SDK Development -Run tests with `make docker-test` +Run tests with `make docker-test`. To set up the sandbox-based test harness without standing up the go-algorand docker image use `make harness`. # Quick Start diff --git a/client/v2/algod/algod.go b/client/v2/algod/algod.go index 80681c63..f5ad2205 100644 --- a/client/v2/algod/algod.go +++ b/client/v2/algod/algod.go @@ -80,8 +80,8 @@ func (c *Client) Block(round uint64) *Block { return &Block{c: c, round: round} } -func (c *Client) GetProof(round uint64, txid string) *GetProof { - return &GetProof{c: c, round: round, txid: txid} +func (c *Client) GetTransactionProof(round uint64, txid string) *GetTransactionProof { + return &GetTransactionProof{c: c, round: round, txid: txid} } func (c *Client) Supply() *Supply { @@ -112,6 +112,14 @@ func (c *Client) PendingTransactionInformation(txid string) *PendingTransactionI return &PendingTransactionInformation{c: c, txid: txid} } +func (c *Client) GetStateProof(round uint64) *GetStateProof { + return &GetStateProof{c: c, round: round} +} + +func (c *Client) GetLightBlockHeaderProof(round uint64) *GetLightBlockHeaderProof { + return &GetLightBlockHeaderProof{c: c, round: round} +} + func (c *Client) GetApplicationByID(applicationId uint64) *GetApplicationByID { return &GetApplicationByID{c: c, applicationId: applicationId} } diff --git a/client/v2/algod/getLightBlockHeaderProof.go b/client/v2/algod/getLightBlockHeaderProof.go new file mode 100644 index 00000000..66888891 --- /dev/null +++ b/client/v2/algod/getLightBlockHeaderProof.go @@ -0,0 +1,23 @@ +package algod + +import ( + "context" + "fmt" + + "github.com/algorand/go-algorand-sdk/client/v2/common" + "github.com/algorand/go-algorand-sdk/client/v2/common/models" +) + +// GetLightBlockHeaderProof gets a proof for a given light block header inside a +// state proof commitment +type GetLightBlockHeaderProof struct { + c *Client + + round uint64 +} + +// Do performs the HTTP request +func (s *GetLightBlockHeaderProof) Do(ctx context.Context, headers ...*common.Header) (response models.LightBlockHeaderProof, err error) { + err = s.c.get(ctx, &response, fmt.Sprintf("/v2/blocks/%s/lightheader/proof", common.EscapeParams(s.round)...), nil, headers) + return +} diff --git a/client/v2/algod/getStateProof.go b/client/v2/algod/getStateProof.go new file mode 100644 index 00000000..be65cd11 --- /dev/null +++ b/client/v2/algod/getStateProof.go @@ -0,0 +1,22 @@ +package algod + +import ( + "context" + "fmt" + + "github.com/algorand/go-algorand-sdk/client/v2/common" + "github.com/algorand/go-algorand-sdk/client/v2/common/models" +) + +// GetStateProof get a state proof that covers a given round +type GetStateProof struct { + c *Client + + round uint64 +} + +// Do performs the HTTP request +func (s *GetStateProof) Do(ctx context.Context, headers ...*common.Header) (response models.StateProof, err error) { + err = s.c.get(ctx, &response, fmt.Sprintf("/v2/stateproofs/%s", common.EscapeParams(s.round)...), nil, headers) + return +} diff --git a/client/v2/algod/getProof.go b/client/v2/algod/getTransactionProof.go similarity index 63% rename from client/v2/algod/getProof.go rename to client/v2/algod/getTransactionProof.go index d7e19167..59271184 100644 --- a/client/v2/algod/getProof.go +++ b/client/v2/algod/getTransactionProof.go @@ -8,8 +8,8 @@ import ( "github.com/algorand/go-algorand-sdk/client/v2/common/models" ) -// GetProofParams contains all of the query parameters for url serialization. -type GetProofParams struct { +// GetTransactionProofParams contains all of the query parameters for url serialization. +type GetTransactionProofParams struct { // Format configures whether the response object is JSON or MessagePack encoded. Format string `url:"format,omitempty"` @@ -20,26 +20,26 @@ type GetProofParams struct { Hashtype string `url:"hashtype,omitempty"` } -// GetProof get a Merkle proof for a transaction in a block. -type GetProof struct { +// GetTransactionProof get a proof for a transaction in a block. +type GetTransactionProof struct { c *Client round uint64 txid string - p GetProofParams + p GetTransactionProofParams } // Hashtype the type of hash function used to create the proof, must be one of: // * sha512_256 // * sha256 -func (s *GetProof) Hashtype(Hashtype string) *GetProof { +func (s *GetTransactionProof) Hashtype(Hashtype string) *GetTransactionProof { s.p.Hashtype = Hashtype return s } // Do performs the HTTP request -func (s *GetProof) Do(ctx context.Context, headers ...*common.Header) (response models.ProofResponse, err error) { +func (s *GetTransactionProof) Do(ctx context.Context, headers ...*common.Header) (response models.TransactionProofResponse, err error) { err = s.c.get(ctx, &response, fmt.Sprintf("/v2/blocks/%s/transactions/%s/proof", common.EscapeParams(s.round, s.txid)...), s.p, headers) return } diff --git a/client/v2/common/models/block.go b/client/v2/common/models/block.go index 266d2005..0c433121 100644 --- a/client/v2/common/models/block.go +++ b/client/v2/common/models/block.go @@ -22,6 +22,9 @@ type Block struct { // Seed (seed) Sortition seed. Seed []byte `json:"seed"` + // StateProofTracking tracks the status of state proofs. + StateProofTracking []StateProofTracking `json:"state-proof-tracking,omitempty"` + // Timestamp (ts) Block creation timestamp in seconds since eposh Timestamp uint64 `json:"timestamp"` @@ -37,6 +40,12 @@ type Block struct { // the same TxnRoot. TransactionsRoot []byte `json:"transactions-root"` + // TransactionsRootSha256 (txn256) TransactionsRootSHA256 is an auxiliary + // TransactionRoot, built using a vector commitment instead of a merkle tree, and + // SHA256 hash function instead of the default SHA512_256. This commitment can be + // used on environments where only the SHA256 function exists. + TransactionsRootSha256 []byte `json:"transactions-root-sha256"` + // TxnCounter (tc) TxnCounter counts the number of transactions committed in the // ledger, from the time at which support for this feature was introduced. // Specifically, TxnCounter is the number of the next transaction that will be diff --git a/client/v2/common/models/hash_factory.go b/client/v2/common/models/hash_factory.go new file mode 100644 index 00000000..1de325cf --- /dev/null +++ b/client/v2/common/models/hash_factory.go @@ -0,0 +1,7 @@ +package models + +// HashFactory defines a model for HashFactory. +type HashFactory struct { + // HashType (t) + HashType uint64 `json:"hash-type,omitempty"` +} diff --git a/client/v2/common/models/indexer_state_proof_message.go b/client/v2/common/models/indexer_state_proof_message.go new file mode 100644 index 00000000..316a7c9f --- /dev/null +++ b/client/v2/common/models/indexer_state_proof_message.go @@ -0,0 +1,19 @@ +package models + +// IndexerStateProofMessage defines a model for IndexerStateProofMessage. +type IndexerStateProofMessage struct { + // BlockHeadersCommitment (b) + BlockHeadersCommitment []byte `json:"block-headers-commitment,omitempty"` + + // FirstAttestedRound (f) + FirstAttestedRound uint64 `json:"first-attested-round,omitempty"` + + // LatestAttestedRound (l) + LatestAttestedRound uint64 `json:"latest-attested-round,omitempty"` + + // LnProvenWeight (P) + LnProvenWeight uint64 `json:"ln-proven-weight,omitempty"` + + // VotersCommitment (v) + VotersCommitment []byte `json:"voters-commitment,omitempty"` +} diff --git a/client/v2/common/models/light_block_header_proof.go b/client/v2/common/models/light_block_header_proof.go new file mode 100644 index 00000000..bb5530f7 --- /dev/null +++ b/client/v2/common/models/light_block_header_proof.go @@ -0,0 +1,14 @@ +package models + +// LightBlockHeaderProof proof of membership and position of a light block header. +type LightBlockHeaderProof struct { + // Index the index of the light block header in the vector commitment tree + Index uint64 `json:"index"` + + // Proof the encoded proof. + Proof []byte `json:"proof"` + + // Treedepth 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"` +} diff --git a/client/v2/common/models/merkle_array_proof.go b/client/v2/common/models/merkle_array_proof.go new file mode 100644 index 00000000..3ae363c4 --- /dev/null +++ b/client/v2/common/models/merkle_array_proof.go @@ -0,0 +1,13 @@ +package models + +// MerkleArrayProof defines a model for MerkleArrayProof. +type MerkleArrayProof struct { + // HashFactory + HashFactory HashFactory `json:"hash-factory,omitempty"` + + // Path (pth) + Path [][]byte `json:"path,omitempty"` + + // TreeDepth (td) + TreeDepth uint64 `json:"tree-depth,omitempty"` +} diff --git a/client/v2/common/models/state_proof.go b/client/v2/common/models/state_proof.go new file mode 100644 index 00000000..74459112 --- /dev/null +++ b/client/v2/common/models/state_proof.go @@ -0,0 +1,10 @@ +package models + +// StateProof represents a state proof and its corresponding message +type StateProof struct { + // Message represents the message that the state proofs are attesting to. + Message StateProofMessage `json:"Message"` + + // Stateproof the encoded StateProof for the message. + Stateproof []byte `json:"StateProof"` +} diff --git a/client/v2/common/models/state_proof_fields.go b/client/v2/common/models/state_proof_fields.go new file mode 100644 index 00000000..7d0870f7 --- /dev/null +++ b/client/v2/common/models/state_proof_fields.go @@ -0,0 +1,28 @@ +package models + +// StateProofFields (sp) represents a state proof. +// Definition: +// crypto/stateproof/structs.go : StateProof +type StateProofFields struct { + // PartProofs (P) + PartProofs MerkleArrayProof `json:"part-proofs,omitempty"` + + // PositionsToReveal (pr) Sequence of reveal positions. + PositionsToReveal []uint64 `json:"positions-to-reveal,omitempty"` + + // Reveals (r) Note that this is actually stored as a map[uint64] - Reveal in the + // actual msgp + Reveals []StateProofReveal `json:"reveals,omitempty"` + + // SaltVersion (v) Salt version of the merkle signature. + SaltVersion uint64 `json:"salt-version,omitempty"` + + // SigCommit (c) + SigCommit []byte `json:"sig-commit,omitempty"` + + // SigProofs (S) + SigProofs MerkleArrayProof `json:"sig-proofs,omitempty"` + + // SignedWeight (w) + SignedWeight uint64 `json:"signed-weight,omitempty"` +} diff --git a/client/v2/common/models/state_proof_message.go b/client/v2/common/models/state_proof_message.go new file mode 100644 index 00000000..99f76729 --- /dev/null +++ b/client/v2/common/models/state_proof_message.go @@ -0,0 +1,23 @@ +package models + +// StateProofMessage represents the message that the state proofs are attesting to. +type StateProofMessage struct { + // Blockheaderscommitment the vector commitment root on all light block headers + // within a state proof interval. + Blockheaderscommitment []byte `json:"BlockHeadersCommitment"` + + // Firstattestedround the first round the message attests to. + Firstattestedround uint64 `json:"FirstAttestedRound"` + + // Lastattestedround the last round the message attests to. + Lastattestedround uint64 `json:"LastAttestedRound"` + + // Lnprovenweight 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"` + + // Voterscommitment the vector commitment root of the top N accounts to sign the + // next StateProof. + Voterscommitment []byte `json:"VotersCommitment"` +} diff --git a/client/v2/common/models/state_proof_participant.go b/client/v2/common/models/state_proof_participant.go new file mode 100644 index 00000000..42a7516d --- /dev/null +++ b/client/v2/common/models/state_proof_participant.go @@ -0,0 +1,10 @@ +package models + +// StateProofParticipant defines a model for StateProofParticipant. +type StateProofParticipant struct { + // Verifier (p) + Verifier StateProofVerifier `json:"verifier,omitempty"` + + // Weight (w) + Weight uint64 `json:"weight,omitempty"` +} diff --git a/client/v2/common/models/state_proof_reveal.go b/client/v2/common/models/state_proof_reveal.go new file mode 100644 index 00000000..68eb702d --- /dev/null +++ b/client/v2/common/models/state_proof_reveal.go @@ -0,0 +1,14 @@ +package models + +// StateProofReveal defines a model for StateProofReveal. +type StateProofReveal struct { + // Participant (p) + Participant StateProofParticipant `json:"participant,omitempty"` + + // Position the position in the signature and participants arrays corresponding to + // this entry. + Position uint64 `json:"position,omitempty"` + + // SigSlot (s) + SigSlot StateProofSigSlot `json:"sig-slot,omitempty"` +} diff --git a/client/v2/common/models/state_proof_sig_slot.go b/client/v2/common/models/state_proof_sig_slot.go new file mode 100644 index 00000000..9d5bd903 --- /dev/null +++ b/client/v2/common/models/state_proof_sig_slot.go @@ -0,0 +1,10 @@ +package models + +// StateProofSigSlot defines a model for StateProofSigSlot. +type StateProofSigSlot struct { + // LowerSigWeight (l) The total weight of signatures in the lower-numbered slots. + LowerSigWeight uint64 `json:"lower-sig-weight,omitempty"` + + // Signature + Signature StateProofSignature `json:"signature,omitempty"` +} diff --git a/client/v2/common/models/state_proof_signature.go b/client/v2/common/models/state_proof_signature.go new file mode 100644 index 00000000..add9909e --- /dev/null +++ b/client/v2/common/models/state_proof_signature.go @@ -0,0 +1,16 @@ +package models + +// StateProofSignature defines a model for StateProofSignature. +type StateProofSignature struct { + // FalconSignature + FalconSignature []byte `json:"falcon-signature,omitempty"` + + // MerkleArrayIndex + MerkleArrayIndex uint64 `json:"merkle-array-index,omitempty"` + + // Proof + Proof MerkleArrayProof `json:"proof,omitempty"` + + // VerifyingKey (vkey) + VerifyingKey []byte `json:"verifying-key,omitempty"` +} diff --git a/client/v2/common/models/state_proof_tracking.go b/client/v2/common/models/state_proof_tracking.go new file mode 100644 index 00000000..8f59f159 --- /dev/null +++ b/client/v2/common/models/state_proof_tracking.go @@ -0,0 +1,18 @@ +package models + +// StateProofTracking defines a model for StateProofTracking. +type StateProofTracking struct { + // NextRound (n) Next round for which we will accept a state proof transaction. + NextRound uint64 `json:"next-round,omitempty"` + + // OnlineTotalWeight (t) The total number of microalgos held by the online accounts + // during the StateProof round. + OnlineTotalWeight uint64 `json:"online-total-weight,omitempty"` + + // Type state Proof Type. Note the raw object uses map with this as key. + Type uint64 `json:"type,omitempty"` + + // VotersCommitment (v) Root of a vector commitment containing online accounts that + // will help sign the proof. + VotersCommitment []byte `json:"voters-commitment,omitempty"` +} diff --git a/client/v2/common/models/state_proof_verifier.go b/client/v2/common/models/state_proof_verifier.go new file mode 100644 index 00000000..2e8bf462 --- /dev/null +++ b/client/v2/common/models/state_proof_verifier.go @@ -0,0 +1,10 @@ +package models + +// StateProofVerifier defines a model for StateProofVerifier. +type StateProofVerifier struct { + // Commitment (cmt) Represents the root of the vector commitment tree. + Commitment []byte `json:"commitment,omitempty"` + + // KeyLifetime (lf) Key lifetime. + KeyLifetime uint64 `json:"key-lifetime,omitempty"` +} diff --git a/client/v2/common/models/transaction.go b/client/v2/common/models/transaction.go index 1974340f..99adadb4 100644 --- a/client/v2/common/models/transaction.go +++ b/client/v2/common/models/transaction.go @@ -133,6 +133,11 @@ type Transaction struct { // signatures should be provided. Signature TransactionSignature `json:"signature,omitempty"` + // StateProofTransaction fields for a state proof transaction. + // Definition: + // data/transactions/stateproof.go : StateProofTxnFields + StateProofTransaction TransactionStateProof `json:"state-proof-transaction,omitempty"` + // Type (type) Indicates what type of transaction this is. Different types have // different fields. // Valid types, and where their fields are stored: @@ -142,5 +147,6 @@ type Transaction struct { // * (axfer) asset-transfer-transaction // * (afrz) asset-freeze-transaction // * (appl) application-transaction + // * (stpf) state-proof-transaction Type string `json:"tx-type,omitempty"` } diff --git a/client/v2/common/models/proof_response.go b/client/v2/common/models/transaction_proof_response.go similarity index 79% rename from client/v2/common/models/proof_response.go rename to client/v2/common/models/transaction_proof_response.go index 6cb41710..535145ce 100644 --- a/client/v2/common/models/proof_response.go +++ b/client/v2/common/models/transaction_proof_response.go @@ -1,7 +1,7 @@ package models -// ProofResponse proof of transaction in a block. -type ProofResponse struct { +// TransactionProofResponse proof of transaction in a block. +type TransactionProofResponse struct { // Hashtype the type of hash function used to create the proof, must be one of: // * sha512_256 // * sha256 @@ -10,7 +10,7 @@ type ProofResponse struct { // Idx index of the transaction in the block's payset. Idx uint64 `json:"idx"` - // Proof merkle proof of transaction membership. + // Proof proof of transaction membership. Proof []byte `json:"proof"` // Stibhash hash of SignedTxnInBlock for verifying proof. diff --git a/client/v2/common/models/transaction_state_proof.go b/client/v2/common/models/transaction_state_proof.go new file mode 100644 index 00000000..419970e3 --- /dev/null +++ b/client/v2/common/models/transaction_state_proof.go @@ -0,0 +1,18 @@ +package models + +// TransactionStateProof fields for a state proof transaction. +// Definition: +// data/transactions/stateproof.go : StateProofTxnFields +type TransactionStateProof struct { + // Message (spmsg) + Message IndexerStateProofMessage `json:"message,omitempty"` + + // StateProof (sp) represents a state proof. + // Definition: + // crypto/stateproof/structs.go : StateProof + StateProof StateProofFields `json:"state-proof,omitempty"` + + // StateProofType (sptype) Type of the state proof. Integer representing an entry + // defined in protocol/stateproof.go + StateProofType uint64 `json:"state-proof-type,omitempty"` +} diff --git a/crypto/account.go b/crypto/account.go index 9fb368d5..4aead19e 100644 --- a/crypto/account.go +++ b/crypto/account.go @@ -189,6 +189,8 @@ type LogicSigAccount struct { // MakeLogicSigAccountEscrow creates a new escrow LogicSigAccount. The address // of this account will be a hash of its program. +// Deprecated: This method is deprecated for not applying basic sanity check over program bytes, +// use `MakeLogicSigAccountEscrowChecked` instead. func MakeLogicSigAccountEscrow(program []byte, args [][]byte) LogicSigAccount { return LogicSigAccount{ Lsig: types.LogicSig{ @@ -198,6 +200,16 @@ func MakeLogicSigAccountEscrow(program []byte, args [][]byte) LogicSigAccount { } } +// MakeLogicSigAccountEscrowChecked creates a new escrow LogicSigAccount. +// The address of this account will be a hash of its program. +func MakeLogicSigAccountEscrowChecked(program []byte, args [][]byte) (LogicSigAccount, error) { + lsig, err := MakeLogicSig(program, args, nil, MultisigAccount{}) + if err != nil { + return LogicSigAccount{}, err + } + return LogicSigAccount{Lsig: lsig}, nil +} + // MakeLogicSigAccountDelegated creates a new delegated LogicSigAccount. This // type of LogicSig has the authority to sign transactions on behalf of another // account, called the delegating account. If the delegating account is a diff --git a/crypto/crypto.go b/crypto/crypto.go index a47c7f55..a652dbd2 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -3,15 +3,16 @@ package crypto import ( "bytes" "crypto/rand" + "crypto/sha256" "crypto/sha512" "encoding/base32" + "encoding/base64" "encoding/binary" "fmt" "golang.org/x/crypto/ed25519" "github.com/algorand/go-algorand-sdk/encoding/msgpack" - "github.com/algorand/go-algorand-sdk/logic" "github.com/algorand/go-algorand-sdk/types" ) @@ -36,6 +37,12 @@ var programDataPrefix = []byte("ProgData") // appIDPrefix is prepended to application IDs in order to compute addresses var appIDPrefix = []byte("appID") +// StateProofMessagePrefix is prepended to the canonical msgpack encoded state proof message when computing its hash. +var StateProofMessagePrefix = []byte("spm") + +// LightBlockHeaderPrefix is prepended to the canonical msgpack encoded light block header when computing its vector commitment leaf. +var LightBlockHeaderPrefix = []byte("B256") + // RandomBytes fills the passed slice with randomness, and panics if it is // unable to do so func RandomBytes(s []byte) { @@ -160,7 +167,7 @@ func SignBytes(sk ed25519.PrivateKey, bytesToSign []byte) (signature []byte, err return } -//VerifyBytes verifies that the signature is valid +// VerifyBytes verifies that the signature is valid func VerifyBytes(pk ed25519.PublicKey, message, signature []byte) bool { msgParts := [][]byte{bytesPrefix, message} toBeVerified := bytes.Join(msgParts, nil) @@ -454,6 +461,39 @@ func ComputeGroupID(txgroup []types.Transaction) (gid types.Digest, err error) { /* LogicSig support */ +func isAsciiPrintableByte(symbol byte) bool { + isBreakLine := symbol == '\n' + isStdPrintable := symbol >= ' ' && symbol <= '~' + return isBreakLine || isStdPrintable +} + +func isAsciiPrintable(program []byte) bool { + for _, b := range program { + if !isAsciiPrintableByte(b) { + return false + } + } + return true +} + +// sanityCheckProgram performs heuristic program validation: +// check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes +func sanityCheckProgram(program []byte) error { + if len(program) == 0 { + return fmt.Errorf("empty program") + } + if isAsciiPrintable(program) { + if _, err := types.DecodeAddress(string(program)); err == nil { + return fmt.Errorf("requesting program bytes, get Algorand address") + } + if _, err := base64.StdEncoding.DecodeString(string(program)); err == nil { + return fmt.Errorf("program should not be b64 encoded") + } + return fmt.Errorf("program bytes are all ASCII printable characters, not looking like Teal byte code") + } + return nil +} + // VerifyLogicSig verifies that a LogicSig contains a valid program and, if a // delegated signature is present, that the signature is valid. // @@ -462,7 +502,7 @@ func ComputeGroupID(txgroup []types.Transaction) (gid types.Digest, err error) { // multsig account). In that case, it should be the address of the delegating // account. func VerifyLogicSig(lsig types.LogicSig, singleSigner types.Address) (result bool) { - if err := logic.CheckProgram(lsig.Logic, lsig.Args); err != nil { + if err := sanityCheckProgram(lsig.Logic); err != nil { return false } @@ -602,8 +642,9 @@ func AddressFromProgram(program []byte) types.Address { // MakeLogicSig produces a new LogicSig signature. // -// THIS FUNCTION IS DEPRECATED. It will be removed in v2 of this library. Use -// one of MakeLogicSigAccountEscrow, MakeLogicSigAccountDelegated, or +// Deprecated: THIS FUNCTION IS DEPRECATED. +// It will be removed in v2 of this library. +// Use one of MakeLogicSigAccountEscrow, MakeLogicSigAccountDelegated, or // MakeLogicSigAccountDelegatedMsig instead. // // The function can work in three modes: @@ -611,11 +652,7 @@ func AddressFromProgram(program []byte) types.Address { // 2. If no ma provides, it returns Sig delegated LogicSig // 3. If both sk and ma specified the function returns Multisig delegated LogicSig func MakeLogicSig(program []byte, args [][]byte, sk ed25519.PrivateKey, ma MultisigAccount) (lsig types.LogicSig, err error) { - if len(program) == 0 { - err = errLsigInvalidProgram - return - } - if err = logic.CheckProgram(program, args); err != nil { + if err = sanityCheckProgram(program); err != nil { return } @@ -726,3 +763,23 @@ func GetApplicationAddress(appID uint64) types.Address { hash := sha512.Sum512_256(toBeHashed) return types.Address(hash) } + +func HashStateProofMessage(stateProofMessage *types.Message) types.MessageHash { + msgPackedStateProofMessage := msgpack.Encode(stateProofMessage) + + stateProofMessageData := make([]byte, 0, len(StateProofMessagePrefix)+len(msgPackedStateProofMessage)) + stateProofMessageData = append(stateProofMessageData, StateProofMessagePrefix...) + stateProofMessageData = append(stateProofMessageData, msgPackedStateProofMessage...) + + return sha256.Sum256(stateProofMessageData) +} + +func HashLightBlockHeader(lightBlockHeader types.LightBlockHeader) types.Digest { + msgPackedLightBlockHeader := msgpack.Encode(lightBlockHeader) + + lightBlockHeaderData := make([]byte, 0, len(LightBlockHeaderPrefix)+len(msgPackedLightBlockHeader)) + lightBlockHeaderData = append(lightBlockHeaderData, LightBlockHeaderPrefix...) + lightBlockHeaderData = append(lightBlockHeaderData, msgpack.Encode(lightBlockHeader)...) + + return sha256.Sum256(lightBlockHeaderData) +} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index f7101fac..3da8677a 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -327,13 +327,6 @@ func TestMakeLogicSigBasic(t *testing.T) { err = msgpack.Decode(encoded, &lsig1) require.NoError(t, err) require.Equal(t, lsig, lsig1) - - // check invalid program fails - programMod := make([]byte, len(program)) - copy(programMod[:], program) - programMod[0] = 128 - lsig, err = MakeLogicSig(programMod, args, sk, pk) - require.Error(t, err) } func TestMakeLogicSigSingle(t *testing.T) { diff --git a/go.mod b/go.mod index ae2fc87c..1e6db569 100644 --- a/go.mod +++ b/go.mod @@ -13,9 +13,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 3fd56df2..f34f5ef3 100644 --- a/go.sum +++ b/go.sum @@ -13,11 +13,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -50,9 +45,7 @@ golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logic/logic.go b/logic/logic.go index d2dfcca6..1e98768c 100644 --- a/logic/logic.go +++ b/logic/logic.go @@ -10,12 +10,14 @@ import ( "github.com/algorand/go-algorand-sdk/types" ) +// Deprecated type langSpec struct { EvalMaxVersion int LogicSigVersion int Ops []operation } +// Deprecated type operation struct { Opcode int Name string @@ -29,16 +31,23 @@ type operation struct { Group []string } +// Deprecated var spec *langSpec + +// Deprecated var opcodes []operation // CheckProgram performs basic program validation: instruction count and program cost +// Deprecated: Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions. +// The behavior of `CheckProgram` relies on `langspec.json`. Thus, this method is being deprecated. func CheckProgram(program []byte, args [][]byte) error { _, _, err := ReadProgram(program, args) return err } // ReadProgram is used to validate a program as well as extract found variables +// Deprecated: Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions. +// The behavior of `ReadProgram` relies on `langspec.json`. Thus, this method is being deprecated. func ReadProgram(program []byte, args [][]byte) (ints []uint64, byteArrays [][]byte, err error) { const intcblockOpcode = 32 const bytecblockOpcode = 38 @@ -138,6 +147,7 @@ func ReadProgram(program []byte, args [][]byte) (ints []uint64, byteArrays [][]b return } +// Deprecated func readIntConstBlock(program []byte, pc int) (size int, ints []uint64, err error) { size = 1 numInts, bytesUsed := binary.Uvarint(program[pc+size:]) @@ -163,6 +173,7 @@ func readIntConstBlock(program []byte, pc int) (size int, ints []uint64, err err return } +// Deprecated func readByteConstBlock(program []byte, pc int) (size int, byteArrays [][]byte, err error) { size = 1 numInts, bytesUsed := binary.Uvarint(program[pc+size:]) @@ -195,6 +206,7 @@ func readByteConstBlock(program []byte, pc int) (size int, byteArrays [][]byte, return } +// Deprecated func readPushIntOp(program []byte, pc int) (size int, foundInt uint64, err error) { size = 1 foundInt, bytesUsed := binary.Uvarint(program[pc+size:]) @@ -207,6 +219,7 @@ func readPushIntOp(program []byte, pc int) (size int, foundInt uint64, err error return } +// Deprecated func readPushByteOp(program []byte, pc int) (size int, byteArray []byte, err error) { size = 1 itemLen, bytesUsed := binary.Uvarint(program[pc+size:]) diff --git a/logic/source_map.go b/logic/source_map.go index bd6e042f..0d798971 100644 --- a/logic/source_map.go +++ b/logic/source_map.go @@ -1,6 +1,7 @@ package logic import ( + "encoding/json" "fmt" "strings" ) @@ -19,42 +20,20 @@ type SourceMap struct { } func DecodeSourceMap(ism map[string]interface{}) (SourceMap, error) { - sm := SourceMap{} + var sm SourceMap - if v, ok := ism["version"]; ok { - sm.Version = int(v.(float64)) + buff, err := json.Marshal(ism) + if err != nil { + return sm, err } - if sm.Version != 3 { - return sm, fmt.Errorf("only version 3 is supported") - } - - if f, ok := ism["file"]; ok { - sm.File = f.(string) - } - - if sr, ok := ism["sourceRoot"]; ok { - sm.SourceRoot = sr.(string) - } - - if srcs, ok := ism["sources"]; ok { - srcSlice := srcs.([]interface{}) - sm.Sources = make([]string, len(srcSlice)) - for idx, s := range srcSlice { - sm.Sources[idx] = s.(string) - } + err = json.Unmarshal(buff, &sm) + if err != nil { + return sm, err } - if names, ok := ism["names"]; ok { - nameSlice := names.([]interface{}) - sm.Names = make([]string, len(nameSlice)) - for idx, n := range nameSlice { - sm.Names[idx] = n.(string) - } - } - - if m, ok := ism["mappings"]; ok { - sm.Mappings = m.(string) + if sm.Version != 3 { + return sm, fmt.Errorf("only version 3 is supported") } if sm.Mappings == "" { diff --git a/test-harness.sh b/test-harness.sh new file mode 100755 index 00000000..910562de --- /dev/null +++ b/test-harness.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +set -euo pipefail + +START=$(date "+%s") + +THIS=$(basename "$0") +ENV_FILE=".test-env" +TEST_DIR="test" + +set -a +source "$ENV_FILE" +set +a + +rootdir=$(dirname "$0") +pushd "$rootdir" + +echo "$THIS: VERBOSE_HARNESS=$VERBOSE_HARNESS" + +## Reset test harness +if [ -d "$SDK_TESTING_HARNESS" ]; then + pushd "$SDK_TESTING_HARNESS" + ./scripts/down.sh + popd + rm -rf "$SDK_TESTING_HARNESS" +else + echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" +fi + +git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" + +echo "$THIS: OVERWRITE_TESTING_ENVIRONMENT=$OVERWRITE_TESTING_ENVIRONMENT" +if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then + echo "$THIS: OVERWRITE replaced $SDK_TESTING_HARNESS/.env with $ENV_FILE:" + cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env +fi + +echo "$THIS: REMOVE_LOCAL_FEATURES=$REMOVE_LOCAL_FEATURES" +## Copy feature files into the project resources +if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then + echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features" + if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/features && echo "$THIS: see the previous for files deleted" ) || true + fi + rm -rf $TEST_DIR/features +fi +mkdir -p $TEST_DIR/features +cp -r "$SDK_TESTING_HARNESS"/features/* $TEST_DIR/features +if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/features && echo "$THIS: see the previous for files copied over" ) || true +fi +echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" + + +## Start test harness environment +pushd "$SDK_TESTING_HARNESS" + +[[ "$VERBOSE_HARNESS" = 1 ]] && V_FLAG="-v" || V_FLAG="" +echo "$THIS: standing up harnness with command [./up.sh $V_FLAG]" +./scripts/up.sh "$V_FLAG" + +popd +echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s" +echo "" +echo "--------------------------------------------------------------------------------" +echo "|" +echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox " +echo "|" +echo "--------------------------------------------------------------------------------" diff --git a/test/algodclientv2_test.go b/test/algodclientv2_test.go index db889d48..05c9a854 100644 --- a/test/algodclientv2_test.go +++ b/test/algodclientv2_test.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/algorand/go-algorand-sdk/client/v2/algod" - "github.com/algorand/go-algorand-sdk/client/v2/common/models" modelsV2 "github.com/algorand/go-algorand-sdk/client/v2/common/models" "github.com/algorand/go-algorand-sdk/types" @@ -20,7 +19,6 @@ func AlgodClientV2Context(s *godog.Suite) { s.Step(`^we make any Pending Transaction Information call$`, weMakeAnyPendingTransactionInformationCall) s.Step(`^the parsed Pending Transaction Information response should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse) s.Step(`^we make any Pending Transactions Information call$`, weMakeAnyPendingTransactionsInformationCall) - s.Step(`^the parsed Pending Transactions Information response should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse) s.Step(`^we make any Send Raw Transaction call$`, weMakeAnySendRawTransactionCall) s.Step(`^the parsed Send Raw Transaction response should have txid "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse) s.Step(`^we make any Pending Transactions By Address call$`, weMakeAnyPendingTransactionsByAddressCall) @@ -38,11 +36,8 @@ func AlgodClientV2Context(s *godog.Suite) { s.Step(`^we make any Suggested Transaction Parameters call$`, weMakeAnySuggestedTransactionParametersCall) s.Step(`^the parsed Suggested Transaction Parameters response should have first round valid of (\d+)$`, theParsedResponseShouldEqualTheMockResponse) s.Step(`^expect the path used to be "([^"]*)"$`, expectThePathUsedToBe) - s.Step(`^we make a Pending Transaction Information against txid "([^"]*)" with max (\d+)$`, weMakeAPendingTransactionInformationAgainstTxidWithMax) - s.Step(`^we make a Pending Transactions By Address call against account "([^"]*)" and max (\d+)$`, weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax) s.Step(`^we make a Status after Block call with round (\d+)$`, weMakeAStatusAfterBlockCallWithRound) s.Step(`^we make an Account Information call against account "([^"]*)"$`, weMakeAnAccountInformationCallAgainstAccount) - s.Step(`^we make a Get Block call against block number (\d+)$`, weMakeAGetBlockCallAgainstBlockNumber) s.Step(`^the parsed Pending Transactions Information response should contain an array of len (\d+) and element number (\d+) should have sender "([^"]*)"$`, theParsedResponseShouldEqualTheMockResponse) s.Step(`^we make a Pending Transaction Information against txid "([^"]*)" with format "([^"]*)"$`, weMakeAPendingTransactionInformationAgainstTxidWithFormat) s.Step(`^we make a Pending Transaction Information with max (\d+) and format "([^"]*)"$`, weMakeAPendingTransactionInformationWithMaxAndFormat) @@ -53,6 +48,8 @@ func AlgodClientV2Context(s *godog.Suite) { s.Step(`^we make an Account Information call against account "([^"]*)" with exclude "([^"]*)"$`, weMakeAnAccountInformationCallAgainstAccountWithExclude) s.Step(`^we make an Account Asset Information call against account "([^"]*)" assetID (\d+)$`, weMakeAnAccountAssetInformationCallAgainstAccountAssetID) s.Step(`^we make an Account Application Information call against account "([^"]*)" applicationID (\d+)$`, weMakeAnAccountApplicationInformationCallAgainstAccountApplicationID) + s.Step(`^we make a GetLightBlockHeaderProof call for round (\d+)$`, weMakeAGetLightBlockHeaderProofCallForRound) + s.Step(`^we make a GetStateProof call for round (\d+)$`, weMakeAGetStateProofCallForRound) s.BeforeScenario(func(interface{}) { globalErrForExamination = nil @@ -120,24 +117,6 @@ func weMakeAnySuggestedTransactionParametersCall() error { return weMakeAnyCallTo("algod", "TransactionParams") } -func weMakeAPendingTransactionInformationAgainstTxidWithMax(txid string, max int) error { - algodClient, err := algod.MakeClient(mockServer.URL, "") - if err != nil { - return err - } - _, _, globalErrForExamination = algodClient.PendingTransactionInformation(txid).Do(context.Background()) - return nil -} - -func weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax(account string, max int) error { - algodClient, err := algod.MakeClient(mockServer.URL, "") - if err != nil { - return err - } - _, _, globalErrForExamination = algodClient.PendingTransactionsByAddress(account).Max(uint64(max)).Do(context.Background()) - return nil -} - func weMakeAStatusAfterBlockCallWithRound(round int) error { algodClient, err := algod.MakeClient(mockServer.URL, "") if err != nil { @@ -156,22 +135,18 @@ func weMakeAnAccountInformationCallAgainstAccount(account string) error { return nil } -func weMakeAGetBlockCallAgainstBlockNumber(blocknum int) error { +func weMakeAPendingTransactionInformationAgainstTxidWithFormat(txid, format string) error { + if format != "msgpack" { + return fmt.Errorf("this sdk does not support format %s", format) + } algodClient, err := algod.MakeClient(mockServer.URL, "") if err != nil { return err } - _, globalErrForExamination = algodClient.Block(uint64(blocknum)).Do(context.Background()) + _, _, globalErrForExamination = algodClient.PendingTransactionInformation(txid).Do(context.Background()) return nil } -func weMakeAPendingTransactionInformationAgainstTxidWithFormat(txid, format string) error { - if format != "msgpack" { - return fmt.Errorf("this sdk does not support format %s", format) - } - return weMakeAPendingTransactionInformationAgainstTxidWithMax(txid, 0) -} - func weMakeAPendingTransactionInformationWithMaxAndFormat(max int, format string) error { if format != "msgpack" { return fmt.Errorf("this sdk does not support format %s", format) @@ -188,17 +163,27 @@ func weMakeAPendingTransactionsByAddressCallAgainstAccountAndMaxAndFormat(accoun if format != "msgpack" { return fmt.Errorf("this sdk does not support format %s", format) } - return weMakeAPendingTransactionsByAddressCallAgainstAccountAndMax(account, max) + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + _, _, globalErrForExamination = algodClient.PendingTransactionsByAddress(account).Max(uint64(max)).Do(context.Background()) + return nil } func weMakeAGetBlockCallAgainstBlockNumberWithFormat(blocknum int, format string) error { if format != "msgpack" { return fmt.Errorf("this sdk does not support format %s", format) } - return weMakeAGetBlockCallAgainstBlockNumber(blocknum) + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + _, globalErrForExamination = algodClient.Block(uint64(blocknum)).Do(context.Background()) + return nil } -var dryrunResponse models.DryrunResponse +var dryrunResponse modelsV2.DryrunResponse func weMakeAnyDryrunCall() (err error) { algodClient, err := algod.MakeClient(mockServer.URL, "") @@ -257,3 +242,21 @@ func weMakeAnAccountApplicationInformationCallAgainstAccountApplicationID(accoun algodClient.AccountApplicationInformation(account, uint64(appID)).Do(context.Background()) return nil } + +func weMakeAGetLightBlockHeaderProofCallForRound(round int) error { + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + algodClient.GetLightBlockHeaderProof(uint64(round)).Do(context.Background()) + return nil +} + +func weMakeAGetStateProofCallForRound(round int) error { + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + algodClient.GetStateProof(uint64(round)).Do(context.Background()) + return nil +} diff --git a/test/applications_integration_test.go b/test/applications_integration_test.go index 5215e69b..14f9ab06 100644 --- a/test/applications_integration_test.go +++ b/test/applications_integration_test.go @@ -12,7 +12,6 @@ import ( "regexp" "strconv" "strings" - "time" "github.com/cucumber/godog" @@ -211,7 +210,7 @@ func iBuildAnApplicationTransaction( } case "call": - tx, err = future.MakeApplicationCallTx(applicationId, args, accs, + tx, _ = future.MakeApplicationCallTx(applicationId, args, accs, fApp, fAssets, types.NoOpOC, approvalP, clearP, gSchema, lSchema, suggestedParams, transientAccount.Address, nil, types.Digest{}, [32]byte{}, types.Address{}) case "optin": @@ -448,124 +447,11 @@ func theTransientAccountShouldHave(appCreated string, byteSlices, uints int, } } if !found { - fmt.Errorf("Could not find key '%s'", key) + return fmt.Errorf("Could not find key '%s'", key) } return nil } -func theUnconfirmedPendingTransactionByIDShouldHaveNoApplyDataFields() error { - status, _, err := algodV2client.PendingTransactionInformation(txid).Do(context.Background()) - if err != nil { - return err - } - if status.ConfirmedRound == 0 { - if len(status.GlobalStateDelta) != 0 { - return fmt.Errorf("unexpected global state delta, there should be none: %v", status.GlobalStateDelta) - } - if len(status.LocalStateDelta) != 0 { - return fmt.Errorf("unexpected local state delta, there should be none: %v", status.LocalStateDelta) - } - } - return nil -} - -func getAccountDelta(addr string, data []models.AccountStateDelta) []models.EvalDeltaKeyValue { - for _, v := range data { - if v.Address == addr { - return v.Delta - } - } - return nil -} -func theConfirmedPendingTransactionByIDShouldHaveAStateChangeForToIndexerShouldAlsoConfirmThis(stateLocation, key, newValue string, indexer int) error { - status, _, err := algodV2client.PendingTransactionInformation(txid).Do(context.Background()) - if err != nil { - return err - } - - c1 := make(chan models.Transaction, 1) - - go func() { - for true { - indexerResponse, _ := indexerClients[indexer].SearchForTransactions().TXID(txid).Do(context.Background()) - if len(indexerResponse.Transactions) == 1 { - c1 <- indexerResponse.Transactions[0] - } - time.Sleep(time.Second) - } - }() - - var indexerTx models.Transaction - select { - case res := <-c1: - indexerTx = res - case <-time.After(5 * time.Second): - return fmt.Errorf("timeout waiting for indexer trasaction") - } - - var algodKeyValues []models.EvalDeltaKeyValue - var indexerKeyValues []models.EvalDeltaKeyValue - - switch stateLocation { - case "local": - addr := indexerTx.Sender - algodKeyValues = getAccountDelta(addr, status.LocalStateDelta) - indexerKeyValues = getAccountDelta(addr, indexerTx.LocalStateDelta) - case "global": - algodKeyValues = status.GlobalStateDelta - indexerKeyValues = indexerTx.GlobalStateDelta - default: - return fmt.Errorf("unknown location: " + stateLocation) - } - - // algod - if len(algodKeyValues) != 1 { - return fmt.Errorf("expected 1 key value, found: %d", len(algodKeyValues)) - } - if algodKeyValues[0].Key != key { - return fmt.Errorf("wrong key in algod: %s != %s", algodKeyValues[0].Key, key) - } - - // indexer - if len(indexerKeyValues) != 1 { - return fmt.Errorf("expected 1 key value, found: %d", len(indexerKeyValues)) - } - if indexerKeyValues[0].Key != key { - return fmt.Errorf("wrong key in indexer: %s != %s", indexerKeyValues[0].Key, key) - } - - if indexerKeyValues[0].Value.Action != algodKeyValues[0].Value.Action { - return fmt.Errorf("action mismatch between algod and indexer") - } - - switch algodKeyValues[0].Value.Action { - case uint64(1): - // bytes - if algodKeyValues[0].Value.Bytes != newValue { - return fmt.Errorf("algod value mismatch: %s != %s", algodKeyValues[0].Value.Bytes, newValue) - } - if indexerKeyValues[0].Value.Bytes != newValue { - return fmt.Errorf("indexer value mismatch: %s != %s", indexerKeyValues[0].Value.Bytes, newValue) - } - case uint64(2): - // int - newValueInt, err := strconv.ParseUint(newValue, 10, 64) - if err != nil { - return fmt.Errorf("problem parsing new int value: %s", newValue) - } - - if algodKeyValues[0].Value.Uint != newValueInt { - return fmt.Errorf("algod value mismatch: %d != %s", algodKeyValues[0].Value.Uint, newValue) - } - if indexerKeyValues[0].Value.Uint != newValueInt { - return fmt.Errorf("indexer value mismatch: %d != %s", indexerKeyValues[0].Value.Uint, newValue) - } - default: - return fmt.Errorf("unexpected action: %d", algodKeyValues[0].Value.Action) - } - - return nil -} func suggestedParamsAlgodV2() error { var err error @@ -883,8 +769,6 @@ func ApplicationsContext(s *godog.Suite) { s.Step(`^I get the account address for the current application and see that it matches the app id\'s hash$`, iGetTheAccountAddressForTheCurrentApp) s.Step(`^The transient account should have the created app "([^"]*)" and total schema byte-slices (\d+) and uints (\d+), the application "([^"]*)" state contains key "([^"]*)" with value "([^"]*)"$`, theTransientAccountShouldHave) - s.Step(`^the unconfirmed pending transaction by ID should have no apply data fields\.$`, theUnconfirmedPendingTransactionByIDShouldHaveNoApplyDataFields) - s.Step(`^the confirmed pending transaction by ID should have a "([^"]*)" state change for "([^"]*)" to "([^"]*)", indexer (\d+) should also confirm this\.$`, theConfirmedPendingTransactionByIDShouldHaveAStateChangeForToIndexerShouldAlsoConfirmThis) s.Step(`^suggested transaction parameters from the algod v2 client$`, suggestedParamsAlgodV2) s.Step(`^I add the current transaction with signer to the composer\.$`, iAddTheCurrentTransactionWithSignerToTheComposer) s.Step(`^I clone the composer\.$`, iCloneTheComposer) diff --git a/test/applications_unit_test.go b/test/applications_unit_test.go index e0b8e442..060a5af2 100644 --- a/test/applications_unit_test.go +++ b/test/applications_unit_test.go @@ -2,9 +2,7 @@ package test import ( "context" - "encoding/base64" "fmt" - "github.com/algorand/go-algorand-sdk/types" "github.com/cucumber/godog" @@ -37,24 +35,6 @@ func feeFieldNotInTxn() error { return nil } -func getSuggestedParams( - fee, fv, lv uint64, - gen, ghb64 string, - flat bool) (types.SuggestedParams, error) { - gh, err := base64.StdEncoding.DecodeString(ghb64) - if err != nil { - return types.SuggestedParams{}, err - } - return types.SuggestedParams{ - Fee: types.MicroAlgos(fee), - GenesisID: gen, - GenesisHash: gh, - FirstRoundValid: types.Round(fv), - LastRoundValid: types.Round(lv), - FlatFee: flat, - }, err -} - func weMakeAGetAssetByIDCall(assetID int) error { clt, err := algod.MakeClient(mockServer.URL, "") if err != nil { diff --git a/test/docker/run_docker.sh b/test/docker/run_docker.sh deleted file mode 100755 index 462a57fd..00000000 --- a/test/docker/run_docker.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -set -e - -# reset test harness -rm -rf test-harness -rm -rf test/features -git clone --single-branch --branch master https://github.com/algorand/algorand-sdk-testing.git test-harness -#copy feature files into project -mv test-harness/features test/features - -GO_VERSION=$(go version | cut -d' ' -f 3 | cut -d'.' -f 1,2) -GO_IMAGE=golang:${GO_VERSION:2}-stretch - -echo "Building docker image from base \"$GO_IMAGE\"" - -#build test environment -docker build -t go-sdk-testing --build-arg GO_IMAGE="$GO_IMAGE" -f test/docker/Dockerfile "$(pwd)" - -# Start test harness environment -./test-harness/scripts/up.sh -p - -docker run -it \ - --network host \ - go-sdk-testing:latest diff --git a/test/docker/sdk.py b/test/docker/sdk.py deleted file mode 100644 index 5b9ff84b..00000000 --- a/test/docker/sdk.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 - -import subprocess -import sys - -default_dirs = { - 'features_dir': '/opt/go/src/github.com/algorand/go-algorand-sdk/test/features', - 'source': '/opt/go/src/github.com/algorand/go-algorand-sdk', - 'docker': '/opt/go/src/github.com/algorand/go-algorand-sdk/test/docker', - 'test': '/opt/go/src/github.com/algorand/go-algorand-sdk/test' -} - -def setup_sdk(): - """ - Setup go cucumber environment. - """ - subprocess.check_call(['go generate %s/...' % default_dirs['source']], shell=True) - -def test_sdk(): - sys.stdout.flush() - subprocess.check_call(['go test'], shell=True, cwd=default_dirs['test']) \ No newline at end of file diff --git a/test/helpers.go b/test/helpers.go index 2519acf6..c9e36142 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -10,8 +10,6 @@ import ( "os" "path" "strings" - - "github.com/algorand/go-algorand-sdk/client/v2/common/models" ) func loadMockJsons(commaDelimitedFilenames, pathToJsons string) ([][]byte, error) { @@ -96,35 +94,6 @@ func expectErrorStringToContain(contains string) error { "actual error string: %s", contains, globalErrForExamination.Error()) } -func comparisonCheck(varname string, expected, actual interface{}) error { - if expected != actual { - return fmt.Errorf("expected %s value %v did not match actual value %v", varname, expected, actual) - } - return nil -} - -func findAssetInHoldingsList(list []models.AssetHolding, desiredId uint64) (models.AssetHolding, error) { - for _, holding := range list { - if holding.AssetId == desiredId { - return holding, nil - } - } - return models.AssetHolding{}, fmt.Errorf("could not find asset ID %d in passed list of asset holdings", desiredId) -} - -func getSigtypeFromTransaction(transaction models.Transaction) string { - if len(transaction.Signature.Sig) != 0 { - return "sig" - } - if len(transaction.Signature.Multisig.Subsignature) != 0 { - return "msig" - } - if len(transaction.Signature.Logicsig.MultisigSignature.Subsignature) != 0 || - len(transaction.Signature.Logicsig.Logic) != 0 { - return "lsig" - } - return "unknown sigtype" -} func loadResource(filepath string) ([]byte, error) { return ioutil.ReadFile(path.Join("features", "resources", filepath)) diff --git a/test/indexer_integration_test.go b/test/indexer_integration_test.go deleted file mode 100644 index caa593f5..00000000 --- a/test/indexer_integration_test.go +++ /dev/null @@ -1,674 +0,0 @@ -package test - -import ( - "context" - "encoding/base64" - "fmt" - "path" - "strconv" - "strings" - "time" - - "github.com/cucumber/godog" - - "github.com/algorand/go-algorand-sdk/client/v2/common/models" - "github.com/algorand/go-algorand-sdk/client/v2/indexer" - "github.com/algorand/go-algorand-sdk/encoding/json" -) - -func IndexerIntegrationTestContext(s *godog.Suite) { - s.Step(`^indexer client (\d+) at "([^"]*)" port (\d+) with token "([^"]*)"$`, indexerClientAtPortWithToken) - s.Step(`^I use (\d+) to check the services health$`, iUseToCheckTheServicesHealth) - s.Step(`^I receive status code (\d+)$`, iReceiveStatusCode) - s.Step(`^I use (\d+) to lookup block (\d+)$`, iUseToLookupBlock) - s.Step(`^The block was confirmed at (\d+), contains (\d+) transactions, has the previous block hash "([^"]*)"$`, theBlockWasConfirmedAtContainsTransactionsHasThePreviousBlockHash) - s.Step(`^I use (\d+) to lookup account "([^"]*)" at round (\d+)$`, iUseToLookupAccountAtRound) - s.Step(`^The account has (\d+) assets, the first is asset (\d+) has a frozen status of "([^"]*)" and amount (\d+)\.$`, theAccountHasAssetsTheFirstIsAssetHasAFrozenStatusOfAndAmount) - s.Step(`^The account created (\d+) assets, the first is asset (\d+) is named "([^"]*)" with a total amount of (\d+) "([^"]*)"$`, theAccountCreatedAssetsTheFirstIsAssetIsNamedWithATotalAmountOf) - s.Step(`^The account has (\d+) μalgos and (\d+) assets, (\d+) has (\d+)$`, theAccountHasAlgosAndAssetsHas) - s.Step(`^I use (\d+) to lookup asset (\d+)$`, iUseToLookupAsset) - s.Step(`^The asset found has: "([^"]*)", "([^"]*)", "([^"]*)", (\d+), "([^"]*)", (\d+), "([^"]*)"$`, theAssetFoundHas) - s.Step(`^I use (\d+) to lookup asset balances for (\d+) with (\d+), (\d+), (\d+) and token "([^"]*)"$`, iUseToLookupAssetBalancesForWithAndToken) - s.Step(`^There are (\d+) with the asset, the first is "([^"]*)" has "([^"]*)" and (\d+)$`, thereAreWithTheAssetTheFirstIsHasAnd) - s.Step(`^I get the next page using (\d+) to lookup asset balances for (\d+) with (\d+), (\d+), (\d+)$`, iGetTheNextPageUsingToLookupAssetBalancesForWith) - s.Step(`^I use (\d+) to search for an account with (\d+), (\d+), (\d+), (\d+) and token "([^"]*)"$`, iUseToSearchForAnAccountWithAndToken) - s.Step(`^There are (\d+), the first has (\d+), (\d+), (\d+), (\d+), "([^"]*)", (\d+), "([^"]*)", "([^"]*)"$`, thereAreTheFirstHas) - s.Step(`^The first account is online and has "([^"]*)", (\d+), (\d+), (\d+), "([^"]*)", "([^"]*)"$`, theFirstAccountIsOnlineAndHas) - s.Step(`^I get the next page using (\d+) to search for an account with (\d+), (\d+), (\d+) and (\d+)$`, iGetTheNextPageUsingToSearchForAnAccountWithAnd) - s.Step(`^I use (\d+) to search for transactions with (\d+), "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)", (\d+), (\d+), (\d+), (\d+), "([^"]*)", "([^"]*)", (\d+), (\d+), "([^"]*)", "([^"]*)", "([^"]*)" and token "([^"]*)"$`, iUseToSearchForTransactionsWithAndToken) - s.Step(`^there are (\d+) transactions in the response, the first is "([^"]*)"\.$`, thereAreTransactionsInTheResponseTheFirstIs) - s.Step(`^Every transaction has tx-type "([^"]*)"$`, everyTransactionHasTxtype) - s.Step(`^Every transaction has sig-type "([^"]*)"$`, everyTransactionHasSigtype) - s.Step(`^Every transaction has round (\d+)$`, everyTransactionHasRoundEqual) - s.Step(`^Every transaction has round >= (\d+)$`, everyTransactionHasRoundGreaterThan) - s.Step(`^Every transaction has round <= (\d+)$`, everyTransactionHasRoundLessThan) - s.Step(`^Every transaction works with asset-id (\d+)$`, everyTransactionWorksWithAssetid) - s.Step(`^Every transaction is older than "([^"]*)"$`, everyTransactionIsOlderThan) - s.Step(`^Every transaction is newer than "([^"]*)"$`, everyTransactionIsNewerThan) - s.Step(`^Every transaction moves between (\d+) and (\d+) currency$`, everyTransactionMovesBetweenAndCurrency) - s.Step(`^I use (\d+) to search for all "([^"]*)" transactions$`, iUseToSearchForAllTransactions) - s.Step(`^I use (\d+) to search for all (\d+) asset transactions$`, iUseToSearchForAllAssetTransactions) - s.Step(`^I get the next page using (\d+) to search for transactions with (\d+) and (\d+)$`, iGetTheNextPageUsingToSearchForTransactionsWithAnd) - s.Step(`^I use (\d+) to search for assets with (\d+), (\d+), "([^"]*)", "([^"]*)", "([^"]*)", and token "([^"]*)"$`, iUseToSearchForAssetsWithAndToken) - s.Step(`^there are (\d+) assets in the response, the first is (\d+)\.$`, thereAreAssetsInTheResponseTheFirstIs) - - //@indexer.applications - s.Step(`^I use (\d+) to search for applications with (\d+), (\d+), and token "([^"]*)"$`, deprecatedIUseToSearchForApplicationsWithAndToken) - s.Step(`^the parsed response should equal "([^"]*)"\.$`, theParsedResponseShouldEqual) - s.Step(`^I use (\d+) to lookup application with (\d+)$`, deprecatedIUseToLookupApplicationWith) - s.Step(`^I use (\d+) to search for transactions with (\d+), "([^"]*)", "([^"]*)", "([^"]*)", "([^"]*)", (\d+), (\d+), (\d+), (\d+), "([^"]*)", "([^"]*)", (\d+), (\d+), "([^"]*)", "([^"]*)", "([^"]*)", (\d+) and token "([^"]*)"$`, iUseToSearchForTransactionsWithAppIdAndToken) - s.Step(`^I use (\d+) to search for an account with (\d+), (\d+), (\d+), (\d+), "([^"]*)", (\d+) and token "([^"]*)"$`, deprecatedIUseToSearchForAnAccountWithAppIdAndToken) - - //@indexer.231 - s.Step(`^I use (\d+) to search for applications with (\d+), (\d+), "([^"]*)" and token "([^"]*)"$`, iUseToSearchForApplicationsWithAndToken) - s.Step(`^I use (\d+) to lookup application with (\d+) and "([^"]*)"$`, iUseToLookupApplicationWithAnd) - s.Step(`^I use (\d+) to search for an account with (\d+), (\d+), (\d+), (\d+), "([^"]*)", (\d+), "([^"]*)" and token "([^"]*)"$`, iUseToSearchForAnAccount) - - s.BeforeScenario(func(interface{}) { - }) -} - -var indexerClients = make(map[int]*indexer.Client) - -func indexerClientAtPortWithToken(clientNum int, host string, port int, token string) error { - portAsString := strconv.Itoa(port) - fullHost := "http://" + host + ":" + portAsString - ic, err := indexer.MakeClient(fullHost, token) - indexerClients[clientNum] = ic - return err -} - -var indexerHealthCheckResponse models.HealthCheckResponse -var indexerHealthCheckError error - -func iUseToCheckTheServicesHealth(clientNum int) error { - ic := indexerClients[clientNum] - indexerHealthCheckResponse, indexerHealthCheckError = ic.HealthCheck().Do(context.Background()) - return nil -} - -func iReceiveStatusCode(code int) error { - if code == 200 && indexerHealthCheckError == nil { - return nil - } - return fmt.Errorf("Did not receive expected error code: %v", indexerHealthCheckError) -} - -var indexerBlockResponse models.Block - -func iUseToLookupBlock(clientNum, blockNum int) error { - ic := indexerClients[clientNum] - var err error - indexerBlockResponse, err = ic.LookupBlock(uint64(blockNum)).Do(context.Background()) - return err -} - -func theBlockWasConfirmedAtContainsTransactionsHasThePreviousBlockHash(timestamp, numTransactions int, prevBlockHash string) error { - err := comparisonCheck("timestamp", uint64(timestamp), indexerBlockResponse.Timestamp) - if err != nil { - return err - } - err = comparisonCheck("number of transactions", numTransactions, len(indexerBlockResponse.Transactions)) - if err != nil { - return err - } - err = comparisonCheck("previous blockhash", prevBlockHash, base64.StdEncoding.EncodeToString(indexerBlockResponse.PreviousBlockHash)) - return err -} - -var indexerAccountResponse models.Account - -func iUseToLookupAccountAtRound(clientNum int, account string, round int) error { - ic := indexerClients[clientNum] - var err error - _, indexerAccountResponse, err = ic.LookupAccountByID(account).Round(uint64(round)).Do(context.Background()) - return err -} - -func theAccountHasAssetsTheFirstIsAssetHasAFrozenStatusOfAndAmount(numAssets, firstAssetIndex int, firstAssetFrozenStatus string, firstAssetAmount int) error { - err := comparisonCheck("number of asset holdings", numAssets, len(indexerAccountResponse.Assets)) - if err != nil { - return err - } - assetUnderScrutiny := indexerAccountResponse.Assets[0] - err = comparisonCheck("first asset index", uint64(firstAssetIndex), assetUnderScrutiny.AssetId) - if err != nil { - return err - } - frozen, err := strconv.ParseBool(firstAssetFrozenStatus) - if err != nil { - frozen = false - } - err = comparisonCheck("first asset frozen status", frozen, assetUnderScrutiny.IsFrozen) - if err != nil { - return err - } - err = comparisonCheck("first asset amount", uint64(firstAssetAmount), assetUnderScrutiny.Amount) - return err -} - -func theAccountCreatedAssetsTheFirstIsAssetIsNamedWithATotalAmountOf(numCreatedAssets, firstAssetIndex int, assetName string, assetIssuance int, assetUnitName string) error { - err := comparisonCheck("number of created assets", numCreatedAssets, len(indexerAccountResponse.CreatedAssets)) - if err != nil { - return err - } - assetUnderScrutiny := indexerAccountResponse.CreatedAssets[0] - err = comparisonCheck("first created asset index", uint64(firstAssetIndex), assetUnderScrutiny.Index) - if err != nil { - return err - } - err = comparisonCheck("first created asset name", assetName, assetUnderScrutiny.Params.Name) - if err != nil { - return err - } - err = comparisonCheck("first created asset issuance", uint64(assetIssuance), assetUnderScrutiny.Params.Total) - if err != nil { - return err - } - err = comparisonCheck("first created asset unit name", assetUnitName, assetUnderScrutiny.Params.UnitName) - return err -} - -func theAccountHasAlgosAndAssetsHas(microAlgos, numAssets, assetIndex, assetAmount int) error { - err := comparisonCheck("microalgo balance", uint64(microAlgos), indexerAccountResponse.Amount) - if err != nil { - return err - } - err = comparisonCheck("number of asset holdings", numAssets, len(indexerAccountResponse.Assets)) - if err != nil { - return err - } - if numAssets == 0 || assetIndex == 0 { - return nil - } - assetUnderScrutiny, err := findAssetInHoldingsList(indexerAccountResponse.Assets, uint64(assetIndex)) - if err != nil { - return err - } - err = comparisonCheck(fmt.Sprintf("amount for asset %d", uint64(assetIndex)), uint64(assetAmount), assetUnderScrutiny.Amount) - return err -} - -var indexerAssetResponse models.Asset - -func iUseToLookupAsset(clientNum, assetId int) error { - ic := indexerClients[clientNum] - var err error - _, indexerAssetResponse, err = ic.LookupAssetByID(uint64(assetId)).Do(context.Background()) - return err -} - -func theAssetFoundHas(assetName, assetUnits, assetCreator string, assetDecimals int, assetDefaultFrozen string, assetIssuance int, assetClawbackAddress string) error { - err := comparisonCheck("asset name", assetName, indexerAssetResponse.Params.Name) - if err != nil { - return err - } - err = comparisonCheck("asset units", assetUnits, indexerAssetResponse.Params.UnitName) - if err != nil { - return err - } - err = comparisonCheck("asset creator", assetCreator, indexerAssetResponse.Params.Creator) - if err != nil { - return err - } - err = comparisonCheck("asset decimals", uint64(assetDecimals), indexerAssetResponse.Params.Decimals) - if err != nil { - return err - } - frozen, err := strconv.ParseBool(assetDefaultFrozen) - if err != nil { - frozen = false - } - err = comparisonCheck("asset default frozen state", frozen, indexerAssetResponse.Params.DefaultFrozen) - if err != nil { - return err - } - err = comparisonCheck("asset issuance", uint64(assetIssuance), indexerAssetResponse.Params.Total) - if err != nil { - return err - } - err = comparisonCheck("asset clawback address", assetClawbackAddress, indexerAssetResponse.Params.Clawback) - return err -} - -var indexerAssetBalancesResponse models.AssetBalancesResponse - -func iUseToLookupAssetBalancesForWithAndToken(clientNum, assetId, currencyGreater, currencyLesser, limit int, token string) error { - ic := indexerClients[clientNum] - var err error - indexerAssetBalancesResponse, err = ic.LookupAssetBalances(uint64(assetId)). - CurrencyGreaterThan(uint64(currencyGreater)). - CurrencyLessThan(uint64(currencyLesser)). - Limit(uint64(limit)). - NextToken(token). - Do(context.Background()) - return err -} - -func thereAreWithTheAssetTheFirstIsHasAnd(numAccountsWithAsset int, firstHolder, firstHolderIsFrozen string, firstHolderAmount int) error { - err := comparisonCheck("number of asset holders", numAccountsWithAsset, len(indexerAssetBalancesResponse.Balances)) - if err != nil { - return err - } - if numAccountsWithAsset == 0 { - return nil - } - accountUnderScrutiny := indexerAssetBalancesResponse.Balances[0] - err = comparisonCheck("first account holder", firstHolder, accountUnderScrutiny.Address) - if err != nil { - return err - } - frozen, err := strconv.ParseBool(firstHolderIsFrozen) - if err != nil { - frozen = false - } - err = comparisonCheck("first account holder frozen state", frozen, accountUnderScrutiny.IsFrozen) - if err != nil { - return err - } - err = comparisonCheck("first account holder asset balance", uint64(firstHolderAmount), accountUnderScrutiny.Amount) - return err -} - -func iGetTheNextPageUsingToLookupAssetBalancesForWith(clientNum, assetId, currencyGreater, currencyLesser, limit int) error { - ic := indexerClients[clientNum] - var err error - indexerAssetBalancesResponse, err = ic.LookupAssetBalances(uint64(assetId)).CurrencyGreaterThan(uint64(currencyGreater)).CurrencyLessThan(uint64(currencyLesser)).Limit(uint64(limit)).NextToken(indexerAssetBalancesResponse.NextToken).Do(context.Background()) - return err -} - -var indexerSearchAccountsResponse models.AccountsResponse - -// Saves the response in a separate type so that we can capture the next token. -func iUseToSearchForAnAccountWithAndToken(clientNum, assetIndex, limit, currencyGreater, currencyLesser int, token string) error { - ic := indexerClients[clientNum] - var err error - query := ic.SearchAccounts(). - AssetID(uint64(assetIndex)). - Limit(uint64(limit)). - CurrencyGreaterThan(uint64(currencyGreater)). - CurrencyLessThan(uint64(currencyLesser)). - NextToken(token) - indexerSearchAccountsResponse, err = query.Do(context.Background()) - return err -} - -// Pass the captured next token into the SearchAccounts function. -func iGetTheNextPageUsingToSearchForAnAccountWithAnd(clientNum, assetId, limit, currencyGreater, currencyLesser int) error { - next := indexerSearchAccountsResponse.NextToken - return iUseToSearchForAnAccountWithAndToken(clientNum, assetId, limit, currencyGreater, currencyLesser, next) -} - -func thereAreTheFirstHas(numAccounts, firstAccountPendingRewards, rewardsBase, rewards, withoutRewards int, address string, amount int, accountStatus, accountType string) error { - err := comparisonCheck("number of found accounts", numAccounts, len(indexerSearchAccountsResponse.Accounts)) - if err != nil { - return err - } - if numAccounts == 0 { - return nil - } - accountUnderScrutiny := indexerSearchAccountsResponse.Accounts[0] - err = comparisonCheck("first account pending rewards", uint64(firstAccountPendingRewards), accountUnderScrutiny.PendingRewards) - if err != nil { - return err - } - err = comparisonCheck("first account rewards base", uint64(rewardsBase), accountUnderScrutiny.RewardBase) - if err != nil { - return err - } - err = comparisonCheck("first account rewards", uint64(rewards), accountUnderScrutiny.Rewards) - if err != nil { - return err - } - err = comparisonCheck("first account without-rewards", uint64(withoutRewards), accountUnderScrutiny.AmountWithoutPendingRewards) - if err != nil { - return err - } - err = comparisonCheck("first account address", address, accountUnderScrutiny.Address) - if err != nil { - return err - } - err = comparisonCheck("first account balance", uint64(amount), accountUnderScrutiny.Amount) - if err != nil { - return err - } - err = comparisonCheck("first account status", accountStatus, accountUnderScrutiny.Status) - if err != nil { - return err - } - err = comparisonCheck("first account type", accountType, accountUnderScrutiny.SigType) - return err -} - -func theFirstAccountIsOnlineAndHas(address string, keyDilution, firstValid, lastValid int, voteKey, selKey string) error { - accountUnderScrutiny := indexerSearchAccountsResponse.Accounts[0] - err := comparisonCheck("first account online state", "Online", accountUnderScrutiny.Status) - if err != nil { - return err - } - err = comparisonCheck("first account address", address, accountUnderScrutiny.Address) - if err != nil { - return err - } - err = comparisonCheck("first account key dilution", uint64(keyDilution), accountUnderScrutiny.Participation.VoteKeyDilution) - if err != nil { - return err - } - err = comparisonCheck("first account partkey firstvalid", uint64(firstValid), accountUnderScrutiny.Participation.VoteFirstValid) - if err != nil { - return err - } - err = comparisonCheck("first account partkey lastvalid", uint64(lastValid), accountUnderScrutiny.Participation.VoteLastValid) - if err != nil { - return err - } - voteKeyString := base64.StdEncoding.EncodeToString(accountUnderScrutiny.Participation.VoteParticipationKey) - selKeyString := base64.StdEncoding.EncodeToString(accountUnderScrutiny.Participation.SelectionParticipationKey) - err = comparisonCheck("first account votekey b64", voteKey, voteKeyString) - if err != nil { - return err - } - err = comparisonCheck("first account selkey b64", selKey, selKeyString) - return err -} - -var indexerTransactionsResponse models.TransactionsResponse - -func iUseToSearchForTransactionsWithAndToken(clientNum, limit int, notePrefix, txType, sigType, txid string, round, minRound, maxRound, assetId int, beforeTime, afterTime string, currencyGreater, currencyLesser int, address, addressRole, excludeCloseTo, token string) error { - ic := indexerClients[clientNum] - var err error - notePrefixBytes, err := base64.StdEncoding.DecodeString(notePrefix) - if err != nil { - return err - } - excludeBool, err := strconv.ParseBool(excludeCloseTo) - if err != nil { - excludeBool = false - } - indexerTransactionsResponse, err = ic.SearchForTransactions().Limit(uint64(limit)).NotePrefix(notePrefixBytes).TxType(txType).SigType(sigType).TXID(txid).Round(uint64(round)).MinRound(uint64(minRound)).MaxRound(uint64(maxRound)).AssetID(uint64(assetId)).BeforeTimeString(beforeTime).AfterTimeString(afterTime).CurrencyGreaterThan(uint64(currencyGreater)).CurrencyLessThan(uint64(currencyLesser)).AddressString(address).AddressRole(addressRole).ExcludeCloseTo(excludeBool).NextToken(token).Do(context.Background()) - return err -} - -func thereAreTransactionsInTheResponseTheFirstIs(numTransactions int, firstTransactionTxid string) error { - err := comparisonCheck("number of transactions", numTransactions, len(indexerTransactionsResponse.Transactions)) - if err != nil { - return err - } - if numTransactions == 0 { - return nil - } - txnUnderScrutiny := indexerTransactionsResponse.Transactions[0] - err = comparisonCheck("first txn txid", firstTransactionTxid, txnUnderScrutiny.Id) - return err -} - -func everyTransactionHasTxtype(txType string) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - err := comparisonCheck(fmt.Sprintf("tx type for returned transaction %d", idx), txType, txn.Type) - if err != nil { - return err - } - } - return nil -} - -func everyTransactionHasSigtype(sigType string) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - err := comparisonCheck(fmt.Sprintf("sig type for returned transaction %d", idx), sigType, getSigtypeFromTransaction(txn)) - if err != nil { - return err - } - } - return nil -} - -func everyTransactionHasRoundEqual(round int) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - err := comparisonCheck(fmt.Sprintf("confirmed round for returned transaction %d", idx), uint64(round), txn.ConfirmedRound) - if err != nil { - return err - } - } - return nil -} - -func everyTransactionHasRoundGreaterThan(round int) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - if txn.ConfirmedRound < uint64(round) { - return fmt.Errorf("transaction number %d was confirmed in round %d, but expected a number higher than %d", idx, txn.ConfirmedRound, uint64(round)) - } - } - return nil -} - -func everyTransactionHasRoundLessThan(round int) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - if txn.ConfirmedRound > uint64(round) { - return fmt.Errorf("transaction number %d was confirmed in round %d, but expected a number lower than %d", idx, txn.ConfirmedRound, uint64(round)) - } - } - return nil -} - -func everyTransactionWorksWithAssetid(assetId int) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - actualId := uint64(0) - if txn.AssetTransferTransaction.AssetId != 0 { - actualId = txn.AssetTransferTransaction.AssetId - } - if txn.AssetFreezeTransaction.AssetId != 0 { - actualId = txn.AssetFreezeTransaction.AssetId - } - if txn.AssetConfigTransaction.AssetId != 0 { - actualId = txn.AssetConfigTransaction.AssetId - } - if txn.CreatedAssetIndex != 0 { - actualId = txn.CreatedAssetIndex - } - err := comparisonCheck(fmt.Sprintf("asset id for returned transaction %d", idx), uint64(assetId), actualId) - if err != nil { - return err - } - } - return nil -} - -func everyTransactionIsOlderThan(olderThan string) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - olderThanDateTime, err := time.Parse(time.RFC3339, olderThan) - if err != nil { - return err - } - olderThanUnixTime := uint64(olderThanDateTime.Unix()) - if txn.RoundTime > olderThanUnixTime { - return fmt.Errorf("txn number %d has round time of %d, but expected it to be older than unix time %d (RFC format: %s)", idx, txn.RoundTime, olderThanUnixTime, olderThan) - } - } - return nil -} - -func everyTransactionIsNewerThan(newerThan string) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - newerThanDateTime, err := time.Parse(time.RFC3339, newerThan) - if err != nil { - return err - } - newerThanUnixTime := uint64(newerThanDateTime.Unix()) - if txn.RoundTime < newerThanUnixTime { - return fmt.Errorf("txn number %d has round time of %d, but expected it to be newer than unix time %d (RFC format: %s)", idx, txn.RoundTime, newerThanUnixTime, newerThan) - } - } - return nil -} - -func everyTransactionMovesBetweenAndCurrency(currencyGreater, currencyLesser int) error { - for idx, txn := range indexerTransactionsResponse.Transactions { - if txn.Type == "pay" { - if txn.PaymentTransaction.Amount < uint64(currencyGreater) { - return fmt.Errorf("txn number %d moved %d microAlgos, but expected it to move more than %d", idx, txn.PaymentTransaction.Amount, currencyGreater) - } - if currencyLesser != 0 && txn.PaymentTransaction.Amount > uint64(currencyLesser) { - return fmt.Errorf("txn number %d moved %d microAlgos, but expected it to move less than %d", idx, txn.PaymentTransaction.Amount, currencyLesser) - } - } - if txn.Type == "axfer" { - if txn.AssetTransferTransaction.Amount < uint64(currencyGreater) { - return fmt.Errorf("txn number %d moved %d asset units, but expected it to move more than %d", idx, txn.AssetTransferTransaction.Amount, currencyGreater) - } - if currencyLesser != 0 && txn.AssetTransferTransaction.Amount > uint64(currencyLesser) { - return fmt.Errorf("txn number %d moved %d asset units, but expected it to move less than %d", idx, txn.AssetTransferTransaction.Amount, currencyLesser) - } - } - } - return nil -} - -func iUseToSearchForAllTransactions(clientNum int, account string) error { - ic := indexerClients[clientNum] - var err error - indexerTransactionsResponse, err = ic.LookupAccountTransactions(account).Do(context.Background()) - return err -} - -func iUseToSearchForAllAssetTransactions(clientNum, assetId int) error { - ic := indexerClients[clientNum] - var err error - indexerTransactionsResponse, err = ic.LookupAssetTransactions(uint64(assetId)).Do(context.Background()) - return err -} - -func iGetTheNextPageUsingToSearchForTransactionsWithAnd(clientNum, limit, maxRound int) error { - ic := indexerClients[clientNum] - var err error - indexerTransactionsResponse, err = ic.SearchForTransactions().Limit(uint64(limit)).MaxRound(uint64(maxRound)).NextToken(indexerTransactionsResponse.NextToken).Do(context.Background()) - return err -} - -var indexerSearchForAssetsResponse []models.Asset - -func iUseToSearchForAssetsWithAndToken(clientNum, zero, assetId int, creator, name, unit, token string) error { - ic := indexerClients[clientNum] - resp, err := ic.SearchForAssets().AssetID(uint64(assetId)).Creator(creator).Name(name).Unit(unit).NextToken(token).Do(context.Background()) - indexerSearchForAssetsResponse = resp.Assets - return err -} - -func thereAreAssetsInTheResponseTheFirstIs(numAssetsInResponse, assetIdFirstAsset int) error { - err := comparisonCheck("number assets in search for assets response", numAssetsInResponse, len(indexerSearchForAssetsResponse)) - if err != nil { - return err - } - if numAssetsInResponse == 0 { - return nil - } - assetUnderScrutiny := indexerSearchForAssetsResponse[0] - err = comparisonCheck("first asset's ID", uint64(assetIdFirstAsset), assetUnderScrutiny.Index) - return err -} - -// @indexer.applications -func deprecatedIUseToSearchForApplicationsWithAndToken(indexer, limit, appId int, token string) error { - return iUseToSearchForApplicationsWithAndToken(indexer, limit, appId, "false", token) -} - -func iUseToSearchForApplicationsWithAndToken(indexer, limit, appId int, includeAll, token string) error { - ic := indexerClients[indexer] - var err error - query := ic.SearchForApplications(). - ApplicationId(uint64(appId)). - Limit(uint64(limit)). - Next(token) - if ia, err := strconv.ParseBool(includeAll); err == nil && ia { - query.IncludeAll(ia) - } - response, err = query.Do(context.Background()) - return err -} - -func theParsedResponseShouldEqual(jsonfileName string) error { - var responseJson string - - baselinePath := path.Join("./features/resources/", jsonfileName) - if responseStr, ok := response.(string); ok { - responseJson = responseStr - } else { - responseJson = string(json.Encode(response)) - } - - return VerifyResponse(baselinePath, responseJson) -} - -func deprecatedIUseToLookupApplicationWith(indexer, appid int) error { - return iUseToLookupApplicationWithAnd(indexer, appid, "false") -} - -func iUseToLookupApplicationWithAnd(indexer, appid int, includeAll string) error { - ic := indexerClients[indexer] - var err error - query := ic.LookupApplicationByID(uint64(appid)) - if ia, err := strconv.ParseBool(includeAll); err == nil && ia { - query.IncludeAll(ia) - } - response, err = query.Do(context.Background()) - // allow 404... - if err != nil && strings.HasPrefix(err.Error(), "HTTP") { - err = nil - } - return nil -} - -func iUseToSearchForTransactionsWithAppIdAndToken( - indexer, limit int, notePrefix, txType, sigType, txId string, round, - minRound, maxRound, assetId int, beforeTime, afterTime string, - currencyGt, currencyLt int, address, addressRole, excludeCloseTo string, appId int, token string) error { - ic := indexerClients[indexer] - var err error - notePrefixBytes, err := base64.StdEncoding.DecodeString(notePrefix) - if err != nil { - return err - } - excludeBool, err := strconv.ParseBool(excludeCloseTo) - if err != nil { - excludeBool = false - } - response, err = ic.SearchForTransactions(). - ApplicationId(uint64(appId)). - Limit(uint64(limit)). - NotePrefix(notePrefixBytes). - TxType(txType).SigType(sigType). - TXID(txId).Round(uint64(round)). - MinRound(uint64(minRound)). - MaxRound(uint64(maxRound)). - AssetID(uint64(assetId)). - BeforeTimeString(beforeTime). - AfterTimeString(afterTime). - CurrencyGreaterThan(uint64(currencyGt)). - CurrencyLessThan(uint64(currencyLt)). - AddressString(address). - AddressRole(addressRole). - ExcludeCloseTo(excludeBool). - NextToken(token).Do(context.Background()) - return err -} - -func deprecatedIUseToSearchForAnAccountWithAppIdAndToken(indexer, assetIndex, limit, currencyGreater, currencyLesser int, authAddr string, appId int, token string) error { - return iUseToSearchForAnAccount(indexer, assetIndex, limit, currencyGreater, currencyLesser, authAddr, appId, "false", token) -} - -func iUseToSearchForAnAccount(clientNum, assetIndex, limit, currencyGreater, currencyLesser int, authAddr string, applicationId int, includeAll, token string) error { - ic := indexerClients[clientNum] - var err error - query := ic.SearchAccounts(). - AssetID(uint64(assetIndex)). - Limit(uint64(limit)). - CurrencyGreaterThan(uint64(currencyGreater)). - CurrencyLessThan(uint64(currencyLesser)). - AuthAddress(authAddr). - ApplicationId(uint64(applicationId)). - NextToken(token) - if ia, err := strconv.ParseBool(includeAll); err == nil && ia { - query.IncludeAll(ia) - } - response, err = query.Do(context.Background()) - return err -} diff --git a/test/indexer_unit_test.go b/test/indexer_unit_test.go index 1aa033f9..47e6af97 100644 --- a/test/indexer_unit_test.go +++ b/test/indexer_unit_test.go @@ -36,8 +36,6 @@ func IndexerUnitTestContext(s *godog.Suite) { s.Step(`^we make a Lookup Block call against round (\d+)$`, weMakeALookupBlockCallAgainstRound) s.Step(`^we make a Lookup Account by ID call against account "([^"]*)" with round (\d+)$`, weMakeALookupAccountByIDCallAgainstAccountWithRound) s.Step(`^we make a Lookup Asset by ID call against asset index (\d+)$`, weMakeALookupAssetByIDCallAgainstAssetIndex) - s.Step(`^we make a Search For Transactions call with account "([^"]*)" NotePrefix "([^"]*)" TxType "([^"]*)" SigType "([^"]*)" txid "([^"]*)" round (\d+) minRound (\d+) maxRound (\d+) limit (\d+) beforeTime (\d+) afterTime (\d+) currencyGreaterThan (\d+) currencyLessThan (\d+) assetIndex (\d+) addressRole "([^"]*)" ExcluseCloseTo "([^"]*)"$`, weMakeASearchForTransactionsCallWithAccountNotePrefixTxTypeSigTypeTxidRoundMinRoundMaxRoundLimitBeforeTimeAfterTimeCurrencyGreaterThanCurrencyLessThanAssetIndexAddressRoleExcluseCloseTo) - s.Step(`^we make a SearchForAssets call with limit (\d+) creator "([^"]*)" name "([^"]*)" unit "([^"]*)" index (\d+) and afterAsset (\d+)$`, weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndexAndAfterAsset) s.Step(`^mock server recording request paths`, mockServerRecordingRequestPaths) s.Step(`^we make a Search For Transactions call with account "([^"]*)" NotePrefix "([^"]*)" TxType "([^"]*)" SigType "([^"]*)" txid "([^"]*)" round (\d+) minRound (\d+) maxRound (\d+) limit (\d+) beforeTime "([^"]*)" afterTime "([^"]*)" currencyGreaterThan (\d+) currencyLessThan (\d+) assetIndex (\d+) addressRole "([^"]*)" ExcluseCloseTo "([^"]*)"$`, weMakeASearchForTransactionsCallWithAccountNotePrefixTxTypeSigTypeTxidRoundMinRoundMaxRoundLimitBeforeTimeAfterTimeCurrencyGreaterThanCurrencyLessThanAssetIndexAddressRoleExcluseCloseTo) s.Step(`^we make a SearchForAssets call with limit (\d+) creator "([^"]*)" name "([^"]*)" unit "([^"]*)" index (\d+)$`, weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndex) @@ -240,28 +238,24 @@ func weMakeASearchForTransactionsCallWithAccountNotePrefixTxTypeSigTypeTxidRound return nil } -func weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndexAndAfterAsset(limit int, creator, name, unit string, assetIndex, _ int) error { +func weMakeASearchAccountsCallWithAssetIDLimitCurrencyGreaterThanCurrencyLessThanAndRound(assetID, limit, currencyGreater, currencyLesser, round int) error { indexerClient, err := indexer.MakeClient(mockServer.URL, "") if err != nil { return err } - _, globalErrForExamination = indexerClient.SearchForAssets().AssetID(uint64(assetIndex)).Limit(uint64(limit)).Creator(creator).Name(name).Unit(unit).Do(context.Background()) + _, globalErrForExamination = indexerClient.SearchAccounts().AssetID(uint64(assetID)).Limit(uint64(limit)).CurrencyLessThan(uint64(currencyLesser)).CurrencyGreaterThan(uint64(currencyGreater)).Round(uint64(round)).Do(context.Background()) return nil } -func weMakeASearchAccountsCallWithAssetIDLimitCurrencyGreaterThanCurrencyLessThanAndRound(assetID, limit, currencyGreater, currencyLesser, round int) error { +func weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndex(limit int, creator, name, unit string, index int) error { indexerClient, err := indexer.MakeClient(mockServer.URL, "") if err != nil { return err } - _, globalErrForExamination = indexerClient.SearchAccounts().AssetID(uint64(assetID)).Limit(uint64(limit)).CurrencyLessThan(uint64(currencyLesser)).CurrencyGreaterThan(uint64(currencyGreater)).Round(uint64(round)).Do(context.Background()) + _, globalErrForExamination = indexerClient.SearchForAssets().AssetID(uint64(index)).Limit(uint64(limit)).Creator(creator).Name(name).Unit(unit).Do(context.Background()) return nil } -func weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndex(limit int, creator, name, unit string, index int) error { - return weMakeASearchForAssetsCallWithLimitCreatorNameUnitIndexAndAfterAsset(limit, creator, name, unit, index, 0) -} - func weMakeALookupApplicationLogsByIDCallWithApplicationIDLimitMinRoundMaxRoundNextTokenSenderAndTxID(appID, limit, minRound, maxRound int, nextToken, sender, txID string) error { indexerClient, err := indexer.MakeClient(mockServer.URL, "") if err != nil { diff --git a/test/integration.tags b/test/integration.tags new file mode 100644 index 00000000..e0ea6997 --- /dev/null +++ b/test/integration.tags @@ -0,0 +1,13 @@ +@abi +@algod +@applications.verified +@assets +@auction +@c2c +@compile +@compile.sourcemap +@dryrun +@kmd +@rekey_v1 +@send +@send.keyregtxn \ No newline at end of file diff --git a/test/responses_unit_test.go b/test/responses_unit_test.go index 45ad82eb..bf7582f3 100644 --- a/test/responses_unit_test.go +++ b/test/responses_unit_test.go @@ -151,8 +151,10 @@ func weMakeAnyCallTo(client /* algod/indexer */, endpoint string) (err error) { } case "DryRun": response, err = algodC.TealDryrun(models.DryrunRequest{}).Do(context.Background()) + case "GetTransactionProof": + fallthrough case "Proof": - response, err = algodC.GetProof(10, "asdf").Do(context.Background()) + response, err = algodC.GetTransactionProof(10, "asdf").Do(context.Background()) case "GetGenesis": response, err = algodC.GetGenesis().Do(context.Background()) case "AccountApplicationInformation": @@ -161,6 +163,12 @@ func weMakeAnyCallTo(client /* algod/indexer */, endpoint string) (err error) { case "AccountAssetInformation": response, err = algodC.AccountAssetInformation("abc", 123).Do(context.Background()) + case "GetLightBlockHeaderProof": + response, err = + algodC.GetLightBlockHeaderProof(123).Do(context.Background()) + case "GetStateProof": + response, err = + algodC.GetStateProof(123).Do(context.Background()) case "any": // This is an error case // pickup the error as the response diff --git a/test/steps_test.go b/test/steps_test.go index 9d581c9f..b35dc7bc 100644 --- a/test/steps_test.go +++ b/test/steps_test.go @@ -10,7 +10,6 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" "math/rand" "os" "path" @@ -20,8 +19,6 @@ import ( "testing" "time" - "path/filepath" - "golang.org/x/crypto/ed25519" "github.com/algorand/go-algorand-sdk/abi" @@ -93,9 +90,7 @@ var votefst uint64 var votelst uint64 var votekd uint64 var nonpart bool -var num string var backupTxnSender string -var groupTxnBytes []byte var data []byte var sig types.Signature var abiMethod abi.Method @@ -112,6 +107,8 @@ var txTrace future.DryrunTxnResult var trace string var sourceMap logic.SourceMap var srcMapping map[string]interface{} +var seeminglyProgram []byte +var sanityCheckError error var assetTestFixture struct { Creator string @@ -215,7 +212,6 @@ func TestMain(m *testing.M) { FeatureContext(s) AlgodClientV2Context(s) IndexerUnitTestContext(s) - IndexerIntegrationTestContext(s) TransactionsUnitContext(s) ApplicationsContext(s) ApplicationsUnitContext(s) @@ -240,7 +236,6 @@ func FeatureContext(s *godog.Suite) { s.Step("the wallet handle should not work", tryHandle) s.Step(`payment transaction parameters (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" (\d+) "([^"]*)" "([^"]*)"`, txnParams) s.Step(`mnemonic for private key "([^"]*)"`, mnForSk) - s.Step("I create the payment transaction", createTxn) s.Step(`multisig addresses "([^"]*)"`, msigAddresses) s.Step("I create the multisig payment transaction$", createMsigTxn) s.Step("I create the multisig payment transaction with zero fee", createMsigTxnZeroFee) @@ -288,10 +283,6 @@ func FeatureContext(s *godog.Suite) { s.Step("the signed transaction should equal the kmd signed transaction", signBothEqual) s.Step("I sign the multisig transaction with kmd", signMsigKmd) s.Step("the multisig transaction should equal the kmd signed multisig transaction", signMsigBothEqual) - s.Step(`I read a transaction "([^"]*)" from file "([^"]*)"`, readTxn) - s.Step("I write the transaction to file", writeTxn) - s.Step("the transaction should still be the same", checkEnc) - s.Step("I do my part", createSaveTxn) s.Step(`^the node should be healthy`, nodeHealth) s.Step(`^I get the ledger supply`, ledger) s.Step(`^I get transactions by address and round`, txnsByAddrRound) @@ -318,14 +309,8 @@ func FeatureContext(s *godog.Suite) { s.Step(`^it should still be the same amount of microalgos (\d+)`, checkAlgos) s.Step(`I get account information`, accInfo) s.Step("I sign the bid", signBid) - s.Step("I get transactions by address only", txnsByAddrOnly) - s.Step("I get transactions by address and date", txnsByAddrDate) - s.Step(`key registration transaction parameters (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)" "([^"]*)" (\d+) (\d+) (\d+) "([^"]*)" "([^"]*)`, keyregTxnParams) - s.Step("I create the key registration transaction", createKeyregTxn) s.Step(`default V2 key registration transaction "([^"]*)"`, createKeyregWithStateProof) - s.Step(`^I get recent transactions, limited by (\d+) transactions$`, getTxnsByCount) s.Step(`^I can get account information`, newAccInfo) - s.Step(`^I can get the transaction by ID$`, txnbyID) s.Step("asset test fixture", createAssetTestFixture) s.Step(`^default asset creation transaction with total issuance (\d+)$`, defaultAssetCreateTxn) s.Step(`^I update the asset index$`, getAssetIndex) @@ -404,6 +389,9 @@ func FeatureContext(s *godog.Suite) { s.Step(`^the resulting source map is the same as the json "([^"]*)"$`, theResultingSourceMapIsTheSameAsTheJson) s.Step(`^getting the line associated with a pc "([^"]*)" equals "([^"]*)"$`, gettingTheLineAssociatedWithAPcEquals) s.Step(`^getting the last pc associated with a line "([^"]*)" equals "([^"]*)"$`, gettingTheLastPcAssociatedWithALineEquals) + s.Step(`^a base64 encoded program bytes for heuristic sanity check "([^"]*)"$`, takeB64encodedBytes) + s.Step(`^I start heuristic sanity check over the bytes$`, heuristicCheckOverBytes) + s.Step(`^if the heuristic sanity check throws an error, the error contains "([^"]*)"$`, checkErrorIfMatching) s.BeforeScenario(func(interface{}) { stxObj = types.SignedTxn{} @@ -554,24 +542,6 @@ func mnForSk(mn string) error { } return err } - -func createTxn() error { - var err error - paramsToUse := types.SuggestedParams{ - Fee: types.MicroAlgos(fee), - GenesisID: gen, - GenesisHash: gh, - FirstRoundValid: types.Round(fv), - LastRoundValid: types.Round(lv), - FlatFee: false, - } - txn, err = future.MakePaymentTxn(a.String(), to, amt, note, close, paramsToUse) - if err != nil { - return err - } - return err -} - func msigAddresses(addresses string) error { var err error addrlist := strings.Fields(addresses) @@ -983,10 +953,6 @@ func defaultTxn(iamt int, inote string) error { return defaultTxnWithAddress(iamt, inote, accounts[0]) } -func defaultTxnRekey(iamt int, inote string) error { - return defaultTxnWithAddress(iamt, inote, rekey) -} - func defaultMsigTxn(iamt int, inote string) error { var err error if inote != "none" { @@ -1082,6 +1048,7 @@ func sendMsigTxn() error { return nil } +// TODO: this needs to be modified/removed when v1 is no longer supported func checkTxn() error { waitForAlgodInDevMode() _, err := acl.PendingTransactionInformation(txid) @@ -1096,15 +1063,10 @@ func checkTxn() error { if err != nil { return err } - _, err = acl.TransactionByID(txid) - return err -} - -func txnbyID() error { - var err error - waitForAlgodInDevMode() - _, err = acl.TransactionByID(txid) - return err + // v1 indexer dependency: + // _, err = acl.TransactionByID(txid) + // return err + return nil } func txnFail() error { @@ -1133,6 +1095,9 @@ func signBothEqual() error { func signMsigKmd() error { kcl.ImportMultisig(handle, msig.Version, msig.Threshold, msig.Pks) decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(pk) + if err != nil { + return fmt.Errorf("signMsigKmd: %w", err) + } s, err := kcl.MultisigSignTransaction(handle, walletPswd, txn, decoded[:32], types.MultisigSig{}) if err != nil { return err @@ -1158,79 +1123,6 @@ func signMsigBothEqual() error { } -func readTxn(encodedTxn string, inum string) error { - encodedBytes, err := base64.StdEncoding.DecodeString(encodedTxn) - if err != nil { - return err - } - path, err := os.Getwd() - if err != nil { - return err - } - num = inum - path = filepath.Dir(filepath.Dir(path)) + "/temp/old" + num + ".tx" - err = ioutil.WriteFile(path, encodedBytes, 0644) - data, err := ioutil.ReadFile(path) - if err != nil { - return err - } - err = msgpack.Decode(data, &stxObj) - return err -} - -func writeTxn() error { - path, err := os.Getwd() - if err != nil { - return err - } - path = filepath.Dir(filepath.Dir(path)) + "/temp/raw" + num + ".tx" - data := msgpack.Encode(stxObj) - err = ioutil.WriteFile(path, data, 0644) - return err -} - -func checkEnc() error { - path, err := os.Getwd() - if err != nil { - return err - } - pathold := filepath.Dir(filepath.Dir(path)) + "/temp/old" + num + ".tx" - dataold, err := ioutil.ReadFile(pathold) - - pathnew := filepath.Dir(filepath.Dir(path)) + "/temp/raw" + num + ".tx" - datanew, err := ioutil.ReadFile(pathnew) - - if bytes.Equal(dataold, datanew) { - return nil - } - return fmt.Errorf("should be equal") -} - -func createSaveTxn() error { - var err error - - amt = 100000 - pk = accounts[0] - params, err := acl.BuildSuggestedParams() - if err != nil { - return err - } - lastRound = uint64(params.FirstRoundValid) - txn, err = future.MakePaymentTxn(accounts[0], accounts[1], amt, note, "", params) - if err != nil { - return err - } - - path, err := os.Getwd() - if err != nil { - return err - } - path = filepath.Dir(filepath.Dir(path)) + "/temp/txn.tx" - data := msgpack.Encode(txn) - err = ioutil.WriteFile(path, data, 0644) - return err -} - func nodeHealth() error { err := acl.HealthCheck() return err @@ -1250,17 +1142,6 @@ func txnsByAddrRound() error { return err } -func txnsByAddrOnly() error { - _, err := acl.TransactionsByAddrLimit(accounts[0], 10) - return err -} - -func txnsByAddrDate() error { - fromDate := time.Now().Format("2006-01-02") - _, err := acl.TransactionsByAddrForDate(accounts[0], fromDate, fromDate) - return err -} - func txnsPending() error { _, err := acl.GetPendingTransactions(10) return err @@ -1441,58 +1322,6 @@ func newAccInfo() error { return err } -func keyregTxnParams(ifee, ifv, ilv int, igh, ivotekey, iselkey string, ivotefst, ivotelst, ivotekd int, igen, inote string) error { - var err error - if inote != "none" { - note, err = base64.StdEncoding.DecodeString(inote) - if err != nil { - return err - } - } else { - note, err = base64.StdEncoding.DecodeString("") - if err != nil { - return err - } - } - gh, err = base64.StdEncoding.DecodeString(igh) - if err != nil { - return err - } - votekey = ivotekey - selkey = iselkey - fee = uint64(ifee) - fv = uint64(ifv) - lv = uint64(ilv) - votefst = uint64(ivotefst) - votelst = uint64(ivotelst) - votekd = uint64(ivotekd) - if igen != "none" { - gen = igen - } else { - gen = "" - } - if err != nil { - return err - } - return nil -} - -func createKeyregTxn() (err error) { - paramsToUse := types.SuggestedParams{ - Fee: types.MicroAlgos(fee), - GenesisID: gen, - GenesisHash: gh, - FirstRoundValid: types.Round(fv), - LastRoundValid: types.Round(lv), - FlatFee: false, - } - txn, err = future.MakeKeyRegTxn(a.String(), note, paramsToUse, votekey, selkey, votefst, votelst, votekd) - if err != nil { - return err - } - return err -} - func createKeyregWithStateProof(keyregType string) (err error) { params, err := acl.BuildSuggestedParams() if err != nil { @@ -1534,11 +1363,6 @@ func createKeyregWithStateProof(keyregType string) (err error) { return err } -func getTxnsByCount(cnt int) error { - _, err := acl.TransactionsByAddrLimit(accounts[0], uint64(cnt)) - return err -} - func createAssetTestFixture() error { assetTestFixture.Creator = "" assetTestFixture.AssetIndex = 1 @@ -2815,3 +2639,30 @@ func theResultingSourceMapIsTheSameAsTheJson(expectedJsonPath string) error { return nil } + +func takeB64encodedBytes(b64encodedBytes string) error { + var err error + seeminglyProgram, err = base64.StdEncoding.DecodeString(b64encodedBytes) + if err != nil { + return err + } + return nil +} + +func heuristicCheckOverBytes() error { + _, sanityCheckError = crypto.MakeLogicSigAccountEscrowChecked(seeminglyProgram, nil) + return nil +} + +func checkErrorIfMatching(errMsg string) error { + if len(errMsg) == 0 { + if sanityCheckError != nil { + return fmt.Errorf("expected err message to be empty, but sanity check says %w", sanityCheckError) + } + } else { + if sanityCheckError == nil || !strings.Contains(sanityCheckError.Error(), errMsg) { + return fmt.Errorf("expected err to contain %s, but sanity check error not matching: %w", errMsg, sanityCheckError) + } + } + return nil +} diff --git a/test/transactions_test.go b/test/transactions_test.go index 3c22d08e..ca3e6e72 100644 --- a/test/transactions_test.go +++ b/test/transactions_test.go @@ -16,7 +16,6 @@ import ( "golang.org/x/crypto/ed25519" ) -var signingAccount crypto.Account var sk1 ed25519.PrivateKey var addr1 types.Address @@ -37,28 +36,6 @@ func aSigningAccountWithAddressAndMnemonic(address, mnem string) error { return err } -func suggestedTransactionParametersTxn(fee int, flatFee string, firstValid, LastValid int, genesisHash, genesisId string) error { - if flatFee != "true" && flatFee != "false" { - return fmt.Errorf("flatFee must be either 'true' or 'false'") - } - - genHash, err := base64.StdEncoding.DecodeString(genesisHash) - if err != nil { - return err - } - - sugParams = types.SuggestedParams{ - Fee: types.MicroAlgos(fee), - GenesisID: genesisId, - GenesisHash: genHash, - FirstRoundValid: types.Round(firstValid), - LastRoundValid: types.Round(LastValid), - FlatFee: flatFee == "true", - } - - return nil -} - func signTheTransaction() error { var err error txid, stx, err = crypto.SignTransaction(sk1, tx) diff --git a/test/unit.tags b/test/unit.tags new file mode 100644 index 00000000..5cff292b --- /dev/null +++ b/test/unit.tags @@ -0,0 +1,30 @@ +@unit.abijson +@unit.abijson.byname +@unit.algod +@unit.algod.ledger_refactoring +@unit.applications +@unit.atomic_transaction_composer +@unit.dryrun +@unit.dryrun.trace.application +@unit.feetest +@unit.indexer +@unit.indexer.ledger_refactoring +@unit.indexer.logs +@unit.indexer.rekey +@unit.offline +@unit.program_sanity_check +@unit.rekey +@unit.responses +@unit.responses.231 +@unit.responses.genesis +@unit.responses.messagepack +@unit.responses.messagepack.231 +@unit.responses.unlimited_assets +@unit.sourcemap +@unit.stateproof.paths +@unit.stateproof.responses +@unit.stateproof.responses.msgp +@unit.tealsign +@unit.transactions +@unit.transactions.keyreg +@unit.transactions.payment diff --git a/types/basics.go b/types/basics.go index 28a102ee..f5456e04 100644 --- a/types/basics.go +++ b/types/basics.go @@ -25,6 +25,8 @@ const ( AssetFreezeTx TxType = "afrz" // ApplicationCallTx allows creating, deleting, and interacting with an application ApplicationCallTx TxType = "appl" + // StateProofTx records a state proof + StateProofTx TxType = "stpf" ) const masterDerivationKeyLenBytes = 32 diff --git a/types/block.go b/types/block.go index 88322db3..a751d687 100644 --- a/types/block.go +++ b/types/block.go @@ -17,11 +17,7 @@ type ( // Sortition seed Seed [32]byte `codec:"seed"` - // TxnRoot authenticates the set of transactions appearing in the block. - // More specifically, it's the root of a merkle tree whose leaves are the block's Txids. - // Note that the TxnRoot does not authenticate the signatures on the transactions, only the transactions themselves. - // Two blocks with the same transactions but with different signatures will have the same TxnRoot. - TxnRoot Digest `codec:"txn"` + TxnCommitments // TimeStamp in seconds since epoch TimeStamp int64 `codec:"ts"` @@ -93,11 +89,28 @@ type ( // started being supported). TxnCounter uint64 `codec:"tc"` + // 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[StateProofType]StateProofTrackingData `codec:"spt,allocbound=NumStateProofTypes"` + // ParticipationUpdates contains the information needed to mark // certain accounts offline because their participation keys expired ParticipationUpdates } + // TxnCommitments represents the commitments computed from the transactions in the block. + // It contains multiple commitments based on different algorithms and hash functions, to support different use cases. + TxnCommitments struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + // Root of transaction merkle tree using SHA512_256 hash function. + // This commitment is computed based on the PaysetCommit type specified in the block's consensus protocol. + NativeSha512_256Commitment Digest `codec:"txn"` + + // Root of transaction vector commitment merkle tree using SHA256 hash function + Sha256Commitment Digest `codec:"txn256"` + } + // ParticipationUpdates represents participation account data that // needs to be checked/acted on by the network ParticipationUpdates struct { @@ -167,6 +180,28 @@ type ( NextProtocolSwitchOn Round `codec:"nextswitch"` } + // StateProofTrackingData tracks the status of state proofs. + StateProofTrackingData struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // 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. + StateProofVotersCommitment []byte `codec:"v"` + + // 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 MicroAlgos `codec:"t"` + + // StateProofNextRound is the next round for which we will accept + // a StateProof transaction. + StateProofNextRound Round `codec:"n"` + } + // A Block contains the Payset and metadata corresponding to a given Round. Block struct { BlockHeader diff --git a/types/lightBlockHeader.go b/types/lightBlockHeader.go new file mode 100644 index 00000000..c427b1b1 --- /dev/null +++ b/types/lightBlockHeader.go @@ -0,0 +1,17 @@ +package types + +// A Seed contains cryptographic entropy which can be used to determine a +// committee. +type Seed [32]byte + +// 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"` + + Seed Seed `codec:"0"` + RoundNumber Round `codec:"r"` + GenesisHash Digest `codec:"gh"` + Sha256TxnCommitment Digest `codec:"tc,allocbound=Sha256Size"` +} diff --git a/types/stateproof.go b/types/stateproof.go new file mode 100644 index 00000000..c0f3d527 --- /dev/null +++ b/types/stateproof.go @@ -0,0 +1,30 @@ +package types + +// MessageHash represents the message that a state proof will attest to. +type MessageHash [32]byte + +// StateProofType identifies a particular configuration of state proofs. +type StateProofType uint64 + +// 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=Sha256Size"` + VotersCommitment []byte `codec:"v,allocbound=SumhashDigestSize"` + LnProvenWeight uint64 `codec:"P"` + FirstAttestedRound uint64 `codec:"f"` + LastAttestedRound uint64 `codec:"l"` +} + +// StateProofTxnFields captures the fields used for stateproof transactions. +type StateProofTxnFields struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + StateProofType StateProofType `codec:"sptype"` + StateProof interface{} `codec:"sp"` + Message Message `codec:"spmsg"` +} diff --git a/types/transaction.go b/types/transaction.go index ad461da8..68a9dbd9 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -17,6 +17,7 @@ type Transaction struct { AssetTransferTxnFields AssetFreezeTxnFields ApplicationFields + StateProofTxnFields } // SignedTxn wraps a transaction and a signature. The encoding of this struct