Skip to content

Commit

Permalink
Maintain and use the acct.Incentiveligible flag
Browse files Browse the repository at this point in the history
  • Loading branch information
jannotti committed Dec 1, 2023
1 parent 24b013e commit cb65ad4
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 27 deletions.
7 changes: 7 additions & 0 deletions daemon/algod/api/server/v2/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/basics"
"golang.org/x/exp/slices"
Expand Down Expand Up @@ -200,12 +201,16 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) {
var voteFirstValid basics.Round
var voteLastValid basics.Round
var voteKeyDilution uint64
var stateProofID merklesignature.Commitment
if a.Participation != nil {
copy(voteID[:], a.Participation.VoteParticipationKey)
copy(selID[:], a.Participation.SelectionParticipationKey)
voteFirstValid = basics.Round(a.Participation.VoteFirstValid)
voteLastValid = basics.Round(a.Participation.VoteLastValid)
voteKeyDilution = a.Participation.VoteKeyDilution
if a.Participation.StateProofKey != nil {
copy(stateProofID[:], *a.Participation.StateProofKey)
}
}

var rewardsBase uint64
Expand Down Expand Up @@ -351,11 +356,13 @@ func AccountToAccountData(a *model.Account) (basics.AccountData, error) {
MicroAlgos: basics.MicroAlgos{Raw: a.Amount},
RewardsBase: rewardsBase,
RewardedMicroAlgos: basics.MicroAlgos{Raw: a.Rewards},
IncentiveEligible: nilToZero(a.IncentiveEligible),
VoteID: voteID,
SelectionID: selID,
VoteFirstValid: voteFirstValid,
VoteLastValid: voteLastValid,
VoteKeyDilution: voteKeyDilution,
StateProofID: stateProofID,
Assets: assets,
AppLocalStates: appLocalStates,
AppParams: appParams,
Expand Down
21 changes: 21 additions & 0 deletions daemon/algod/api/server/v2/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/daemon/algod/api/server/v2/generated/model"
"github.com/algorand/go-algorand/data/basics"
ledgertesting "github.com/algorand/go-algorand/ledger/testing"
"github.com/algorand/go-algorand/protocol"
"github.com/algorand/go-algorand/test/partitiontest"
)

func TestAccount(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

proto := config.Consensus[protocol.ConsensusFuture]
appIdx1 := basics.AppIndex(1)
appIdx2 := basics.AppIndex(2)
Expand Down Expand Up @@ -203,3 +206,21 @@ func TestAccount(t *testing.T) {
}
})
}

func TestAccountRandomRoundTrip(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

for _, simple := range []bool{true, false} {
accts := ledgertesting.RandomAccounts(20, simple)
for addr, acct := range accts {
round := basics.Round(2)
proto := config.Consensus[protocol.ConsensusFuture]
conv, err := AccountDataToAccount(addr.String(), &acct, round, &proto, acct.MicroAlgos)
require.NoError(t, err)
c, err := AccountToAccountData(&conv)
require.NoError(t, err)
require.Equal(t, acct, c)
}
}
}
18 changes: 18 additions & 0 deletions data/basics/units.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package basics

