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

Merged
merged 26 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
21db44d
periodically track top N online accounts in Ledger, and use when buil…
cce Jul 25, 2024
e968515
run make msgp
cce Jul 26, 2024
8587b28
update votersTracker
cce Aug 9, 2024
38d4b8d
CR fixes
cce Aug 9, 2024
9740ddc
rename and simplify
cce Aug 9, 2024
b8b9673
add LastProposed/LastHeartbeat to BaseOnlineAccountData handling
cce Aug 9, 2024
0b7fbad
fill out LastHeartbeat/LastProposed from #5965
cce Aug 9, 2024
b2f1130
fix remaining tests
cce Aug 9, 2024
5242990
fix missing AccountData => OnlineAccountData transformation function …
cce Aug 9, 2024
0baf81f
Merge remote-tracking branch 'upstream/master' into track-incentive-c…
cce Aug 10, 2024
be464cf
Update ledger/ledger.go
cce Aug 20, 2024
97d0bcf
Update ledger/ledger.go
cce Aug 21, 2024
55d5068
Merge remote-tracking branch 'upstream/master' into track-incentive-c…
cce Aug 28, 2024
01b150a
update TestAbsenteeChecks
cce Sep 18, 2024
0f954d1
Merge remote-tracking branch 'upstream/master' into track-incentive-c…
cce Sep 18, 2024
c24e809
also consider candidates for ExpiredParticipationAccounts
cce Sep 18, 2024
c252d95
update TestExpiredAccountGeneration
cce Sep 18, 2024
bb82a97
Fix TestAbsentTracking
cce Sep 19, 2024
04c0a84
add TestLatestCompletedVotersUpTo
cce Sep 24, 2024
c41a49a
don't propose to suspend yourself, and update TestAbsenteeChallenges
cce Oct 4, 2024
e476730
Update TestExpiredAccountGeneration
cce Oct 4, 2024
9115aae
update TestAbsenteeChecks for proposer change
cce Oct 4, 2024
23b9996
update TestTotalWeightChanges and update comment on generateKnockOffl…
cce Oct 4, 2024
9bc39aa
update TestEvalFunctionForExpiredAccounts
cce Oct 4, 2024
c558d59
update TestOnlineActModel*
cce Oct 4, 2024
9d46fa6
add TestOnlineAccountsCacheSizeBiggerThanStateProofTopVoters
cce Oct 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ $(GOPATH1)/bin/%:
test: build
$(GOTESTCOMMAND) $(GOTAGS) -race $(UNIT_TEST_SOURCES) -timeout 1h -coverprofile=coverage.txt -covermode=atomic

testc:
echo $(UNIT_TEST_SOURCES) | xargs -P8 -n1 go test -c

benchcheck: build
$(GOTESTCOMMAND) $(GOTAGS) -race $(UNIT_TEST_SOURCES) -run ^NOTHING -bench Benchmark -benchtime 1x -timeout 1h

Expand Down
4 changes: 4 additions & 0 deletions cmd/tealdbg/localLedger.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@ func (l *localLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (ba
}, nil
}

func (l *localLedger) GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
cce marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil
}

func (l *localLedger) OnlineCirculation(rnd basics.Round, voteRound basics.Round) (basics.MicroAlgos, error) {
// A constant is fine for tealdbg
return basics.Algos(1_000_000_000), nil // 1B
Expand Down
4 changes: 4 additions & 0 deletions daemon/algod/api/server/v2/dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ func (dl *dryrunLedger) LookupAgreement(rnd basics.Round, addr basics.Address) (
}, nil
}

func (dl *dryrunLedger) GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return nil, nil
}

func (dl *dryrunLedger) OnlineCirculation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
// dryrun doesn't support setting the global online stake, so we'll just return a constant
return basics.Algos(1_000_000_000), nil // 1B
Expand Down
3 changes: 3 additions & 0 deletions data/basics/userBalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ type VotingData struct {
type OnlineAccountData struct {
MicroAlgosWithRewards MicroAlgos
VotingData

IncentiveEligible bool
LastProposed Round
LastHeartbeat Round
}

// AccountData contains the data associated with a given address.
Expand Down
4 changes: 4 additions & 0 deletions ledger/acctdeltas.go
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,11 @@ func accountDataToOnline(address basics.Address, ad *ledgercore.AccountData, pro
NormalizedOnlineBalance: ad.NormalizedOnlineBalance(proto),
VoteFirstValid: ad.VoteFirstValid,
VoteLastValid: ad.VoteLastValid,
VoteID: ad.VoteID,
StateProofID: ad.StateProofID,
LastProposed: ad.LastProposed,
LastHeartbeat: ad.LastHeartbeat,
IncentiveEligible: ad.IncentiveEligible,
}
}

