Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incentives: cache top online accounts and use when building AbsentParticipationAccounts #6085

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

cce
Copy link
Contributor

@cce cce commented Jul 26, 2024

Summary

In #5757 a mechanism was introduced to suspend "absentee" accounts that don't participate (by making a proposal, or heartbeat as in #5799), by adding a block header AbsentParticipationAccounts, similar to ExpiredParticipationAccounts.

Currently, the list is generated by considering any account touched in the current block, since this data is available in memory at endOfBlock() time. This PR adds a periodically-updated cache of top online accounts to the ledger, for use in considering accounts that are not involved in the current block for absentee or expired status.

To get a list of recent top online accounts, this PR grabs the most recently-completed collection of top online accounts compiled by the state proof worker. Every 256 rounds, the state proof system performs a TopOnlineAccounts query, so access was added to get its most recently-updated list of online addresses, and look up their values for the latest round from the online account cache.

LastProposed and LastHeartbeat are added to the online accounts table's DB representation in this PR. This also fixes an issue introduced in #5965 where uses of ledgercore.OnlineAccountData (which didn't have LastHeartbeat/LastProposed fields) were replaced by basics.OnlineAccountData (which did) and ended up with those fields not being set in a couple of conversions from AccountData.

Test Plan

  • update TestAbsenteeChecks (currently failing intentionally, due to automatic suspension)
  • update TestExpiredAccountGeneration (currently this PR does not automatically expire accounts; just suspends them)
  • maybe update TestExpiredAccountGenerationWithDiskFailure?
  • update TestAbsentTracking
  • added new TestLatestCompletedVotersUpTo
  • update TestAbsenteeChallenges
  • update test/e2e-go/features/incentives/suspension_test.go

@cce cce force-pushed the track-incentive-candidates branch from 975ddb4 to 21db44d Compare July 26, 2024 17:35
ledger/ledgercore/onlineacct.go Outdated Show resolved Hide resolved
Copy link
Contributor

@algorandskiy algorandskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Just a thought - why not to init top online on startup and then maintain the list in acctonline while processing incoming blocks?

@cce
Copy link
Contributor Author

cce commented Jul 27, 2024

Just a thought - why not to init top online on startup and then maintain the list in acctonline while processing incoming blocks?

My first approach was to make it a field in the onlineAccounts tracker like the ao.voters sub-tracker, but as I kept cutting it down to MVP and removed its access to the onlineAccounts.deltas, and took it out of being called in onlineAccounts.newBlockImpl(), I moved it out since it had no dependencies left. It felt like I kept adding duplicate state that was already being maintained in onlineAccounts, and duplicate logic for looking it up. So I wanted to try just the absolute minimal approach to start, by relying totally on the onlineAccounts tracker's caching system (which we already know works correctly) rather than add a new cached list of online accounts and delta-processing code..

cmd/tealdbg/localLedger.go Outdated Show resolved Hide resolved
ledger/eval/eval.go Outdated Show resolved Hide resolved
ledger/eval/prefetcher/prefetcher_alignment_test.go Outdated Show resolved Hide resolved
ledger/toponline.go Outdated Show resolved Hide resolved
@@ -1647,7 +1701,7 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() {

if acctData.Status == basics.Online {
lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat)
if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, current) ||
if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgosWithRewards, lastSeen, current) ||
Copy link
Contributor Author

@cce cce Aug 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jannotti in your earlier version, you were able to use the (ledgercore.AccountData).MicroAlgos value returned from eval.state.mods.Accts.GetData here without calling AccountData.WithUpdatedRewards() because you knew all accounts in mods would be guaranteed to already have their rewards updated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall thinking it through that way, but it seems true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth checking rewardsLevel or anything to make sure they were applied? I imagine all accounts touched should end up having them applied them but maybe there are some that don't?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do a little further checking. I think any account considered touched here will also be updated. I think for weird cases like an axfer from A to B, only A is touched (since its algo balance changes when paying fee). Similar stuff can happen with accounts in app calls. Probably/hopefully eval.state.modifiedAccounts() are exactly the ones that have truly been touched and will therefore have updated balances.

Copy link

codecov bot commented Aug 9, 2024

Codecov Report

Attention: Patch coverage is 57.47126% with 37 lines in your changes missing coverage. Please review.

Project coverage is 53.07%. Comparing base (47fd1c9) to head (b2f1130).
Report is 20 commits behind head on master.

Files Patch % Lines
ledger/eval/eval.go 67.56% 10 Missing and 2 partials ⚠️
ledger/ledger.go 43.75% 8 Missing and 1 partial ⚠️
ledger/ledgercore/votersForRound.go 0.00% 4 Missing ⚠️
ledger/tracker.go 0.00% 3 Missing ⚠️
ledger/voters.go 72.72% 2 Missing and 1 partial ⚠️
cmd/tealdbg/localLedger.go 0.00% 2 Missing ⚠️
daemon/algod/api/server/v2/dryrun.go 0.00% 2 Missing ⚠️
ledger/ledgercore/accountdata.go 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6085      +/-   ##
==========================================
- Coverage   55.81%   53.07%   -2.74%     
==========================================
  Files         488      488              
  Lines       69581    69656      +75     
==========================================
- Hits        38838    36973    -1865     
- Misses      28049    29925    +1876     
- Partials     2694     2758      +64     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

ledger/eval/eval.go Show resolved Hide resolved
@@ -1647,7 +1701,7 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() {

if acctData.Status == basics.Online {
lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat)
if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, current) ||
if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgosWithRewards, lastSeen, current) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall thinking it through that way, but it seems true.

ledger/ledger.go Outdated Show resolved Hide resolved
Co-authored-by: John Jannotti <[email protected]>
@cce cce requested a review from algorandskiy August 21, 2024 15:22
ledger/ledger.go Outdated Show resolved Hide resolved
// First, ask the ledger for the top N online accounts, with their latest
// online account data, current up to the previous round.
if maxSuspensions > 0 {
knockOfflineCandidates, err := eval.state.knockOfflineCandidates()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this depends on a node state (not chain state) it could produce different results on different nodes - one can return actual voters and other voters for previous period. In this case network would not agree on block.
Even with "if a current period voters not ready then return empty" rule it is the same story, some might have it ready.
To mitigate consider this:

  1. move the invocation of generateKnockOfflineAccountsList after the stateProofVotersAndTotal in endOfBlock
  2. always aim for the current interval, and return empty if not ready.
    This would allows us to ensure that a current interval voters are ready, and all nodes use the same input data.

Upd: I realized it is called only in mode=generate, so probably does not matter, but maybe makes sense to comment/document it is expected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants