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 25 commits
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 @@ -293,6 +293,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 @@
}, nil
}

func (l *localLedger) GetKnockOfflineCandidates(basics.Round, config.ConsensusParams) (map[basics.Address]basics.OnlineAccountData, error) {
return nil, nil

Check warning on line 363 in cmd/tealdbg/localLedger.go

View check run for this annotation

Codecov / codecov/patch

cmd/tealdbg/localLedger.go#L362-L363

Added lines #L362 - L363 were not covered by tests
}

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 @@
}, nil
}

func (dl *dryrunLedger) GetKnockOfflineCandidates(basics.Round, config.ConsensusParams) (map[basics.Address]basics.OnlineAccountData, error) {
return nil, nil

Check warning on line 333 in daemon/algod/api/server/v2/dryrun.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/v2/dryrun.go#L332-L333

Added lines #L332 - L333 were not covered by tests
}

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
5 changes: 5 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 Expand Up @@ -561,6 +564,8 @@ func (u AccountData) OnlineAccountData() OnlineAccountData {
VoteKeyDilution: u.VoteKeyDilution,
},
IncentiveEligible: u.IncentiveEligible,
LastProposed: u.LastProposed,
LastHeartbeat: u.LastHeartbeat,
}
}

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: 2 additions & 2 deletions ledger/acctonline_expired_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ func TestOnlineAcctModelSimple(t *testing.T) {
})
// test same scenario on double ledger
t.Run("DoubleLedger", func(t *testing.T) {
m := newDoubleLedgerAcctModel(t, protocol.ConsensusFuture, true)
m := newDoubleLedgerAcctModel(t, protocol.ConsensusV39, true) // TODO simulate heartbeats
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not keep this on future?

Copy link
Contributor Author

@cce cce Oct 10, 2024

Choose a reason for hiding this comment

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

It fails because heartbeats aren't implemented, but proposers aren't being set, so the big accounts are challenged and kicked offline, and all the stake numbers don't match the test expectations. I could have tried to fix this by ensuring all the test accounts show up as proposers as often as necessary to avoid suspension, but I thought maybe it would be better to see after heartbeats were implemented whether that would make the tests pass without as much modification.

defer m.teardown()
testOnlineAcctModelSimple(t, m)
})
Expand Down Expand Up @@ -626,7 +626,7 @@ func TestOnlineAcctModelScenario(t *testing.T) {
})
// test same scenario on double ledger
t.Run("DoubleLedger", func(t *testing.T) {
m := newDoubleLedgerAcctModel(t, protocol.ConsensusFuture, true)
m := newDoubleLedgerAcctModel(t, protocol.ConsensusV39, true) // TODO simulate heartbeats
defer m.teardown()
runScenario(t, m, tc.scenario)
})
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) knockOfflineCandidates() (map[basics.Address]basics.OnlineAccountData, 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)
knockOfflineCandidates() (map[basics.Address]basics.OnlineAccountData, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe a NIT: should we actually call this top online accounts or similar naming? It's very clear from comments that's what we are requesting, more a debate over if the name should be based on what it's sourced from vs the use-case we have for this atm.

Copy link
Contributor Author

@cce cce Oct 10, 2024

Choose a reason for hiding this comment

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

It is a potentially stale list of top online accounts, if new accounts appeared online in the last 256 rounds (since the last state proof) they wouldn't appear. So the word "candidates" was intended to make it seem a little less definitive that this was the complete list of top online accounts for the round... but happy to pick any other name, I wasn't particularly happy with this name.

This is already being used in a method JJ called "generateKnockOfflineAccountsList" in #5757 which is where the "knockOffline" part came from.


// 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) knockOfflineCandidates() (map[basics.Address]basics.OnlineAccountData, error) {
return cb.lookupParent.knockOfflineCandidates()
}

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) knockOfflineCandidates() (map[basics.Address]basics.OnlineAccountData, 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
129 changes: 108 additions & 21 deletions ledger/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"github.com/algorand/go-algorand/ledger/ledgercore"
"github.com/algorand/go-algorand/logging"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/util"
"github.com/algorand/go-algorand/util/execpool"
)

