Skip to content

Commit a2b1297

Browse files
committed
Minimize flakiness in TestBasicPayouts
Flakiness seemed to come from one of two places 1) Testing balances after a proposal as sometimes wrong, if the proposer proposed AGAIN before we performed the checks. 2) An off by one error such that if account01 (which proposed rarely) proposed in exactly the lookback'th round after burning algos, we thought it should not get paid, but it should.
1 parent 005495b commit a2b1297

File tree

1 file changed

+43
-64
lines changed

1 file changed

+43
-64
lines changed

test/e2e-go/features/incentives/payouts_test.go

+43-64
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,22 @@ func TestBasicPayouts(t *testing.T) {
4949
// Make the seed lookback shorter, otherwise we need to wait 320 rounds to become IncentiveEligible.
5050
const lookback = 32
5151
fixture.FasterConsensus(protocol.ConsensusFuture, time.Second, lookback)
52-
fmt.Printf("lookback is %d\n", lookback)
52+
t.Logf("lookback is %d\n", lookback)
5353
fixture.Setup(t, filepath.Join("nettemplates", "Payouts.json"))
5454
defer fixture.Shutdown()
5555

5656
// Overview of this test:
5757
// rereg to become eligible (must pay extra fee)
5858
// show payouts are paid (from fees and bonuses)
5959
// deplete feesink to ensure it's graceful
60-
60+
addressToNode := make(map[string]string)
6161
clientAndAccount := func(name string) (libgoal.Client, model.Account) {
6262
c := fixture.GetLibGoalClientForNamedNode(name)
6363
accounts, err := fixture.GetNodeWalletsSortedByBalance(c)
6464
a.NoError(err)
6565
a.Len(accounts, 1)
66-
fmt.Printf("Client %s is %v\n", name, accounts[0].Address)
66+
t.Logf("Client %s is %v\n", name, accounts[0].Address)
67+
addressToNode[accounts[0].Address] = name
6768
return c, accounts[0]
6869
}
6970

@@ -74,31 +75,42 @@ func TestBasicPayouts(t *testing.T) {
7475
data01 := rekeyreg(&fixture, a, c01, account01.Address, true)
7576
data15 := rekeyreg(&fixture, a, c15, account15.Address, true)
7677

78+
// Wait a few rounds after rekeyreg, this means that `lookback` rounds after
79+
// those rekeyregs, the nodes will be IncentiveEligible, but both will have
80+
// too much stake to earn rewards. Then we'll burn from account01, so
81+
// lookback rounds after _that_ account01 will start earning.
82+
client := fixture.LibGoalClient
83+
status, err := client.Status()
84+
a.NoError(err)
85+
fixture.WaitForRoundWithTimeout(status.LastRound + 10)
86+
7787
// have account01 burn some money to get below the eligibility cap
7888
// Starts with 100M, so burn 60M and get under 70M cap.
7989
txn, err := c01.SendPaymentFromUnencryptedWallet(account01.Address, basics.Address{}.String(),
8090
1000, 60_000_000_000_000, nil)
8191
a.NoError(err)
8292
burn, err := fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String())
8393
a.NoError(err)
94+
burnRound := *burn.ConfirmedRound
95+
t.Logf("burn round is %d", burnRound)
8496
// sync up with the network
85-
_, err = c01.WaitForRound(*burn.ConfirmedRound)
97+
_, err = c01.WaitForRound(burnRound)
8698
a.NoError(err)
8799
data01, err = c01.AccountData(account01.Address)
88100
a.NoError(err)
89101

90-
// Go 31 rounds after the burn happened. During this time, incentive
91-
// eligibility is not in effect yet, so regardless of who proposes, they
92-
// won't earn anything.
93-
client := fixture.LibGoalClient
94-
status, err := client.Status()
102+
// Start advancing. IncentiveEligibile will come into effect 32 rounds after
103+
// the rekeregs but earning will only happen 32 rounds after burnRound, and
104+
// only for account01 (the one that burned to get under the cap).
105+
status, err = client.Status()
95106
a.NoError(err)
96-
for status.LastRound < *burn.ConfirmedRound+lookback-1 {
107+
account1earned := false
108+
for !account1earned {
97109
block, err := client.BookkeepingBlock(status.LastRound)
98110
a.NoError(err)
99111

100-
fmt.Printf("block %d proposed by %v\n", status.LastRound, block.Proposer())
101-
a.Zero(block.ProposerPayout()) // nobody is eligible yet (hasn't worked back to balance round)
112+
t.Logf("block %d proposed by %s %v\n",
113+
status.LastRound, addressToNode[block.Proposer().String()], block.Proposer())
102114
a.EqualValues(bonus1, block.Bonus.Raw)
103115

104116
// all nodes agree the proposer proposed. The paranoia here is
@@ -109,7 +121,7 @@ func TestBasicPayouts(t *testing.T) {
109121
// optimization, and it would cause failures here. Interface changes
110122
// made since they should make such a problem impossible, but...
111123
for i, c := range []libgoal.Client{c15, c01, relay} {
112-
fmt.Printf("checking block %v\n", block.Round())
124+
t.Logf("checking block %v\n", block.Round())
113125
bb, err := getblock(c, status.LastRound)
114126
a.NoError(err)
115127
a.Equal(block.Proposer(), bb.Proposer())
@@ -125,12 +137,26 @@ func TestBasicPayouts(t *testing.T) {
125137
next, err := client.AccountData(block.Proposer().String())
126138
a.NoError(err)
127139
a.LessOrEqual(int(status.LastRound), int(next.LastProposed))
128-
// regardless of proposer, nobody gets paid
129140
switch block.Proposer().String() {
130141
case account01.Address:
131-
a.Equal(data01.MicroAlgos, next.MicroAlgos)
142+
if uint64(block.Round()) < burnRound+lookback {
143+
// until the burn is lookback rounds old, account01 can't earn
144+
a.Zero(block.ProposerPayout())
145+
a.Equal(data01.MicroAlgos, next.MicroAlgos)
146+
} else {
147+
a.EqualValues(bonus1, block.ProposerPayout().Raw)
148+
// We'd like to do test if account one got paid the bonus:
149+
// a.EqualValues(data01.MicroAlgos.Raw+bonus1, next.MicroAlgos.Raw)
150+
151+
// But we can't because it might have already proposed again. So
152+
// let's check if it has received one OR two bonuses.
153+
earned := int(next.MicroAlgos.Raw - data01.MicroAlgos.Raw)
154+
a.True(earned == bonus1 || earned == 2*bonus1, "earned %d", earned)
155+
account1earned = true
156+
}
132157
data01 = next
133158
case account15.Address:
159+
a.Zero(block.ProposerPayout())
134160
a.Equal(data15.MicroAlgos, next.MicroAlgos)
135161
data15 = next
136162
default:
@@ -141,54 +167,6 @@ func TestBasicPayouts(t *testing.T) {
141167
a.NoError(err)
142168
}
143169

144-
// all nodes are in sync
145-
for _, c := range []libgoal.Client{c15, c01, relay} {
146-
_, err := c.WaitForRound(status.LastRound)
147-
a.NoError(err)
148-
}
149-
150-
// Wait until each have proposed, so we can see that 01 gets paid and 15 does not (too much balance)
151-
proposed01 := false
152-
proposed15 := false
153-
for i := 0; !proposed01 || !proposed15; i++ {
154-
status, err := client.Status()
155-
a.NoError(err)
156-
block, err := client.BookkeepingBlock(status.LastRound)
157-
a.NoError(err)
158-
a.EqualValues(bonus1, block.Bonus.Raw)
159-
160-
next, err := client.AccountData(block.Proposer().String())
161-
a.NoError(err)
162-
fmt.Printf(" proposer %v has %d after proposing round %d\n", block.Proposer(), next.MicroAlgos.Raw, status.LastRound)
163-
164-
// all nodes agree the proposer proposed
165-
for i, c := range []libgoal.Client{c15, c01, relay} {
166-
_, err := c.WaitForRound(status.LastRound)
167-
a.NoError(err)
168-
data, err := c.AccountData(block.Proposer().String())
169-
a.NoError(err)
170-
// <= in case one node is behind, and the others have already advanced
171-
a.LessOrEqual(block.Round(), data.LastProposed, i)
172-
}
173-
174-
// 01 would get paid (because under balance cap) 15 would not
175-
switch block.Proposer().String() {
176-
case account01.Address:
177-
a.EqualValues(bonus1, block.ProposerPayout().Raw)
178-
a.EqualValues(data01.MicroAlgos.Raw+bonus1, next.MicroAlgos.Raw) // 01 earns
179-
proposed01 = true
180-
data01 = next
181-
case account15.Address:
182-
a.Zero(block.ProposerPayout())
183-
a.Equal(data15.MicroAlgos, next.MicroAlgos) // didn't earn
184-
data15 = next
185-
proposed15 = true
186-
default:
187-
a.Fail("bad proposer", "%v proposed", block.Proposer)
188-
}
189-
fixture.WaitForRoundWithTimeout(status.LastRound + 1)
190-
}
191-
192170
// Now that we've proven incentives get paid, let's drain the FeeSink and
193171
// ensure it happens gracefully. Have account15 go offline so that (after
194172
// 32 rounds) only account01 (who is eligible) is proposing, so drainage
@@ -203,7 +181,8 @@ func TestBasicPayouts(t *testing.T) {
203181
offTxn, err := fixture.WaitForConfirmedTxn(uint64(offline.LastValid), offlineTxID)
204182
a.NoError(err)
205183

206-
fmt.Printf(" c15 (%s) will be truly offline (not proposing) after round %d\n", account15.Address, *offTxn.ConfirmedRound+lookback)
184+
t.Logf(" c15 (%s) will be truly offline (not proposing) after round %d\n",
185+
account15.Address, *offTxn.ConfirmedRound+lookback)
207186

208187
var feesink basics.Address
209188
for i := 0; i < 100; i++ {

0 commit comments

Comments
 (0)