Skip to content

Commit fd31e6e

Browse files
Fix and enable TestNewAccountCanGoOnlineAndParticipate (algorand#2238)
* TestNewAccountCanGoOnlineAndParticipate was failing because the test was not waiting enough to get to the round where the newly funded account's funds will be considered for proposing purposes. It was miscalculating the round that it should wait form. Moreover, the rounds considered to when the account is funded was prone to race conditions. In addition, the test was using WaitForRoundWithTimeout which may be very slower if the current round is already ahead. Instead, now it is using ClientWaitForRound, which does not care about individual rounds delayed. * Addressing review commnets: - fixing a typo - getting exact transaction round for funding the account - testing exact blocks for the proposer - using a single node network instead of two nodes - waiting for exactly one round for the new account to propose and checking that - sending the funds and closing the rich account so there will be no possiblity of that proposing a block
1 parent 730d951 commit fd31e6e

File tree

5 files changed

+89
-49
lines changed

5 files changed

+89
-49
lines changed

test/e2e-go/features/multisig/multisig_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func TestBasicMultisig(t *testing.T) {
6868
// fund account with enough Algos to allow for 3 transactions and still keep a minBalance in the account
6969
amountToFund := 4*minAcctBalance + 3*minTxnFee
7070
curStatus, err := client.Status()
71-
fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, minTxnFee, fundingAddr, multisigAddr)
71+
fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, minTxnFee, fundingAddr, multisigAddr, "")
7272
// try to transact with 1 of 3
7373
amountToSend := minAcctBalance
7474
unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], minTxnFee, amountToSend, nil, "", [32]byte{}, 0, 0)
@@ -193,7 +193,7 @@ func TestDuplicateKeys(t *testing.T) {
193193
amountToFund := 3 * minAcctBalance
194194
txnFee := minTxnFee
195195
curStatus, _ := client.Status()
196-
fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, txnFee, fundingAddr, multisigAddr)
196+
fixture.SendMoneyAndWait(curStatus.LastRound, amountToFund, txnFee, fundingAddr, multisigAddr, "")
197197
// try to transact with "1" signature (though, this is a signature from "every" member of the multisig)
198198
amountToSend := minAcctBalance
199199
unsignedTransaction, err := client.ConstructPayment(multisigAddr, addrs[0], txnFee, amountToSend, nil, "", [32]byte{}, 0, 0)

test/e2e-go/features/participation/onlineOfflineParticipation_test.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ package participation
1818

1919
import (
2020
"path/filepath"
21+
"runtime"
2122
"testing"
23+
"time"
2224

2325
"github.com/stretchr/testify/require"
2426

2527
"github.com/algorand/go-algorand/config"
2628
"github.com/algorand/go-algorand/data/basics"
29+
"github.com/algorand/go-algorand/protocol"
30+
"github.com/algorand/go-algorand/test/e2e-go/globals"
2731
"github.com/algorand/go-algorand/test/framework/fixtures"
2832
)
2933

@@ -95,20 +99,45 @@ func waitForAccountToProposeBlock(a *require.Assertions, fixture *fixtures.RestC
9599
return false
96100
}
97101

102+
// TestNewAccountCanGoOnlineAndParticipate tests two behaviors:
103+
// - When the account does not have enough stake, or after receivning algos, but before the lookback rounds,
104+
// it should not be proposing blocks
105+
// - When the account balance receives enough stake, it should be proposing after lookback rounds
98106
func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) {
99-
/*if runtime.GOOS == "darwin" {
100-
t.Skip()
101-
}
102107
if testing.Short() {
103108
t.Skip()
104-
}*/
105-
t.Skip() // temporary disable the test since it's failing.
109+
}
106110

107111
t.Parallel()
108112
a := require.New(fixtures.SynchronizedTest(t))
109113

114+
// Make the seed lookback shorter, otherwise will wait for 320 rounds
115+
consensus := make(config.ConsensusProtocols)
116+
shortPartKeysProtocol := config.Consensus[protocol.ConsensusCurrentVersion]
117+
shortPartKeysProtocol.ApprovedUpgrades = map[protocol.ConsensusVersion]uint64{}
118+
shortPartKeysProtocol.SeedLookback = 2
119+
shortPartKeysProtocol.SeedRefreshInterval = 8
120+
if runtime.GOARCH == "amd64" {
121+
// amd64 platforms are generally quite capable, so accelerate the round times to make the test run faster.
122+
shortPartKeysProtocol.AgreementFilterTimeoutPeriod0 = 1 * time.Second
123+
shortPartKeysProtocol.AgreementFilterTimeout = 1 * time.Second
124+
}
125+
consensus[protocol.ConsensusVersion("shortpartkeysprotocol")] = shortPartKeysProtocol
126+
110127
var fixture fixtures.RestClientFixture
111-
fixture.Setup(t, filepath.Join("nettemplates", "TwoNodesOneOnline.json"))
128+
fixture.SetConsensus(consensus)
129+
fixture.SetupNoStart(t, filepath.Join("nettemplates", "OneNode.json"))
130+
131+
// update the config file by setting the ParticipationKeysRefreshInterval to 5 second.
132+
nodeDirectory, err := fixture.GetNodeDir("Primary")
133+
a.NoError(err)
134+
cfg, err := config.LoadConfigFromDisk(nodeDirectory)
135+
a.NoError(err)
136+
cfg.ParticipationKeysRefreshInterval = 5 * time.Second
137+
cfg.SaveToDisk(nodeDirectory)
138+
139+
fixture.Start()
140+
112141
defer fixture.Shutdown()
113142
client := fixture.LibGoalClient
114143

@@ -129,7 +158,7 @@ func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) {
129158

130159
transactionFee := minTxnFee
131160
amountToSendInitial := 5 * minAcctBalance
132-
fixture.SendMoneyAndWait(initialRound, amountToSendInitial, transactionFee, richAccount, newAccount)
161+
fixture.SendMoneyAndWait(initialRound, amountToSendInitial, transactionFee, richAccount, newAccount, "")
133162
amt, err := client.GetBalance(newAccount)
134163
a.NoError(err)
135164
nodeStatus, err := client.Status()
@@ -145,7 +174,7 @@ func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) {
145174
a.NoError(err, "rest client should be able to add participation key to new account")
146175
a.Equal(newAccount, partkeyResponse.Parent.String(), "partkey response should echo queried account")
147176
// account uses part key to go online
148-
goOnlineTx, err := client.MakeUnsignedGoOnlineTx(newAccount, nil, 0, 0, transactionFee, [32]byte{})
177+
goOnlineTx, err := client.MakeUnsignedGoOnlineTx(newAccount, &partkeyResponse, 0, 0, transactionFee, [32]byte{})
149178
a.NoError(err, "should be able to make go online tx")
150179
a.Equal(newAccount, goOnlineTx.Src().String(), "go online response should echo queried account")
151180
onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, goOnlineTx)
@@ -159,35 +188,37 @@ func TestNewAccountCanGoOnlineAndParticipate(t *testing.T) {
159188
newAccountStatus, err := client.AccountInformation(newAccount)
160189
a.NoError(err, "client should be able to get information about new account")
161190
a.Equal(basics.Online.String(), newAccountStatus.Status, "new account should be online")
162-
// account receives almost all of rich account's stake (minus enough to
163-
// keep it over MinBalance), so it will be selected for participation
191+
192+
// transfer the funds and close to the new account
164193
amountToSend := richBalance - 3*transactionFee - amountToSendInitial - minAcctBalance
165-
fixture.SendMoneyAndWait(onlineRound, amountToSend, transactionFee, richAccount, newAccount)
194+
txn := fixture.SendMoneyAndWait(onlineRound, amountToSend, transactionFee, richAccount, newAccount, newAccount)
195+
fundedRound := txn.ConfirmedRound
166196

167197
nodeStatus, _ = client.Status()
168-
fundedRound := nodeStatus.LastRound
169-
170198
params, err := client.ConsensusParams(nodeStatus.LastRound)
171199
a.NoError(err)
172-
lookbackRound := balanceRound(basics.Round(nodeStatus.LastRound), params)
173-
delta := int64(nodeStatus.LastRound) - int64(lookbackRound)
174-
a.True(delta > 0)
200+
accountProposesStarting := balanceRoundOf(basics.Round(fundedRound), params)
175201

176202
// Need to wait for funding to take effect on selection, then we can see if we're participating
177203
// Stop before the account should become eligible for selection so we can ensure it wasn't
178-
err = fixture.WaitForRoundWithTimeout(fundedRound + uint64(delta) - 1)
204+
err = fixture.ClientWaitForRound(fixture.AlgodClient, uint64(accountProposesStarting-1),
205+
time.Duration(uint64(globals.MaxTimePerRound)*uint64(accountProposesStarting-1)))
179206
a.NoError(err)
180207

181-
blockWasProposed := fixture.VerifyBlockProposed(newAccount, int(delta)-1)
182-
a.False(blockWasProposed, "account should not be selected until BalLookback (round %d) passes", int(delta)-1)
208+
// Check if the account did not propose any blocks up to this round
209+
blockWasProposed := fixture.VerifyBlockProposedRange(newAccount, int(accountProposesStarting)-1,
210+
int(accountProposesStarting)-1)
211+
a.False(blockWasProposed, "account should not be selected until BalLookback (round %d) passes", int(accountProposesStarting-1))
212+
213+
// Now wait until the round where the funded account will be used.
214+
err = fixture.ClientWaitForRound(fixture.AlgodClient, uint64(accountProposesStarting), 10*globals.MaxTimePerRound)
215+
a.NoError(err)
183216

184-
// check that account starts participating after a while
185-
proposalWindow := 20 // arbitrary
186-
blockWasProposedByNewAccountRecently := waitForAccountToProposeBlock(a, &fixture, newAccount, proposalWindow)
217+
blockWasProposedByNewAccountRecently := fixture.VerifyBlockProposedRange(newAccount, int(accountProposesStarting), 1)
187218
a.True(blockWasProposedByNewAccountRecently, "newly online account should be proposing blocks")
188219
}
189220