Expand All @@ -48,6 +49,7 @@
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)
GetKnockOfflineCandidates(basics.Round, config.ConsensusParams) (map[basics.Address]basics.OnlineAccountData, error)
jannotti marked this conversation as resolved.
Show resolved Hide resolved
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 +239,10 @@
return ad, err
}

func (x *roundCowBase) knockOfflineCandidates() (map[basics.Address]basics.OnlineAccountData, error) {
return x.l.GetKnockOfflineCandidates(x.rnd, x.proto)
}

// 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 @@ -1339,7 +1345,13 @@
}

// Call "endOfBlock" after all the block's rewards and transactions are processed.
func (eval *BlockEvaluator) endOfBlock() error {
// When generating a block, participating addresses are passed to prevent a
// proposer from suspending itself.
func (eval *BlockEvaluator) endOfBlock(participating ...basics.Address) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why ...basics.Address instead of []basics.Address? I assume callers always have a slice, as opposed to call sites with, say, 5 explicit arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's true, this is just me optimizing for a smaller diff, to not change other endOfBlock callers, but the idea is to pass a slice — can change

if participating != nil && !eval.generate {
panic("logic error: only pass partAddresses to endOfBlock when generating")

Check warning on line 1352 in ledger/eval/eval.go

View check run for this annotation

Codecov / codecov/patch

ledger/eval/eval.go#L1352

Added line #L1352 was not covered by tests
}

if eval.generate {
var err error
eval.block.TxnCommitments, err = eval.block.PaysetCommit()
Expand All @@ -1364,7 +1376,7 @@
}
}

eval.generateKnockOfflineAccountsList()
eval.generateKnockOfflineAccountsList(participating)

if eval.proto.StateProofInterval > 0 {
var basicStateProof bookkeeping.StateProofTrackingData
Expand Down Expand Up @@ -1607,25 +1619,94 @@
// deltas and testing if any of them needs to be reset/suspended. Expiration
// takes precedence - if an account is expired, it should be knocked offline and
// key material deleted. If it is only suspended, the key material will remain.
func (eval *BlockEvaluator) generateKnockOfflineAccountsList() {
//
// Different ndoes may propose different list of addresses based on node state.
Copy link
Contributor

Choose a reason for hiding this comment

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

ndoes -> nodes

// Block validators only check whether ExpiredParticipationAccounts or
// AbsentParticipationAccounts meet the criteria for expiration or suspension,
// not whether the lists are complete.
//
// This function is passed a list of participating addresses so a node will not
// propose a block that suspends or expires itself.
func (eval *BlockEvaluator) generateKnockOfflineAccountsList(participating []basics.Address) {
Copy link
Contributor

Choose a reason for hiding this comment

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

participating is really "participating accounts excluding any I host"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

here, the "participating" argument is the accounts that the node hosts.

if !eval.generate {
return
}
current := eval.Round()

current := eval.Round()
maxExpirations := eval.proto.MaxProposedExpiredOnlineAccounts
maxSuspensions := eval.proto.Payouts.MaxMarkAbsent

updates := &eval.block.ParticipationUpdates

ch := activeChallenge(&eval.proto, uint64(eval.Round()), eval.state)
ch := activeChallenge(&eval.proto, uint64(current), 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)
partAddrs := util.MakeSet(participating...)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we do anything else with this slice? Maybe we should push the Set type up through the callers, so that it is built as a Set when it is first created to pass to endOfBlock?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used in GenerateBlock while making a map of end-of-block account state for participating addresses, to include in the UnfinishedBlock ... if we pushed it up to GenerateBlock then it could protect against looking up the same participating address twice, if duplicate addresses were passed to GenerateBlock.


// 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()
cce marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
// Log an error and keep going; generating lists of absent and expired
Copy link
Contributor

Choose a reason for hiding this comment

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

So this implies some nodes can "choose" not to search for absent/expired accounts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, when generating a block it is not required they put any accounts in the {Absent,Expired}ParticipationAccounts block headers, but if they are in the list, validation rules require that the accounts are actually absent or expired.

// accounts is not required by block validation rules.
logging.Base().Warnf("error fetching knockOfflineCandidates: %v", err)
knockOfflineCandidates = nil

Check warning on line 1664 in ledger/eval/eval.go

View check run for this annotation

Codecov / codecov/patch

ledger/eval/eval.go#L1663-L1664

Added lines #L1663 - L1664 were not covered by tests
}
for accountAddr, acctData := range knockOfflineCandidates {
// 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
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess need a test to enforce knockOfflineCandidates -> lookupOnlineAccountData control flow

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 knockOfflineCandidates() 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.WithUpdatedRewards(eval.proto, eval.state.rewardsLevel()).MicroAlgos,
IncentiveEligible: acctData.IncentiveEligible,
}
}

// Now, check these candidate accounts to see if they are expired or absent.
for accountAddr, acctData := range candidates {
if acctData.MicroAlgosWithRewards.IsZero() {
Copy link
Contributor

Choose a reason for hiding this comment

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

100% of time, zero balance implies being closed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's correct, my understanding is currently the only way you can have a zero balance at the end of the round is if your account has been closed.

continue // don't check accounts that are being closed
}

if _, ok := partAddrs[accountAddr]; ok {
continue // don't check our own participation accounts
}

// Expired check: are this account's voting keys no longer valid?
// 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 @@ -1641,13 +1722,15 @@
}
}