Expand Down
5 changes: 0 additions & 5 deletions ledger/acctonline.go
Original file line number Diff line number Diff line change
Expand Up @@ -622,11 +622,6 @@ func (ao *onlineAccounts) onlineTotals(rnd basics.Round) (basics.MicroAlgos, pro
return basics.MicroAlgos{Raw: onlineRoundParams.OnlineSupply}, onlineRoundParams.CurrentProtocol, nil
}

// LookupOnlineAccountData returns the online account data for a given address at a given round.
func (ao *onlineAccounts) LookupOnlineAccountData(rnd basics.Round, addr basics.Address) (data basics.OnlineAccountData, err error) {
return ao.lookupOnlineAccountData(rnd, addr)
}

// roundOffset calculates the offset of the given round compared to the current dbRound. Requires that the lock would be taken.
func (ao *onlineAccounts) roundOffset(rnd basics.Round) (offset uint64, err error) {
if rnd < ao.cachedDBRoundOnline {
Expand Down
4 changes: 4 additions & 0 deletions ledger/eval/appcow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func (ml *emptyLedger) onlineStake() (basics.MicroAlgos, error) {
return basics.MicroAlgos{}, nil
}

func (ml *emptyLedger) incentiveCandidates(uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return nil, nil
}

func (ml *emptyLedger) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) {
return ledgercore.AppParamsDelta{}, true, nil
}
Expand Down
5 changes: 5 additions & 0 deletions ledger/eval/cow.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type roundCowParent interface {
// lookup retrieves agreement data about an address, querying the ledger if necessary.
lookupAgreement(basics.Address) (basics.OnlineAccountData, error)
onlineStake() (basics.MicroAlgos, error)
incentiveCandidates(rewardsLevel uint64) (data map[basics.Address]basics.OnlineAccountData, err error)

// lookupAppParams, lookupAssetParams, lookupAppLocalState, and lookupAssetHolding retrieve data for a given address and ID.
// If cacheOnly is set, the ledger DB will not be queried, and only the cache will be consulted.
Expand Down Expand Up @@ -192,6 +193,10 @@ func (cb *roundCowState) lookupAgreement(addr basics.Address) (data basics.Onlin
return cb.lookupParent.lookupAgreement(addr)
}

func (cb *roundCowState) incentiveCandidates(rewardsLevel uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return cb.lookupParent.incentiveCandidates(rewardsLevel)
}

func (cb *roundCowState) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) {
params, ok := cb.mods.Accts.GetAppParams(addr, aidx)
if ok {
Expand Down
4 changes: 4 additions & 0 deletions ledger/eval/cow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ func (ml *mockLedger) onlineStake() (basics.MicroAlgos, error) {
return basics.Algos(55_555), nil
}

func (ml *mockLedger) incentiveCandidates(uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return nil, nil
}

func (ml *mockLedger) lookupAppParams(addr basics.Address, aidx basics.AppIndex, cacheOnly bool) (ledgercore.AppParamsDelta, bool, error) {
params, ok := ml.balanceMap[addr].AppParams[aidx]
return ledgercore.AppParamsDelta{Params: &params}, ok, nil // XXX make a copy?
Expand Down
64 changes: 55 additions & 9 deletions ledger/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type LedgerForCowBase interface {
CheckDup(config.ConsensusParams, basics.Round, basics.Round, basics.Round, transactions.Txid, ledgercore.Txlease) error
LookupWithoutRewards(basics.Round, basics.Address) (ledgercore.AccountData, basics.Round, error)
LookupAgreement(basics.Round, basics.Address) (basics.OnlineAccountData, error)
GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (data map[basics.Address]basics.OnlineAccountData, err error)
LookupAsset(basics.Round, basics.Address, basics.AssetIndex) (ledgercore.AssetResource, error)
LookupApplication(basics.Round, basics.Address, basics.AppIndex) (ledgercore.AppResource, error)
LookupKv(basics.Round, string) ([]byte, error)
Expand Down Expand Up @@ -237,6 +238,10 @@ func (x *roundCowBase) lookupAgreement(addr basics.Address) (basics.OnlineAccoun
return ad, err
}

func (x *roundCowBase) incentiveCandidates(rewardsLevel uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return x.l.GetIncentiveKickoffCandidates(x.rnd, x.proto, rewardsLevel)
}

// onlineStake returns the total online stake as of the start of the round. It
// caches the result to prevent repeated calls to the ledger.
func (x *roundCowBase) onlineStake() (basics.MicroAlgos, error) {
Expand Down Expand Up @@ -1620,12 +1625,61 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() {

ch := activeChallenge(&eval.proto, uint64(eval.Round()), eval.state)

// Make a set of candidate addresses to check for expired or absentee status.
type candidateData struct {
VoteLastValid basics.Round
VoteID crypto.OneTimeSignatureVerifier
Status basics.Status
LastProposed basics.Round
LastHeartbeat basics.Round
MicroAlgosWithRewards basics.MicroAlgos
IncentiveEligible bool // currently unused below, but may be needed in the future
}
candidates := make(map[basics.Address]candidateData)

// First, ask the ledger for the top N online accounts, with their latest
// online account data, current up to the previous round.
incentiveCandidates, err := eval.state.incentiveCandidates(eval.state.rewardsLevel())
if err != nil {
// Log an error and keep going; generating lists of absent and expired
// accounts is not required by block validation rules.
logging.Base().Warnf("error fetching incentiveCandidates: %v", err)
incentiveCandidates = nil
}
for accountAddr, acctData := range incentiveCandidates {
// acctData is from previous block: doesn't include any updates in mods
candidates[accountAddr] = candidateData{
VoteLastValid: acctData.VoteLastValid,
VoteID: acctData.VoteID,
Status: basics.Online, // from lookupOnlineAccountData, which only returns online accounts
LastProposed: acctData.LastProposed,
LastHeartbeat: acctData.LastHeartbeat,
MicroAlgosWithRewards: acctData.MicroAlgosWithRewards,
IncentiveEligible: acctData.IncentiveEligible,
}
}

// Then add any accounts modified in this block, with their state at the
// end of the round.
for _, accountAddr := range eval.state.modifiedAccounts() {
acctData, found := eval.state.mods.Accts.GetData(accountAddr)
if !found {
continue
}
// This will overwrite data from the incentiveCandidates() list, if they were modified in the current block.
candidates[accountAddr] = candidateData{
VoteLastValid: acctData.VoteLastValid,
VoteID: acctData.VoteID,
Status: acctData.Status,
jannotti marked this conversation as resolved.
Show resolved Hide resolved
LastProposed: acctData.LastProposed,
LastHeartbeat: acctData.LastHeartbeat,
MicroAlgosWithRewards: acctData.RewardedMicroAlgos,
cce marked this conversation as resolved.
Show resolved Hide resolved
IncentiveEligible: acctData.IncentiveEligible,
}
}

// Now, check these candidate accounts to see if they are expired or absent.
for accountAddr, acctData := range candidates {
// Regardless of being online or suspended, if voting data exists, the
// account can be expired to remove it. This means an offline account
// can be expired (because it was already suspended).
Expand All @@ -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) ||
jannotti marked this conversation as resolved.
Show resolved Hide resolved
gmalouf marked this conversation as resolved.
Show resolved Hide resolved
failsChallenge(ch, accountAddr, lastSeen) {
updates.AbsentParticipationAccounts = append(
updates.AbsentParticipationAccounts,
Expand All @@ -1658,14 +1712,6 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() {
}
}

// delete me in Go 1.21
func max(a, b basics.Round) basics.Round {
if a > b {
return a
}
return b
}

// bitsMatch checks if the first n bits of two byte slices match. Written to
// work on arbitrary slices, but we expect that n is small. Only user today
// calls with n=5.
Expand Down
8 changes: 8 additions & 0 deletions ledger/eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,10 @@ func (ledger *evalTestLedger) LookupAgreement(rnd basics.Round, addr basics.Addr
return convertToOnline(ad), err
}

func (ledger *evalTestLedger) GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (data map[basics.Address]basics.OnlineAccountData, err error) {
return nil, nil
}

// OnlineCirculation just returns a deterministic value for a given round.
func (ledger *evalTestLedger) OnlineCirculation(rnd, voteRound basics.Round) (basics.MicroAlgos, error) {
return basics.MicroAlgos{Raw: uint64(rnd) * 1_000_000}, nil
Expand Down Expand Up @@ -1025,6 +1029,10 @@ func (l *testCowBaseLedger) LookupAgreement(rnd basics.Round, addr basics.Addres
return basics.OnlineAccountData{}, errors.New("not implemented")
}

func (l *testCowBaseLedger) GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (map[basics.Address]basics.OnlineAccountData, error) {
return nil, errors.New("not implemented")
}

func (l *testCowBaseLedger) OnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) {
return basics.MicroAlgos{}, errors.New("not implemented")
}
Expand Down
13 changes: 12 additions & 1 deletion ledger/eval/prefetcher/prefetcher_alignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,16 +119,23 @@ func (l *prefetcherAlignmentTestLedger) LookupWithoutRewards(_ basics.Round, add
}
return ledgercore.AccountData{}, 0, nil
}

func (l *prefetcherAlignmentTestLedger) LookupAgreement(_ basics.Round, addr basics.Address) (basics.OnlineAccountData, error) {
// prefetch alignment tests do not check for prefetching of online account data
// because it's quite different and can only occur in AVM opcodes, which
// aren't handled anyway (just as we don't know if a holding or app local
// will be accessed in AVM.)
return basics.OnlineAccountData{}, errors.New("not implemented")
}

func (l *prefetcherAlignmentTestLedger) OnlineCirculation(rnd, voteRnd basics.Round) (basics.MicroAlgos, error) {
return basics.MicroAlgos{}, errors.New("not implemented")
panic("not implemented")
cce marked this conversation as resolved.
Show resolved Hide resolved
}

func (l *prefetcherAlignmentTestLedger) GetIncentiveKickoffCandidates(basics.Round, config.ConsensusParams, uint64) (map[basics.Address]basics.OnlineAccountData, error) {
return nil, errors.New("not implemented")
}

func (l *prefetcherAlignmentTestLedger) LookupApplication(rnd basics.Round, addr basics.Address, aidx basics.AppIndex) (ledgercore.AppResource, error) {
l.mu.Lock()
if l.requestedApps == nil {
Expand All @@ -144,6 +151,7 @@ func (l *prefetcherAlignmentTestLedger) LookupApplication(rnd basics.Round, addr

return l.apps[addr][aidx], nil
}

func (l *prefetcherAlignmentTestLedger) LookupAsset(rnd basics.Round, addr basics.Address, aidx basics.AssetIndex) (ledgercore.AssetResource, error) {
l.mu.Lock()
if l.requestedAssets == nil {
Expand All @@ -159,9 +167,11 @@ func (l *prefetcherAlignmentTestLedger) LookupAsset(rnd basics.Round, addr basic

return l.assets[addr][aidx], nil
}

func (l *prefetcherAlignmentTestLedger) LookupKv(rnd basics.Round, key string) ([]byte, error) {
panic("not implemented")
}

func (l *prefetcherAlignmentTestLedger) GetCreatorForRound(_ basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType) (basics.Address, bool, error) {
l.mu.Lock()
if l.requestedCreators == nil {
Expand All @@ -175,6 +185,7 @@ func (l *prefetcherAlignmentTestLedger) GetCreatorForRound(_ basics.Round, cidx
}
return basics.Address{}, false, nil
}

func (l *prefetcherAlignmentTestLedger) GenesisHash() crypto.Digest {
return crypto.Digest{}
}
Expand Down
28 changes: 27 additions & 1 deletion ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type Ledger struct {
notifier blockNotifier
metrics metricsTracker
spVerification spVerificationTracker
topOnlineCache topOnlineCache

trackers trackerRegistry
trackerMu deadlock.RWMutex
Expand Down Expand Up @@ -635,10 +636,35 @@ func (l *Ledger) LookupAgreement(rnd basics.Round, addr basics.Address) (basics.
defer l.trackerMu.RUnlock()

// Intentionally apply (pending) rewards up to rnd.
data, err := l.acctsOnline.LookupOnlineAccountData(rnd, addr)
data, err := l.acctsOnline.lookupOnlineAccountData(rnd, addr)
return data, err
}

// GetIncentiveKickoffCandidates retrieves a list of online accounts who may not have
// proposed or sent a heartbeat recently.
func (l *Ledger) GetIncentiveKickoffCandidates(rnd basics.Round, proto config.ConsensusParams, rewardsLevel uint64) (map[basics.Address]basics.OnlineAccountData, error) {
l.trackerMu.RLock()
defer l.trackerMu.RUnlock()

// get cached list of top N addresses
addrs, err := l.topOnlineCache.topN(&l.acctsOnline, rnd, proto, rewardsLevel)
if err != nil {
return nil, err
}

// fetch data for this round from online account cache. These accounts should all
// be in cache, as long as topOnlineCacheSize < onlineAccountsCacheMaxSize.
ret := make(map[basics.Address]basics.OnlineAccountData)
for _, addr := range addrs {
data, err := l.acctsOnline.lookupOnlineAccountData(rnd, addr)
if err != nil {
continue // skip missing / not online accounts
Copy link
Contributor

Choose a reason for hiding this comment

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

why would voters ever return non-online account?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the voters are only calculating Top N every 256 rounds, so if a lookup for the current round (for the cached addr from the last state proof interval) being requested is that the account was closed/deleted, you could hit an error here.

Copy link
Contributor Author

@cce cce Oct 31, 2024

Choose a reason for hiding this comment

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

I should add a comment and write a test exercising this case, realizing it is kind of complicated now after writing it out

}
ret[addr] = data
}
return ret, nil
}

// LookupWithoutRewards is like Lookup but does not apply pending rewards up
// to the requested round rnd.
func (l *Ledger) LookupWithoutRewards(rnd basics.Round, addr basics.Address) (ledgercore.AccountData, basics.Round, error) {
Expand Down
9 changes: 7 additions & 2 deletions ledger/ledgercore/onlineacct.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
package ledgercore

import (
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/data/basics"
)

// An OnlineAccount corresponds to an account whose AccountData.Status
// is Online. This is used for a Merkle tree commitment of online
// is Online. This is used for a Merkle tree commitment of online
// accounts, which is subsequently used to validate participants for
// a state proof.
// a state proof. It is also used to track incentives participants.
type OnlineAccount struct {
// These are a subset of the fields from the corresponding AccountData.
Address basics.Address
Expand All @@ -33,5 +34,9 @@ type OnlineAccount struct {
NormalizedOnlineBalance uint64
VoteFirstValid basics.Round
VoteLastValid basics.Round
VoteID crypto.OneTimeSignatureVerifier
StateProofID merklesignature.Commitment
LastProposed basics.Round
LastHeartbeat basics.Round
IncentiveEligible bool
cce marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 6 additions & 0 deletions ledger/store/trackerdb/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ type BaseOnlineAccountData struct {

BaseVotingData

LastProposed basics.Round `codec:"V"`
LastHeartbeat basics.Round `codec:"W"`
IncentiveEligible bool `codec:"X"`
MicroAlgos basics.MicroAlgos `codec:"Y"`
RewardsBase uint64 `codec:"Z"`
Expand Down Expand Up @@ -469,7 +471,11 @@ func (bo *BaseOnlineAccountData) GetOnlineAccount(addr basics.Address, normBalan
NormalizedOnlineBalance: normBalance,
VoteFirstValid: bo.VoteFirstValid,
VoteLastValid: bo.VoteLastValid,
VoteID: bo.VoteID,
StateProofID: bo.StateProofID,
LastHeartbeat: bo.LastHeartbeat,
LastProposed: bo.LastProposed,
IncentiveEligible: bo.IncentiveEligible,
}
}

Expand Down
Loading
Loading