Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
9 changes: 7 additions & 2 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,9 @@ type ConsensusParams struct {
// occur, extra funds need to be put into the FeeSink. The bonus amount
// decays exponentially.
Bonus BonusPlan

// Heartbeat support
Heartbeat bool
}

// ProposerPayoutRules puts several related consensus parameters in one place. The same
Expand Down Expand Up @@ -1513,7 +1516,7 @@ func initConsensusProtocols() {
vFuture.LogicSigVersion = 11 // When moving this to a release, put a new higher LogicSigVersion here

vFuture.Payouts.Enabled = true
vFuture.Payouts.Percent = 75
vFuture.Payouts.Percent = 50
vFuture.Payouts.GoOnlineFee = 2_000_000 // 2 algos
vFuture.Payouts.MinBalance = 30_000_000_000 // 30,000 algos
vFuture.Payouts.MaxBalance = 70_000_000_000_000 // 70M algos
Expand All @@ -1524,7 +1527,9 @@ func initConsensusProtocols() {

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

vFuture.Heartbeat = true

Consensus[protocol.ConsensusFuture] = vFuture

Expand Down
10 changes: 0 additions & 10 deletions data/basics/userBalance.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package basics
import (
"encoding/binary"
"fmt"
"reflect"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
Expand Down Expand Up @@ -581,15 +580,6 @@ func (u OnlineAccountData) KeyDilution(proto config.ConsensusParams) uint64 {
return proto.DefaultKeyDilution
}

// IsZero checks if an AccountData value is the same as its zero value.
func (u AccountData) IsZero() bool {
if u.Assets != nil && len(u.Assets) == 0 {
u.Assets = nil
}

return reflect.DeepEqual(u, AccountData{})
}

// NormalizedOnlineBalance returns a “normalized” balance for this account.
//
// The normalization compensates for rewards that have not yet been applied,
Expand Down
24 changes: 12 additions & 12 deletions data/bookkeeping/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,11 +1013,11 @@ func TestFirstYearsBonus(t *testing.T) {
fmt.Printf("paid %d algos\n", suma)
fmt.Printf("bonus start: %d end: %d\n", plan.BaseAmount, bonus)

// pays about 88M algos
a.InDelta(88_500_000, suma, 100_000)
// pays about 103.5M algos
a.InDelta(103_500_000, suma, 100_000)

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

// year 2
for i := 0; i < yearRounds; i++ {
Expand All @@ -1033,11 +1033,11 @@ func TestFirstYearsBonus(t *testing.T) {
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)
// pays about 196M algos (total for 2 years)
a.InDelta(196_300_000, sum2, 100_000)

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

// year 3
for i := 0; i < yearRounds; i++ {
Expand All @@ -1053,9 +1053,9 @@ func TestFirstYearsBonus(t *testing.T) {
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)
// pays about 279M algos (total for 3 years)
a.InDelta(279_500_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)
// declined to about 72% (but foundation funding probably gone anyway)
a.InDelta(0.72, float64(bonus)/float64(plan.BaseAmount), 0.01)
}
67 changes: 16 additions & 51 deletions data/committee/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/crypto"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/protocol"
)

Expand All @@ -33,98 +32,64 @@ type selectionParameterListFn func(addr []basics.Address) (bool, []BalanceRecord

var proto = config.Consensus[protocol.ConsensusCurrentVersion]

func newAccount(t testing.TB, gen io.Reader, latest basics.Round, keyBatchesForward uint) (basics.Address, *crypto.SignatureSecrets, *crypto.VrfPrivkey, *crypto.OneTimeSignatureSecrets) {
func newAccount(t testing.TB, gen io.Reader) (basics.Address, *crypto.SignatureSecrets, *crypto.VrfPrivkey) {
var seed crypto.Seed
gen.Read(seed[:])
s := crypto.GenerateSignatureSecrets(seed)
_, v := crypto.VrfKeygenFromSeed(seed)
o := crypto.GenerateOneTimeSignatureSecrets(basics.OneTimeIDForRound(latest, proto.DefaultKeyDilution).Batch, uint64(keyBatchesForward))
addr := basics.Address(s.SignatureVerifier)
return addr, s, &v, o
return addr, s, &v
}

func signTx(s *crypto.SignatureSecrets, t transactions.Transaction) transactions.SignedTxn {
return t.Sign(s)
// testingenv creates a random set of participating accounts and the associated
// selection parameters for use testing committee membership and credential
// validation. seedGen is provided as an external source of randomness for the
// selection seed; if the caller persists seedGen between calls to testingenv,
// each iteration that calls testingenv will exercise a new selection seed.
// formerly, testingenv, generated transactions and one-time secrets as well,
// but they were not used by the tests.
func testingenv(t testing.TB, numAccounts, numTxs int, seedGen io.Reader) (selectionParameterFn, selectionParameterListFn, basics.Round, []basics.Address, []*crypto.SignatureSecrets, []*crypto.VrfPrivkey) {
return testingenvMoreKeys(t, numAccounts, numTxs, seedGen)
}

// testingenv creates a random set of participating accounts and random transactions between them, and
// the associated selection parameters for use testing committee membership and credential validation.
// seedGen is provided as an external source of randomness for the selection seed and transaction notes;
// if the caller persists seedGen between calls to testingenv, each iteration that calls testingenv will
// exercise a new selection seed.
func testingenv(t testing.TB, numAccounts, numTxs int, seedGen io.Reader) (selectionParameterFn, selectionParameterListFn, basics.Round, []basics.Address, []*crypto.SignatureSecrets, []*crypto.VrfPrivkey, []*crypto.OneTimeSignatureSecrets, []transactions.SignedTxn) {
return testingenvMoreKeys(t, numAccounts, numTxs, uint(5), seedGen)
}

func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, keyBatchesForward uint, seedGen io.Reader) (selectionParameterFn, selectionParameterListFn, basics.Round, []basics.Address, []*crypto.SignatureSecrets, []*crypto.VrfPrivkey, []*crypto.OneTimeSignatureSecrets, []transactions.SignedTxn) {
func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, seedGen io.Reader) (selectionParameterFn, selectionParameterListFn, basics.Round, []basics.Address, []*crypto.SignatureSecrets, []*crypto.VrfPrivkey) {
if seedGen == nil {
seedGen = rand.New(rand.NewSource(1)) // same source as setting GODEBUG=randautoseed=0, same as pre-Go 1.20 default seed
}
P := numAccounts // n accounts
TXs := numTxs // n txns
maxMoneyAtStart := 100000 // max money start
minMoneyAtStart := 10000 // max money start
transferredMoney := 100 // max money/txn
maxFee := 10 // max maxFee/txn
E := basics.Round(50) // max round

// generate accounts
genesis := make(map[basics.Address]basics.AccountData)
gen := rand.New(rand.NewSource(2))
addrs := make([]basics.Address, P)
secrets := make([]*crypto.SignatureSecrets, P)
vrfSecrets := make([]*crypto.VrfPrivkey, P)
otSecrets := make([]*crypto.OneTimeSignatureSecrets, P)
proto := config.Consensus[protocol.ConsensusCurrentVersion]
lookback := basics.Round(2*proto.SeedRefreshInterval + proto.SeedLookback + 1)
var total basics.MicroAlgos
for i := 0; i < P; i++ {
addr, sigSec, vrfSec, otSec := newAccount(t, gen, lookback, keyBatchesForward)
addr, sigSec, vrfSec := newAccount(t, gen)
addrs[i] = addr
secrets[i] = sigSec
vrfSecrets[i] = vrfSec
otSecrets[i] = otSec

startamt := uint64(minMoneyAtStart + (gen.Int() % (maxMoneyAtStart - minMoneyAtStart)))
short := addr
genesis[short] = basics.AccountData{
Status: basics.Online,
MicroAlgos: basics.MicroAlgos{Raw: startamt},
SelectionID: vrfSec.Pubkey(),
VoteID: otSec.OneTimeSignatureVerifier,
}
total.Raw += startamt
}

var seed Seed
seedGen.Read(seed[:])

tx := make([]transactions.SignedTxn, TXs)
for i := 0; i < TXs; i++ {
send := gen.Int() % P
recv := gen.Int() % P

saddr := addrs[send]
raddr := addrs[recv]
amt := basics.MicroAlgos{Raw: uint64(gen.Int() % transferredMoney)}
fee := basics.MicroAlgos{Raw: uint64(gen.Int() % maxFee)}

t := transactions.Transaction{
Type: protocol.PaymentTx,
Header: transactions.Header{
Sender: saddr,
Fee: fee,
FirstValid: 0,
LastValid: E,
Note: make([]byte, 4),
},
PaymentTxnFields: transactions.PaymentTxnFields{
Receiver: raddr,
Amount: amt,
},
}
seedGen.Read(t.Note) // to match output from previous versions, which shared global RNG for seed & note
tx[i] = t.Sign(secrets[send])
for i := 0; i < numTxs; i++ {
seedGen.Read(make([]byte, 4)) // to match output from previous versions, which shared global RNG for seed & note
}

selParams := func(addr basics.Address) (bool, BalanceRecord, Seed, basics.MicroAlgos) {
Expand All @@ -149,7 +114,7 @@ func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, keyBatchesForward
return
}

return selParams, selParamsList, lookback, addrs, secrets, vrfSecrets, otSecrets, tx
return selParams, selParamsList, lookback, addrs, secrets, vrfSecrets
}

/* TODO deprecate these types after they have been removed successfully */
Expand Down
21 changes: 10 additions & 11 deletions data/committee/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestAccountSelected(t *testing.T) {
seedGen := rand.New(rand.NewSource(1))
N := 1
for i := 0; i < N; i++ {
selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, seedGen)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, seedGen)
period := Period(0)

leaders := uint64(0)
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestAccountSelected(t *testing.T) {
func TestRichAccountSelected(t *testing.T) {
partitiontest.PartitionTest(t)

selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 10, 2000, nil)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 10, 2000, nil)

period := Period(0)
ok, record, selectionSeed, _ := selParams(addresses[0])
Expand Down Expand Up @@ -159,7 +159,7 @@ func TestPoorAccountSelectedLeaders(t *testing.T) {
failsLeaders := 0
leaders := make([]uint64, N)
for i := 0; i < N; i++ {
selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, seedGen)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, seedGen)
period := Period(0)
for j := range addresses {
ok, record, selectionSeed, _ := selParams(addresses[j])
Expand Down Expand Up @@ -207,7 +207,7 @@ func TestPoorAccountSelectedCommittee(t *testing.T) {
N := 1
committee := uint64(0)
for i := 0; i < N; i++ {
selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, seedGen)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, seedGen)
period := Period(0)

step := Cert
Expand Down Expand Up @@ -250,10 +250,9 @@ func TestNoMoneyAccountNotSelected(t *testing.T) {
seedGen := rand.New(rand.NewSource(1))
N := 1
for i := 0; i < N; i++ {
selParams, _, round, addresses, _, _, _, _ := testingenv(t, 10, 2000, seedGen)
lookback := basics.Round(2*proto.SeedRefreshInterval + proto.SeedLookback + 1)
selParams, _, round, addresses, _, _ := testingenv(t, 10, 2000, seedGen)
gen := rand.New(rand.NewSource(2))
_, _, zeroVRFSecret, _ := newAccount(t, gen, lookback, 5)
_, _, zeroVRFSecret := newAccount(t, gen)
period := Period(0)
ok, record, selectionSeed, _ := selParams(addresses[i])
if !ok {
Expand Down Expand Up @@ -281,7 +280,7 @@ func TestNoMoneyAccountNotSelected(t *testing.T) {
func TestLeadersSelected(t *testing.T) {
partitiontest.PartitionTest(t)

selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, nil)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, nil)

period := Period(0)
step := Propose
Expand Down Expand Up @@ -313,7 +312,7 @@ func TestLeadersSelected(t *testing.T) {
func TestCommitteeSelected(t *testing.T) {
partitiontest.PartitionTest(t)

selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, nil)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, nil)

period := Period(0)
step := Soft
Expand Down Expand Up @@ -345,7 +344,7 @@ func TestCommitteeSelected(t *testing.T) {
func TestAccountNotSelected(t *testing.T) {
partitiontest.PartitionTest(t)

selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(t, 100, 2000, nil)
selParams, _, round, addresses, _, vrfSecrets := testingenv(t, 100, 2000, nil)
period := Period(0)
leaders := uint64(0)
for i := range addresses {
Expand Down Expand Up @@ -375,7 +374,7 @@ func TestAccountNotSelected(t *testing.T) {

// TODO update to remove VRF verification overhead
func BenchmarkSortition(b *testing.B) {
selParams, _, round, addresses, _, vrfSecrets, _, _ := testingenv(b, 100, 2000, nil)
selParams, _, round, addresses, _, vrfSecrets := testingenv(b, 100, 2000, nil)

period := Period(0)
step := Soft
Expand Down
42 changes: 42 additions & 0 deletions data/transactions/heartbeat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2019-2024 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package transactions

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

// HeartbeatTxnFields captures the fields used for an account to prove it is
// online (really, it proves that an entity with the account's part keys is able
// to submit transactions, so it should be able to propose/vote.)
type HeartbeatTxnFields struct {
_struct struct{} `codec:",omitempty,omitemptyarray"`

// HeartbeatAddress is the account this txn is proving onlineness for.
HbAddress basics.Address `codec:"hbad"`

// HbProof is a signature using HeartbeatAddress's partkey, thereby showing it is online.
HbProof crypto.OneTimeSignature `codec:"hbprf"`

// HbSeed must be the block seed for the block before this transaction's
// firstValid. It is supplied in the transaction so that Proof can be
// checked at submit time without a ledger lookup, and must be checked at
// evaluation time for equality with the actual blockseed.
HbSeed committee.Seed `codec:"hbsd"`
}
10 changes: 10 additions & 0 deletions data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2738,6 +2738,16 @@ func AssembleString(text string) (*OpStream, error) {
return AssembleStringWithVersion(text, assemblerNoVersion)
}

// MustAssemble assembles a program an panics on error. It is useful for
// defining globals.
func MustAssemble(text string) []byte {
ops, err := AssembleString(text)
if err != nil {
panic(err)
}
return ops.Program
}

// AssembleStringWithVersion takes an entire program in a string and
// assembles it to bytecode using the assembler version specified. If
// version is assemblerNoVersion it uses #pragma version or fallsback
Expand Down
Loading