diff --git a/CHANGELOG.md b/CHANGELOG.md index 22108e76..479e0d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 1.24.0 +## What's Changed +### Bugfixes +* BugFix: Fix disassemble endpoint by @zyablitsev in https://github.com/algorand/go-algorand-sdk/pull/436 +### Enhancements +* Tests: Support for new cucumber app call txn decoding test by @jasonpaulos in https://github.com/algorand/go-algorand-sdk/pull/433 +* Tests: Migrate v1 algod dependencies to v2 in cucumber tests by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/434 +* REST API: Add KV counts to NodeStatusResponse by @michaeldiamant in https://github.com/algorand/go-algorand-sdk/pull/437 +* Enhancement: allowing zero length static array by @ahangsu in https://github.com/algorand/go-algorand-sdk/pull/438 +* Enhancement: revert generic StateProof txn field by @shiqizng in https://github.com/algorand/go-algorand-sdk/pull/439 +* Refactoring: Move old transaction dependencies to future.transaction by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/435 + +## New Contributors +* @zyablitsev made their first contribution in https://github.com/algorand/go-algorand-sdk/pull/436 + +**Full Changelog**: https://github.com/algorand/go-algorand-sdk/compare/v1.23.0...v1.24.0 + # 1.23.0 ## What's Changed ### New Features diff --git a/client/v2/algod/tealDisassemble.go b/client/v2/algod/tealDisassemble.go index 9efa7d75..6f2bf48b 100644 --- a/client/v2/algod/tealDisassemble.go +++ b/client/v2/algod/tealDisassemble.go @@ -18,6 +18,6 @@ type TealDisassemble struct { // Do performs the HTTP request func (s *TealDisassemble) Do(ctx context.Context, headers ...*common.Header) (response models.DisassembleResponse, err error) { - err = s.c.get(ctx, &response, "/v2/teal/disassemble", nil, headers) + err = s.c.post(ctx, &response, "/v2/teal/disassemble", nil, headers, s.source) return } diff --git a/client/v2/common/common.go b/client/v2/common/common.go index f05f3511..e9552ae0 100644 --- a/client/v2/common/common.go +++ b/client/v2/common/common.go @@ -17,9 +17,10 @@ import ( // rawRequestPaths is a set of paths where the body should not be urlencoded var rawRequestPaths = map[string]bool{ - "/v2/transactions": true, - "/v2/teal/compile": true, - "/v2/teal/dryrun": true, + "/v2/transactions": true, + "/v2/teal/compile": true, + "/v2/teal/disassemble": true, + "/v2/teal/dryrun": true, } // Header is a struct for custom headers. diff --git a/client/v2/common/models/node_status_response.go b/client/v2/common/models/node_status_response.go index 99e1eae3..8f7f2b6d 100644 --- a/client/v2/common/models/node_status_response.go +++ b/client/v2/common/models/node_status_response.go @@ -13,6 +13,10 @@ type NodeStatusResponse struct { // that have been processed so far as part of the catchup CatchpointProcessedAccounts uint64 `json:"catchpoint-processed-accounts,omitempty"` + // CatchpointProcessedKvs the number of key-values (KVs) from the current + // catchpoint that have been processed so far as part of the catchup + CatchpointProcessedKvs uint64 `json:"catchpoint-processed-kvs,omitempty"` + // CatchpointTotalAccounts the total number of accounts included in the current // catchpoint CatchpointTotalAccounts uint64 `json:"catchpoint-total-accounts,omitempty"` @@ -21,10 +25,18 @@ type NodeStatusResponse struct { // the current catchpoint catchup CatchpointTotalBlocks uint64 `json:"catchpoint-total-blocks,omitempty"` + // CatchpointTotalKvs the total number of key-values (KVs) included in the current + // catchpoint + CatchpointTotalKvs uint64 `json:"catchpoint-total-kvs,omitempty"` + // CatchpointVerifiedAccounts the number of accounts from the current catchpoint // that have been verified so far as part of the catchup CatchpointVerifiedAccounts uint64 `json:"catchpoint-verified-accounts,omitempty"` + // CatchpointVerifiedKvs the number of key-values (KVs) from the current catchpoint + // that have been verified so far as part of the catchup + CatchpointVerifiedKvs uint64 `json:"catchpoint-verified-kvs,omitempty"` + // CatchupTime catchupTime in nanoseconds CatchupTime uint64 `json:"catchup-time"` diff --git a/future/transaction.go b/future/transaction.go index 11697c6c..642a4f66 100644 --- a/future/transaction.go +++ b/future/transaction.go @@ -1,9 +1,12 @@ package future import ( + "bytes" "encoding/base64" "fmt" + "github.com/algorand/go-algorand-sdk/crypto" + "github.com/algorand/go-algorand-sdk/encoding/msgpack" "github.com/algorand/go-algorand-sdk/transaction" "github.com/algorand/go-algorand-sdk/types" ) @@ -11,9 +14,12 @@ import ( // MinTxnFee is v5 consensus params, in microAlgos const MinTxnFee = transaction.MinTxnFee +// NumOfAdditionalBytesAfterSigning is the number of bytes added to a txn after signing it +const NumOfAdditionalBytesAfterSigning = 75 + func setFee(tx types.Transaction, params types.SuggestedParams) (types.Transaction, error) { if !params.FlatFee { - eSize, err := transaction.EstimateSize(tx) + eSize, err := EstimateSize(tx) if err != nil { return types.Transaction{}, err } @@ -1273,6 +1279,35 @@ func MakeApplicationCallTxWithBoxes( return setFee(tx, sp) } +// AssignGroupID computes and return list of transactions with Group field set. +// - txns is a list of transactions to process +// - account specifies a sender field of transaction to return. Set to empty string to return all of them +func AssignGroupID(txns []types.Transaction, account string) (result []types.Transaction, err error) { + gid, err := crypto.ComputeGroupID(txns) + if err != nil { + return + } + var decoded types.Address + if account != "" { + decoded, err = types.DecodeAddress(account) + if err != nil { + return + } + } + for _, tx := range txns { + if account == "" || bytes.Compare(tx.Sender[:], decoded[:]) == 0 { + tx.Group = gid + result = append(result, tx) + } + } + return result, nil +} + +// EstimateSize returns the estimated length of the encoded transaction +func EstimateSize(txn types.Transaction) (uint64, error) { + return uint64(len(msgpack.Encode(txn))) + NumOfAdditionalBytesAfterSigning, nil +} + func parseTxnAccounts(accounts []string) (parsed []types.Address, err error) { for _, acct := range accounts { addr, err := types.DecodeAddress(acct) diff --git a/future/transaction_test.go b/future/transaction_test.go index 7f6da1eb..0bd207a7 100644 --- a/future/transaction_test.go +++ b/future/transaction_test.go @@ -7,7 +7,6 @@ import ( "github.com/algorand/go-algorand-sdk/crypto" "github.com/algorand/go-algorand-sdk/encoding/msgpack" "github.com/algorand/go-algorand-sdk/mnemonic" - "github.com/algorand/go-algorand-sdk/transaction" "github.com/algorand/go-algorand-sdk/types" "github.com/stretchr/testify/require" ) @@ -841,15 +840,15 @@ func TestComputeGroupID(t *testing.T) { require.Equal(t, byteFromBase64(goldenTxg), txg) // check transaction.AssignGroupID, do not validate correctness of Group field calculation - result, err := transaction.AssignGroupID([]types.Transaction{tx1, tx2}, "BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4") + result, err := AssignGroupID([]types.Transaction{tx1, tx2}, "BH55E5RMBD4GYWXGX5W5PJ5JAHPGM5OXKDQH5DC4O2MGI7NW4H6VOE4CP4") require.NoError(t, err) require.Equal(t, 0, len(result)) - result, err = transaction.AssignGroupID([]types.Transaction{tx1, tx2}, address) + result, err = AssignGroupID([]types.Transaction{tx1, tx2}, address) require.NoError(t, err) require.Equal(t, 2, len(result)) - result, err = transaction.AssignGroupID([]types.Transaction{tx1, tx2}, "") + result, err = AssignGroupID([]types.Transaction{tx1, tx2}, "") require.NoError(t, err) require.Equal(t, 2, len(result)) } diff --git a/go.mod b/go.mod index 1e6db569..54db4f60 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/algorand/go-algorand-sdk go 1.17 require ( - github.com/algorand/avm-abi v0.1.0 + github.com/algorand/avm-abi v0.1.1 github.com/algorand/go-codec/codec v1.1.8 github.com/cucumber/godog v0.8.1 github.com/google/go-querystring v1.0.0 diff --git a/go.sum b/go.sum index f34f5ef3..fca639dc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/algorand/avm-abi v0.1.0 h1:znZFQXpSUVYz37vXbaH5OZG2VK4snTyXwnc/tV9CVr4= -github.com/algorand/avm-abi v0.1.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= +github.com/algorand/avm-abi v0.1.1 h1:dbyQKzXiyaEbzpmqXFB30yAhyqseBsyqXTyZbNbkh2Y= +github.com/algorand/avm-abi v0.1.1/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g= github.com/algorand/go-codec v1.1.8 h1:XDSreeeZY8gMst6Edz4RBkl08/DGMJOeHYkoXL2B7wI= github.com/algorand/go-codec v1.1.8/go.mod h1:XhzVs6VVyWMLu6cApb9/192gBjGRVGm5cX5j203Heg4= github.com/algorand/go-codec/codec v1.1.8 h1:lsFuhcOH2LiEhpBH3BVUUkdevVmwCRyvb7FCAAPeY6U= diff --git a/test/applications_integration_test.go b/test/applications_integration_test.go index be8bad2c..4392b1c7 100644 --- a/test/applications_integration_test.go +++ b/test/applications_integration_test.go @@ -40,6 +40,7 @@ func anAlgodVClientConnectedToPortWithToken(v int, host string, port int, token portAsString := strconv.Itoa(port) fullHost := "http://" + host + ":" + portAsString algodV2client, err = algod.MakeClient(fullHost, token) + aclv2 = algodV2client gh = []byte("MLWBXKMRJ5W3USARAFOHPQJAF4DN6KY3ZJVPIXKODKNN5ZXSZ2DQ") return err @@ -328,6 +329,10 @@ func parseBoxes(boxesStr string) (staticBoxes []types.AppBoxReference, err error } } + if len(nameBytes) == 0 { + nameBytes = nil + } + staticBoxes = append(staticBoxes, types.AppBoxReference{ AppID: appID, diff --git a/test/steps_test.go b/test/steps_test.go index 36ba6576..f5842509 100644 --- a/test/steps_test.go +++ b/test/steps_test.go @@ -10,7 +10,6 @@ import ( "encoding/json" "flag" "fmt" - "math/rand" "os" "path" "reflect" @@ -23,8 +22,6 @@ import ( "github.com/algorand/go-algorand-sdk/abi" "github.com/algorand/go-algorand-sdk/auction" - "github.com/algorand/go-algorand-sdk/client/algod" - "github.com/algorand/go-algorand-sdk/client/algod/models" "github.com/algorand/go-algorand-sdk/client/kmd" algodV2 "github.com/algorand/go-algorand-sdk/client/v2/algod" commonV2 "github.com/algorand/go-algorand-sdk/client/v2/common" @@ -59,7 +56,6 @@ var a types.Address var msig crypto.MultisigAccount var msigsig types.MultisigSig var kcl kmd.Client -var acl algod.Client var aclv2 *algodV2.Client var iclv2 *indexerV2.Client var walletName string @@ -67,8 +63,6 @@ var walletPswd string var walletID string var handle string var versions []string -var status models.NodeStatus -var statusAfter models.NodeStatus var msigExp kmd.ExportMultisigResponse var pk string var rekey string @@ -76,7 +70,6 @@ var accounts []string var e bool var lastRound uint64 var sugParams types.SuggestedParams -var sugFee models.TransactionFee var bid types.Bid var sbid types.NoteField var oldBid types.NoteField @@ -92,7 +85,6 @@ var votefst uint64 var votelst uint64 var votekd uint64 var nonpart bool -var backupTxnSender string var data []byte var sig types.Signature var abiMethod abi.Method @@ -119,8 +111,8 @@ var assetTestFixture struct { AssetUnitName string AssetURL string AssetMetadataHash string - ExpectedParams models.AssetParams - QueriedParams models.AssetParams + ExpectedParams modelsV2.AssetParams + QueriedParams modelsV2.AssetParams LastTransactionIssued types.Transaction } @@ -155,7 +147,7 @@ func waitForAlgodInDevMode() { } func initializeAccount(accountAddress string) error { - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -170,31 +162,7 @@ func initializeAccount(accountAddress string) error { return err } - _, err = acl.SendRawTransaction(res.SignedTransaction) - if err != nil { - return err - } - waitForAlgodInDevMode() - return err -} - -func selfPayTransaction() error { - params, err := acl.BuildSuggestedParams() - if err != nil { - return err - } - - txn, err = future.MakePaymentTxn(accounts[0], accounts[0], uint64(rand.Intn(devModeInitialAmount*0.01)), []byte{}, "", params) - if err != nil { - return err - } - - res, err := kcl.SignTransaction(handle, walletPswd, txn) - if err != nil { - return err - } - - _, err = acl.SendRawTransaction(res.SignedTransaction) + _, err = aclv2.SendRawTransaction(res.SignedTransaction).Do(context.Background()) if err != nil { return err } @@ -251,10 +219,8 @@ func FeatureContext(s *godog.Suite) { s.Step(`the multisig address should equal the golden "([^"]*)"`, equalMsigAddrGolden) s.Step("I get versions with algod", aclV) s.Step("v1 should be in the versions", v1InVersions) + s.Step("v2 should be in the versions", v2InVersions) s.Step("I get versions with kmd", kclV) - s.Step("I get the status", getStatus) - s.Step(`^I get status after this block`, statusAfterBlock) - s.Step("I can get the block info", block) s.Step("I import the multisig", importMsig) s.Step("the multisig should be in the wallet", msigInWallet) s.Step("I export the multisig", expMsig) @@ -270,7 +236,6 @@ func FeatureContext(s *godog.Suite) { s.Step("I import the key", importKey) s.Step("the private key should be equal to the exported private key", skEqExport) s.Step("a kmd client", kmdClient) - s.Step("an algod client", algodClient) s.Step("wallet information", walletInfo) s.Step(`default transaction with parameters (\d+) "([^"]*)"`, defaultTxn) s.Step(`default multisig transaction with parameters (\d+) "([^"]*)"`, defaultMsigTxn) @@ -279,7 +244,6 @@ func FeatureContext(s *godog.Suite) { s.Step("I send the kmd-signed transaction", sendTxnKmd) s.Step("I send the bogus kmd-signed transaction", sendTxnKmdFailureExpected) s.Step("I send the multisig transaction", sendMsigTxn) - s.Step("the transaction should go through", checkTxn) s.Step("the transaction should not go through", txnFail) s.Step("I sign the transaction with kmd", signKmd) s.Step("the signed transaction should equal the kmd signed transaction", signBothEqual) @@ -287,11 +251,6 @@ func FeatureContext(s *godog.Suite) { s.Step("the multisig transaction should equal the kmd signed multisig transaction", signMsigBothEqual) 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) - s.Step(`^I get pending transactions`, txnsPending) - s.Step(`^I get the suggested params`, suggestedParams) - s.Step(`^I get the suggested fee`, suggestedFee) - s.Step(`^the fee in the suggested params should equal the suggested fee`, checkSuggested) s.Step(`^I create a bid`, createBid) s.Step(`^I encode and decode the bid`, encDecBid) s.Step(`^the bid should still be the same`, checkBid) @@ -309,7 +268,6 @@ func FeatureContext(s *godog.Suite) { s.Step(`^I merge the multisig transactions`, mergeMsig) s.Step(`^I convert (\d+) microalgos to algos and back`, microToAlgos) 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(`default V2 key registration transaction "([^"]*)"`, createKeyregWithStateProof) s.Step(`^I can get account information`, newAccInfo) @@ -377,7 +335,6 @@ func FeatureContext(s *godog.Suite) { s.Step(`^I build a payment transaction with sender "([^"]*)", receiver "([^"]*)", amount (\d+), close remainder to "([^"]*)"$`, iBuildAPaymentTransactionWithSenderReceiverAmountCloseRemainderTo) s.Step(`^I create a transaction with signer with the current transaction\.$`, iCreateATransactionWithSignerWithTheCurrentTransaction) s.Step(`^I append the current transaction with signer to the method arguments array\.$`, iAppendTheCurrentTransactionWithSignerToTheMethodArgumentsArray) - s.Step(`^the decoded transaction should equal the original$`, theDecodedTransactionShouldEqualTheOriginal) s.Step(`^a dryrun response file "([^"]*)" and a transaction at index "([^"]*)"$`, aDryrunResponseFileAndATransactionAtIndex) s.Step(`^calling app trace produces "([^"]*)"$`, callingAppTraceProduces) s.Step(`^I append to my Method objects list in the case of a non-empty signature "([^"]*)"$`, iAppendToMyMethodObjectsListInTheCaseOfANonemptySignature) @@ -669,7 +626,7 @@ func equalMsigGolden(golden string) error { } func aclV() error { - v, err := acl.Versions() + v, err := aclv2.Versions().Do(context.Background()) if err != nil { return err } @@ -686,31 +643,18 @@ func v1InVersions() error { return fmt.Errorf("v1 not found") } -func kclV() error { - v, err := kcl.Version() - versions = v.Versions - return err -} - -func getStatus() error { - var err error - status, err = acl.Status() - lastRound = status.LastRound - return err -} - -func statusAfterBlock() error { - var err error - selfPayTransaction() - statusAfter, err = acl.StatusAfterBlock(lastRound) - if err != nil { - return err +func v2InVersions() error { + for _, b := range versions { + if b == "v2" { + return nil + } } - return nil + return fmt.Errorf("v2 not found") } -func block() error { - _, err := acl.Block(status.LastRound) +func kclV() error { + v, err := kcl.Version() + versions = v.Versions return err } @@ -879,18 +823,6 @@ func kmdClient() error { return err } -func algodClient() error { - algodToken := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - algodAddress := "http://localhost:" + "60000" - var err error - acl, err = algod.MakeClient(algodAddress, algodToken) - if err != nil { - return err - } - _, err = acl.StatusAfterBlock(1) - return err -} - func algodClientV2() error { algodToken := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" algodAddress := "http://localhost:" + "60000" @@ -951,7 +883,7 @@ func defaultTxnWithAddress(iamt int, inote string, senderAddress string) error { amt = uint64(iamt) pk = senderAddress - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -995,7 +927,7 @@ func defaultMsigTxn(iamt int, inote string) error { if err != nil { return err } - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1021,37 +953,33 @@ func getSk() error { } func sendTxn() error { - tx, err := acl.SendRawTransaction(stx) + tx, err := aclv2.SendRawTransaction(stx).Do(context.Background()) if err != nil { return err } - txid = tx.TxID + txid = tx return nil } func sendTxnKmd() error { - tx, err := acl.SendRawTransaction(stxKmd) - if err != nil { - e = true - } - txid = tx.TxID - return nil + var err error + txid, err = aclv2.SendRawTransaction(stxKmd).Do(context.Background()) + return err } func sendTxnKmdFailureExpected() error { - tx, err := acl.SendRawTransaction(stxKmd) + tx, err := aclv2.SendRawTransaction(stxKmd).Do(context.Background()) if err == nil { e = false return fmt.Errorf("expected an error when sending kmd-signed transaction but no error occurred") } e = true - txid = tx.TxID + txid = tx return nil } func sendMsigTxn() error { - _, err := acl.SendRawTransaction(stx) - + _, err := aclv2.SendRawTransaction(stx).Do(context.Background()) if err != nil { e = true } @@ -1059,27 +987,6 @@ 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) - if err != nil { - return err - } - if txn.Sender.String() != "" && txn.Sender.String() != "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ" { - _, err = acl.TransactionInformation(txn.Sender.String(), txid) - } else { - _, err = acl.TransactionInformation(backupTxnSender, txid) - } - if err != nil { - return err - } - // v1 indexer dependency: - // _, err = acl.TransactionByID(txid) - // return err - return nil -} - func txnFail() error { if e { return nil @@ -1135,48 +1042,15 @@ func signMsigBothEqual() error { } func nodeHealth() error { - err := acl.HealthCheck() + err := aclv2.HealthCheck().Do(context.Background()) return err } func ledger() error { - _, err := acl.LedgerSupply() - return err -} - -func txnsByAddrRound() error { - lr, err := acl.Status() - if err != nil { - return err - } - _, err = acl.TransactionsByAddr(accounts[0], 1, lr.LastRound) - return err -} - -func txnsPending() error { - _, err := acl.GetPendingTransactions(10) + _, err := aclv2.Supply().Do(context.Background()) return err } -func suggestedParams() error { - var err error - sugParams, err = acl.BuildSuggestedParams() - return err -} - -func suggestedFee() error { - var err error - sugFee, err = acl.SuggestedFee() - return err -} - -func checkSuggested() error { - if uint64(sugParams.Fee) != sugFee.Fee { - return fmt.Errorf("suggested fee from params should be equal to suggested fee") - } - return nil -} - func createBid() error { var err error account = crypto.GenerateAccount() @@ -1322,19 +1196,14 @@ func checkAlgos(ma int) error { return nil } -func accInfo() error { - _, err := acl.AccountInformation(accounts[0]) - return err -} - func newAccInfo() error { - _, err := acl.AccountInformation(pk) + _, err := aclv2.AccountInformation(pk).Do(context.Background()) _, _ = kcl.DeleteKey(handle, walletPswd, pk) return err } func createKeyregWithStateProof(keyregType string) (err error) { - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1381,24 +1250,24 @@ func createAssetTestFixture() error { assetTestFixture.AssetUnitName = "coins" assetTestFixture.AssetURL = "http://test" assetTestFixture.AssetMetadataHash = "fACPO4nRgO55j1ndAK3W6Sgc4APkcyFh" - assetTestFixture.ExpectedParams = models.AssetParams{} - assetTestFixture.QueriedParams = models.AssetParams{} + assetTestFixture.ExpectedParams = modelsV2.AssetParams{} + assetTestFixture.QueriedParams = modelsV2.AssetParams{} assetTestFixture.LastTransactionIssued = types.Transaction{} return nil } -func convertTransactionAssetParamsToModelsAssetParam(input types.AssetParams) models.AssetParams { - result := models.AssetParams{ +func convertTransactionAssetParamsToModelsAssetParam(input types.AssetParams) modelsV2.AssetParams { + result := modelsV2.AssetParams{ Total: input.Total, - Decimals: input.Decimals, + Decimals: uint64(input.Decimals), DefaultFrozen: input.DefaultFrozen, - ManagerAddr: input.Manager.String(), - ReserveAddr: input.Reserve.String(), - FreezeAddr: input.Freeze.String(), - ClawbackAddr: input.Clawback.String(), + Manager: input.Manager.String(), + Reserve: input.Reserve.String(), + Freeze: input.Freeze.String(), + Clawback: input.Clawback.String(), UnitName: input.UnitName, - AssetName: input.AssetName, - URL: input.URL, + Name: input.AssetName, + Url: input.URL, MetadataHash: input.MetadataHash[:], } // input doesn't have Creator so that will remain empty @@ -1409,7 +1278,7 @@ func assetCreateTxnHelper(issuance int, frozenState bool) error { accountToUse := accounts[0] assetTestFixture.Creator = accountToUse creator := assetTestFixture.Creator - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1443,7 +1312,7 @@ func defaultAssetCreateTxnWithDefaultFrozen(issuance int) error { func createNoManagerAssetReconfigure() error { creator := assetTestFixture.Creator - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1457,15 +1326,15 @@ func createNoManagerAssetReconfigure() error { assetTestFixture.LastTransactionIssued = assetReconfigureTxn txn = assetReconfigureTxn // update expected params - assetTestFixture.ExpectedParams.ReserveAddr = reserve - assetTestFixture.ExpectedParams.FreezeAddr = freeze - assetTestFixture.ExpectedParams.ClawbackAddr = clawback + assetTestFixture.ExpectedParams.Reserve = reserve + assetTestFixture.ExpectedParams.Freeze = freeze + assetTestFixture.ExpectedParams.Clawback = clawback return err } func createAssetDestroy() error { creator := assetTestFixture.Creator - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1475,46 +1344,43 @@ func createAssetDestroy() error { assetTestFixture.LastTransactionIssued = assetDestroyTxn txn = assetDestroyTxn // update expected params - assetTestFixture.ExpectedParams.ReserveAddr = "" - assetTestFixture.ExpectedParams.FreezeAddr = "" - assetTestFixture.ExpectedParams.ClawbackAddr = "" - assetTestFixture.ExpectedParams.ManagerAddr = "" + assetTestFixture.ExpectedParams.Reserve = "" + assetTestFixture.ExpectedParams.Freeze = "" + assetTestFixture.ExpectedParams.Clawback = "" + assetTestFixture.ExpectedParams.Manager = "" return err } // used in getAssetIndex and similar to get the index of the most recently operated on asset -func getMaxKey(numbers map[uint64]models.AssetParams) uint64 { - var maxNumber uint64 - for n := range numbers { - maxNumber = n - break - } - for n := range numbers { - if n > maxNumber { - maxNumber = n +func getMaxKey(numbers []modelsV2.Asset) uint64 { + var maxNumber uint64 = 0 + for _, asset := range numbers { + idx := asset.Index + if idx > maxNumber { + maxNumber = idx } } return maxNumber } func getAssetIndex() error { - accountResp, err := acl.AccountInformation(assetTestFixture.Creator) + accountResp, err := aclv2.AccountInformation(assetTestFixture.Creator).Do(context.Background()) if err != nil { return err } // get most recent asset index - assetTestFixture.AssetIndex = getMaxKey(accountResp.AssetParams) + assetTestFixture.AssetIndex = getMaxKey(accountResp.CreatedAssets) return nil } func getAssetInfo() error { - response, err := acl.AssetInformation(assetTestFixture.AssetIndex) - assetTestFixture.QueriedParams = response + response, err := aclv2.GetAssetByID(assetTestFixture.AssetIndex).Do(context.Background()) + assetTestFixture.QueriedParams = response.Params return err } func failToGetAssetInfo() error { - _, err := acl.AssetInformation(assetTestFixture.AssetIndex) + _, err := aclv2.GetAssetByID(assetTestFixture.AssetIndex).Do(context.Background()) if err != nil { return nil } @@ -1525,20 +1391,20 @@ func failToGetAssetInfo() error { func checkExpectedVsActualAssetParams() error { expectedParams := assetTestFixture.ExpectedParams actualParams := assetTestFixture.QueriedParams - nameMatch := expectedParams.AssetName == actualParams.AssetName + nameMatch := expectedParams.Name == actualParams.Name if !nameMatch { return fmt.Errorf("expected asset name was %v but actual asset name was %v", - expectedParams.AssetName, actualParams.AssetName) + expectedParams.Name, actualParams.Name) } unitMatch := expectedParams.UnitName == actualParams.UnitName if !unitMatch { return fmt.Errorf("expected unit name was %v but actual unit name was %v", expectedParams.UnitName, actualParams.UnitName) } - urlMatch := expectedParams.URL == actualParams.URL + urlMatch := expectedParams.Url == actualParams.Url if !urlMatch { return fmt.Errorf("expected URL was %v but actual URL was %v", - expectedParams.URL, actualParams.URL) + expectedParams.Url, actualParams.Url) } hashMatch := reflect.DeepEqual(expectedParams.MetadataHash, actualParams.MetadataHash) if !hashMatch { @@ -1555,37 +1421,47 @@ func checkExpectedVsActualAssetParams() error { return fmt.Errorf("expected default frozen state %v but actual default frozen state was %v", expectedParams.DefaultFrozen, actualParams.DefaultFrozen) } - managerMatch := expectedParams.ManagerAddr == actualParams.ManagerAddr + managerMatch := expectedParams.Manager == actualParams.Manager if !managerMatch { return fmt.Errorf("expected asset manager was %v but actual asset manager was %v", - expectedParams.ManagerAddr, actualParams.ManagerAddr) + expectedParams.Manager, actualParams.Manager) } - reserveMatch := expectedParams.ReserveAddr == actualParams.ReserveAddr + reserveMatch := expectedParams.Reserve == actualParams.Reserve if !reserveMatch { return fmt.Errorf("expected asset reserve was %v but actual asset reserve was %v", - expectedParams.ReserveAddr, actualParams.ReserveAddr) + expectedParams.Reserve, actualParams.Reserve) } - freezeMatch := expectedParams.FreezeAddr == actualParams.FreezeAddr + freezeMatch := expectedParams.Freeze == actualParams.Freeze if !freezeMatch { return fmt.Errorf("expected freeze manager was %v but actual freeze manager was %v", - expectedParams.FreezeAddr, actualParams.FreezeAddr) + expectedParams.Freeze, actualParams.Freeze) } - clawbackMatch := expectedParams.ClawbackAddr == actualParams.ClawbackAddr + clawbackMatch := expectedParams.Clawback == actualParams.Clawback if !clawbackMatch { return fmt.Errorf("expected revocation (clawback) manager was %v but actual revocation manager was %v", - expectedParams.ClawbackAddr, actualParams.ClawbackAddr) + expectedParams.Clawback, actualParams.Clawback) } return nil } +func findAssetID(assets []modelsV2.AssetHolding, assetID uint64) (foundAsset modelsV2.AssetHolding, err error) { + for _, asset := range assets { + if asset.AssetId == assetID { + return asset, nil + } + } + return modelsV2.AssetHolding{}, fmt.Errorf("asset ID %d was not found", assetID) +} + func theCreatorShouldHaveAssetsRemaining(expectedBal int) error { expectedBalance := uint64(expectedBal) - accountResp, err := acl.AccountInformation(assetTestFixture.Creator) + accountResp, err := aclv2.AccountInformation(assetTestFixture.Creator).Do(context.Background()) if err != nil { return err } - holding, ok := accountResp.Assets[assetTestFixture.AssetIndex] - if !ok { + // Find asset ID + holding, err := findAssetID(accountResp.Assets, assetTestFixture.AssetIndex) + if err != nil { return fmt.Errorf("attempted to get balance of account %v for creator %v and index %v, but no balance was found for that index", assetTestFixture.Creator, assetTestFixture.Creator, assetTestFixture.AssetIndex) } if holding.Amount != expectedBalance { @@ -1596,7 +1472,7 @@ func theCreatorShouldHaveAssetsRemaining(expectedBal int) error { func createAssetAcceptanceForSecondAccount() error { accountToUse := accounts[1] - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1611,7 +1487,7 @@ func createAssetAcceptanceForSecondAccount() error { func createAssetTransferTransactionToSecondAccount(amount int) error { recipient := accounts[1] creator := assetTestFixture.Creator - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1628,7 +1504,7 @@ func createAssetTransferTransactionToSecondAccount(amount int) error { func createAssetTransferTransactionFromSecondAccountToCreator(amount int) error { recipient := assetTestFixture.Creator sender := accounts[1] - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1645,7 +1521,7 @@ func createAssetTransferTransactionFromSecondAccountToCreator(amount int) error // sets up a freeze transaction, with freeze state `setting` against target account `target` // assumes creator is asset freeze manager func freezeTransactionHelper(target string, setting bool) error { - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -1666,7 +1542,7 @@ func createUnfreezeTransactionTargetingSecondAccount() error { } func createRevocationTransaction(amount int) error { - params, err := acl.BuildSuggestedParams() + params, err := aclv2.SuggestedParams().Do(context.Background()) if err != nil { return err } @@ -2505,7 +2381,13 @@ func theDecodedTransactionShouldEqualTheOriginal() error { return err } - // direct tx equality checking isn't fully implemented in go-sdk so this test is incomplete + // This test isn't perfect as it's sensitive to non-meaningful changes (e.g. nil slice vs 0 + // length slice), but it's good enough for now. We may want a Transaction.Equals method in the + // future. + if !reflect.DeepEqual(tx, decodedTx.Txn) { + return fmt.Errorf("Transactions unequal: %#v != %#v", tx, decodedTx.Txn) + } + return nil } diff --git a/transaction/transaction.go b/transaction/transaction.go index d112c60f..5c1ba7e8 100644 --- a/transaction/transaction.go +++ b/transaction/transaction.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/base64" "fmt" + "github.com/algorand/go-algorand-sdk/crypto" "github.com/algorand/go-algorand-sdk/encoding/msgpack" "github.com/algorand/go-algorand-sdk/types" diff --git a/types/block.go b/types/block.go index a751d687..d68baa5f 100644 --- a/types/block.go +++ b/types/block.go @@ -190,7 +190,7 @@ type ( // are a multiple of ConsensusParams.StateProofRounds. For blocks // that are not a multiple of ConsensusParams.StateProofRounds, // this value is zero. - StateProofVotersCommitment []byte `codec:"v"` + StateProofVotersCommitment GenericDigest `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). diff --git a/types/stateproof.go b/types/stateproof.go index c0f3d527..1aa78240 100644 --- a/types/stateproof.go +++ b/types/stateproof.go @@ -1,11 +1,212 @@ package types +import ( + "bytes" + "crypto/sha256" + "crypto/sha512" +) + // 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 +const ( + // StateProofBasic is our initial state proof setup. using falcon keys and subset-sum hash + StateProofBasic StateProofType = 0 + + // NumStateProofTypes is the max number of types of state proofs + // that we support. This is used as an allocation bound for a map + // containing different stateproof types in msgpack encoding. + NumStateProofTypes int = 1 + + // MaxReveals is a bound on allocation and on numReveals to limit log computation + MaxReveals int = 640 + + // MaxEncodedTreeDepth is the maximum tree depth (root only depth 0) for a tree which + // is being encoded (either by msbpack or by the fixed length encoding) + MaxEncodedTreeDepth = 16 + + // MaxNumLeavesOnEncodedTree is the maximum number of leaves allowed for a tree which + // is being encoded (either by msbpack or by the fixed length encoding) + MaxNumLeavesOnEncodedTree = 1 << MaxEncodedTreeDepth +) + +// GenericDigest is a digest that implements CustomSizeDigest, and can be used as hash output. +//msgp:allocbound GenericDigest MaxHashDigestSize +type GenericDigest []byte + +// ToSlice is used inside the Tree itself when interacting with TreeDigest +func (d GenericDigest) ToSlice() []byte { return d } + +// IsEqual compare two digests +func (d GenericDigest) IsEqual(other GenericDigest) bool { + return bytes.Equal(d, other) +} + +// IsEmpty checks wether the generic digest is an empty one or not +func (d GenericDigest) IsEmpty() bool { + return len(d) == 0 +} + +// Sumhash512DigestSize The size in bytes of the sumhash checksum +const Sumhash512DigestSize = 64 + +//size of each hash +const ( + Sha512_256Size = sha512.Size256 + SumhashDigestSize = Sumhash512DigestSize + Sha256Size = sha256.Size +) + +// HashType represents different hash functions +type HashType uint16 + +// types of hashes +const ( + Sha512_256 HashType = iota + Sumhash + Sha256 + MaxHashType +) + +// HashFactory is responsible for generating new hashes accordingly to the type it stores. +//msgp:postunmarshalcheck HashFactory Validate +type HashFactory struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + HashType HashType `codec:"t"` +} + +// Proof is used to convince a verifier about membership of leaves: h0,h1...hn +// at indexes i0,i1...in on a tree. The verifier has a trusted value of the tree +// root hash. +type Proof struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // Path is bounded by MaxNumLeavesOnEncodedTree since there could be multiple reveals, and + // given the distribution of the elt positions and the depth of the tree, + // the path length can increase up to 2^MaxEncodedTreeDepth / 2 + Path []GenericDigest `codec:"pth,allocbound=MaxNumLeavesOnEncodedTree/2"` + HashFactory HashFactory `codec:"hsh"` + // TreeDepth represents the depth of the tree that is being proven. + // It is the number of edges from the root to a leaf. + TreeDepth uint8 `codec:"td"` +} + +const MerkleSignatureSchemeRootSize = SumhashDigestSize + +// Commitment represents the root of the vector commitment tree built upon the MSS keys. +type Commitment [MerkleSignatureSchemeRootSize]byte + +// Verifier is used to verify a merklesignature.Signature produced by merklesignature.Secrets. +type Verifier struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Commitment Commitment `codec:"cmt"` + KeyLifetime uint64 `codec:"lf"` +} + +// A Participant corresponds to an account whose AccountData.Status +// is Online, and for which the expected sigRound satisfies +// AccountData.VoteFirstValid <= sigRound <= AccountData.VoteLastValid. +// +// In the Algorand ledger, it is possible for multiple accounts to have +// the same PK. Thus, the PK is not necessarily unique among Participants. +// However, each account will produce a unique Participant struct, to avoid +// potential DoS attacks where one account claims to have the same VoteID PK +// as another account. +type Participant struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // PK is the identifier used to verify the signature for a specific participant + PK Verifier `codec:"p"` + + // Weight is AccountData.MicroAlgos. + Weight uint64 `codec:"w"` +} + +// MerkleSignature represents a Falcon signature in a compressed-form +//msgp:allocbound MerkleSignature FalconMaxSignatureSize +type MerkleSignature []byte + +// SingleLeafProof is used to convince a verifier about membership of a specific +// leaf h at index i on a tree. The verifier has a trusted value of the tree +// root hash. it corresponds to merkle verification path. +type SingleLeafProof struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Proof +} + +// FalconPublicKeySize pulled out of falcon.go +const FalconPublicKeySize = 0x701 + +// FalconPublicKey is a wrapper for cfalcon.PublicKeySizey (used for packing) +type FalconPublicKey [FalconPublicKeySize]byte + +// FalconVerifier implements the type Verifier interface for the falcon signature scheme. +type FalconVerifier struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + PublicKey FalconPublicKey `codec:"k"` +} + +// FalconSignatureStruct represents a signature in the merkle signature scheme using falcon signatures as an underlying crypto scheme. +// It consists of an ephemeral public key, a signature, a merkle verification path and an index. +// The merkle signature considered valid only if the Signature is verified under the ephemeral public key and +// the Merkle verification path verifies that the ephemeral public key is located at the given index of the tree +// (for the root given in the long-term public key). +// More details can be found on Algorand's spec +type FalconSignatureStruct struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Signature MerkleSignature `codec:"sig"` + VectorCommitmentIndex uint64 `codec:"idx"` + Proof SingleLeafProof `codec:"prf"` + VerifyingKey FalconVerifier `codec:"vkey"` +} + +// A sigslotCommit is a single slot in the sigs array that forms the state proof. +type sigslotCommit struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // Sig is a signature by the participant on the expected message. + Sig FalconSignatureStruct `codec:"s"` + + // L is the total weight of signatures in lower-numbered slots. + // This is initialized once the builder has collected a sufficient + // number of signatures. + L uint64 `codec:"l"` +} + +// Reveal is a single array position revealed as part of a state +// proof. It reveals an element of the signature array and +// the corresponding element of the participants array. +type Reveal struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + SigSlot sigslotCommit `codec:"s"` + Part Participant `codec:"p"` +} + +// StateProof represents a proof on Algorand's state. +type StateProof struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + SigCommit GenericDigest `codec:"c"` + SignedWeight uint64 `codec:"w"` + SigProofs Proof `codec:"S"` + PartProofs Proof `codec:"P"` + MerkleSignatureSaltVersion byte `codec:"v"` + // Reveals is a sparse map from the position being revealed + // to the corresponding elements from the sigs and participants + // arrays. + Reveals map[uint64]Reveal `codec:"r,allocbound=MaxReveals"` + PositionsToReveal []uint64 `codec:"pr,allocbound=MaxReveals"` +} + // 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 @@ -25,6 +226,6 @@ type StateProofTxnFields struct { _struct struct{} `codec:",omitempty,omitemptyarray"` StateProofType StateProofType `codec:"sptype"` - StateProof interface{} `codec:"sp"` + StateProof StateProof `codec:"sp"` Message Message `codec:"spmsg"` }