diff --git a/cmd/goal/clerk.go b/cmd/goal/clerk.go index e324ad6d93..b9de36a627 100644 --- a/cmd/goal/clerk.go +++ b/cmd/goal/clerk.go @@ -24,6 +24,7 @@ import ( "os" "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" @@ -61,8 +62,8 @@ func init() { sendCmd.Flags().StringVarP(&toAddress, "to", "t", "", "Address to send to money to (required)") sendCmd.Flags().Uint64VarP(&amount, "amount", "a", 0, "The amount to be transferred (required), in microAlgos") sendCmd.Flags().Uint64Var(&fee, "fee", 0, "The transaction fee (automatically determined by default), in microAlgos") - sendCmd.Flags().Uint64Var(&firstValid, "firstvalid", 0, "The first round where the transaction may be committed to the ledger (currently ignored)") - sendCmd.Flags().Uint64Var(&lastValid, "lastvalid", 0, "The last round where the transaction may be committed to the ledger (currently ignored)") + sendCmd.Flags().Uint64Var(&firstValid, "firstvalid", 0, "The first round where the transaction may be committed to the ledger") + sendCmd.Flags().Uint64Var(&lastValid, "lastvalid", 0, "The last round where the transaction may be committed to the ledger") sendCmd.Flags().StringVar(¬eBase64, "noteb64", "", "Note (URL-base64 encoded)") sendCmd.Flags().StringVarP(¬eText, "note", "n", "", "Note text (ignored if --noteb64 used also)") sendCmd.Flags().StringVarP(&txFilename, "out", "o", "", "Dump an unsigned tx to the given file. In order to dump a signed transaction, pass -s") @@ -145,7 +146,7 @@ var sendCmd = &cobra.Command{ if txFilename == "" { // Sign and broadcast the tx wh, pw := ensureWalletHandleMaybePassword(dataDir, walletName, true) - tx, err := client.SendPaymentFromWallet(wh, pw, fromAddressResolved, toAddressResolved, fee, amount, noteBytes, closeToAddressResolved) + tx, err := client.SendPaymentFromWallet(wh, pw, fromAddressResolved, toAddressResolved, fee, amount, noteBytes, closeToAddressResolved, basics.Round(firstValid), basics.Round(lastValid)) // update information from Transaction txid := tx.ID().String() @@ -191,7 +192,7 @@ var sendCmd = &cobra.Command{ } } } else { - payment, err := client.ConstructPayment(fromAddressResolved, toAddressResolved, fee, amount, noteBytes, closeToAddressResolved) + payment, err := client.ConstructPayment(fromAddressResolved, toAddressResolved, fee, amount, noteBytes, closeToAddressResolved, basics.Round(firstValid), basics.Round(lastValid)) if err != nil { reportErrorf(errorConstructingTX, err) } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 97ca13e569..f4d8f6fa52 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -428,13 +428,17 @@ type MultisigInfo struct { } // SendPaymentFromWallet signs a transaction using the given wallet and returns the resulted transaction id -func (c *Client) SendPaymentFromWallet(walletHandle, pw []byte, from, to string, fee, amount uint64, note []byte, closeTo string) (transactions.Transaction, error) { +func (c *Client) SendPaymentFromWallet(walletHandle, pw []byte, from, to string, fee, amount uint64, note []byte, closeTo string, firstValid, lastValid basics.Round) (transactions.Transaction, error) { // Build the transaction - tx, err := c.ConstructPayment(from, to, fee, amount, note, closeTo) + tx, err := c.ConstructPayment(from, to, fee, amount, note, closeTo, firstValid, lastValid) if err != nil { return transactions.Transaction{}, err } + return c.signAndBroadcastTransactionWithWallet(walletHandle, pw, tx) +} + +func (c *Client) signAndBroadcastTransactionWithWallet(walletHandle, pw []byte, tx transactions.Transaction) (transactions.Transaction, error) { // Sign the transaction kmd, err := c.ensureKmdClient() if err != nil { @@ -467,7 +471,9 @@ func (c *Client) SendPaymentFromWallet(walletHandle, pw []byte, from, to string, // ConstructPayment builds a payment transaction to be signed // If the fee is 0, the function will use the suggested one form the network -func (c *Client) ConstructPayment(from, to string, fee, amount uint64, note []byte, closeTo string) (transactions.Transaction, error) { +// if the lastValid is 0, firstValid + maxTxnLifetime will be used +// if the firstValid is 0, lastRound + 1 will be used +func (c *Client) ConstructPayment(from, to string, fee, amount uint64, note []byte, closeTo string, firstValid, lastValid basics.Round) (transactions.Transaction, error) { fromAddr, err := basics.UnmarshalChecksumAddress(from) if err != nil { return transactions.Transaction{}, err @@ -484,15 +490,25 @@ func (c *Client) ConstructPayment(from, to string, fee, amount uint64, note []by return transactions.Transaction{}, err } - round := params.LastRound cp := config.Consensus[protocol.ConsensusVersion(params.ConsensusVersion)] + if firstValid == 0 && lastValid == 0 { + firstValid = basics.Round(params.LastRound + 1) + lastValid = firstValid + basics.Round(cp.MaxTxnLife) + } else if firstValid != 0 && lastValid == 0 { + lastValid = firstValid + basics.Round(cp.MaxTxnLife) + } else if firstValid > lastValid { + return transactions.Transaction{}, fmt.Errorf("cannot construct payment: txn would first be valid on round %d which is after last valid round %d", firstValid, lastValid) + } else if lastValid-firstValid > basics.Round(cp.MaxTxnLife) { + return transactions.Transaction{}, fmt.Errorf("cannot construct payment: txn validity period ( %d to %d ) is greater than protocol max txn lifetime %d ", firstValid, lastValid, cp.MaxTxnLife) + } + tx := transactions.Transaction{ Type: protocol.PaymentTx, Header: transactions.Header{ Sender: fromAddr, Fee: basics.MicroAlgos{Raw: fee}, - FirstValid: basics.Round(round), - LastValid: basics.Round(round) + basics.Round(cp.MaxTxnLife), + FirstValid: firstValid, + LastValid: lastValid, Note: note, }, PaymentTxnFields: transactions.PaymentTxnFields{ diff --git a/libgoal/unencryptedWallet.go b/libgoal/unencryptedWallet.go index 943e0c20cb..2fce2396ec 100644 --- a/libgoal/unencryptedWallet.go +++ b/libgoal/unencryptedWallet.go @@ -35,7 +35,7 @@ func (c *Client) SendPaymentFromUnencryptedWallet(from, to string, fee, amount u return transactions.Transaction{}, err } - return c.SendPaymentFromWallet(wh, nil, from, to, fee, amount, note, "") + return c.SendPaymentFromWallet(wh, nil, from, to, fee, amount, note, "", 0, 0) } // GetUnencryptedWalletHandle returns the unencrypted wallet handle. If there diff --git a/test/e2e-go/cli/perf/payment_test.go b/test/e2e-go/cli/perf/payment_test.go index 0aeb23c96e..c16293430c 100644 --- a/test/e2e-go/cli/perf/payment_test.go +++ b/test/e2e-go/cli/perf/payment_test.go @@ -58,7 +58,7 @@ func BenchmarkSendPayment(b *testing.B) { for i := 0; i < b.N; i++ { var nonce [8]byte crypto.RandBytes(nonce[:]) - tx, err = c.ConstructPayment(addr, addr, 1, 1, nonce[:], "") + tx, err = c.ConstructPayment(addr, addr, 1, 1, nonce[:], "", 0, 0) require.NoError(b, err) } }) @@ -74,7 +74,7 @@ func BenchmarkSendPayment(b *testing.B) { for i := 0; i < b.N; i++ { var nonce [8]byte crypto.RandBytes(nonce[:]) - _, err := c.SendPaymentFromWallet(wallet, nil, addr, addr, 1, 1, nonce[:], "") + _, err := c.SendPaymentFromWallet(wallet, nil, addr, addr, 1, 1, nonce[:], "", 0, 0) require.NoError(b, err) } }) diff --git a/test/e2e-go/features/multisig/multisig_test.go b/test/e2e-go/features/multisig/multisig_test.go index b13ca6d791..c09f8564ee 100644 --- a/test/e2e-go/features/multisig/multisig_test.go +++ b/test/e2e-go/features/multisig/multisig_test.go @@ -71,7 +71,7 @@ func TestBasicMultisig(t *testing.T) { fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, minTxnFee, fundingAddr, multisigAddr) // try to transact with 1 of 3 amountToSend := minAcctBalance - unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, nil, "") + unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, nil, "", 0, 0) r.NoError(err, "Unexpected error when constructing payment transaction") emptyPartial := crypto.MultisigSig{} emptySignature := crypto.Signature{} @@ -90,7 +90,7 @@ func TestBasicMultisig(t *testing.T) { r.True(fixture.WaitForTxnConfirmation(curStatus.LastRound+uint64(5), multisigAddr, txid)) // Need a new txid to avoid dup detection - unsignedTransaction, err = client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, []byte("foobar"), "") + unsignedTransaction, err = client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, []byte("foobar"), "", 0, 0) r.NoError(err, "Unexpected error when constructing payment transaction") signatureWithOne, err = client.UnencryptedMultisigSignTransaction(unsignedTransaction, addrs[0], emptyPartial) r.NoError(err, "first signing returned error") @@ -196,7 +196,7 @@ func TestDuplicateKeys(t *testing.T) { fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, txnFee, fundingAddr, multisigAddr) // try to transact with "1" signature (though, this is a signature from "every" member of the multisig) amountToSend := minAcctBalance - unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], txnFee, amountToSend, nil, "") + unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], txnFee, amountToSend, nil, "", 0, 0) r.NoError(err, "Unexpected error when constructing payment transaction") emptyPartial := crypto.MultisigSig{} emptySignature := crypto.Signature{} diff --git a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go index 35a9144c6b..cf7956deef 100644 --- a/test/e2e-go/features/participation/onlineOfflineParticipation_test.go +++ b/test/e2e-go/features/participation/onlineOfflineParticipation_test.go @@ -59,7 +59,7 @@ func TestParticipationKeyOnlyAccountParticipatesCorrectly(t *testing.T) { amountToSend := uint64(10000) // arbitrary wh, err := client.GetUnencryptedWalletHandle() a.NoError(err, "should get unencrypted wallet handle") - _, err = client.SendPaymentFromWallet(wh, nil, partkeyOnlyAccount, richAccount, amountToSend, transactionFee, nil, "") + _, err = client.SendPaymentFromWallet(wh, nil, partkeyOnlyAccount, richAccount, amountToSend, transactionFee, nil, "", basics.Round(0), basics.Round(0)) a.Error(err, "attempt to send money from partkey-only account should be treated as though wallet is not controlled") // partkeyonly_account attempts to go offline, should fail (no rootkey to sign txn with) goOfflineUTx, err := client.MakeUnsignedGoOfflineTx(partkeyOnlyAccount, 0, 0, transactionFee) diff --git a/test/e2e-go/features/transactions/close_account_test.go b/test/e2e-go/features/transactions/close_account_test.go index 0b24f26fca..74656076c2 100644 --- a/test/e2e-go/features/transactions/close_account_test.go +++ b/test/e2e-go/features/transactions/close_account_test.go @@ -62,7 +62,7 @@ func TestAccountsCanClose(t *testing.T) { a.NoError(err) fixture.WaitForConfirmedTxn(status.LastRound+10, baseAcct, tx.ID().String()) - tx, err = client.SendPaymentFromWallet(walletHandle, nil, acct0, acct1, 1, 100000, nil, acct2) + tx, err = client.SendPaymentFromWallet(walletHandle, nil, acct0, acct1, 1, 100000, nil, acct2, 0, 0) a.NoError(err) fixture.WaitForConfirmedTxn(status.LastRound+10, acct0, tx.ID().String()) diff --git a/test/e2e-go/restAPI/restClient_test.go b/test/e2e-go/restAPI/restClient_test.go index 4ee5a479aa..fac1ab9b95 100644 --- a/test/e2e-go/restAPI/restClient_test.go +++ b/test/e2e-go/restAPI/restClient_test.go @@ -193,7 +193,7 @@ func TestTransactionsByAddr(t *testing.T) { t.Error("no addr with funds") } toAddress := getDestAddr(t, testClient, addresses, someAddress, wh) - tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "") + tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "", 0, 0) require.NoError(t, err) txID := tx.ID() rnd, err := testClient.Status() @@ -256,7 +256,7 @@ func TestClientRejectsBadFromAddressWhenSending(t *testing.T) { require.NoError(t, err) badAccountAddress := "This is absolutely not a valid account address." goodAccountAddress := addresses[0] - _, err = testClient.SendPaymentFromWallet(wh, nil, badAccountAddress, goodAccountAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, badAccountAddress, goodAccountAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -269,7 +269,7 @@ func TestClientRejectsBadToAddressWhenSending(t *testing.T) { require.NoError(t, err) badAccountAddress := "This is absolutely not a valid account address." goodAccountAddress := addresses[0] - _, err = testClient.SendPaymentFromWallet(wh, nil, goodAccountAddress, badAccountAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, goodAccountAddress, badAccountAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -289,7 +289,7 @@ func TestClientRejectsMutatedFromAddressWhenSending(t *testing.T) { require.NoError(t, err) } mutatedAccountAddress := mutateStringAtIndex(unmutatedAccountAddress, 0) - _, err = testClient.SendPaymentFromWallet(wh, nil, mutatedAccountAddress, goodAccountAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, mutatedAccountAddress, goodAccountAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -309,7 +309,7 @@ func TestClientRejectsMutatedToAddressWhenSending(t *testing.T) { require.NoError(t, err) } mutatedAccountAddress := mutateStringAtIndex(unmutatedAccountAddress, 0) - _, err = testClient.SendPaymentFromWallet(wh, nil, goodAccountAddress, mutatedAccountAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, goodAccountAddress, mutatedAccountAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -322,7 +322,7 @@ func TestClientRejectsSendingMoneyFromAccountForWhichItHasNoKey(t *testing.T) { require.NoError(t, err) goodAccountAddress := addresses[0] nodeDoesNotHaveKeyForThisAddress := "NJY27OQ2ZXK6OWBN44LE4K43TA2AV3DPILPYTHAJAMKIVZDWTEJKZJKO4A" - _, err = testClient.SendPaymentFromWallet(wh, nil, nodeDoesNotHaveKeyForThisAddress, goodAccountAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, nodeDoesNotHaveKeyForThisAddress, goodAccountAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -344,7 +344,7 @@ func TestClientOversizedNote(t *testing.T) { } maxTxnNoteBytes := config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnNoteBytes note := make([]byte, maxTxnNoteBytes+1) - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, 100000, note, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, 100000, note, "", 0, 0) require.Error(t, err) } @@ -363,7 +363,7 @@ func TestClientCanSendAndGetNote(t *testing.T) { toAddress := getDestAddr(t, testClient, addresses, someAddress, wh) maxTxnNoteBytes := config.Consensus[protocol.ConsensusCurrentVersion].MaxTxnNoteBytes note := make([]byte, maxTxnNoteBytes) - tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, note, "") + tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, note, "", 0, 0) require.NoError(t, err) txStatus, err := waitForTransaction(t, testClient, someAddress, tx.ID().String(), 15*time.Second) require.NoError(t, err) @@ -383,7 +383,7 @@ func TestClientCanGetTransactionStatus(t *testing.T) { t.Error("no addr with funds") } toAddress := getDestAddr(t, testClient, addresses, someAddress, wh) - tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "") + tx, err := testClient.SendPaymentFromWallet(wh, nil, someAddress, toAddress, 10000, 100000, nil, "", 0, 0) t.Log(string(protocol.EncodeJSON(tx))) require.NoError(t, err) t.Log(tx.ID().String()) @@ -430,22 +430,22 @@ func TestSendingTooMuchFails(t *testing.T) { fromBalance, err := testClient.GetBalance(fromAddress) require.NoError(t, err) // too much amount - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, fromBalance+100, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, fromBalance+100, nil, "", 0, 0) t.Log(err) require.Error(t, err) // waaaay too much amount - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, math.MaxUint64, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, math.MaxUint64, nil, "", 0, 0) t.Log(err) require.Error(t, err) // too much fee - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, fromBalance+100, 10000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, fromBalance+100, 10000, nil, "", 0, 0) t.Log(err) require.Error(t, err) // waaaay too much fee - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, math.MaxUint64, 10000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, math.MaxUint64, 10000, nil, "", 0, 0) t.Log(err) require.Error(t, err) } @@ -482,7 +482,7 @@ func TestSendingFromEmptyAccountFails(t *testing.T) { toAddress, err = testClient.GenerateAddress(wh) require.NoError(t, err) } - _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, 100000, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, fromAddress, toAddress, 10000, 100000, nil, "", 0, 0) require.Error(t, err) } @@ -511,7 +511,7 @@ func TestSendingTooLittleToEmptyAccountFails(t *testing.T) { if someAddress == "" { t.Error("no addr with funds") } - _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, 1, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, 1, nil, "", 0, 0) require.Error(t, err) } @@ -531,7 +531,7 @@ func TestSendingLowFeeFails(t *testing.T) { t.Errorf("balance too low %d < %d", someBal, sendAmount) } toAddress := getDestAddr(t, testClient, addresses, someAddress, wh) - utx, err := testClient.ConstructPayment(someAddress, toAddress, 1, sendAmount, nil, "") + utx, err := testClient.ConstructPayment(someAddress, toAddress, 1, sendAmount, nil, "", 0, 0) require.NoError(t, err) utx.Fee.Raw = 1 stx, err := testClient.SignTransactionWithWallet(wh, nil, utx) @@ -586,7 +586,7 @@ func TestSendingNotClosingAccountFails(t *testing.T) { t.Error("no addr with funds") } amt := someBal - 10000 - 1 - _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, amt, nil, "") + _, err = testClient.SendPaymentFromWallet(wh, nil, someAddress, emptyAddress, 10000, amt, nil, "", 0, 0) require.Error(t, err) } diff --git a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go index 8ace23ae54..acd2666735 100644 --- a/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go +++ b/test/e2e-go/stress/transactions/createManyAndGoOnline_test.go @@ -36,7 +36,7 @@ func cascadeCreateAndFundAccounts(amountToSend, transactionFee uint64, fundingAc a.NoError(err, "should be able to get unencrypted wallet handle") newAddress, err := client.GenerateAddress(wh) a.NoError(err, "should be able to generate new address") - tx, err := client.SendPaymentFromWallet(wh, nil, fundingAccount, newAddress, transactionFee, amountToSend, nil, "") + tx, err := client.SendPaymentFromWallet(wh, nil, fundingAccount, newAddress, transactionFee, amountToSend, nil, "", 0, 0) a.NoError(err, "should be no errors when funding new accounts, send number %v", i) i++ outputTxidsToAccounts[tx.ID().String()] = newAddress diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 8be8d6d1fa..de7ab81131 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -276,7 +276,7 @@ func (f *RestClientFixture) SendMoneyAndWait(curRound, amountToSend, transaction // SendMoneyAndWaitFromWallet is as above, but for a specific wallet func (f *RestClientFixture) SendMoneyAndWaitFromWallet(walletHandle, walletPassword []byte, curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string) (fundingTxid string) { client := f.LibGoalClient - fundingTx, err := client.SendPaymentFromWallet(walletHandle, walletPassword, fromAccount, toAccount, transactionFee, amountToSend, nil, "") + fundingTx, err := client.SendPaymentFromWallet(walletHandle, walletPassword, fromAccount, toAccount, transactionFee, amountToSend, nil, "", 0, 0) require.NoError(f.t, err, "client should be able to send money from rich to poor account") require.NotEmpty(f.t, fundingTx.ID().String(), "transaction ID should not be empty") waitingDeadline := curRound + uint64(5)