Skip to content

Commit

Permalink
Make sure the 320 lookback time doesn't knock an account off
Browse files Browse the repository at this point in the history
  • Loading branch information
jannotti committed Sep 24, 2024
1 parent e6b4e40 commit dbc8d0f
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 3 deletions.
4 changes: 3 additions & 1 deletion ledger/apply/keyreg.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"fmt"

"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
)
Expand Down Expand Up @@ -79,7 +80,8 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal
}
record.Status = basics.Online
if params.Payouts.Enabled {
record.LastHeartbeat = header.FirstValid
lookback := agreement.BalanceRound(round, balances.ConsensusParams())
record.LastHeartbeat = round + lookback
}
record.VoteFirstValid = keyreg.VoteFirst
record.VoteLastValid = keyreg.VoteLast
Expand Down
116 changes: 114 additions & 2 deletions test/e2e-go/features/incentives/whalejoin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,117 @@ func TestWhaleJoin(t *testing.T) {

// 4. rejoin, with 1.5B against the paltry 100k that's currently online
online(&fixture, 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)
a.NoError(err)

data, err := c15.AccountData(account15.Address)
a.NoError(err)
a.Equal(basics.Online, data.Status)

// even after being in the block to "get noticed"
txn, err := c15.SendPaymentFromUnencryptedWallet(account15.Address, basics.Address{}.String(),
1000, 1, nil)
a.NoError(err)
_, err = fixture.WaitForConfirmedTxn(uint64(txn.LastValid), txn.ID().String())
a.NoError(err)
data, err = c15.AccountData(account15.Address)
a.NoError(err)
a.Equal(basics.Online, data.Status)
}

// TestBigJoin shows that even though an account can't vote during the first 320
// rounds after joining, it is not marked absent because of that gap. This would
// be a problem for "biggish" accounts, that might already be absent after 320
// rounds of not voting.
func TestBigJoin(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)

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

var fixture fixtures.RestClientFixture
// We need lookback to be fairly long, so that we can have a node join with
// 1/16 stake, and have lookback be long enough to risk absenteeism.
const lookback = 164 // > 160, which is 10x the 1/16th's interval
fixture.FasterConsensus(protocol.ConsensusFuture, time.Second/2, lookback)
fixture.Setup(t, filepath.Join("nettemplates", "Payouts.json"))
defer fixture.Shutdown()

// Overview of this test:
// 1. Take wallet01 offline (but retain keys so can back online later)
// 2. Wait `lookback` rounds so it can't propose.
// 3. Rejoin wallet01 which will now have 1/16 of the stake
// 4. Wait 160 rounds and ensure node01 does not get knocked offline for being absent
// 5. Wait the rest of lookback to ensure it _still_ does not get knock off.

clientAndAccount := func(name string) (libgoal.Client, model.Account) {
c := fixture.GetLibGoalClientForNamedNode(name)
accounts, err := fixture.GetNodeWalletsSortedByBalance(c)
a.NoError(err)
a.Len(accounts, 1)
fmt.Printf("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)

// 2. Wait lookback rounds
wait(&fixture, a, lookback)

// 4. rejoin, with 1/16 of total stake
onRound := online(&fixture, a, c01, account01.Address, keys)

// 5. wait for enough rounds to pass, during which c01 can't vote, that is
// could get knocked off.
wait(&fixture, a, 161)
data, err := c01.AccountData(account01.Address)
a.NoError(err)
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)
data, err = c01.AccountData(account01.Address)
a.NoError(err)
a.Equal(basics.Online, data.Status)

// 6. Now wait until lookback after onRound (which should just be a couple
// more rounds). Check again, to ensure that once c01 is _really_
// online/voting, it is still safe for long enough to propose.
a.NoError(fixture.WaitForRoundWithTimeout(onRound + lookback))
data, err = c01.AccountData(account01.Address)
a.NoError(err)
a.Equal(basics.Online, data.Status)

zeroPay(&fixture, a, c01, account01.Address)
data, err = c01.AccountData(account01.Address)
a.NoError(err)
a.Equal(basics.Online, data.Status)

// The node _could_ have gotten lucky and propose in first couple rounds it
// is allowed to propose, so this test is expected to be "flaky" in a
// sense. It would pass about 1/8 of the time, even if we had the problem it
// is looking for.
}

func wait(f *fixtures.RestClientFixture, a *require.Assertions, count uint64) {
res, err := f.AlgodClient.Status()
a.NoError(err)
round := res.LastRound + count
a.NoError(f.WaitForRoundWithTimeout(round))
}

func zeroPay(f *fixtures.RestClientFixture, a *require.Assertions,
c libgoal.Client, address string) {
pay, err := c.SendPaymentFromUnencryptedWallet(address, address, 1000, 0, nil)
a.NoError(err)
_, err = f.WaitForConfirmedTxn(uint64(pay.LastValid), pay.ID().String())
a.NoError(err)
}

// Go offline, but return the key material so it's easy to go back online
Expand Down Expand Up @@ -121,7 +232,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) {
func online(f *fixtures.RestClientFixture, 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 @@ -136,11 +247,12 @@ func online(f *fixtures.RestClientFixture, a *require.Assertions, client libgoal
a.NoError(err)
onlineTxID, err := client.SignAndBroadcastTransaction(wh, nil, onTx)
a.NoError(err)
_, err = f.WaitForConfirmedTxn(uint64(onTx.LastValid), onlineTxID)
receipt, err := f.WaitForConfirmedTxn(uint64(onTx.LastValid), onlineTxID)
a.NoError(err)
data, err = client.AccountData(address)
a.NoError(err)
// Before bug fix, the account would be suspended in the same round of the
// keyreg, so it would not be online.
a.Equal(basics.Online, data.Status)
return *receipt.ConfirmedRound
}

0 comments on commit dbc8d0f

Please sign in to comment.