190-
// helper copied from agreement/selector.go
191-
func balanceRound(r basics.Round, cparams config.ConsensusParams) basics.Round {
192-
return r.SubSaturate(basics.Round(2 * cparams.SeedRefreshInterval * cparams.SeedLookback))
221+
// Returns the earliest round which will have the balanceRound equal to r
222+
func balanceRoundOf(r basics.Round, cparams config.ConsensusParams) basics.Round {
223+
return basics.Round(2*cparams.SeedRefreshInterval*cparams.SeedLookback) + r
193224
}

test/e2e-go/features/participation/participationRewards_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ func TestRewardRateRecalculation(t *testing.T) {
335335
minFee, minBal, err := fixture.MinFeeAndBalance(curStatus.LastRound)
336336
r.NoError(err)
337337
deadline := curStatus.LastRound + uint64(5)
338-
fixture.SendMoneyAndWait(deadline, amountToSend, minFee, richAccount.Address, rewardsAccount)
338+
fixture.SendMoneyAndWait(deadline, amountToSend, minFee, richAccount.Address, rewardsAccount, "")
339339

340340
blk, err := client.Block(curStatus.LastRound)
341341
r.NoError(err)
@@ -361,7 +361,7 @@ func TestRewardRateRecalculation(t *testing.T) {
361361
curStatus, err = client.Status()
362362
r.NoError(err)
363363
deadline = curStatus.LastRound + uint64(5)
364-
fixture.SendMoneyAndWait(deadline, amountToSend, minFee, richAccount.Address, rewardsAccount)
364+
fixture.SendMoneyAndWait(deadline, amountToSend, minFee, richAccount.Address, rewardsAccount, "")
365365

366366
rewardRecalcRound = rewardRecalcRound + consensusParams.RewardsRateRefreshInterval
367367

test/framework/fixtures/restClientFixture.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ func (f *RestClientFixture) SetupShared(testName string, templateFile string) {
5656
f.AlgodClient = f.GetAlgodClientForController(f.NC)
5757
}
5858

59+
// Start can be called to start the fixture's network if SetupNoStart() was used.
60+
func (f *RestClientFixture) Start() {
61+
f.LibGoalFixture.Start()
62+
f.AlgodClient = f.GetAlgodClientForController(f.NC)
63+
}
64+
5965
// GetAlgodClientForController returns a RestClient for the specified NodeController
6066
func (f *RestClientFixture) GetAlgodClientForController(nc nodecontrol.NodeController) client.RestClient {
6167
url, err := nc.ServerURL()
@@ -267,36 +273,33 @@ func (f *RestClientFixture) WaitForAllTxnsToConfirm(roundTimeout uint64, txidsAn
267273

268274
// SendMoneyAndWait uses the rest client to send money and WaitForTxnConfirmation to wait for the send to confirm
269275
// it adds some extra error checking as well
270-
func (f *RestClientFixture) SendMoneyAndWait(curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string) (fundingTxid string) {
276+
func (f *RestClientFixture) SendMoneyAndWait(curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string, closeToAccount string) (txn v1.Transaction) {
271277
client := f.LibGoalClient
272278
wh, err := client.GetUnencryptedWalletHandle()
273279
require.NoError(f.t, err, "client should be able to get unencrypted wallet handle")
274-
fundingTxid = f.SendMoneyAndWaitFromWallet(wh, nil, curRound, amountToSend, transactionFee, fromAccount, toAccount)
280+
txn = f.SendMoneyAndWaitFromWallet(wh, nil, curRound, amountToSend, transactionFee, fromAccount, toAccount, closeToAccount)
275281
return
276282
}
277283

278284
// SendMoneyAndWaitFromWallet is as above, but for a specific wallet
279-
func (f *RestClientFixture) SendMoneyAndWaitFromWallet(walletHandle, walletPassword []byte, curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string) (fundingTxid string) {
285+
func (f *RestClientFixture) SendMoneyAndWaitFromWallet(walletHandle, walletPassword []byte, curRound, amountToSend, transactionFee uint64, fromAccount, toAccount string, closeToAccount string) (txn v1.Transaction) {
280286
client := f.LibGoalClient
281-
fundingTx, err := client.SendPaymentFromWallet(walletHandle, walletPassword, fromAccount, toAccount, transactionFee, amountToSend, nil, "", 0, 0)
287+
fundingTx, err := client.SendPaymentFromWallet(walletHandle, walletPassword, fromAccount, toAccount, transactionFee, amountToSend, nil, closeToAccount, 0, 0)
282288
require.NoError(f.t, err, "client should be able to send money from rich to poor account")
283289
require.NotEmpty(f.t, fundingTx.ID().String(), "transaction ID should not be empty")
284290
waitingDeadline := curRound + uint64(5)
285-
_, err = f.WaitForConfirmedTxn(waitingDeadline, fromAccount, fundingTx.ID().String())
291+
txn, err = f.WaitForConfirmedTxn(waitingDeadline, fromAccount, fundingTx.ID().String())
286292
require.NoError(f.t, err)
287293
return
288294
}
289295

290-
// VerifyBlockProposed checks the last searchRange blocks to see if any blocks were proposed by address
291-
func (f *RestClientFixture) VerifyBlockProposed(account string, searchRange int) (blockWasProposed bool) {
296+
// VerifyBlockProposedRange checks the rounds starting at fromRounds and moving backwards checking countDownNumRounds rounds if any
297+
// blocks were proposed by address
298+
func (f *RestClientFixture) VerifyBlockProposedRange(account string, fromRound, countDownNumRounds int) (blockWasProposed bool) {
292299
c := f.LibGoalClient
293-
currentRound, err := c.CurrentRound()
294-
if err != nil {
295-
require.NoError(f.t, err, "client failed to get the last round")
296-
}
297-
for i := 0; i < searchRange; i++ {
298-
block, err := c.Block(currentRound - uint64(i))
299-
require.NoError(f.t, err, "client failed to get block %d", currentRound-uint64(i))
300+
for i := 0; i < countDownNumRounds; i++ {
301+
block, err := c.Block(uint64(fromRound - i))
302+
require.NoError(f.t, err, "client failed to get block %d", fromRound-i)
300303
if block.Proposer == account {
301304
blockWasProposed = true
302305
break
@@ -305,6 +308,16 @@ func (f *RestClientFixture) VerifyBlockProposed(account string, searchRange int)
305308
return
306309
}
307310

311+
// VerifyBlockProposed checks the last searchRange blocks to see if any blocks were proposed by address
312+
func (f *RestClientFixture) VerifyBlockProposed(account string, searchRange int) (blockWasProposed bool) {
313+
c := f.LibGoalClient
314+
currentRound, err := c.CurrentRound()
315+
if err != nil {
316+
require.NoError(f.t, err, "client failed to get the last round")
317+
}
318+
return f.VerifyBlockProposedRange(account, int(currentRound), int(searchRange))
319+
}
320+
308321
// GetBalancesOnSameRound gets the balances for the passed addresses, and keeps trying until the balances are all the same round
309322
// if it can't get the balances for the same round within maxRetries retries, it will return the last balance seen for each acct
310323
// it also returns whether it got balances all for the same round, and what the last queried round was

test/testdata/nettemplates/TwoNodesOneOnline.json renamed to test/testdata/nettemplates/OneNode.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"Genesis": {
33
"NetworkName": "tbd",
4+
"ConsensusProtocol": "shortpartkeysprotocol",
45
"Wallets": [
56
{
67
"Name": "Rich",
@@ -27,12 +28,7 @@
2728
{ "Name": "Rich",
2829
"ParticipationOnly": false },
2930
{ "Name": "Partkey",
30-
"ParticipationOnly": true }
31-
]
32-
},
33-
{
34-
"Name": "Node",
35-
"Wallets": [
31+
"ParticipationOnly": true },
3632
{ "Name": "Offline",
3733
"ParticipationOnly": true }
3834
]

0 commit comments

Comments
 (0)