diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 0849a5df65..1749327ee3 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -336,6 +336,45 @@ func (client RestClient) WaitForRoundWithTimeout(roundToWaitFor uint64) error { return nil } +// WaitForConfirmedTxn waits until either the passed txid is confirmed +// or until the passed roundTimeout passes +// or until waiting for a round to pass times out +func (client RestClient) WaitForConfirmedTxn(roundTimeout uint64, txid string) (txn v2.PreEncodedTxInfo, err error) { + for { + // Get current round information + curStatus, statusErr := client.Status() + if err != nil { + return txn, statusErr + } + curRound := curStatus.LastRound + + // Check if we know about the transaction yet + var resp []byte + resp, err = client.RawPendingTransactionInformation(txid) + if err == nil { + err = protocol.DecodeReflect(resp, &txn) + if err != nil { + return txn, err + } + } + + // Check if transaction was confirmed + if txn.ConfirmedRound != nil && *txn.ConfirmedRound > 0 { + return + } + // Check if we should wait a round + if curRound > roundTimeout { + err = fmt.Errorf("failed to see confirmed transaction by round %v", roundTimeout) + return txn, err + } + // Wait a round + err = client.WaitForRoundWithTimeout(curRound + 1) + if err != nil { + return txn, err + } + } +} + // HealthCheck does a health check on the potentially running node, // returning an error if the API is down func (client RestClient) HealthCheck() error { diff --git a/libgoal/transactions.go b/libgoal/transactions.go index 61fdf14c96..21b95e28f5 100644 --- a/libgoal/transactions.go +++ b/libgoal/transactions.go @@ -23,6 +23,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/crypto/merklesignature" + v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" "github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" @@ -206,6 +207,15 @@ func (c *Client) SignAndBroadcastTransaction(walletHandle, pw []byte, utx transa return c.BroadcastTransaction(stx) } +// WaitForConfirmedTxn waits for a transaction to be confirmed, returing information about it. +func (c *Client) WaitForConfirmedTxn(roundTimeout uint64, txid string) (txn v2.PreEncodedTxInfo, err error) { + algod, err := c.ensureAlgodClient() + if err != nil { + return + } + return algod.WaitForConfirmedTxn(roundTimeout, txid) +} + // generateRegistrationTransaction returns a transaction object for registering a Participation with its parent this is // similar to account.Participation.GenerateRegistrationTransaction. func generateRegistrationTransaction(part model.ParticipationKey, fee basics.MicroAlgos, txnFirstValid, txnLastValid basics.Round, leaseBytes [32]byte) (transactions.Transaction, error) { diff --git a/test/e2e-go/features/incentives/challenge_test.go b/test/e2e-go/features/incentives/challenge_test.go index 59d27dbd2e..c0ee38d37f 100644 --- a/test/e2e-go/features/incentives/challenge_test.go +++ b/test/e2e-go/features/incentives/challenge_test.go @@ -97,11 +97,11 @@ func testChallengesOnce(t *testing.T, a *require.Assertions) (retry bool) { // eligible accounts1 will get challenged with node offline, and suspended for _, account := range accounts1 { - rekeyreg(&fixture, a, c1, account.Address, eligible(account.Address)) + rekeyreg(a, c1, account.Address, eligible(account.Address)) } // eligible accounts2 will get challenged, but node2 will heartbeat for them for _, account := range accounts2 { - rekeyreg(&fixture, a, c2, account.Address, eligible(account.Address)) + rekeyreg(a, c2, account.Address, eligible(account.Address)) } // turn off node 1, so it can't heartbeat diff --git a/test/e2e-go/features/incentives/payouts_test.go b/test/e2e-go/features/incentives/payouts_test.go index 5e2801a3c7..8f3dde1297 100644 --- a/test/e2e-go/features/incentives/payouts_test.go +++ b/test/e2e-go/features/incentives/payouts_test.go @@ -72,8 +72,8 @@ func TestBasicPayouts(t *testing.T) { c01, account01 := clientAndAccount("Node01") relay, _ := clientAndAccount("Relay") - data01 := rekeyreg(&fixture, a, c01, account01.Address, true) - data15 := rekeyreg(&fixture, a, c15, account15.Address, true) + data01 := rekeyreg(a, c01, account01.Address, true) + data15 := rekeyreg(a, c15, account15.Address, true) // Wait a few rounds after rekeyreg, this means that `lookback` rounds after // those rekeyregs, the nodes will be IncentiveEligible, but both will have @@ -296,7 +296,7 @@ func getblock(client libgoal.Client, round uint64) (bookkeeping.Block, error) { return client.BookkeepingBlock(round) } -func rekeyreg(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal.Client, address string, becomeEligible bool) basics.AccountData { +func rekeyreg(a *require.Assertions, client libgoal.Client, address string, becomeEligible bool) basics.AccountData { // we start by making an _offline_ tx here, because we want to populate the // key material ourself with a copy of the account's existing material. That // makes it an _online_ keyreg. That allows the running node to chug along @@ -329,7 +329,7 @@ func rekeyreg(f *fixtures.RestClientFixture, a *require.Assertions, client libgo a.NoError(err) onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, reReg) a.NoError(err) - txn, err := f.WaitForConfirmedTxn(uint64(reReg.LastValid), onlineTxID) + txn, err := client.WaitForConfirmedTxn(uint64(reReg.LastValid), onlineTxID) a.NoError(err) // sync up with the network _, err = client.WaitForRound(*txn.ConfirmedRound) diff --git a/test/e2e-go/features/incentives/suspension_test.go b/test/e2e-go/features/incentives/suspension_test.go index 60ba86b27d..edc32fe881 100644 --- a/test/e2e-go/features/incentives/suspension_test.go +++ b/test/e2e-go/features/incentives/suspension_test.go @@ -70,8 +70,8 @@ func TestBasicSuspension(t *testing.T) { c10, account10 := clientAndAccount("Node10") c20, account20 := clientAndAccount("Node20") - rekeyreg(&fixture, a, c10, account10.Address, true) - rekeyreg(&fixture, a, c20, account20.Address, true) + rekeyreg(a, c10, account10.Address, true) + rekeyreg(a, c20, account20.Address, true) // Accounts are now suspendable whether they have proposed yet or not // because keyreg sets LastHeartbeat. Stop c20 which means account20 will be diff --git a/test/e2e-go/features/incentives/whalejoin_test.go b/test/e2e-go/features/incentives/whalejoin_test.go index 58267c6546..4c0f200942 100644 --- a/test/e2e-go/features/incentives/whalejoin_test.go +++ b/test/e2e-go/features/incentives/whalejoin_test.go @@ -17,7 +17,6 @@ package suspension import ( - "fmt" "path/filepath" "testing" "time" @@ -65,7 +64,7 @@ func TestWhaleJoin(t *testing.T) { accounts, err := fixture.GetNodeWalletsSortedByBalance(c) a.NoError(err) a.Len(accounts, 1) - fmt.Printf("Client %s is %v\n", name, accounts[0].Address) + t.Logf("Client %s is %v\n", name, accounts[0].Address) return c, accounts[0] } @@ -73,7 +72,7 @@ func TestWhaleJoin(t *testing.T) { c01, account01 := clientAndAccount("Node01") // 1. take wallet15 offline - keys := offline(&fixture, a, c15, account15.Address) + keys := offline(a, c15, account15.Address) // 2. c01 starts with 100M, so burn 99.9M to get total online stake down burn, err := c01.SendPaymentFromUnencryptedWallet(account01.Address, basics.Address{}.String(), @@ -87,7 +86,7 @@ func TestWhaleJoin(t *testing.T) { a.NoError(err) // 4. rejoin, with 1.5B against the paltry 100k that's currently online - online(&fixture, a, c15, account15.Address, keys) + online(a, c15, account15.Address, keys) // 5. wait for agreement balances to kick in (another lookback's worth, plus some slack) _, err = c01.WaitForRound(*receipt.ConfirmedRound + 2*lookback + 5) @@ -139,20 +138,20 @@ func TestBigJoin(t *testing.T) { accounts, err := fixture.GetNodeWalletsSortedByBalance(c) a.NoError(err) a.Len(accounts, 1) - fmt.Printf("Client %s is %v\n", name, accounts[0].Address) + t.Logf("Client %s is %v\n", name, accounts[0].Address) return c, accounts[0] } c01, account01 := clientAndAccount("Node01") // 1. take wallet01 offline - keys := offline(&fixture, a, c01, account01.Address) + keys := offline(a, c01, account01.Address) // 2. Wait lookback rounds wait(&fixture, a, lookback) // 4. rejoin, with 1/16 of total stake - onRound := online(&fixture, a, c01, account01.Address, keys) + onRound := online(a, c01, account01.Address, keys) // 5. wait for enough rounds to pass, during which c01 can't vote, that is // could get knocked off. @@ -162,7 +161,7 @@ func TestBigJoin(t *testing.T) { a.Equal(basics.Online, data.Status) // 5a. just to be sure, do a zero pay to get it "noticed" - zeroPay(&fixture, a, c01, account01.Address) + zeroPay(a, c01, account01.Address) data, err = c01.AccountData(account01.Address) a.NoError(err) a.Equal(basics.Online, data.Status) @@ -175,7 +174,7 @@ func TestBigJoin(t *testing.T) { a.NoError(err) a.Equal(basics.Online, data.Status) - zeroPay(&fixture, a, c01, account01.Address) + zeroPay(a, c01, account01.Address) data, err = c01.AccountData(account01.Address) a.NoError(err) a.Equal(basics.Online, data.Status) @@ -215,7 +214,7 @@ func TestBigIncrease(t *testing.T) { accounts, err := fixture.GetNodeWalletsSortedByBalance(c) a.NoError(err) a.Len(accounts, 1) - fmt.Printf("Client %s is %v\n", name, accounts[0].Address) + t.Logf("Client %s is %v\n", name, accounts[0].Address) return c, accounts[0] } @@ -226,14 +225,14 @@ func TestBigIncrease(t *testing.T) { // certainly will not have proposed by pure luck just before the critical // round. If we don't do that, 1/16 of stake is enough that it will probably // have a fairly recent proposal, and not get knocked off. - pay(&fixture, a, c1, account01.Address, account15.Address, 99*account01.Amount/100) + pay(a, c1, account01.Address, account15.Address, 99*account01.Amount/100) - rekeyreg(&fixture, a, c1, account01.Address, true) + rekeyreg(a, c1, account01.Address, true) // 2. Wait lookback rounds wait(&fixture, a, lookback) - tx := pay(&fixture, a, c15, account15.Address, account01.Address, 50*account15.Amount/100) + tx := pay(a, c15, account15.Address, account01.Address, 50*account15.Amount/100) data, err := c15.AccountData(account01.Address) a.NoError(err) a.EqualValues(*tx.ConfirmedRound+lookback, data.LastHeartbeat) @@ -252,22 +251,21 @@ func wait(f *fixtures.RestClientFixture, a *require.Assertions, count uint64) { a.NoError(f.WaitForRoundWithTimeout(round)) } -func pay(f *fixtures.RestClientFixture, a *require.Assertions, - c libgoal.Client, from string, to string, amount uint64) v2.PreEncodedTxInfo { +func pay(a *require.Assertions, c libgoal.Client, + from string, to string, amount uint64) v2.PreEncodedTxInfo { pay, err := c.SendPaymentFromUnencryptedWallet(from, to, 1000, amount, nil) a.NoError(err) - tx, err := f.WaitForConfirmedTxn(uint64(pay.LastValid), pay.ID().String()) + tx, err := c.WaitForConfirmedTxn(uint64(pay.LastValid), pay.ID().String()) a.NoError(err) return tx } -func zeroPay(f *fixtures.RestClientFixture, a *require.Assertions, - c libgoal.Client, address string) { - pay(f, a, c, address, address, 0) +func zeroPay(a *require.Assertions, c libgoal.Client, address string) { + pay(a, c, address, address, 0) } // Go offline, but return the key material so it's easy to go back online -func offline(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal.Client, address string) transactions.KeyregTxnFields { +func offline(a *require.Assertions, client libgoal.Client, address string) transactions.KeyregTxnFields { offTx, err := client.MakeUnsignedGoOfflineTx(address, 0, 0, 100_000, [32]byte{}) a.NoError(err) @@ -286,7 +284,7 @@ func offline(f *fixtures.RestClientFixture, a *require.Assertions, client libgoa a.NoError(err) onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, offTx) a.NoError(err) - txn, err := f.WaitForConfirmedTxn(uint64(offTx.LastValid), onlineTxID) + txn, err := client.WaitForConfirmedTxn(uint64(offTx.LastValid), onlineTxID) a.NoError(err) // sync up with the network _, err = client.WaitForRound(*txn.ConfirmedRound) @@ -298,7 +296,7 @@ func offline(f *fixtures.RestClientFixture, a *require.Assertions, client libgoa } // Go online with the supplied key material -func online(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal.Client, address string, keys transactions.KeyregTxnFields) uint64 { +func online(a *require.Assertions, client libgoal.Client, address string, keys transactions.KeyregTxnFields) uint64 { // sanity check that we start offline data, err := client.AccountData(address) a.NoError(err) @@ -313,7 +311,7 @@ func online(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal a.NoError(err) onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, onTx) a.NoError(err) - receipt, err := f.WaitForConfirmedTxn(uint64(onTx.LastValid), onlineTxID) + receipt, err := client.WaitForConfirmedTxn(uint64(onTx.LastValid), onlineTxID) a.NoError(err) data, err = client.AccountData(address) a.NoError(err) diff --git a/test/framework/fixtures/restClientFixture.go b/test/framework/fixtures/restClientFixture.go index 13fd63185d..f4131b5422 100644 --- a/test/framework/fixtures/restClientFixture.go +++ b/test/framework/fixtures/restClientFixture.go @@ -27,7 +27,6 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/netdeploy" - "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/daemon/algod/api/client" v2 "github.com/algorand/go-algorand/daemon/algod/api/server/v2" @@ -192,34 +191,7 @@ func (f *RestClientFixture) WaitForTxnConfirmation(roundTimeout uint64, txid str // or until the passed roundTimeout passes // or until waiting for a round to pass times out func (f *RestClientFixture) WaitForConfirmedTxn(roundTimeout uint64, txid string) (txn v2.PreEncodedTxInfo, err error) { - client := f.AlgodClient - for { - // Get current round information - curStatus, statusErr := client.Status() - require.NoError(f.t, statusErr, "fixture should be able to get node status") - curRound := curStatus.LastRound - - // Check if we know about the transaction yet - var resp []byte - resp, err = client.RawPendingTransactionInformation(txid) - if err == nil { - err = protocol.DecodeReflect(resp, &txn) - require.NoError(f.t, err) - } - - // Check if transaction was confirmed - if txn.ConfirmedRound != nil && *txn.ConfirmedRound > 0 { - return - } - // Check if we should wait a round - if curRound > roundTimeout { - err = fmt.Errorf("failed to see confirmed transaction by round %v", roundTimeout) - return - } - // Wait a round - err = f.WaitForRoundWithTimeout(curRound + 1) - require.NoError(f.t, err, "fixture should be able to wait for one round to pass") - } + return f.AlgodClient.WaitForConfirmedTxn(roundTimeout, txid) } // WaitForAllTxnsToConfirm is as WaitForTxnConfirmation, @@ -295,7 +267,7 @@ func (f *RestClientFixture) WaitForAccountFunded(roundTimeout uint64, accountAdd return fmt.Errorf("failed to see confirmed transaction by round %v", roundTimeout) } // Wait a round - err = f.WaitForRoundWithTimeout(curRound + 1) + err = client.WaitForRoundWithTimeout(curRound + 1) require.NoError(f.t, err, "fixture should be able to wait for one round to pass") } } @@ -318,7 +290,7 @@ func (f *RestClientFixture) SendMoneyAndWaitFromWallet(walletHandle, walletPassw 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) - txn, err = f.WaitForConfirmedTxn(waitingDeadline, fundingTx.ID().String()) + txn, err = client.WaitForConfirmedTxn(waitingDeadline, fundingTx.ID().String()) require.NoError(f.t, err) return }