Skip to content

Commit

Permalink
ledger: Exclude stake at R-320 that is expired by R (#34)
Browse files Browse the repository at this point in the history
This adds a DB method to calculate the total expired stake registered at rnd that is expired by voteRnd.

It adds a consensus check to the ledger implementation of the Circulation(rnd) call that agreement makes, which in turn calls onlineAccounts.onlineTotals. If ExcludeExpiredCirculation is set, it subtracts expired stake at rnd+320 from the total online stake at rnd.

Details:
* when the next round is going to be round R, a proposal/vote will come in for round R (or some other trigger we could add)
* query the DB's onlineaccounts table looking for accounts online at R that are expired in any round before R+320 (this using the "updated as of rnd" and "expired by rnd" indexes)
* sum up the expired online stake for these accounts
* subtract expired stake from total online stake and put in cache to use for all votes seen for R

Co-authored-by: Pavel Zbitskiy <[email protected]>
  • Loading branch information
cce and algorandskiy authored May 19, 2023
1 parent 5e1f8a2 commit ed14cbb
Show file tree
Hide file tree
Showing 26 changed files with 1,417 additions and 97 deletions.
6 changes: 3 additions & 3 deletions agreement/abstractions.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ type LedgerReader interface {
// protocol may lose liveness.
LookupAgreement(basics.Round, basics.Address) (basics.OnlineAccountData, error)

// Circulation returns the total amount of money in circulation at the
// conclusion of a given round.
// Circulation returns the total amount of online money in circulation at the
// conclusion of a given round rnd that is eligible for voting at voteRnd.
//
// This method returns an error if the given Round has not yet been
// confirmed. It may also return an error if the given Round is
// unavailable by the storage device. In that case, the agreement
// protocol may lose liveness.
Circulation(basics.Round) (basics.MicroAlgos, error)
Circulation(rnd basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error)

// LookupDigest returns the Digest of the entry that was agreed on in a
// given round.
Expand Down
2 changes: 1 addition & 1 deletion agreement/agreementtest/simulate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
return l.state[a].OnlineAccountData(), nil
}

func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) {
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
l.mu.Lock()
defer l.mu.Unlock()

Expand Down
2 changes: 1 addition & 1 deletion agreement/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
return l.state[a].OnlineAccountData(), nil
}

