Skip to content

Commit

Permalink
Testing: Move WaitForConfirmedTxn down into clients (#6218)
Browse files Browse the repository at this point in the history
  • Loading branch information
jannotti authored Jan 8, 2025
1 parent 45e94f5 commit 0b8d9e0
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 62 deletions.
39 changes: 39 additions & 0 deletions daemon/algod/api/client/restClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
10 changes: 10 additions & 0 deletions libgoal/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-go/features/incentives/challenge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions test/e2e-go/features/incentives/payouts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-go/features/incentives/suspension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 21 additions & 23 deletions test/e2e-go/features/incentives/whalejoin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package suspension

import (
"fmt"
"path/filepath"
"testing"
"time"
Expand Down Expand Up @@ -65,15 +64,15 @@ 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]
}

c15, account15 := clientAndAccount("Node15")
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(),
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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]
}

Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
34 changes: 3 additions & 31 deletions test/framework/fixtures/restClientFixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")
}
}
Expand All @@ -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
}
Expand Down

0 comments on commit 0b8d9e0

Please sign in to comment.