From c63bf591d6e3bd3a6aec8c4083cb86db9220d28e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Thu, 16 Mar 2023 17:28:22 -0400 Subject: [PATCH 01/12] update libgoal restclient stuffs --- cmd/goal/application.go | 2 +- cmd/goal/clerk.go | 52 +++++++++++++++++++++++++++ daemon/algod/api/client/restClient.go | 8 ++++- libgoal/libgoal.go | 22 +++++++++--- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/cmd/goal/application.go b/cmd/goal/application.go index 0920793300..0c5e9e0cbf 100644 --- a/cmd/goal/application.go +++ b/cmd/goal/application.go @@ -1063,7 +1063,7 @@ var infoAppCmd = &cobra.Command{ } // populateMethodCallTxnArgs parses and loads transactions from the files indicated by the values -// slice. An error will occur if the transaction does not matched the expected type, it has a nonzero +// slice. An error will occur if the transaction does not match the expected type, it has a nonzero // group ID, or if it is signed by a normal signature or Msig signature (but not Lsig signature) func populateMethodCallTxnArgs(types []string, values []string) ([]transactions.SignedTxn, error) { loadedTxns := make([]transactions.SignedTxn, len(values)) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 2147828947..6951348047 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -76,6 +76,8 @@ func init() { clerkCmd.AddCommand(compileCmd) clerkCmd.AddCommand(dryrunCmd) clerkCmd.AddCommand(dryrunRemoteCmd) + clerkCmd.AddCommand(simulateCmd) + clerkCmd.AddCommand(simulateRemoteCmd) // Wallet to be used for the clerk operation clerkCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation") @@ -1253,6 +1255,56 @@ var dryrunRemoteCmd = &cobra.Command{ }, } +var simulateCmd = &cobra.Command{ + Use: "simulate", + Short: "Simulate a transaction or transaction group offline", + Long: `Simulate a transaction or transaction group offline under various conditions and verbosity.`, + Run: func(cmd *cobra.Command, args []string) { + data, err := readFile(txFilename) + if err != nil { + reportErrorf(fileReadError, txFilename, err) + } + dec := protocol.NewMsgpDecoderBytes(data) + stxns := make([]transactions.SignedTxn, 0, 16) + for { + var txn transactions.SignedTxn + err = dec.Decode(&txn) + if err == io.EOF { + break + } + if err != nil { + reportErrorf(txDecodeError, txFilename, err) + } + stxns = append(stxns, txn) + } + + }, +} + +var simulateRemoteCmd = &cobra.Command{ + Use: "simulate-remote", + Short: "Simulate a transaction or transaction group with algod's simulate REST endpoint", + Long: `Simulate a transaction or transaction group with algod's simulate REST endpoint under various conditions and verbosity.`, + Run: func(cmd *cobra.Command, args []string) { + data, err := readFile(txFilename) + if err != nil { + reportErrorf(fileReadError, txFilename, err) + } + + dataDir := datadir.EnsureSingleDataDir() + client := ensureFullClient(dataDir) + resp, err := client.MakeTransactionSimulation(data) + if err != nil { + reportErrorf("simulate-remote: %s", err.Error()) + } + if rawOutput { + fmt.Fprintf(os.Stdout, string(protocol.EncodeJSON(&resp))) + return + } + + }, +} + // unmarshalSlice converts string addresses to basics.Address func unmarshalSlice(accts []string) ([]basics.Address, error) { result := make([]basics.Address, 0, len(accts)) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index f3f5415734..76e8245754 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -284,7 +284,7 @@ func (client RestClient) WaitForBlock(round basics.Round) (response model.NodeSt return } -// HealthCheck does a health check on the the potentially running node, +// HealthCheck does a health check on the potentially running node, // returning an error if the API is down func (client RestClient) HealthCheck() error { return client.get(nil, "/health", nil) @@ -629,6 +629,12 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } +// RawTransactionSimulate gets the raw transaction or raw transaction group, and returns relevant simulation results. +func (client RestClient) RawTransactionSimulate(data []byte) (response model.SimulateResponse, err error) { + err = client.post(&response, "/v2/transactions/simulate", data, false) + return +} + // StateProofs gets a state proof that covers a given round func (client RestClient) StateProofs(round uint64) (response model.StateProofResponse, err error) { err = client.get(&response, fmt.Sprintf("/v2/stateproofs/%d", round), nil) diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 4756aa155f..4d818f850f 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -504,11 +504,10 @@ func (c *Client) signAndBroadcastTransactionWithWallet(walletHandle, pw []byte, // // validRounds | lastValid | result (lastValid) // ------------------------------------------------- -// 0 | 0 | firstValid + maxTxnLife -// 0 | N | lastValid -// M | 0 | first + validRounds - 1 -// M | M | error -// +// 0 | 0 | firstValid + maxTxnLife +// 0 | N | lastValid +// M | 0 | first + validRounds - 1 +// M | M | error func (c *Client) ComputeValidityRounds(firstValid, lastValid, validRounds uint64) (first, last, latest uint64, err error) { params, err := c.cachedSuggestedParams() if err != nil { @@ -1270,6 +1269,19 @@ func (c *Client) Dryrun(data []byte) (resp model.DryrunResponse, err error) { return } +// MakeTransactionSimulation takes raw transaction or raw transaction group, and returns relevant simulation results. +func (c *Client) MakeTransactionSimulation(data []byte) (resp model.SimulateResponse, err error) { + algod, err := c.ensureAlgodClient() + if err != nil { + return + } + resp, err = algod.RawTransactionSimulate(data) + if err != nil { + return + } + return +} + // TransactionProof returns a Merkle proof for a transaction in a block. func (c *Client) TransactionProof(txid string, round uint64, hashType crypto.HashType) (resp model.TransactionProofResponse, err error) { algod, err := c.ensureAlgodClient() From c808575692573b97d27dfcc4d59faa86ff6d3968 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 17 Mar 2023 17:51:36 -0400 Subject: [PATCH 02/12] prototyped one --- cmd/goal/clerk.go | 44 ++++++++++++-------------------------------- libgoal/libgoal.go | 4 ++-- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 6951348047..9cd414d9bb 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -77,7 +77,6 @@ func init() { clerkCmd.AddCommand(dryrunCmd) clerkCmd.AddCommand(dryrunRemoteCmd) clerkCmd.AddCommand(simulateCmd) - clerkCmd.AddCommand(simulateRemoteCmd) // Wallet to be used for the clerk operation clerkCmd.PersistentFlags().StringVarP(&walletName, "wallet", "w", "", "Set the wallet to be used for the selected operation") @@ -145,6 +144,9 @@ func init() { dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "output raw response from algod") dryrunRemoteCmd.MarkFlagRequired("dryrun-state") + simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "transaction or transaction-group to test") + simulateCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing simulation result") + panicIfErr(simulateCmd.MarkFlagRequired("txfile")) } var clerkCmd = &cobra.Command{ @@ -1259,32 +1261,6 @@ var simulateCmd = &cobra.Command{ Use: "simulate", Short: "Simulate a transaction or transaction group offline", Long: `Simulate a transaction or transaction group offline under various conditions and verbosity.`, - Run: func(cmd *cobra.Command, args []string) { - data, err := readFile(txFilename) - if err != nil { - reportErrorf(fileReadError, txFilename, err) - } - dec := protocol.NewMsgpDecoderBytes(data) - stxns := make([]transactions.SignedTxn, 0, 16) - for { - var txn transactions.SignedTxn - err = dec.Decode(&txn) - if err == io.EOF { - break - } - if err != nil { - reportErrorf(txDecodeError, txFilename, err) - } - stxns = append(stxns, txn) - } - - }, -} - -var simulateRemoteCmd = &cobra.Command{ - Use: "simulate-remote", - Short: "Simulate a transaction or transaction group with algod's simulate REST endpoint", - Long: `Simulate a transaction or transaction group with algod's simulate REST endpoint under various conditions and verbosity.`, Run: func(cmd *cobra.Command, args []string) { data, err := readFile(txFilename) if err != nil { @@ -1293,15 +1269,19 @@ var simulateRemoteCmd = &cobra.Command{ dataDir := datadir.EnsureSingleDataDir() client := ensureFullClient(dataDir) - resp, err := client.MakeTransactionSimulation(data) + resp, err := client.TransactionSimulation(data) if err != nil { - reportErrorf("simulate-remote: %s", err.Error()) + reportErrorf("simulation error: %s", err.Error()) } - if rawOutput { + + if outFilename != "" { + err = writeFile(outFilename, protocol.EncodeJSON(&resp), 0600) + if err != nil { + reportErrorf("write file error: %s", err.Error()) + } + } else { fmt.Fprintf(os.Stdout, string(protocol.EncodeJSON(&resp))) - return } - }, } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 4d818f850f..a68604e077 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1269,8 +1269,8 @@ func (c *Client) Dryrun(data []byte) (resp model.DryrunResponse, err error) { return } -// MakeTransactionSimulation takes raw transaction or raw transaction group, and returns relevant simulation results. -func (c *Client) MakeTransactionSimulation(data []byte) (resp model.SimulateResponse, err error) { +// TransactionSimulation takes raw transaction or raw transaction group, and returns relevant simulation results. +func (c *Client) TransactionSimulation(data []byte) (resp model.SimulateResponse, err error) { algod, err := c.ensureAlgodClient() if err != nil { return From e2bb71d19ea4c7e48b40f472539b6c75d75a0010 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 17 Mar 2023 18:20:16 -0400 Subject: [PATCH 03/12] bark bark lint dog --- cmd/goal/clerk.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 9cd414d9bb..07859d2e78 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -1280,7 +1280,7 @@ var simulateCmd = &cobra.Command{ reportErrorf("write file error: %s", err.Error()) } } else { - fmt.Fprintf(os.Stdout, string(protocol.EncodeJSON(&resp))) + fmt.Println(string(protocol.EncodeJSON(&resp))) } }, } From 0ca45a2bae83a63c30e44448922c983bf4e42e1e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 17 Mar 2023 19:06:33 -0400 Subject: [PATCH 04/12] add an entry for rawRequestPaths --- daemon/algod/api/client/restClient.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 76e8245754..b3cc9ce49a 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -45,10 +45,11 @@ const ( // rawRequestPaths is a set of paths where the body should not be urlencoded var rawRequestPaths = map[string]bool{ - "/v2/transactions": true, - "/v2/teal/dryrun": true, - "/v2/teal/compile": true, - "/v2/participation": true, + "/v2/transactions": true, + "/v2/teal/dryrun": true, + "/v2/teal/compile": true, + "/v2/participation": true, + "/v2/transactions/simulate": true, } // unauthorizedRequestError is generated when we receive 401 error from the server. This error includes the inner error @@ -631,7 +632,7 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { // RawTransactionSimulate gets the raw transaction or raw transaction group, and returns relevant simulation results. func (client RestClient) RawTransactionSimulate(data []byte) (response model.SimulateResponse, err error) { - err = client.post(&response, "/v2/transactions/simulate", data, false) + err = client.submitForm(&response, "/v2/transactions/simulate", data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) return } @@ -676,7 +677,6 @@ func (client RestClient) GetParticipationKeyByID(participationID string) (respon func (client RestClient) RemoveParticipationKeyByID(participationID string) (err error) { err = client.delete(nil, fmt.Sprintf("/v2/participation/%s", participationID), nil, true) return - } /* Endpoint registered for follower nodes */ From 31dc89354513675b185a2bfdcb3aca7c7df440c4 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 17 Mar 2023 19:45:36 -0400 Subject: [PATCH 05/12] capitalize usage string of options --- cmd/goal/clerk.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index 07859d2e78..edea66e85f 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -89,7 +89,7 @@ func init() { sendCmd.Flags().StringVar(&rekeyToAddress, "rekey-to", "", "Rekey account to the given spending key/address. (Future transactions from this account will need to be signed with the new key.)") sendCmd.Flags().StringVarP(&programSource, "from-program", "F", "", "Program source to use as account logic") sendCmd.Flags().StringVarP(&progByteFile, "from-program-bytes", "P", "", "Program binary to use as account logic") - sendCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "base64 encoded args to pass to transaction logic") + sendCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "Base64 encoded args to pass to transaction logic") sendCmd.Flags().StringVarP(&logicSigFile, "logic-sig", "L", "", "LogicSig to apply to transaction") sendCmd.Flags().StringVar(&msigParams, "msig-params", "", "Multisig preimage parameters - [threshold] [Address 1] [Address 2] ...\nUsed to add the necessary fields in case the account was rekeyed to a multisig account") sendCmd.MarkFlagRequired("to") @@ -109,8 +109,8 @@ func init() { signCmd.Flags().StringVarP(&signerAddress, "signer", "S", "", "Address of key to sign with, if different from transaction \"from\" address due to rekeying") signCmd.Flags().StringVarP(&programSource, "program", "p", "", "Program source to use as account logic") signCmd.Flags().StringVarP(&logicSigFile, "logic-sig", "L", "", "LogicSig to apply to transaction") - signCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "base64 encoded args to pass to transaction logic") - signCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "consensus protocol version id string") + signCmd.Flags().StringSliceVar(&argB64Strings, "argb64", nil, "Base64 encoded args to pass to transaction logic") + signCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "Consensus protocol version id string") signCmd.MarkFlagRequired("infile") signCmd.MarkFlagRequired("outfile") @@ -124,27 +124,27 @@ func init() { splitCmd.MarkFlagRequired("infile") splitCmd.MarkFlagRequired("outfile") - compileCmd.Flags().BoolVarP(&disassemble, "disassemble", "D", false, "disassemble a compiled program") - compileCmd.Flags().BoolVarP(&noProgramOutput, "no-out", "n", false, "don't write contract program binary") - compileCmd.Flags().BoolVarP(&writeSourceMap, "map", "m", false, "write out source map") - compileCmd.Flags().BoolVarP(&signProgram, "sign", "s", false, "sign program, output is a binary signed LogicSig record") + compileCmd.Flags().BoolVarP(&disassemble, "disassemble", "D", false, "Disassemble a compiled program") + compileCmd.Flags().BoolVarP(&noProgramOutput, "no-out", "n", false, "Don't write contract program binary") + compileCmd.Flags().BoolVarP(&writeSourceMap, "map", "m", false, "Write out source map") + compileCmd.Flags().BoolVarP(&signProgram, "sign", "s", false, "Sign program, output is a binary signed LogicSig record") compileCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename to write program bytes or signed LogicSig to") compileCmd.Flags().StringVarP(&account, "account", "a", "", "Account address to sign the program (If not specified, uses default account)") - dryrunCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "transaction or transaction-group to test") - dryrunCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "consensus protocol version id string") + dryrunCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test") + dryrunCmd.Flags().StringVarP(&protoVersion, "proto", "P", "", "Consensus protocol version id string") dryrunCmd.Flags().BoolVar(&dumpForDryrun, "dryrun-dump", false, "Dump in dryrun format acceptable by dryrun REST api instead of running") dryrunCmd.Flags().Var(&dumpForDryrunFormat, "dryrun-dump-format", "Dryrun dump format: "+dumpForDryrunFormat.AllowedString()) - dryrunCmd.Flags().StringSliceVar(&dumpForDryrunAccts, "dryrun-accounts", nil, "additional accounts to include into dryrun request obj") + dryrunCmd.Flags().StringSliceVar(&dumpForDryrunAccts, "dryrun-accounts", nil, "Additional accounts to include into dryrun request obj") dryrunCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing dryrun state object") dryrunCmd.MarkFlagRequired("txfile") - dryrunRemoteCmd.Flags().StringVarP(&txFilename, "dryrun-state", "D", "", "dryrun request object to run") - dryrunRemoteCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "print more info") - dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "output raw response from algod") + dryrunRemoteCmd.Flags().StringVarP(&txFilename, "dryrun-state", "D", "", "Dryrun request object to run") + dryrunRemoteCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Print more info") + dryrunRemoteCmd.Flags().BoolVarP(&rawOutput, "raw", "r", false, "Output raw response from algod") dryrunRemoteCmd.MarkFlagRequired("dryrun-state") - simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "transaction or transaction-group to test") + simulateCmd.Flags().StringVarP(&txFilename, "txfile", "t", "", "Transaction or transaction-group to test") simulateCmd.Flags().StringVarP(&outFilename, "outfile", "o", "", "Filename for writing simulation result") panicIfErr(simulateCmd.MarkFlagRequired("txfile")) } From 5c5b32dca704aa59e8c8c51895893b34760ce1e2 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Fri, 17 Mar 2023 20:36:58 -0400 Subject: [PATCH 06/12] see if e2e test work --- test/scripts/e2e_subs/e2e-app-simulate.sh | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100755 test/scripts/e2e_subs/e2e-app-simulate.sh diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh new file mode 100755 index 0000000000..080c475a18 --- /dev/null +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +date '+app-simple-test start %Y%m%d_%H%M%S' + +set -e +set -x +set -o pipefail +set -o nounset +export SHELLOPTS + +WALLET=$1 + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx + +cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx +${gcmd} clerk split -i grouped.tx -o grouped.tx + +${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx +${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx + +cat grouped-0.stx grouped-1.stx > grouped.stx + +RES=$(${gcmd} clerk simulate -t grouped.stx) +EXPSUCCESS='"would-succeed": true' + +if [[ $RES != *"${EXPSUCCESS}"* ]]; then + date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' + false +fi \ No newline at end of file From c454f2c0a4c54db1ef80c350d5cb177e2d9cbd88 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 10:52:12 -0400 Subject: [PATCH 07/12] test more things on ABI method call simulation --- test/scripts/e2e_subs/e2e-app-simulate.sh | 54 ++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 080c475a18..4b2e719c23 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -10,6 +10,9 @@ export SHELLOPTS WALLET=$1 +# Directory of this bash program +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') @@ -31,4 +34,53 @@ EXPSUCCESS='"would-succeed": true' if [[ $RES != *"${EXPSUCCESS}"* ]]; then date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' false -fi \ No newline at end of file +fi + +####################################################### +# NOW WE TRY TO TEST SIMULATION WITH ABI METHOD CALLS # +####################################################### + +printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" +printf '#pragma version 3\nint 1' > "${TEMPDIR}/simple-v3.teal" + +# Real Create +RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) +EXPECTED="method create(uint64)uint64 succeeded with output: 2468" +if [[ $RES != *"${EXPECTED}"* ]]; then + date '+app-simulate-test FAIL the method call to create(uint64)uint64 should not fail %Y%m%d_%H%M%S' + false +fi + +APPID=$(echo "$RES" | grep Created | awk '{ print $6 }') + +# SIMULATION! empty()void +${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 -o empty.tx + +# SIMULATE without a signature first +RES=$(${gcmd} clerk simulate -t empty.tx) + +EXPFAIL='"would-succeed": false' + +FAIL_REASON_SIG_MISSING='"missing-signature": true' + +# confirm that without signature, the simulation should fail +if [[ $RES != *"${EXPFAIL}"* ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should not succeed %Y%m%d_%H%M%S' + false +fi + +# check again the simulation failing reason +if [[ $RES != *"${FAIL_REASON_SIG_MISSING}"* ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void without signature should fail with missing-signature %Y%m%d_%H%M%S' + false +fi + +# SIMULATE with a signature +${gcmd} clerk sign -i empty.tx -o empty.stx +RES=$(${gcmd} clerk simulate -t empty.stx) + +# with signature, simulation app-call should succeed +if [[ $RES != *"${EXPSUCCESS}"* ]]; then + date '+app-simulate-test FAIL the simulation call to empty()void should succeed %Y%m%d_%H%M%S' + false +fi From 9a0e6c9e2bee21134de63f6cb348fc9c01e781b0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 11:35:57 -0400 Subject: [PATCH 08/12] rewritten with jq, some more coverage on txn group --- test/scripts/e2e_subs/e2e-app-simulate.sh | 31 ++++++++++++++--------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index 4b2e719c23..dafb5cb2c5 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -17,10 +17,23 @@ gcmd="goal -w ${WALLET}" ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') +CONST_TRUE="true" +CONST_FALSE="false" + ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx + +# We first test transaction group simulation WITHOUT signatures +RES=$(${gcmd} clerk simulate -t grouped.tx | jq '."would-succeed"') + +if [[ $RES != $CONST_FALSE ]]; then + date '+app-simulate-test FAIL the simulation transaction group without signatures should not succeed %Y%m%d_%H%M%S' + false +fi + +# We then test transaction group simulation WITH signatures ${gcmd} clerk split -i grouped.tx -o grouped.tx ${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx @@ -28,10 +41,9 @@ ${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx cat grouped-0.stx grouped-1.stx > grouped.stx -RES=$(${gcmd} clerk simulate -t grouped.stx) -EXPSUCCESS='"would-succeed": true' +RES=$(${gcmd} clerk simulate -t grouped.stx | jq '."would-succeed"') -if [[ $RES != *"${EXPSUCCESS}"* ]]; then +if [[ $RES != $CONST_TRUE ]]; then date '+app-simulate-test FAIL should pass to simulate self pay transaction group %Y%m%d_%H%M%S' false fi @@ -41,7 +53,6 @@ fi ####################################################### printf '#pragma version 2\nint 1' > "${TEMPDIR}/simple-v2.teal" -printf '#pragma version 3\nint 1' > "${TEMPDIR}/simple-v3.teal" # Real Create RES=$(${gcmd} app method --method "create(uint64)uint64" --arg "1234" --create --approval-prog ${DIR}/tealprogs/app-abi-method-example.teal --clear-prog ${TEMPDIR}/simple-v2.teal --global-byteslices 0 --global-ints 0 --local-byteslices 1 --local-ints 0 --extra-pages 0 --from $ACCOUNT 2>&1 || true) @@ -59,28 +70,24 @@ ${gcmd} app method --method "empty()void" --app-id $APPID --from $ACCOUNT 2>&1 - # SIMULATE without a signature first RES=$(${gcmd} clerk simulate -t empty.tx) -EXPFAIL='"would-succeed": false' - -FAIL_REASON_SIG_MISSING='"missing-signature": true' - # confirm that without signature, the simulation should fail -if [[ $RES != *"${EXPFAIL}"* ]]; then +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void without signature should not succeed %Y%m%d_%H%M%S' false fi # check again the simulation failing reason -if [[ $RES != *"${FAIL_REASON_SIG_MISSING}"* ]]; then +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void without signature should fail with missing-signature %Y%m%d_%H%M%S' false fi # SIMULATE with a signature ${gcmd} clerk sign -i empty.tx -o empty.stx -RES=$(${gcmd} clerk simulate -t empty.stx) +RES=$(${gcmd} clerk simulate -t empty.stx | jq '."would-succeed"') # with signature, simulation app-call should succeed -if [[ $RES != *"${EXPSUCCESS}"* ]]; then +if [[ $RES != $CONST_TRUE ]]; then date '+app-simulate-test FAIL the simulation call to empty()void should succeed %Y%m%d_%H%M%S' false fi From 3e1772908f3643ceb818b3afca18da07e6f606bc Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 15:28:05 -0400 Subject: [PATCH 09/12] redundant error return --- libgoal/libgoal.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index a68604e077..dce8eb658e 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1276,9 +1276,6 @@ func (c *Client) TransactionSimulation(data []byte) (resp model.SimulateResponse return } resp, err = algod.RawTransactionSimulate(data) - if err != nil { - return - } return } From d10ed71057a26579777a7b8af28c26ed92d266b0 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 17:12:11 -0400 Subject: [PATCH 10/12] missing signature check --- test/scripts/e2e_subs/e2e-app-simulate.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index dafb5cb2c5..a1c15e8808 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -26,13 +26,25 @@ ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx # We first test transaction group simulation WITHOUT signatures -RES=$(${gcmd} clerk simulate -t grouped.tx | jq '."would-succeed"') +RES=$(${gcmd} clerk simulate -t grouped.tx) -if [[ $RES != $CONST_FALSE ]]; then +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then date '+app-simulate-test FAIL the simulation transaction group without signatures should not succeed %Y%m%d_%H%M%S' false fi +# check the simulation failing reason, first transaction has no signature +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation transaction group first transaction has NO signature %Y%m%d_%H%M%S' + false +fi + +# check the simulation failing reason, second transaction has no signature +if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[1]."missing-signature"') != $CONST_TRUE ]]; then + date '+app-simulate-test FAIL the simulation transaction group second transaction has NO signature %Y%m%d_%H%M%S' + false +fi + # We then test transaction group simulation WITH signatures ${gcmd} clerk split -i grouped.tx -o grouped.tx From 5a6212f6f3acc740d4f421e6e560b20a38b48290 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 17:33:48 -0400 Subject: [PATCH 11/12] overspend testing --- test/scripts/e2e_subs/e2e-app-simulate.sh | 38 +++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/test/scripts/e2e_subs/e2e-app-simulate.sh b/test/scripts/e2e_subs/e2e-app-simulate.sh index a1c15e8808..cae8ebeab3 100755 --- a/test/scripts/e2e_subs/e2e-app-simulate.sh +++ b/test/scripts/e2e_subs/e2e-app-simulate.sh @@ -20,6 +20,10 @@ ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') CONST_TRUE="true" CONST_FALSE="false" +############################################## +# WE FIRST TEST TRANSACTION GROUP SIMULATION # +############################################## + ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx ${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx @@ -35,13 +39,13 @@ fi # check the simulation failing reason, first transaction has no signature if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[0]."missing-signature"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the simulation transaction group first transaction has NO signature %Y%m%d_%H%M%S' + date '+app-simulate-test FAIL the simulation transaction group FAIL for first transaction has NO signature %Y%m%d_%H%M%S' false fi # check the simulation failing reason, second transaction has no signature if [[ $(echo "$RES" | jq '."txn-groups"[0]."txn-results"[1]."missing-signature"') != $CONST_TRUE ]]; then - date '+app-simulate-test FAIL the simulation transaction group second transaction has NO signature %Y%m%d_%H%M%S' + date '+app-simulate-test FAIL the simulation transaction group FAIL for second transaction has NO signature %Y%m%d_%H%M%S' false fi @@ -60,6 +64,36 @@ if [[ $RES != $CONST_TRUE ]]; then false fi +############################################### +# WE ALSO TEST OVERSPEND IN TRANSACTION GROUP # +############################################### + +${gcmd} clerk send -a 1000000000000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay1.tx +${gcmd} clerk send -a 10000 -f ${ACCOUNT} -t ${ACCOUNT} -o pay2.tx + +cat pay1.tx pay2.tx | ${gcmd} clerk group -i - -o grouped.tx + +${gcmd} clerk split -i grouped.tx -o grouped.tx + +${gcmd} clerk sign -i grouped-0.tx -o grouped-0.stx +${gcmd} clerk sign -i grouped-1.tx -o grouped-1.stx + +cat grouped-0.stx grouped-1.stx > grouped.stx + +RES=$(${gcmd} clerk simulate -t grouped.stx) + +if [[ $(echo "$RES" | jq '."would-succeed"') != $CONST_FALSE ]]; then + data '+app-simulate-test FAIL should FAIL for overspending in simulate self pay transaction group %Y%m%d_%H%M%S' + false +fi + +OVERSPEND_INFO="overspend" + +if [[ $(echo "$RES" | jq '."txn-groups"[0]."failure-message"') != *"$OVERSPEND_INFO"* ]]; then + data '+app-simulate-test FAIL first overspending transaction in transaction group should contain message OVERSPEND %Y%m%d_%H%M%S' + false +fi + ####################################################### # NOW WE TRY TO TEST SIMULATION WITH ABI METHOD CALLS # ####################################################### From 478d81629fdefab3996d94b81f09f96e5227e0b3 Mon Sep 17 00:00:00 2001 From: Hang Su Date: Mon, 20 Mar 2023 18:14:54 -0400 Subject: [PATCH 12/12] per pr comment --- cmd/goal/clerk.go | 4 ++-- daemon/algod/api/client/restClient.go | 4 ++-- libgoal/libgoal.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index edea66e85f..f044915fba 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -1259,8 +1259,8 @@ var dryrunRemoteCmd = &cobra.Command{ var simulateCmd = &cobra.Command{ Use: "simulate", - Short: "Simulate a transaction or transaction group offline", - Long: `Simulate a transaction or transaction group offline under various conditions and verbosity.`, + Short: "Simulate a transaction or transaction group with algod's simulate REST endpoint", + Long: `Simulate a transaction or transaction group with algod's simulate REST endpoint under various configurations.`, Run: func(cmd *cobra.Command, args []string) { data, err := readFile(txFilename) if err != nil { diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index b3cc9ce49a..c9bdb0a833 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -630,8 +630,8 @@ func (client RestClient) RawDryrun(data []byte) (response []byte, err error) { return } -// RawTransactionSimulate gets the raw transaction or raw transaction group, and returns relevant simulation results. -func (client RestClient) RawTransactionSimulate(data []byte) (response model.SimulateResponse, err error) { +// SimulateRawTransaction gets the raw transaction or raw transaction group, and returns relevant simulation results. +func (client RestClient) SimulateRawTransaction(data []byte) (response model.SimulateResponse, err error) { err = client.submitForm(&response, "/v2/transactions/simulate", data, "POST", false /* encodeJSON */, true /* decodeJSON */, false) return } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index dce8eb658e..5951793de3 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1275,7 +1275,7 @@ func (c *Client) TransactionSimulation(data []byte) (resp model.SimulateResponse if err != nil { return } - resp, err = algod.RawTransactionSimulate(data) + resp, err = algod.SimulateRawTransaction(data) return }