func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) {
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
l.mu.Lock()
defer l.mu.Unlock()

Expand Down
2 changes: 1 addition & 1 deletion agreement/demux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ func (t *demuxTester) LookupAgreement(basics.Round, basics.Address) (basics.Onli
}

// implement Ledger
func (t *demuxTester) Circulation(basics.Round) (basics.MicroAlgos, error) {
func (t *demuxTester) Circulation(basics.Round, basics.Round) (basics.MicroAlgos, error) {
// we don't care about this function in this test.
return basics.MicroAlgos{}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion agreement/fuzzer/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func (l *testLedger) LookupAgreement(r basics.Round, a basics.Address) (basics.O
return l.state[a].OnlineAccountData(), nil
}

func (l *testLedger) Circulation(r basics.Round) (basics.MicroAlgos, error) {
func (l *testLedger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
l.mu.Lock()
defer l.mu.Unlock()

Expand Down
2 changes: 1 addition & 1 deletion agreement/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func membership(l LedgerReader, addr basics.Address, r basics.Round, p period, s
return
}

total, err := l.Circulation(balanceRound)
total, err := l.Circulation(balanceRound, r)
if err != nil {
err = fmt.Errorf("Service.initializeVote (r=%d): Failed to obtain total circulation in round %d: %v", r, balanceRound, err)
return
Expand Down
2 changes: 1 addition & 1 deletion catchup/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,7 @@ func (m *mockedLedger) Block(r basics.Round) (bookkeeping.Block, error) {
func (m *mockedLedger) Lookup(basics.Round, basics.Address) (basics.AccountData, error) {
return basics.AccountData{}, errors.New("not needed for mockedLedger")
}
func (m *mockedLedger) Circulation(basics.Round) (basics.MicroAlgos, error) {
func (m *mockedLedger) Circulation(basics.Round, basics.Round) (basics.MicroAlgos, error) {
return basics.MicroAlgos{}, errors.New("not needed for mockedLedger")
}
func (m *mockedLedger) ConsensusVersion(basics.Round) (protocol.ConsensusVersion, error) {
Expand Down
7 changes: 7 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,11 @@ type ConsensusParams struct {

// EnableBoxRefNameError specifies that box ref names should be validated early
EnableBoxRefNameError bool

// ExcludeExpiredCirculation excludes expired stake from the total online stake
// used by agreement for Circulation, and updates the calculation of StateProofOnlineTotalWeight used
// by state proofs to use the same method (rather than excluding stake from the top N stakeholders as before).
ExcludeExpiredCirculation bool
}

// PaysetCommitType enumerates possible ways for the block header to commit to
Expand Down Expand Up @@ -1281,6 +1286,8 @@ func initConsensusProtocols() {
vFuture.StateProofUseTrackerVerification = true
vFuture.EnableCatchpointsWithSPContexts = true

vFuture.ExcludeExpiredCirculation = true

Consensus[protocol.ConsensusFuture] = vFuture

// vAlphaX versions are an separate series of consensus parameters and versions for alphanet
Expand Down
4 changes: 2 additions & 2 deletions data/datatest/impls.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ func (i ledgerImpl) LookupAgreement(r basics.Round, addr basics.Address) (basics
}

// Circulation implements Ledger.Circulation.
func (i ledgerImpl) Circulation(r basics.Round) (basics.MicroAlgos, error) {
return i.l.Circulation(r)
func (i ledgerImpl) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
return i.l.Circulation(r, voteRnd)
}

// Wait implements Ledger.Wait.
Expand Down
18 changes: 10 additions & 8 deletions data/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,17 @@ type Ledger struct {
lastRoundSeed atomic.Value
}

// roundCirculationPair used to hold a pair of matching round number and the amount of online money
type roundCirculationPair struct {
// roundCirculationItem used to hold matching round number, vote round and the amount of online money
type roundCirculationItem struct {
round basics.Round
voteRound basics.Round
onlineMoney basics.MicroAlgos
}

// roundCirculation is the cache for the circulating coins
type roundCirculation struct {
// elements holds several round-onlineMoney pairs
elements [2]roundCirculationPair
elements [2]roundCirculationItem
}

// roundSeedPair is the cache for a single seed at a given round
Expand Down Expand Up @@ -174,28 +175,29 @@ func (l *Ledger) NextRound() basics.Round {
}

// Circulation implements agreement.Ledger.Circulation.
func (l *Ledger) Circulation(r basics.Round) (basics.MicroAlgos, error) {
func (l *Ledger) Circulation(r basics.Round, voteRnd basics.Round) (basics.MicroAlgos, error) {
circulation, cached := l.lastRoundCirculation.Load().(roundCirculation)
if cached && r != basics.Round(0) {
for _, element := range circulation.elements {
if element.round == r {
if element.round == r && element.voteRound == voteRnd {
return element.onlineMoney, nil
}
}
}

totals, err := l.OnlineTotals(r)
totals, err := l.OnlineCirculation(r, voteRnd)
if err != nil {
return basics.MicroAlgos{}, err
}

if !cached || r > circulation.elements[1].round {
if !cached || r > circulation.elements[1].round || voteRnd > circulation.elements[1].voteRound {
l.lastRoundCirculation.Store(
roundCirculation{
elements: [2]roundCirculationPair{
elements: [2]roundCirculationItem{
circulation.elements[1],
{
round: r,
voteRound: voteRnd,
onlineMoney: totals},
},
})
Expand Down
23 changes: 14 additions & 9 deletions data/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ func testGenerateInitState(tb testing.TB, proto protocol.ConsensusVersion) (gene
func TestLedgerCirculation(t *testing.T) {
partitiontest.PartitionTest(t)

genesisInitState, keys := testGenerateInitState(t, protocol.ConsensusCurrentVersion)
proto := protocol.ConsensusCurrentVersion
genesisInitState, keys := testGenerateInitState(t, proto)

const inMem = true
cfg := config.GetDefaultLocal()
Expand Down Expand Up @@ -171,6 +172,8 @@ func TestLedgerCirculation(t *testing.T) {
srcAccountKey := keys[sourceAccount]
require.NotNil(t, srcAccountKey)

params := config.Consensus[proto]

for rnd := basics.Round(1); rnd < basics.Round(600); rnd++ {
blk.BlockHeader.Round++
blk.BlockHeader.TimeStamp += int64(crypto.RandUint64() % 100 * 1000)
Expand All @@ -191,6 +194,8 @@ func TestLedgerCirculation(t *testing.T) {
require.NoError(t, l.AddBlock(blk, agreement.Certificate{}))
l.WaitForCommit(rnd)

var voteRoundOffset = basics.Round(2 * params.SeedRefreshInterval * params.SeedLookback)

// test most recent round
if rnd < basics.Round(500) {
data, validThrough, _, err = realLedger.LookupAccount(rnd, destAccount)
Expand All @@ -202,11 +207,11 @@ func TestLedgerCirculation(t *testing.T) {
require.Equal(t, rnd, validThrough)
require.Equal(t, baseDestValue+uint64(rnd), data.MicroAlgos.Raw)

roundCirculation, err := realLedger.OnlineTotals(rnd)
roundCirculation, err := realLedger.OnlineCirculation(rnd, rnd+voteRoundOffset)
require.NoError(t, err)
require.Equal(t, baseCirculation-uint64(rnd)*(10001), roundCirculation.Raw)

roundCirculation, err = l.OnlineTotals(rnd)
roundCirculation, err = l.OnlineCirculation(rnd, rnd+voteRoundOffset)
require.NoError(t, err)
require.Equal(t, baseCirculation-uint64(rnd)*(10001), roundCirculation.Raw)
} else if rnd < basics.Round(510) {
Expand All @@ -220,11 +225,11 @@ func TestLedgerCirculation(t *testing.T) {
require.Equal(t, rnd-1, validThrough)
require.Equal(t, baseDestValue+uint64(rnd)-1, data.MicroAlgos.Raw)

roundCirculation, err := realLedger.OnlineTotals(rnd - 1)
roundCirculation, err := realLedger.OnlineCirculation(rnd-1, rnd-1+voteRoundOffset)
require.NoError(t, err)
require.Equal(t, baseCirculation-uint64(rnd-1)*(10001), roundCirculation.Raw)

roundCirculation, err = l.OnlineTotals(rnd - 1)
roundCirculation, err = l.OnlineCirculation(rnd-1, rnd-1+voteRoundOffset)
require.NoError(t, err)
require.Equal(t, baseCirculation-uint64(rnd-1)*(10001), roundCirculation.Raw)
} else if rnd < basics.Round(520) {
Expand All @@ -236,17 +241,17 @@ func TestLedgerCirculation(t *testing.T) {
require.Error(t, err)
require.Equal(t, uint64(0), data.MicroAlgos.Raw)

_, err = realLedger.OnlineTotals(rnd + 1)
_, err = realLedger.OnlineCirculation(rnd+1, rnd+1+voteRoundOffset)
require.Error(t, err)

_, err = l.OnlineTotals(rnd + 1)
_, err = l.OnlineCirculation(rnd+1, rnd+1+voteRoundOffset)
require.Error(t, err)
} else if rnd < basics.Round(520) {
// test expired round ( expected error )
_, err = realLedger.OnlineTotals(rnd - 500)
_, err = realLedger.OnlineCirculation(rnd-500, rnd-500+voteRoundOffset)
require.Error(t, err)

_, err = l.OnlineTotals(rnd - 500)
_, err = l.OnlineCirculation(rnd-500, rnd-500+voteRoundOffset)
require.Error(t, err)
}
}
Expand Down
Loading

0 comments on commit ed14cbb

Please sign in to comment.