// Absent check: has it been too long since the last heartbeat/proposal, or
// has this online account failed a challenge?
if len(updates.AbsentParticipationAccounts) >= maxSuspensions {
continue // no more room (don't break the loop, since we may have more expiries)
}

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 +1741,6 @@
}
}

// 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 Expand Up @@ -1821,6 +1896,9 @@
if acctData.Status != basics.Online {
return fmt.Errorf("proposed absent account %v was %v, not Online", accountAddr, acctData.Status)
}
if acctData.MicroAlgos.IsZero() {
return fmt.Errorf("proposed absent account %v with zero algos", accountAddr)

Check warning on line 1900 in ledger/eval/eval.go

View check run for this annotation

Codecov / codecov/patch

ledger/eval/eval.go#L1900

Added line #L1900 was not covered by tests
}
jannotti marked this conversation as resolved.
Show resolved Hide resolved

lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat)
if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, eval.Round()) {
Expand Down Expand Up @@ -1890,7 +1968,16 @@
// After a call to GenerateBlock, the BlockEvaluator can still be used to
// accept transactions. However, to guard against reuse, subsequent calls
// to GenerateBlock on the same BlockEvaluator will fail.
func (eval *BlockEvaluator) GenerateBlock(addrs []basics.Address) (*ledgercore.UnfinishedBlock, error) {
//
// A list of participating addresses is passed to GenerateBlock. This lets
// the BlockEvaluator know which of this node's participating addresses might
// be proposing this block. This information is used when:
// - generating lists of absent accounts (don't suspend yourself)
// - preparing a ledgercore.UnfinishedBlock, which contains the end-of-block
// state of each potential proposer. This allows for a final check in
// UnfinishedBlock.FinishBlock to ensure the proposer hasn't closed its
// account before setting the ProposerPayout header.
func (eval *BlockEvaluator) GenerateBlock(participating []basics.Address) (*ledgercore.UnfinishedBlock, error) {
if !eval.generate {
logging.Base().Panicf("GenerateBlock() called but generate is false")
}
Expand All @@ -1899,19 +1986,19 @@
return nil, fmt.Errorf("GenerateBlock already called on this BlockEvaluator")
}

err := eval.endOfBlock()
err := eval.endOfBlock(participating...)
if err != nil {
return nil, err
}

// look up set of participation accounts passed to GenerateBlock (possible proposers)
finalAccounts := make(map[basics.Address]ledgercore.AccountData, len(addrs))
for i := range addrs {
acct, err := eval.state.lookup(addrs[i])
// look up end-of-block state of possible proposers passed to GenerateBlock
finalAccounts := make(map[basics.Address]ledgercore.AccountData, len(participating))
for i := range participating {
acct, err := eval.state.lookup(participating[i])
if err != nil {
return nil, err
}
finalAccounts[addrs[i]] = acct
finalAccounts[participating[i]] = acct
}

vb := ledgercore.MakeUnfinishedBlock(eval.block, eval.state.deltas(), finalAccounts)
Expand Down
Loading
Loading