import (
"math"

"github.com/algorand/go-codec/codec"
"github.com/algorand/msgp/msgp"

Expand All @@ -43,6 +45,11 @@ func (a MicroAlgos) GreaterThan(b MicroAlgos) bool {
return a.Raw > b.Raw
}

// GTE implements arithmetic comparison for MicroAlgos
func (a MicroAlgos) GTE(b MicroAlgos) bool {
return a.Raw >= b.Raw
}

// IsZero implements arithmetic comparison for MicroAlgos
func (a MicroAlgos) IsZero() bool {
return a.Raw == 0
Expand Down Expand Up @@ -122,6 +129,17 @@ func MicroAlgosMaxSize() (s int) {
return msgp.Uint64Size
}

// Algos is a convenience function so that whole Algos can be written easily. It
// panics on overflow because it should only be used constants - things that are
// best human-readable in source code - not used on arbitrary values from, say,
// transactions.
func Algos(algos uint64) MicroAlgos {
if algos > math.MaxUint64/1_000_000 {
panic(algos)
}
return MicroAlgos{Raw: algos * 1_000_000}
}

// Round represents a protocol round index
type Round uint64

Expand Down
3 changes: 3 additions & 0 deletions data/basics/userBalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ type AccountData struct {
// This allows key rotation, changing the members in a multisig, etc.
AuthAddr Address `codec:"spend"`

// IncentiveEligible indicates whether the account came online with the
// extra fee required to be eligible for block incentives. At proposal time,
// balance limits must also be met to receive incentives.
IncentiveEligible bool `codec:"ie"`

// AppLocalStates stores the local states associated with any applications
Expand Down
3 changes: 3 additions & 0 deletions data/txntest/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/crypto/stateproof"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/stateproofmsg"
Expand Down Expand Up @@ -55,6 +56,7 @@ type Txn struct {
VoteLast basics.Round
VoteKeyDilution uint64
Nonparticipation bool
StateProofPK merklesignature.Commitment

Receiver basics.Address
Amount uint64
Expand Down Expand Up @@ -228,6 +230,7 @@ func (tx Txn) Txn() transactions.Transaction {
VoteLast: tx.VoteLast,
VoteKeyDilution: tx.VoteKeyDilution,
Nonparticipation: tx.Nonparticipation,
StateProofPK: tx.StateProofPK,
},
PaymentTxnFields: transactions.PaymentTxnFields{
Receiver: tx.Receiver,
Expand Down
12 changes: 11 additions & 1 deletion ledger/apply/keyreg.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var errKeyregGoingOnlineFirstVotingInFuture = errors.New("transaction tries to m
// Keyreg applies a KeyRegistration transaction using the Balances interface.
func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, balances Balances, spec transactions.SpecialAddresses, ad *transactions.ApplyData, round basics.Round) error {
if header.Sender == spec.FeeSink {
return fmt.Errorf("cannot register participation key for fee sink's address %v ", header.Sender)
return fmt.Errorf("cannot register participation key for fee sink's address %v", header.Sender)
}

// Get the user's balance entry
Expand Down Expand Up @@ -67,6 +67,7 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal
record.VoteFirstValid = 0
record.VoteLastValid = 0
record.VoteKeyDilution = 0
record.IncentiveEligible = false
} else {
if params.EnableKeyregCoherencyCheck {
if keyreg.VoteLast <= round {
Expand All @@ -80,6 +81,9 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal
record.VoteFirstValid = keyreg.VoteFirst
record.VoteLastValid = keyreg.VoteLast
record.VoteKeyDilution = keyreg.VoteKeyDilution
if header.Fee.GTE(incentiveFeeForEligibility) && params.EnableMining {
record.IncentiveEligible = true
}
}

// Write the updated entry
Expand All @@ -90,3 +94,9 @@ func Keyreg(keyreg transactions.KeyregTxnFields, header transactions.Header, bal

return nil
}

// incentiveFeeForEligibility imparts a small cost on moving from offline to
// online. This will impose a cost to running unreliable nodes that get
// suspended and then come back online. Becomes a consensus param if ever
// changed.
var incentiveFeeForEligibility = basics.Algos(2)
11 changes: 10 additions & 1 deletion ledger/apptxn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/data/transactions/logic"
Expand Down Expand Up @@ -60,11 +61,19 @@ func TestPayAction(t *testing.T) {

// We're going to test some mining effects here too, so that we have an inner transaction example.
proposer := basics.Address{0x01, 0x02, 0x03}
dl.txn(&txntest.Txn{
dl.txns(&txntest.Txn{
Type: "pay",
Sender: addrs[7],
Receiver: proposer,
Amount: 1_000_000 * 1_000_000, // 1 million algos is surely an eligible amount
}, &txntest.Txn{
Type: "keyreg",
Sender: proposer,
Fee: 3_000_000,
VotePK: crypto.OneTimeSignatureVerifier{0x01},
SelectionPK: crypto.VRFVerifier{0x02},
StateProofPK: merklesignature.Commitment{0x03},
VoteFirst: 1, VoteLast: 1000,
})

payout1 := txntest.Txn{
Expand Down
14 changes: 6 additions & 8 deletions ledger/eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ func StartEvaluator(l LedgerForEvaluator, hdr bookkeeping.BlockHeader, evalOpts
return eval, nil
}

const (
var (
// these would become ConsensusParameters if we ever wanted to change them

// incentiveMinBalance is the minimum balance an account must have to be
Expand All @@ -812,31 +812,29 @@ const (
// that assurance, it is difficult to model their behaviour - might many
// participants join for the hope of easy financial rewards, but without
// caring enough to run a high-quality node?
incentiveMinBalance = 100_000 * 1_000_000 // 100K algos
incentiveMinBalance = basics.Algos(100_000)

// incentiveMaxBalance is the maximum balance an account might have to be
// eligible for incentives. It encourages large accounts to split their
// stake to add resilience to consensus in the case of outages. Of course,
// nothing in protocol can prevent such accounts from running nodes that
// share fate (same machine, same data center, etc), but this serves as a
// gentle reminder.
incentiveMaxBalance = 100_000_000 * 1_000_000 // 100M algos
incentiveMaxBalance = basics.Algos(100_000_000)
)

func (eval *BlockEvaluator) eligibleForIncentives(proposer basics.Address) bool {
proposerState, err := eval.state.Get(proposer, true)
if err != nil {
return false
}
if proposerState.MicroAlgos.Raw < incentiveMinBalance {
if proposerState.MicroAlgos.LessThan(incentiveMinBalance) {
return false
}
if proposerState.MicroAlgos.Raw > incentiveMaxBalance {
if proposerState.MicroAlgos.GreaterThan(incentiveMaxBalance) {
return false
}
// We'll also need a flag on the account, set to true if the account
// properly key-regged for incentives by including the "entry fee".
return true
return proposerState.IncentiveEligible
}

// hotfix for testnet stall 08/26/2019; move some algos from testnet bank to rewards pool to give it enough time until protocol upgrade occur.
Expand Down
88 changes: 84 additions & 4 deletions ledger/eval_simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/algorand/go-algorand/agreement"
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/crypto/merklesignature"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/bookkeeping"
"github.com/algorand/go-algorand/data/transactions"
Expand Down Expand Up @@ -229,14 +230,31 @@ func TestMiningFees(t *testing.T) {
smallest := basics.Address{0x01, 0x033}
biggest := basics.Address{0x01, 0x044}

dl.txns(&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: tooBig, Amount: 100_000_000*1_000_000 + 1},
&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: tooSmall, Amount: 100_000*1_000_000 - 1},
&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: smallest, Amount: 100_000 * 1_000_000},
&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: biggest, Amount: 100_000_000 * 1_000_000},
const eFee = 3_000_000
dl.txns(
&txntest.Txn{Type: "pay", Sender: addrs[1],
Receiver: tooBig, Amount: eFee + 100_000_000*1_000_000 + 1},
&txntest.Txn{Type: "pay", Sender: addrs[1],
Receiver: tooSmall, Amount: eFee + 100_000*1_000_000 - 1},
&txntest.Txn{Type: "pay", Sender: addrs[1],
Receiver: smallest, Amount: eFee + 100_000*1_000_000},
&txntest.Txn{Type: "pay", Sender: addrs[1],
Receiver: biggest, Amount: eFee + 100_000_000*1_000_000},
)

for _, proposer := range []basics.Address{tooBig, tooSmall, smallest, biggest} {
t.Log(proposer)

dl.txn(&txntest.Txn{
Type: "keyreg",
Sender: proposer,
Fee: eFee,
VotePK: crypto.OneTimeSignatureVerifier{0x01},
SelectionPK: crypto.VRFVerifier{0x02},
StateProofPK: merklesignature.Commitment{0x03},
VoteFirst: 1, VoteLast: 1000,
})

dl.fullBlock() // start with an empty block, so no mining fees are paid at start of next one

presink := micros(dl.t, dl.generator, genBalances.FeeSink)
Expand Down Expand Up @@ -293,6 +311,68 @@ func TestMiningFees(t *testing.T) {
})
}

// TestIncentiveEligible checks that keyreg with extra fee turns on the incentive eligible flag
func TestIncentiveEligible(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

genBalances, addrs, _ := ledgertesting.NewTestGenesis()
// Incentive-eligible appears in v39. Start checking in v38 to test that is unchanged.
ledgertesting.TestConsensusRange(t, 38, 0, func(t *testing.T, ver int, cv protocol.ConsensusVersion, cfg config.Local) {
dl := NewDoubleLedger(t, genBalances, cv, cfg)
defer dl.Close()

tooSmall := basics.Address{0x01, 0x011}
smallest := basics.Address{0x01, 0x022}

// They begin ineligible
for _, addr := range []basics.Address{tooSmall, smallest} {
acct, _, _, err := dl.generator.LookupLatest(addr)
require.NoError(t, err)
require.False(t, acct.IncentiveEligible)
}

// Fund everyone
dl.txns(&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: tooSmall, Amount: 10_000_000},
&txntest.Txn{Type: "pay", Sender: addrs[1], Receiver: smallest, Amount: 10_000_000},
)

// Keyreg (but offline) with various fees. No effect on incentive eligible
dl.txns(&txntest.Txn{Type: "keyreg", Sender: tooSmall, Fee: 2_000_000 - 1},
&txntest.Txn{Type: "keyreg", Sender: smallest, Fee: 2_000_000},
)

for _, addr := range []basics.Address{tooSmall, smallest} {
acct, _, _, err := dl.generator.LookupLatest(addr)
require.NoError(t, err)
require.False(t, acct.IncentiveEligible)
}

// Keyreg to get online with various fees. Sufficient fee gets `smallest` eligible
keyreg := txntest.Txn{
Type: "keyreg",
VotePK: crypto.OneTimeSignatureVerifier{0x01},
SelectionPK: crypto.VRFVerifier{0x02},
StateProofPK: merklesignature.Commitment{0x03},
VoteFirst: 1, VoteLast: 1000,
}
tooSmallKR := keyreg
tooSmallKR.Sender = tooSmall
tooSmallKR.Fee = 2_000_000 - 1

smallKR := keyreg
smallKR.Sender = smallest
smallKR.Fee = 2_000_000
dl.txns(&tooSmallKR, &smallKR)
a, _, _, err := dl.generator.LookupLatest(tooSmall)
require.NoError(t, err)
require.False(t, a.IncentiveEligible)
a, _, _, err = dl.generator.LookupLatest(smallest)
require.NoError(t, err)
require.Equal(t, a.IncentiveEligible, ver > 38)
})
}

// TestHoldingGet tests some of the corner cases for the asset_holding_get
// opcode: the asset doesn't exist, the account doesn't exist, account not opted
// in, vs it has none of the asset. This is tested here, even though it should
Expand Down
6 changes: 3 additions & 3 deletions ledger/testing/randomAccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@ func RandomAccountData(rewardsBase uint64) basics.AccountData {
switch crypto.RandUint64() % 3 {
case 0:
data.Status = basics.Online
data.VoteID = crypto.OneTimeSignatureVerifier{0x01}
data.IncentiveEligible = crypto.RandUint64()%5 == 0
data.VoteFirstValid = 1
data.VoteLastValid = 10000
case 1:
data.Status = basics.Offline
data.VoteLastValid = 0
default:
data.Status = basics.NotParticipating
}

data.VoteFirstValid = 0
data.RewardsBase = rewardsBase
data.IncentiveEligible = crypto.RandUint64()%5 == 0
return data
}

Expand Down
Loading

0 comments on commit cb65ad4

Please sign in to comment.