Skip to content

Commit

Permalink
Merge branch 'absenteeism' into absenteeism-split-assembledblock
Browse files Browse the repository at this point in the history
  • Loading branch information
jannotti authored Mar 29, 2024
2 parents 6305819 + a914ee1 commit e82769e
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 187 deletions.
1 change: 1 addition & 0 deletions agreement/proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk Assemb

blk = blk.WithProposer(newSeed, address, eligible)
prop := makeProposalFromAssembledBlock(blk, seedProof, period, address)

value := proposalValue{
OriginalPeriod: period,
OriginalProposer: address,
Expand Down
4 changes: 2 additions & 2 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -1522,9 +1522,9 @@ func initConsensusProtocols() {
vFuture.Payouts.ChallengeGracePeriod = 200
vFuture.Payouts.ChallengeBits = 5

vFuture.Bonus.BaseAmount = 5_000_000 // 5 Algos
vFuture.Bonus.BaseAmount = 10_000_000 // 10 Algos
// 2.9 sec rounds gives about 10.8M rounds per year.
vFuture.Bonus.DecayInterval = 1_000_000 // .99^(10.8/1) ~ .897 ~ 10% decay per year
vFuture.Bonus.DecayInterval = 250_000 // .99^(10.8/0.25) ~ .648. So 35% decay per year

Consensus[protocol.ConsensusFuture] = vFuture

Expand Down
59 changes: 50 additions & 9 deletions data/bookkeeping/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,8 +987,8 @@ func TestBonusUpgrades(t *testing.T) {
a.Equal(ma100, computeBonus(151, ma100, d90, d90)) // no decay (interval)
}

// TestFirstYearBonus shows what about a year's worth of block bonuses would pay out.
func TestFirstYearBonus(t *testing.T) {
// TestFirstYearsBonus shows what the bonuses look like
func TestFirstYearsBonus(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
a := require.New(t)
Expand All @@ -1000,21 +1000,62 @@ func TestFirstYearBonus(t *testing.T) {
sum := uint64(0)
bonus := plan.BaseAmount
interval := int(plan.DecayInterval)
r := 0
for i := 0; i < yearRounds; i++ {
r++
sum += bonus
if i%interval == 0 {
if r%interval == 0 {
bonus, _ = basics.Muldiv(bonus, 99, 100)
}
}
sum /= 1_000_000 // micro to Algos
suma := sum / 1_000_000 // micro to Algos

fmt.Printf("paid %d algos\n", sum)
fmt.Printf("paid %d algos\n", suma)
fmt.Printf("bonus start: %d end: %d\n", plan.BaseAmount, bonus)

// pays about 51M algos
a.InDelta(51_000_000, sum, 500_000)
// pays about 88M algos
a.InDelta(88_500_000, suma, 100_000)

// decline about 10%
a.InDelta(0.90, float64(bonus)/float64(plan.BaseAmount), 0.01)
// decline about 35%
a.InDelta(0.65, float64(bonus)/float64(plan.BaseAmount), 0.01)

// year 2
for i := 0; i < yearRounds; i++ {
r++
sum += bonus
if r%interval == 0 {
bonus, _ = basics.Muldiv(bonus, 99, 100)
}
}

sum2 := sum / 1_000_000 // micro to Algos

fmt.Printf("paid %d algos after 2 years\n", sum2)
fmt.Printf("bonus end: %d\n", bonus)

// pays about 146M algos (total for 2 years)
a.InDelta(145_700_000, sum2, 100_000)

// decline about 58%
a.InDelta(0.42, float64(bonus)/float64(plan.BaseAmount), 0.01)

// year 3
for i := 0; i < yearRounds; i++ {
r++
sum += bonus
if r%interval == 0 {
bonus, _ = basics.Muldiv(bonus, 99, 100)
}
}

sum3 := sum / 1_000_000 // micro to Algos

fmt.Printf("paid %d algos after 3 years\n", sum3)
fmt.Printf("bonus end: %d\n", bonus)

// pays about 182M algos (total for 3 years)
a.InDelta(182_600_000, sum3, 100_000)

// declined to about 27% (but foundation funding probably gone anyway)
a.InDelta(0.27, float64(bonus)/float64(plan.BaseAmount), 0.01)
}
12 changes: 3 additions & 9 deletions ledger/apptxn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,9 @@ func TestPayAction(t *testing.T) {
postsink := micros(dl.t, dl.generator, genBalances.FeeSink)
postprop := micros(dl.t, dl.generator, proposer)

// Payout checks
require.EqualValues(t, 0, postprop-preprop) // payout not moved yet
require.EqualValues(t, 2000, postsink-presink)

dl.fullBlock()
postsink = micros(dl.t, dl.generator, genBalances.FeeSink)
postprop = micros(dl.t, dl.generator, proposer)
dl.t.Log("postsink", postsink, "postprop", postprop)
if ver >= payoutsVer {
bonus := 5_000_000 // block.go
bonus := 10_000_000 // config/consensus.go
assert.EqualValues(t, bonus-500, presink-postsink) // based on 75% in config/consensus.go
require.EqualValues(t, bonus+1500, postprop-preprop)
} else {
Expand Down Expand Up @@ -3395,7 +3388,8 @@ ok:
vb := dl.endBlock()
deltas := vb.Delta()

params, _ := deltas.Accts.GetAppParams(addrs[0], appID)
params, ok := deltas.Accts.GetAppParams(addrs[0], appID)
require.True(t, ok)
require.Equal(t, basics.TealKeyValue{
"caller": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])},
"creator": {Type: basics.TealBytesType, Bytes: string(addrs[0][:])},
Expand Down
68 changes: 26 additions & 42 deletions ledger/double_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package ledger
import (
"testing"

"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
Expand Down Expand Up @@ -62,7 +63,8 @@ func (dl DoubleLedger) Close() {
func NewDoubleLedger(t testing.TB, balances bookkeeping.GenesisBalances, cv protocol.ConsensusVersion, cfg config.Local, opts ...simpleLedgerOption) DoubleLedger {
g := newSimpleLedgerWithConsensusVersion(t, balances, cv, cfg, opts...)
v := newSimpleLedgerFull(t, balances, cv, g.GenesisHash(), cfg, opts...)
return DoubleLedger{t, g, v, nil, balances.FeeSink} // FeeSink as proposer will make old code work as expected
// FeeSink as proposer will make old tests work as expected, because payouts will stay put.
return DoubleLedger{t, g, v, nil, balances.FeeSink}
}

func (dl *DoubleLedger) beginBlock() *eval.BlockEvaluator {
Expand Down Expand Up @@ -176,52 +178,34 @@ func (dl *DoubleLedger) reloadLedgers() {
require.NoError(dl.t, dl.validator.reloadLedger())
}

func checkBlock(t testing.TB, checkLedger *Ledger, vb *ledgercore.ValidatedBlock) {
bl := vb.Block()
func checkBlock(t testing.TB, checkLedger *Ledger, gvb *ledgercore.ValidatedBlock) {
bl := gvb.Block()
msg := bl.MarshalMsg(nil)
var reconstituted bookkeeping.Block
_, err := reconstituted.UnmarshalMsg(msg)
require.NoError(t, err)

check := nextCheckBlock(t, checkLedger, reconstituted.RewardsState)
var group []transactions.SignedTxnWithAD
for _, stib := range reconstituted.Payset {
stxn, ad, err := reconstituted.BlockHeader.DecodeSignedTxn(stib)
require.NoError(t, err)
stad := transactions.SignedTxnWithAD{SignedTxn: stxn, ApplyData: ad}
// If txn we're looking at belongs in the current group, append
if group == nil || (!stxn.Txn.Group.IsZero() && group[0].Txn.Group == stxn.Txn.Group) {
group = append(group, stad)
} else if group != nil {
err := check.TransactionGroup(group)
require.NoError(t, err)
group = []transactions.SignedTxnWithAD{stad}
}
}
if group != nil {
err := check.TransactionGroup(group)
require.NoError(t, err, "%+v", reconstituted.Payset)
}
check.SetGenerateForTesting(true)
// We use the same value for seed and proposer. But the proposer is
// sometimes zero'd to account for mining being disabled. So we get the
// blocks to match by providing the Seed as the proposer.
cb := endBlock(t, checkLedger, check, basics.Address(vb.Block().BlockHeader.Seed))
check.SetGenerateForTesting(false)
require.Equal(t, vb.Block(), cb.Block())

// vb.Delta() need not actually be Equal, in the sense of require.Equal
// because the order of the records in Accts is determined by the way the
// cb.sdeltas map (and then the maps in there) is iterated when the
// StateDelta is constructed by roundCowState.deltas(). They should be
// semantically equivalent, but those fields are not exported, so checking
// equivalence is hard. If vb.Delta() is, in fact, different, even though
// vb.Block() is the same, then there is something seriously broken going
// on, that is unlikely to have anything to do with these tests. So upshot:
// we skip trying a complicated equality check.

// This is the part of checking Delta() equality that wouldn't work right.
// require.Equal(t, vb.Delta().Accts, cb.Delta().Accts)
cvb, err := validateWithoutSignatures(t, checkLedger, reconstituted)
require.NoError(t, err)
cvbd := cvb.Delta()
cvbd.Dehydrate()
gvbd := gvb.Delta()
gvbd.Dehydrate()

// There are some things in the deltas that won't be identical. Erase them.
// Hdr was put in here at _start_ of block, and not updated. So gvb is in
// initial state, cvd got to see the whole thing.
gvbd.Hdr = nil
cvbd.Hdr = nil

require.Equal(t, gvbd, cvbd)

// Hydration/Dehydration is done in-place, so rehydrate so to avoid external evidence
cvbd.Hydrate()
gvbd.Hydrate()

err = checkLedger.AddValidatedBlock(*cvb, agreement.Certificate{})
require.NoError(t, err)
}

func nextCheckBlock(t testing.TB, ledger *Ledger, rs bookkeeping.RewardsState) *eval.BlockEvaluator {
Expand Down
112 changes: 59 additions & 53 deletions ledger/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,39 +791,6 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts
return nil, fmt.Errorf("overflowed subtracting rewards for block %v", hdr.Round)
}

// Move last block's proposer payout to the proposer
if !prevHeader.Proposer.IsZero() {
// Payout the previous proposer.
if !prevHeader.ProposerPayout.IsZero() {
// Use the FeeSink from that header, since that's the checked
// balance. (Though we have no expectation it will ever change.)
err = eval.state.Move(prevHeader.FeeSink, prevHeader.Proposer, prevHeader.ProposerPayout, nil, nil)
if err != nil {
// Should be impossible, it was checked when put into the block
return nil, fmt.Errorf("unable to payout block incentive: %v", err)
}
}

// Increment LastProposed on the proposer account
prp, err := eval.state.Get(prevHeader.Proposer, false)
if err != nil {
return nil, err
}
prp.LastProposed = hdr.Round - 1

// An account could propose, even while suspended, because of the 320
// round lookback. Doing so is evidence the account is
// back. Unsuspend. But the account will remain not IncentiveElgible
// until they repay the initial fee.
if prp.Suspended() {
prp.Status = basics.Online
}
err = eval.state.Put(prevHeader.Proposer, prp)
if err != nil {
return nil, err
}
}

if eval.Tracer != nil {
eval.Tracer.BeforeBlock(&eval.block.BlockHeader)
}
Expand Down Expand Up @@ -1390,33 +1357,35 @@ func (eval *BlockEvaluator) endOfBlock() error {
return fmt.Errorf("fees collected wrong: %v != %v", eval.block.FeesCollected, expectedFeesCollected)
}

// agreement will check that the proposer is correct (we can't because
// we don't see the bundle), but agreement allows the proposer to be set
// even if Payouts is not enabled (and unset any time). So make sure
// it's set iff it should be.
if eval.proto.Payouts.Enabled {
if eval.block.Proposer().IsZero() && !eval.generate { // if generating, proposer is set later by agreement
return fmt.Errorf("proposer missing when payouts enabled")
// agreement will check that the payout is zero if the proposer is
// ineligible, but we must check that it is correct if non-zero. We
// allow it to be too low. A proposer can be algruistic.
expectedPayout, err := eval.proposerPayout()
if err != nil {
return err
}
payout := eval.block.ProposerPayout()
if payout.Raw > expectedPayout.Raw {
return fmt.Errorf("proposal wants %d payout, %d is allowed", payout.Raw, expectedPayout.Raw)
}
// agreement will check that the proposer is correct (we can't because
// we don't see the bundle), but agreement allows the proposer to be set
// even if Payouts is not enabled (and unset any time). So make sure
// it's set only if it should be.
if !eval.generate { // if generating, proposer is set later by agreement
proposer := eval.block.Proposer()
if proposer.IsZero() {
return fmt.Errorf("proposer missing when payouts enabled")
}
}
} else {
if !eval.block.Proposer().IsZero() {
return fmt.Errorf("proposer %v present when payouts disabled", eval.block.Proposer())
}
}

// agreement will check that the payout is zero if the proposer is
// ineligible, but we must check that it is correct if non-zero. We allow it
// to be too low. A proposer can be algruistic.
maxPayout := uint64(0)
if eval.proto.Payouts.Enabled {
payout, err := eval.proposerPayout()
if err != nil {
return err
if !eval.block.ProposerPayout().IsZero() {
return fmt.Errorf("payout %d present when payouts disabled", eval.block.ProposerPayout().Raw)
}
maxPayout = payout.Raw
}
if eval.block.ProposerPayout().Raw > maxPayout {
return fmt.Errorf("proposal wants %d payout, %d is allowed", eval.block.ProposerPayout().Raw, maxPayout)
}

expectedVoters, expectedVotersWeight, err2 := eval.stateProofVotersAndTotal()
Expand Down Expand Up @@ -1461,6 +1430,43 @@ func (eval *BlockEvaluator) endOfBlock() error {
}
}

// Try to pay the proposer.
{
proposer := eval.block.Proposer()
payout := eval.block.ProposerPayout()
// The proposer won't be present yet when generating a block
if !proposer.IsZero() {
// We don't propagate the error here, we simply declare that an
// illegal payout is not made. This protects us from stalling if
// there is ever an error in which the generation code thinks the
// payout will be legal, but it turns out not to be. That would be
// a programming error in algod, but not worth stalling over.
if !payout.IsZero() {
err2 := eval.state.Move(eval.block.FeeSink, proposer, payout, nil, nil)
if err2 != nil {
logging.Base().Warnf("Unable to payout %d to %v: %s",
payout, proposer, err2)
}
}
prp, err2 := eval.state.Get(proposer, false)
if err2 != nil {
return err2
}
prp.LastProposed = eval.Round()
// An account could propose, even while suspended, because of the 320
// round lookback. Doing so is evidence the account is
// back. Unsuspend. But the account will remain not IncentiveElgible
// until they keyreg again with the extra fee.
if prp.Suspended() {
prp.Status = basics.Online
}
err2 = eval.state.Put(proposer, prp)
if err2 != nil {
return err2
}
}
}

if err := eval.state.CalculateTotals(); err != nil {
return err
}
Expand Down
Loading

0 comments on commit e82769e

Please sign in to comment.