From 936b219837b436585682370e7e70a4ffd37e0e07 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Mon, 21 Oct 2024 13:02:54 -0400 Subject: [PATCH] Add in the actual heartbeat crypto, simple test of the service --- config/consensus.go | 9 +- data/committee/common_test.go | 8 +- data/committee/credential_test.go | 3 +- data/transactions/heartbeat.go | 10 +- data/transactions/msgp_gen.go | 440 ++++++++++++++++++++------ data/transactions/msgp_gen_test.go | 60 ++++ data/transactions/transaction.go | 5 + data/transactions/transaction_test.go | 15 + heartbeat/service.go | 35 +- heartbeat/service_test.go | 122 +++---- ledger/apply/heartbeat.go | 11 +- ledger/apply/heartbeat_test.go | 6 +- ledger/apply/mockBalances_test.go | 2 +- ledger/apptxn_test.go | 6 +- ledger/eval/eval.go | 51 ++- ledger/eval/eval_test.go | 20 +- 16 files changed, 589 insertions(+), 214 deletions(-) diff --git a/config/consensus.go b/config/consensus.go index f86e45e831..58678571ab 100644 --- a/config/consensus.go +++ b/config/consensus.go @@ -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 @@ -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 @@ -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 diff --git a/data/committee/common_test.go b/data/committee/common_test.go index eef035b8e2..8566a9cd2a 100644 --- a/data/committee/common_test.go +++ b/data/committee/common_test.go @@ -32,7 +32,7 @@ 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) { +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) @@ -49,10 +49,10 @@ func newAccount(t testing.TB, gen io.Reader, latest basics.Round, keyBatchesForw // 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, uint(5), seedGen) + return testingenvMoreKeys(t, numAccounts, numTxs, seedGen) } -func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, keyBatchesForward uint, seedGen io.Reader) (selectionParameterFn, selectionParameterListFn, basics.Round, []basics.Address, []*crypto.SignatureSecrets, []*crypto.VrfPrivkey) { +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 } @@ -70,7 +70,7 @@ func testingenvMoreKeys(t testing.TB, numAccounts, numTxs int, keyBatchesForward lookback := basics.Round(2*proto.SeedRefreshInterval + proto.SeedLookback + 1) var total basics.MicroAlgos for i := 0; i < P; i++ { - addr, sigSec, vrfSec := newAccount(t, gen, lookback, keyBatchesForward) + addr, sigSec, vrfSec := newAccount(t, gen) addrs[i] = addr secrets[i] = sigSec vrfSecrets[i] = vrfSec diff --git a/data/committee/credential_test.go b/data/committee/credential_test.go index b646efdf0a..bbabac62e9 100644 --- a/data/committee/credential_test.go +++ b/data/committee/credential_test.go @@ -251,9 +251,8 @@ func TestNoMoneyAccountNotSelected(t *testing.T) { 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) 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 { diff --git a/data/transactions/heartbeat.go b/data/transactions/heartbeat.go index 9acf847734..873a905079 100644 --- a/data/transactions/heartbeat.go +++ b/data/transactions/heartbeat.go @@ -29,14 +29,14 @@ type HeartbeatTxnFields struct { _struct struct{} `codec:",omitempty,omitemptyarray"` // HeartbeatAddress is the account this txn is proving onlineness for. - HeartbeatAddress basics.Address `codec:"hbad"` + HbAddress basics.Address `codec:"hbad"` - // Proof is a signature using HeartbeatAddress's partkey, thereby showing it is online. - Proof crypto.OneTimeSignature `codec:"hbprf"` + // HbProof is a signature using HeartbeatAddress's partkey, thereby showing it is online. + HbProof crypto.OneTimeSignature `codec:"hbprf"` - // Seed must be the block seed for the block before this transaction's + // 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. - Seed committee.Seed `codec:"hbsd"` + HbSeed committee.Seed `codec:"hbsd"` } diff --git a/data/transactions/msgp_gen.go b/data/transactions/msgp_gen.go index 7cc22db08a..f7ea0e8fbb 100644 --- a/data/transactions/msgp_gen.go +++ b/data/transactions/msgp_gen.go @@ -12,6 +12,7 @@ import ( "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/committee" "github.com/algorand/go-algorand/data/stateproofmsg" "github.com/algorand/go-algorand/protocol" ) @@ -97,6 +98,16 @@ import ( // |-----> (*) MsgIsZero // |-----> HeaderMaxSize() // +// HeartbeatTxnFields +// |-----> (*) MarshalMsg +// |-----> (*) CanMarshalMsg +// |-----> (*) UnmarshalMsg +// |-----> (*) UnmarshalMsgWithState +// |-----> (*) CanUnmarshalMsg +// |-----> (*) Msgsize +// |-----> (*) MsgIsZero +// |-----> HeartbeatTxnFieldsMaxSize() +// // KeyregTxnFields // |-----> (*) MarshalMsg // |-----> (*) CanMarshalMsg @@ -2907,6 +2918,172 @@ func HeaderMaxSize() (s int) { return } +// MarshalMsg implements msgp.Marshaler +func (z *HeartbeatTxnFields) MarshalMsg(b []byte) (o []byte) { + o = msgp.Require(b, z.Msgsize()) + // omitempty: check for empty values + zb0001Len := uint32(3) + var zb0001Mask uint8 /* 4 bits */ + if (*z).HbAddress.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x2 + } + if (*z).HbProof.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x4 + } + if (*z).HbSeed.MsgIsZero() { + zb0001Len-- + zb0001Mask |= 0x8 + } + // variable map header, size zb0001Len + o = append(o, 0x80|uint8(zb0001Len)) + if zb0001Len != 0 { + if (zb0001Mask & 0x2) == 0 { // if not empty + // string "hbad" + o = append(o, 0xa4, 0x68, 0x62, 0x61, 0x64) + o = (*z).HbAddress.MarshalMsg(o) + } + if (zb0001Mask & 0x4) == 0 { // if not empty + // string "hbprf" + o = append(o, 0xa5, 0x68, 0x62, 0x70, 0x72, 0x66) + o = (*z).HbProof.MarshalMsg(o) + } + if (zb0001Mask & 0x8) == 0 { // if not empty + // string "hbsd" + o = append(o, 0xa4, 0x68, 0x62, 0x73, 0x64) + o = (*z).HbSeed.MarshalMsg(o) + } + } + return +} + +func (_ *HeartbeatTxnFields) CanMarshalMsg(z interface{}) bool { + _, ok := (z).(*HeartbeatTxnFields) + return ok +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *HeartbeatTxnFields) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) (o []byte, err error) { + if st.AllowableDepth == 0 { + err = msgp.ErrMaxDepthExceeded{} + return + } + st.AllowableDepth-- + var field []byte + _ = field + var zb0001 int + var zb0002 bool + zb0001, zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) + if _, ok := err.(msgp.TypeError); ok { + zb0001, zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).HbAddress.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbAddress") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).HbProof.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbProof") + return + } + } + if zb0001 > 0 { + zb0001-- + bts, err = (*z).HbSeed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbSeed") + return + } + } + if zb0001 > 0 { + err = msgp.ErrTooManyArrayFields(zb0001) + if err != nil { + err = msgp.WrapError(err, "struct-from-array") + return + } + } + } else { + if err != nil { + err = msgp.WrapError(err) + return + } + if zb0002 { + (*z) = HeartbeatTxnFields{} + } + for zb0001 > 0 { + zb0001-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + err = msgp.WrapError(err) + return + } + switch string(field) { + case "hbad": + bts, err = (*z).HbAddress.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbAddress") + return + } + case "hbprf": + bts, err = (*z).HbProof.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbProof") + return + } + case "hbsd": + bts, err = (*z).HbSeed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbSeed") + return + } + default: + err = msgp.ErrNoField(string(field)) + if err != nil { + err = msgp.WrapError(err) + return + } + } + } + } + o = bts + return +} + +func (z *HeartbeatTxnFields) UnmarshalMsg(bts []byte) (o []byte, err error) { + return z.UnmarshalMsgWithState(bts, msgp.DefaultUnmarshalState) +} +func (_ *HeartbeatTxnFields) CanUnmarshalMsg(z interface{}) bool { + _, ok := (z).(*HeartbeatTxnFields) + return ok +} + +// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message +func (z *HeartbeatTxnFields) Msgsize() (s int) { + s = 1 + 5 + (*z).HbAddress.Msgsize() + 6 + (*z).HbProof.Msgsize() + 5 + (*z).HbSeed.Msgsize() + return +} + +// MsgIsZero returns whether this is a zero value +func (z *HeartbeatTxnFields) MsgIsZero() bool { + return ((*z).HbAddress.MsgIsZero()) && ((*z).HbProof.MsgIsZero()) && ((*z).HbSeed.MsgIsZero()) +} + +// MaxSize returns a maximum valid message size for this message type +func HeartbeatTxnFieldsMaxSize() (s int) { + s = 1 + 5 + basics.AddressMaxSize() + 6 + crypto.OneTimeSignatureMaxSize() + 5 + committee.SeedMaxSize() + return +} + // MarshalMsg implements msgp.Marshaler func (z *KeyregTxnFields) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) @@ -4982,216 +5159,228 @@ func StateProofTxnFieldsMaxSize() (s int) { func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0007Len := uint32(46) - var zb0007Mask uint64 /* 55 bits */ + zb0007Len := uint32(49) + var zb0007Mask uint64 /* 59 bits */ if (*z).AssetTransferTxnFields.AssetAmount == 0 { zb0007Len-- - zb0007Mask |= 0x200 + zb0007Mask |= 0x400 } if (*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x400 + zb0007Mask |= 0x800 } if (*z).AssetFreezeTxnFields.AssetFrozen == false { zb0007Len-- - zb0007Mask |= 0x800 + zb0007Mask |= 0x1000 } if (*z).PaymentTxnFields.Amount.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x1000 + zb0007Mask |= 0x2000 } if len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0 { zb0007Len-- - zb0007Mask |= 0x2000 + zb0007Mask |= 0x4000 } if (*z).ApplicationCallTxnFields.OnCompletion == 0 { zb0007Len-- - zb0007Mask |= 0x4000 + zb0007Mask |= 0x8000 } if len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0 { zb0007Len-- - zb0007Mask |= 0x8000 + zb0007Mask |= 0x10000 } if (*z).AssetConfigTxnFields.AssetParams.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x10000 + zb0007Mask |= 0x20000 } if len((*z).ApplicationCallTxnFields.ForeignAssets) == 0 { zb0007Len-- - zb0007Mask |= 0x20000 + zb0007Mask |= 0x40000 } if len((*z).ApplicationCallTxnFields.Accounts) == 0 { zb0007Len-- - zb0007Mask |= 0x40000 + zb0007Mask |= 0x80000 } if len((*z).ApplicationCallTxnFields.Boxes) == 0 { zb0007Len-- - zb0007Mask |= 0x80000 + zb0007Mask |= 0x100000 } if (*z).ApplicationCallTxnFields.ExtraProgramPages == 0 { zb0007Len-- - zb0007Mask |= 0x100000 + zb0007Mask |= 0x200000 } if len((*z).ApplicationCallTxnFields.ForeignApps) == 0 { zb0007Len-- - zb0007Mask |= 0x200000 + zb0007Mask |= 0x400000 } if (*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x400000 + zb0007Mask |= 0x800000 } if (*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x800000 + zb0007Mask |= 0x1000000 } if (*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x1000000 + zb0007Mask |= 0x2000000 } if len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0 { zb0007Len-- - zb0007Mask |= 0x2000000 + zb0007Mask |= 0x4000000 } if (*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x4000000 + zb0007Mask |= 0x8000000 } if (*z).AssetTransferTxnFields.AssetSender.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x8000000 + zb0007Mask |= 0x10000000 } if (*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x10000000 + zb0007Mask |= 0x20000000 } if (*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x20000000 + zb0007Mask |= 0x40000000 } if (*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x40000000 + zb0007Mask |= 0x80000000 } if (*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x80000000 + zb0007Mask |= 0x100000000 } if (*z).Header.Fee.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x100000000 + zb0007Mask |= 0x200000000 } if (*z).Header.FirstValid.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x200000000 + zb0007Mask |= 0x400000000 } if (*z).Header.GenesisID == "" { zb0007Len-- - zb0007Mask |= 0x400000000 + zb0007Mask |= 0x800000000 } if (*z).Header.GenesisHash.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x800000000 + zb0007Mask |= 0x1000000000 } if (*z).Header.Group.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x1000000000 + zb0007Mask |= 0x2000000000 + } + if (*z).HeartbeatTxnFields.HbAddress.MsgIsZero() { + zb0007Len-- + zb0007Mask |= 0x4000000000 + } + if (*z).HeartbeatTxnFields.HbProof.MsgIsZero() { + zb0007Len-- + zb0007Mask |= 0x8000000000 + } + if (*z).HeartbeatTxnFields.HbSeed.MsgIsZero() { + zb0007Len-- + zb0007Mask |= 0x10000000000 } if (*z).Header.LastValid.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x2000000000 + zb0007Mask |= 0x20000000000 } if (*z).Header.Lease == ([32]byte{}) { zb0007Len-- - zb0007Mask |= 0x4000000000 + zb0007Mask |= 0x40000000000 } if (*z).KeyregTxnFields.Nonparticipation == false { zb0007Len-- - zb0007Mask |= 0x8000000000 + zb0007Mask |= 0x80000000000 } if len((*z).Header.Note) == 0 { zb0007Len-- - zb0007Mask |= 0x10000000000 + zb0007Mask |= 0x100000000000 } if (*z).PaymentTxnFields.Receiver.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x20000000000 + zb0007Mask |= 0x200000000000 } if (*z).Header.RekeyTo.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x40000000000 + zb0007Mask |= 0x400000000000 } if (*z).KeyregTxnFields.SelectionPK.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x80000000000 + zb0007Mask |= 0x800000000000 } if (*z).Header.Sender.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x100000000000 + zb0007Mask |= 0x1000000000000 } if (*z).StateProofTxnFields.StateProof.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x200000000000 + zb0007Mask |= 0x2000000000000 } if (*z).StateProofTxnFields.Message.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x400000000000 + zb0007Mask |= 0x4000000000000 } if (*z).KeyregTxnFields.StateProofPK.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x800000000000 + zb0007Mask |= 0x8000000000000 } if (*z).StateProofTxnFields.StateProofType.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x1000000000000 + zb0007Mask |= 0x10000000000000 } if (*z).Type.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x2000000000000 + zb0007Mask |= 0x20000000000000 } if (*z).KeyregTxnFields.VoteFirst.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x4000000000000 + zb0007Mask |= 0x40000000000000 } if (*z).KeyregTxnFields.VoteKeyDilution == 0 { zb0007Len-- - zb0007Mask |= 0x8000000000000 + zb0007Mask |= 0x80000000000000 } if (*z).KeyregTxnFields.VotePK.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x10000000000000 + zb0007Mask |= 0x100000000000000 } if (*z).KeyregTxnFields.VoteLast.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x20000000000000 + zb0007Mask |= 0x200000000000000 } if (*z).AssetTransferTxnFields.XferAsset.MsgIsZero() { zb0007Len-- - zb0007Mask |= 0x40000000000000 + zb0007Mask |= 0x400000000000000 } // variable map header, size zb0007Len o = msgp.AppendMapHeader(o, zb0007Len) if zb0007Len != 0 { - if (zb0007Mask & 0x200) == 0 { // if not empty + if (zb0007Mask & 0x400) == 0 { // if not empty // string "aamt" o = append(o, 0xa4, 0x61, 0x61, 0x6d, 0x74) o = msgp.AppendUint64(o, (*z).AssetTransferTxnFields.AssetAmount) } - if (zb0007Mask & 0x400) == 0 { // if not empty + if (zb0007Mask & 0x800) == 0 { // if not empty // string "aclose" o = append(o, 0xa6, 0x61, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).AssetTransferTxnFields.AssetCloseTo.MarshalMsg(o) } - if (zb0007Mask & 0x800) == 0 { // if not empty + if (zb0007Mask & 0x1000) == 0 { // if not empty // string "afrz" o = append(o, 0xa4, 0x61, 0x66, 0x72, 0x7a) o = msgp.AppendBool(o, (*z).AssetFreezeTxnFields.AssetFrozen) } - if (zb0007Mask & 0x1000) == 0 { // if not empty + if (zb0007Mask & 0x2000) == 0 { // if not empty // string "amt" o = append(o, 0xa3, 0x61, 0x6d, 0x74) o = (*z).PaymentTxnFields.Amount.MarshalMsg(o) } - if (zb0007Mask & 0x2000) == 0 { // if not empty + if (zb0007Mask & 0x4000) == 0 { // if not empty // string "apaa" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x61) if (*z).ApplicationCallTxnFields.ApplicationArgs == nil { @@ -5203,22 +5392,22 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApplicationArgs[zb0002]) } } - if (zb0007Mask & 0x4000) == 0 { // if not empty + if (zb0007Mask & 0x8000) == 0 { // if not empty // string "apan" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x6e) o = msgp.AppendUint64(o, uint64((*z).ApplicationCallTxnFields.OnCompletion)) } - if (zb0007Mask & 0x8000) == 0 { // if not empty + if (zb0007Mask & 0x10000) == 0 { // if not empty // string "apap" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x70) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ApprovalProgram) } - if (zb0007Mask & 0x10000) == 0 { // if not empty + if (zb0007Mask & 0x20000) == 0 { // if not empty // string "apar" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x72) o = (*z).AssetConfigTxnFields.AssetParams.MarshalMsg(o) } - if (zb0007Mask & 0x20000) == 0 { // if not empty + if (zb0007Mask & 0x40000) == 0 { // if not empty // string "apas" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x73) if (*z).ApplicationCallTxnFields.ForeignAssets == nil { @@ -5230,7 +5419,7 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].MarshalMsg(o) } } - if (zb0007Mask & 0x40000) == 0 { // if not empty + if (zb0007Mask & 0x80000) == 0 { // if not empty // string "apat" o = append(o, 0xa4, 0x61, 0x70, 0x61, 0x74) if (*z).ApplicationCallTxnFields.Accounts == nil { @@ -5242,7 +5431,7 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.Accounts[zb0003].MarshalMsg(o) } } - if (zb0007Mask & 0x80000) == 0 { // if not empty + if (zb0007Mask & 0x100000) == 0 { // if not empty // string "apbx" o = append(o, 0xa4, 0x61, 0x70, 0x62, 0x78) if (*z).ApplicationCallTxnFields.Boxes == nil { @@ -5276,12 +5465,12 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { } } } - if (zb0007Mask & 0x100000) == 0 { // if not empty + if (zb0007Mask & 0x200000) == 0 { // if not empty // string "apep" o = append(o, 0xa4, 0x61, 0x70, 0x65, 0x70) o = msgp.AppendUint32(o, (*z).ApplicationCallTxnFields.ExtraProgramPages) } - if (zb0007Mask & 0x200000) == 0 { // if not empty + if (zb0007Mask & 0x400000) == 0 { // if not empty // string "apfa" o = append(o, 0xa4, 0x61, 0x70, 0x66, 0x61) if (*z).ApplicationCallTxnFields.ForeignApps == nil { @@ -5293,167 +5482,182 @@ func (z *Transaction) MarshalMsg(b []byte) (o []byte) { o = (*z).ApplicationCallTxnFields.ForeignApps[zb0004].MarshalMsg(o) } } - if (zb0007Mask & 0x400000) == 0 { // if not empty + if (zb0007Mask & 0x800000) == 0 { // if not empty // string "apgs" o = append(o, 0xa4, 0x61, 0x70, 0x67, 0x73) o = (*z).ApplicationCallTxnFields.GlobalStateSchema.MarshalMsg(o) } - if (zb0007Mask & 0x800000) == 0 { // if not empty + if (zb0007Mask & 0x1000000) == 0 { // if not empty // string "apid" o = append(o, 0xa4, 0x61, 0x70, 0x69, 0x64) o = (*z).ApplicationCallTxnFields.ApplicationID.MarshalMsg(o) } - if (zb0007Mask & 0x1000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000) == 0 { // if not empty // string "apls" o = append(o, 0xa4, 0x61, 0x70, 0x6c, 0x73) o = (*z).ApplicationCallTxnFields.LocalStateSchema.MarshalMsg(o) } - if (zb0007Mask & 0x2000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000) == 0 { // if not empty // string "apsu" o = append(o, 0xa4, 0x61, 0x70, 0x73, 0x75) o = msgp.AppendBytes(o, (*z).ApplicationCallTxnFields.ClearStateProgram) } - if (zb0007Mask & 0x4000000) == 0 { // if not empty + if (zb0007Mask & 0x8000000) == 0 { // if not empty // string "arcv" o = append(o, 0xa4, 0x61, 0x72, 0x63, 0x76) o = (*z).AssetTransferTxnFields.AssetReceiver.MarshalMsg(o) } - if (zb0007Mask & 0x8000000) == 0 { // if not empty + if (zb0007Mask & 0x10000000) == 0 { // if not empty // string "asnd" o = append(o, 0xa4, 0x61, 0x73, 0x6e, 0x64) o = (*z).AssetTransferTxnFields.AssetSender.MarshalMsg(o) } - if (zb0007Mask & 0x10000000) == 0 { // if not empty + if (zb0007Mask & 0x20000000) == 0 { // if not empty // string "caid" o = append(o, 0xa4, 0x63, 0x61, 0x69, 0x64) o = (*z).AssetConfigTxnFields.ConfigAsset.MarshalMsg(o) } - if (zb0007Mask & 0x20000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000) == 0 { // if not empty // string "close" o = append(o, 0xa5, 0x63, 0x6c, 0x6f, 0x73, 0x65) o = (*z).PaymentTxnFields.CloseRemainderTo.MarshalMsg(o) } - if (zb0007Mask & 0x40000000) == 0 { // if not empty + if (zb0007Mask & 0x80000000) == 0 { // if not empty // string "fadd" o = append(o, 0xa4, 0x66, 0x61, 0x64, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAccount.MarshalMsg(o) } - if (zb0007Mask & 0x80000000) == 0 { // if not empty + if (zb0007Mask & 0x100000000) == 0 { // if not empty // string "faid" o = append(o, 0xa4, 0x66, 0x61, 0x69, 0x64) o = (*z).AssetFreezeTxnFields.FreezeAsset.MarshalMsg(o) } - if (zb0007Mask & 0x100000000) == 0 { // if not empty + if (zb0007Mask & 0x200000000) == 0 { // if not empty // string "fee" o = append(o, 0xa3, 0x66, 0x65, 0x65) o = (*z).Header.Fee.MarshalMsg(o) } - if (zb0007Mask & 0x200000000) == 0 { // if not empty + if (zb0007Mask & 0x400000000) == 0 { // if not empty // string "fv" o = append(o, 0xa2, 0x66, 0x76) o = (*z).Header.FirstValid.MarshalMsg(o) } - if (zb0007Mask & 0x400000000) == 0 { // if not empty + if (zb0007Mask & 0x800000000) == 0 { // if not empty // string "gen" o = append(o, 0xa3, 0x67, 0x65, 0x6e) o = msgp.AppendString(o, (*z).Header.GenesisID) } - if (zb0007Mask & 0x800000000) == 0 { // if not empty + if (zb0007Mask & 0x1000000000) == 0 { // if not empty // string "gh" o = append(o, 0xa2, 0x67, 0x68) o = (*z).Header.GenesisHash.MarshalMsg(o) } - if (zb0007Mask & 0x1000000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000000) == 0 { // if not empty // string "grp" o = append(o, 0xa3, 0x67, 0x72, 0x70) o = (*z).Header.Group.MarshalMsg(o) } - if (zb0007Mask & 0x2000000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000000) == 0 { // if not empty + // string "hbad" + o = append(o, 0xa4, 0x68, 0x62, 0x61, 0x64) + o = (*z).HeartbeatTxnFields.HbAddress.MarshalMsg(o) + } + if (zb0007Mask & 0x8000000000) == 0 { // if not empty + // string "hbprf" + o = append(o, 0xa5, 0x68, 0x62, 0x70, 0x72, 0x66) + o = (*z).HeartbeatTxnFields.HbProof.MarshalMsg(o) + } + if (zb0007Mask & 0x10000000000) == 0 { // if not empty + // string "hbsd" + o = append(o, 0xa4, 0x68, 0x62, 0x73, 0x64) + o = (*z).HeartbeatTxnFields.HbSeed.MarshalMsg(o) + } + if (zb0007Mask & 0x20000000000) == 0 { // if not empty // string "lv" o = append(o, 0xa2, 0x6c, 0x76) o = (*z).Header.LastValid.MarshalMsg(o) } - if (zb0007Mask & 0x4000000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000000) == 0 { // if not empty // string "lx" o = append(o, 0xa2, 0x6c, 0x78) o = msgp.AppendBytes(o, ((*z).Header.Lease)[:]) } - if (zb0007Mask & 0x8000000000) == 0 { // if not empty + if (zb0007Mask & 0x80000000000) == 0 { // if not empty // string "nonpart" o = append(o, 0xa7, 0x6e, 0x6f, 0x6e, 0x70, 0x61, 0x72, 0x74) o = msgp.AppendBool(o, (*z).KeyregTxnFields.Nonparticipation) } - if (zb0007Mask & 0x10000000000) == 0 { // if not empty + if (zb0007Mask & 0x100000000000) == 0 { // if not empty // string "note" o = append(o, 0xa4, 0x6e, 0x6f, 0x74, 0x65) o = msgp.AppendBytes(o, (*z).Header.Note) } - if (zb0007Mask & 0x20000000000) == 0 { // if not empty + if (zb0007Mask & 0x200000000000) == 0 { // if not empty // string "rcv" o = append(o, 0xa3, 0x72, 0x63, 0x76) o = (*z).PaymentTxnFields.Receiver.MarshalMsg(o) } - if (zb0007Mask & 0x40000000000) == 0 { // if not empty + if (zb0007Mask & 0x400000000000) == 0 { // if not empty // string "rekey" o = append(o, 0xa5, 0x72, 0x65, 0x6b, 0x65, 0x79) o = (*z).Header.RekeyTo.MarshalMsg(o) } - if (zb0007Mask & 0x80000000000) == 0 { // if not empty + if (zb0007Mask & 0x800000000000) == 0 { // if not empty // string "selkey" o = append(o, 0xa6, 0x73, 0x65, 0x6c, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.SelectionPK.MarshalMsg(o) } - if (zb0007Mask & 0x100000000000) == 0 { // if not empty + if (zb0007Mask & 0x1000000000000) == 0 { // if not empty // string "snd" o = append(o, 0xa3, 0x73, 0x6e, 0x64) o = (*z).Header.Sender.MarshalMsg(o) } - if (zb0007Mask & 0x200000000000) == 0 { // if not empty + if (zb0007Mask & 0x2000000000000) == 0 { // if not empty // string "sp" o = append(o, 0xa2, 0x73, 0x70) o = (*z).StateProofTxnFields.StateProof.MarshalMsg(o) } - if (zb0007Mask & 0x400000000000) == 0 { // if not empty + if (zb0007Mask & 0x4000000000000) == 0 { // if not empty // string "spmsg" o = append(o, 0xa5, 0x73, 0x70, 0x6d, 0x73, 0x67) o = (*z).StateProofTxnFields.Message.MarshalMsg(o) } - if (zb0007Mask & 0x800000000000) == 0 { // if not empty + if (zb0007Mask & 0x8000000000000) == 0 { // if not empty // string "sprfkey" o = append(o, 0xa7, 0x73, 0x70, 0x72, 0x66, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.StateProofPK.MarshalMsg(o) } - if (zb0007Mask & 0x1000000000000) == 0 { // if not empty + if (zb0007Mask & 0x10000000000000) == 0 { // if not empty // string "sptype" o = append(o, 0xa6, 0x73, 0x70, 0x74, 0x79, 0x70, 0x65) o = (*z).StateProofTxnFields.StateProofType.MarshalMsg(o) } - if (zb0007Mask & 0x2000000000000) == 0 { // if not empty + if (zb0007Mask & 0x20000000000000) == 0 { // if not empty // string "type" o = append(o, 0xa4, 0x74, 0x79, 0x70, 0x65) o = (*z).Type.MarshalMsg(o) } - if (zb0007Mask & 0x4000000000000) == 0 { // if not empty + if (zb0007Mask & 0x40000000000000) == 0 { // if not empty // string "votefst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x66, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteFirst.MarshalMsg(o) } - if (zb0007Mask & 0x8000000000000) == 0 { // if not empty + if (zb0007Mask & 0x80000000000000) == 0 { // if not empty // string "votekd" o = append(o, 0xa6, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x64) o = msgp.AppendUint64(o, (*z).KeyregTxnFields.VoteKeyDilution) } - if (zb0007Mask & 0x10000000000000) == 0 { // if not empty + if (zb0007Mask & 0x100000000000000) == 0 { // if not empty // string "votekey" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6b, 0x65, 0x79) o = (*z).KeyregTxnFields.VotePK.MarshalMsg(o) } - if (zb0007Mask & 0x20000000000000) == 0 { // if not empty + if (zb0007Mask & 0x200000000000000) == 0 { // if not empty // string "votelst" o = append(o, 0xa7, 0x76, 0x6f, 0x74, 0x65, 0x6c, 0x73, 0x74) o = (*z).KeyregTxnFields.VoteLast.MarshalMsg(o) } - if (zb0007Mask & 0x40000000000000) == 0 { // if not empty + if (zb0007Mask & 0x400000000000000) == 0 { // if not empty // string "xaid" o = append(o, 0xa4, 0x78, 0x61, 0x69, 0x64) o = (*z).AssetTransferTxnFields.XferAsset.MarshalMsg(o) @@ -6086,6 +6290,30 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) return } } + if zb0007 > 0 { + zb0007-- + bts, err = (*z).HeartbeatTxnFields.HbAddress.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbAddress") + return + } + } + if zb0007 > 0 { + zb0007-- + bts, err = (*z).HeartbeatTxnFields.HbProof.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbProof") + return + } + } + if zb0007 > 0 { + zb0007-- + bts, err = (*z).HeartbeatTxnFields.HbSeed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "struct-from-array", "HbSeed") + return + } + } if zb0007 > 0 { err = msgp.ErrTooManyArrayFields(zb0007) if err != nil { @@ -6618,6 +6846,24 @@ func (z *Transaction) UnmarshalMsgWithState(bts []byte, st msgp.UnmarshalState) err = msgp.WrapError(err, "Message") return } + case "hbad": + bts, err = (*z).HeartbeatTxnFields.HbAddress.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbAddress") + return + } + case "hbprf": + bts, err = (*z).HeartbeatTxnFields.HbProof.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbProof") + return + } + case "hbsd": + bts, err = (*z).HeartbeatTxnFields.HbSeed.UnmarshalMsgWithState(bts, st) + if err != nil { + err = msgp.WrapError(err, "HbSeed") + return + } default: err = msgp.ErrNoField(string(field)) if err != nil { @@ -6661,13 +6907,13 @@ func (z *Transaction) Msgsize() (s int) { for zb0006 := range (*z).ApplicationCallTxnFields.ForeignAssets { s += (*z).ApplicationCallTxnFields.ForeignAssets[zb0006].Msgsize() } - s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 7 + (*z).StateProofTxnFields.StateProofType.Msgsize() + 3 + (*z).StateProofTxnFields.StateProof.Msgsize() + 6 + (*z).StateProofTxnFields.Message.Msgsize() + s += 5 + (*z).ApplicationCallTxnFields.LocalStateSchema.Msgsize() + 5 + (*z).ApplicationCallTxnFields.GlobalStateSchema.Msgsize() + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ApprovalProgram) + 5 + msgp.BytesPrefixSize + len((*z).ApplicationCallTxnFields.ClearStateProgram) + 5 + msgp.Uint32Size + 7 + (*z).StateProofTxnFields.StateProofType.Msgsize() + 3 + (*z).StateProofTxnFields.StateProof.Msgsize() + 6 + (*z).StateProofTxnFields.Message.Msgsize() + 5 + (*z).HeartbeatTxnFields.HbAddress.Msgsize() + 6 + (*z).HeartbeatTxnFields.HbProof.Msgsize() + 5 + (*z).HeartbeatTxnFields.HbSeed.Msgsize() return } // MsgIsZero returns whether this is a zero value func (z *Transaction) MsgIsZero() bool { - return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) + return ((*z).Type.MsgIsZero()) && ((*z).Header.Sender.MsgIsZero()) && ((*z).Header.Fee.MsgIsZero()) && ((*z).Header.FirstValid.MsgIsZero()) && ((*z).Header.LastValid.MsgIsZero()) && (len((*z).Header.Note) == 0) && ((*z).Header.GenesisID == "") && ((*z).Header.GenesisHash.MsgIsZero()) && ((*z).Header.Group.MsgIsZero()) && ((*z).Header.Lease == ([32]byte{})) && ((*z).Header.RekeyTo.MsgIsZero()) && ((*z).KeyregTxnFields.VotePK.MsgIsZero()) && ((*z).KeyregTxnFields.SelectionPK.MsgIsZero()) && ((*z).KeyregTxnFields.StateProofPK.MsgIsZero()) && ((*z).KeyregTxnFields.VoteFirst.MsgIsZero()) && ((*z).KeyregTxnFields.VoteLast.MsgIsZero()) && ((*z).KeyregTxnFields.VoteKeyDilution == 0) && ((*z).KeyregTxnFields.Nonparticipation == false) && ((*z).PaymentTxnFields.Receiver.MsgIsZero()) && ((*z).PaymentTxnFields.Amount.MsgIsZero()) && ((*z).PaymentTxnFields.CloseRemainderTo.MsgIsZero()) && ((*z).AssetConfigTxnFields.ConfigAsset.MsgIsZero()) && ((*z).AssetConfigTxnFields.AssetParams.MsgIsZero()) && ((*z).AssetTransferTxnFields.XferAsset.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetAmount == 0) && ((*z).AssetTransferTxnFields.AssetSender.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetReceiver.MsgIsZero()) && ((*z).AssetTransferTxnFields.AssetCloseTo.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAccount.MsgIsZero()) && ((*z).AssetFreezeTxnFields.FreezeAsset.MsgIsZero()) && ((*z).AssetFreezeTxnFields.AssetFrozen == false) && ((*z).ApplicationCallTxnFields.ApplicationID.MsgIsZero()) && ((*z).ApplicationCallTxnFields.OnCompletion == 0) && (len((*z).ApplicationCallTxnFields.ApplicationArgs) == 0) && (len((*z).ApplicationCallTxnFields.Accounts) == 0) && (len((*z).ApplicationCallTxnFields.ForeignApps) == 0) && (len((*z).ApplicationCallTxnFields.Boxes) == 0) && (len((*z).ApplicationCallTxnFields.ForeignAssets) == 0) && ((*z).ApplicationCallTxnFields.LocalStateSchema.MsgIsZero()) && ((*z).ApplicationCallTxnFields.GlobalStateSchema.MsgIsZero()) && (len((*z).ApplicationCallTxnFields.ApprovalProgram) == 0) && (len((*z).ApplicationCallTxnFields.ClearStateProgram) == 0) && ((*z).ApplicationCallTxnFields.ExtraProgramPages == 0) && ((*z).StateProofTxnFields.StateProofType.MsgIsZero()) && ((*z).StateProofTxnFields.StateProof.MsgIsZero()) && ((*z).StateProofTxnFields.Message.MsgIsZero()) && ((*z).HeartbeatTxnFields.HbAddress.MsgIsZero()) && ((*z).HeartbeatTxnFields.HbProof.MsgIsZero()) && ((*z).HeartbeatTxnFields.HbSeed.MsgIsZero()) } // MaxSize returns a maximum valid message size for this message type @@ -6689,7 +6935,7 @@ func TransactionMaxSize() (s int) { s += 5 // Calculating size of slice: z.ApplicationCallTxnFields.ForeignAssets s += msgp.ArrayHeaderSize + ((encodedMaxForeignAssets) * (basics.AssetIndexMaxSize())) - s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 7 + protocol.StateProofTypeMaxSize() + 3 + stateproof.StateProofMaxSize() + 6 + stateproofmsg.MessageMaxSize() + s += 5 + basics.StateSchemaMaxSize() + 5 + basics.StateSchemaMaxSize() + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.BytesPrefixSize + config.MaxAvailableAppProgramLen + 5 + msgp.Uint32Size + 7 + protocol.StateProofTypeMaxSize() + 3 + stateproof.StateProofMaxSize() + 6 + stateproofmsg.MessageMaxSize() + 5 + basics.AddressMaxSize() + 6 + crypto.OneTimeSignatureMaxSize() + 5 + committee.SeedMaxSize() return } diff --git a/data/transactions/msgp_gen_test.go b/data/transactions/msgp_gen_test.go index 0ce6b29c38..49ed14f6e3 100644 --- a/data/transactions/msgp_gen_test.go +++ b/data/transactions/msgp_gen_test.go @@ -494,6 +494,66 @@ func BenchmarkUnmarshalHeader(b *testing.B) { } } +func TestMarshalUnmarshalHeartbeatTxnFields(t *testing.T) { + partitiontest.PartitionTest(t) + v := HeartbeatTxnFields{} + bts := v.MarshalMsg(nil) + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func TestRandomizedEncodingHeartbeatTxnFields(t *testing.T) { + protocol.RunEncodingTest(t, &HeartbeatTxnFields{}) +} + +func BenchmarkMarshalMsgHeartbeatTxnFields(b *testing.B) { + v := HeartbeatTxnFields{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgHeartbeatTxnFields(b *testing.B) { + v := HeartbeatTxnFields{} + bts := make([]byte, 0, v.Msgsize()) + bts = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalHeartbeatTxnFields(b *testing.B) { + v := HeartbeatTxnFields{} + bts := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + func TestMarshalUnmarshalKeyregTxnFields(t *testing.T) { partitiontest.PartitionTest(t) v := KeyregTxnFields{} diff --git a/data/transactions/transaction.go b/data/transactions/transaction.go index a17066c3a5..363747a996 100644 --- a/data/transactions/transaction.go +++ b/data/transactions/transaction.go @@ -566,6 +566,11 @@ func (tx Transaction) WellFormed(spec SpecialAddresses, proto config.ConsensusPa return errLeaseMustBeZeroInStateproofTxn } + case protocol.HeartbeatTx: + if !proto.Heartbeat { + return fmt.Errorf("heartbeat transaction not supported") + } + default: return fmt.Errorf("unknown tx type %v", tx.Type) } diff --git a/data/transactions/transaction_test.go b/data/transactions/transaction_test.go index 08dd145a8c..1dbb2e316a 100644 --- a/data/transactions/transaction_test.go +++ b/data/transactions/transaction_test.go @@ -591,6 +591,21 @@ func TestWellFormedErrors(t *testing.T) { proto: protoV36, expectedError: nil, }, + { + tx: Transaction{ + Type: protocol.HeartbeatTx, + Header: okHeader, + }, + proto: protoV36, + expectedError: fmt.Errorf("heartbeat transaction not supported"), + }, + { + tx: Transaction{ + Type: protocol.HeartbeatTx, + Header: okHeader, + }, + proto: futureProto, + }, } for _, usecase := range usecases { err := usecase.tx.WellFormed(SpecialAddresses{}, usecase.proto) diff --git a/heartbeat/service.go b/heartbeat/service.go index 48d99cb959..15c771a68d 100644 --- a/heartbeat/service.go +++ b/heartbeat/service.go @@ -24,6 +24,7 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/bookkeeping" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/go-algorand/ledger/eval" @@ -77,9 +78,13 @@ func (s *Service) Stop() { // they have been challenged. func (s *Service) findChallenged(rules config.ProposerPayoutRules) []account.ParticipationRecordForRound { current := s.ledger.LastRound() - var found []account.ParticipationRecordForRound - ch := eval.ActiveChallenge(rules, current, s.ledger) + ch := eval.FindChallenge(rules, current, s.ledger, eval.ChRisky) + if ch.IsZero() { + return nil + } + + var found []account.ParticipationRecordForRound for _, pr := range s.accts.Keys(current + 1) { // only look at accounts we have part keys for acct, _, _, err := s.ledger.LookupAccount(current, pr.Account) fmt.Printf(" %v is %s at %d\n", pr.Account, acct.Status, current) @@ -89,7 +94,8 @@ func (s *Service) findChallenged(rules config.ProposerPayoutRules) []account.Par } if acct.Status == basics.Online { lastSeen := max(acct.LastProposed, acct.LastHeartbeat) - if eval.FailsChallenge(ch, pr.Account, lastSeen) { + fmt.Printf(" %v was last seen at %d\n", pr.Account, lastSeen) + if ch.Failed(pr.Account, lastSeen) { found = append(found, pr) } } @@ -120,15 +126,15 @@ func (s *Service) loop() { latest = s.ledger.LastRound() - hdr, err := s.ledger.BlockHdr(latest) + lastHdr, err := s.ledger.BlockHdr(latest) if err != nil { s.log.Errorf("heartbeat service could not fetch block header for round %d: %v", latest, err) continue // Try again next round, I guess? } - proto := config.Consensus[hdr.CurrentProtocol] + proto := config.Consensus[lastHdr.CurrentProtocol] for _, pr := range s.findChallenged(proto.Payouts) { - stxn := s.prepareHeartbeat(pr.Account, latest, hdr.GenesisHash) + stxn := s.prepareHeartbeat(pr, lastHdr) err = s.bcast.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{stxn}) if err != nil { s.log.Errorf("error broadcasting heartbeat %v for %v: %v", stxn, pr.Account, err) @@ -137,22 +143,29 @@ func (s *Service) loop() { } } -// AcceptingByteCode is the source to a logic signature that will accept anything (except rekeying). +// acceptingByteCode is the byte code to a logic signature that will accept anything (except rekeying). var acceptingByteCode = logic.MustAssemble(` #pragma version 11 txn RekeyTo; global ZeroAddress; == `) var acceptingSender = basics.Address(logic.HashProgram(acceptingByteCode)) -func (s *Service) prepareHeartbeat(address basics.Address, latest basics.Round, genHash [32]byte) transactions.SignedTxn { +func (s *Service) prepareHeartbeat(pr account.ParticipationRecordForRound, latest bookkeeping.BlockHeader) transactions.SignedTxn { var stxn transactions.SignedTxn stxn.Lsig = transactions.LogicSig{Logic: acceptingByteCode} stxn.Txn.Type = protocol.HeartbeatTx stxn.Txn.Header = transactions.Header{ Sender: acceptingSender, - FirstValid: latest + 1, - LastValid: latest + 1 + 100, // maybe use the grace period? - GenesisHash: genHash, + FirstValid: latest.Round + 1, + LastValid: latest.Round + 1 + 100, // maybe use the grace period? + GenesisHash: latest.GenesisHash, + } + + id := basics.OneTimeIDForRound(latest.Round+1, pr.KeyDilution) + stxn.Txn.HeartbeatTxnFields = transactions.HeartbeatTxnFields{ + HbAddress: pr.Account, + HbProof: pr.Voting.Sign(id, latest.Seed), + HbSeed: latest.Seed, } return stxn diff --git a/heartbeat/service_test.go b/heartbeat/service_test.go index 2246bde174..566645a5c4 100644 --- a/heartbeat/service_test.go +++ b/heartbeat/service_test.go @@ -22,9 +22,11 @@ import ( "time" "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/account" "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/data/bookkeeping" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" @@ -34,52 +36,27 @@ import ( "github.com/stretchr/testify/require" ) -type mockParticipants struct { - accts map[basics.Address]struct{} -} - -func (p *mockParticipants) Keys(rnd basics.Round) []account.ParticipationRecordForRound { - var ret []account.ParticipationRecordForRound - for addr, _ := range p.accts { - ret = append(ret, account.ParticipationRecordForRound{ - ParticipationRecord: account.ParticipationRecord{ - ParticipationID: [32]byte{}, - Account: addr, - FirstValid: 0, - LastValid: 0, - KeyDilution: 0, - LastVote: 0, - LastBlockProposal: 0, - }, - }) - } - return ret -} - -func (p *mockParticipants) add(addr basics.Address) { - if p.accts == nil { - p.accts = make(map[basics.Address]struct{}) - } - p.accts[addr] = struct{}{} -} - type table map[basics.Address]ledgercore.AccountData type mockedLedger struct { mu deadlock.Mutex waiters map[basics.Round]chan struct{} history []table - version protocol.ConsensusVersion - hdrs map[basics.Round]bookkeeping.BlockHeader + hdr bookkeeping.BlockHeader + + participants map[basics.Address]*crypto.OneTimeSignatureSecrets } func newMockedLedger() mockedLedger { return mockedLedger{ waiters: make(map[basics.Round]chan struct{}), history: []table{nil}, // some genesis accounts could go here - version: protocol.ConsensusFuture, + hdr: bookkeeping.BlockHeader{ + UpgradeState: bookkeeping.UpgradeState{ + CurrentProtocol: protocol.ConsensusFuture, + }, + }, } - } func (l *mockedLedger) LastRound() basics.Round { @@ -115,22 +92,12 @@ func (l *mockedLedger) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) if r > l.LastRound() { return bookkeeping.BlockHeader{}, fmt.Errorf("%d is beyond current block (%d)", r, l.LastRound()) } - if hdr, ok := l.hdrs[r]; ok { - return hdr, nil - } - // just return a simple hdr - var hdr bookkeeping.BlockHeader + // return the template hdr, with round + hdr := l.hdr hdr.Round = r - hdr.CurrentProtocol = l.version return hdr, nil } -// addHeader places a block header into the ledger's history. It is used to make -// challenges occur as we'd like. -func (l *mockedLedger) addHeader(hdr bookkeeping.BlockHeader) { - l.hdrs[hdr.Round] = hdr -} - func (l *mockedLedger) addBlock(delta table) error { l.mu.Lock() defer l.mu.Unlock() @@ -181,6 +148,38 @@ func (l *mockedLedger) waitFor(s *Service, a *require.Assertions) { }, time.Second, 10*time.Millisecond) } +func (l *mockedLedger) Keys(rnd basics.Round) []account.ParticipationRecordForRound { + var ret []account.ParticipationRecordForRound + for addr, secrets := range l.participants { + if rnd > l.LastRound() { // Usually we're looking for key material for a future round + rnd = l.LastRound() + } + acct, _, _, err := l.LookupAccount(rnd, addr) + if err != nil { + panic(err.Error()) + } + + ret = append(ret, account.ParticipationRecordForRound{ + ParticipationRecord: account.ParticipationRecord{ + ParticipationID: [32]byte{}, + Account: addr, + Voting: secrets, + FirstValid: acct.VoteFirstValid, + LastValid: acct.VoteLastValid, + KeyDilution: acct.VoteKeyDilution, + }, + }) + } + return ret +} + +func (l *mockedLedger) addParticipant(addr basics.Address, otss *crypto.OneTimeSignatureSecrets) { + if l.participants == nil { + l.participants = make(map[basics.Address]*crypto.OneTimeSignatureSecrets) + } + l.participants[addr] = otss +} + type txnSink [][]transactions.SignedTxn func (ts *txnSink) BroadcastInternalSignedTxGroup(group []transactions.SignedTxn) error { @@ -195,9 +194,8 @@ func TestStartStop(t *testing.T) { a := require.New(t) sink := txnSink{} - accts := &mockParticipants{} ledger := newMockedLedger() - s := NewService(accts, &ledger, &sink, logging.TestingLog(t)) + s := NewService(&ledger, &ledger, &sink, logging.TestingLog(t)) a.NotNil(s) a.NoError(ledger.addBlock(nil)) s.Start() @@ -218,14 +216,14 @@ func TestHeartBeatOnlyWhenChallenged(t *testing.T) { a := require.New(t) sink := txnSink{} - accts := &mockParticipants{} ledger := newMockedLedger() - s := NewService(accts, &ledger, &sink, logging.TestingLog(t)) + s := NewService(&ledger, &ledger, &sink, logging.TestingLog(t)) s.Start() - // joe is a simple, non-online account, service will not heartbeat - joe := basics.Address{0xcc} // 0xcc will matter when we set the challenge - accts.add(joe) + joe := basics.Address{0xcc} // 0xcc will matter when we set the challenge + mary := basics.Address{0xaa} // 0xaa will matter when we set the challenge + ledger.addParticipant(joe, nil) + ledger.addParticipant(mary, nil) acct := ledgercore.AccountData{} @@ -233,19 +231,27 @@ func TestHeartBeatOnlyWhenChallenged(t *testing.T) { ledger.waitFor(s, a) a.Empty(sink) - // now joe is online, but not challenged, so no heartbeat + // now they are online, but not challenged, so no heartbeat acct.Status = basics.Online - - a.NoError(ledger.addBlock(table{joe: acct})) + acct.VoteKeyDilution = 100 + otss := crypto.GenerateOneTimeSignatureSecrets( + basics.OneTimeIDForRound(ledger.LastRound(), acct.VoteKeyDilution).Batch, + 5) + acct.VoteID = otss.OneTimeSignatureVerifier + ledger.addParticipant(joe, otss) + ledger.addParticipant(mary, otss) + + a.NoError(ledger.addBlock(table{joe: acct, mary: acct})) a.Empty(sink) // now we have to make it seem like joe has been challenged. We obtain the // payout rules to find the first challenge round, skip forward to it, then // go forward half a grace period. Only then should the service heartbeat hdr, err := ledger.BlockHdr(ledger.LastRound()) + ledger.hdr.Seed = committee.Seed{0xc8} // share 5 bits with 0xcc a.NoError(err) rules := config.Consensus[hdr.CurrentProtocol].Payouts - for ledger.LastRound() < basics.Round(rules.ChallengeInterval) { + for ledger.LastRound() < basics.Round(rules.ChallengeInterval+rules.ChallengeGracePeriod/2) { a.NoError(ledger.addBlock(table{})) ledger.waitFor(s, a) a.Empty(sink) @@ -253,10 +259,10 @@ func TestHeartBeatOnlyWhenChallenged(t *testing.T) { a.NoError(ledger.addBlock(table{joe: acct})) ledger.waitFor(s, a) - a.Len(sink, 1) // only one heartbeat so far + a.Len(sink, 1) // only one heartbeat (for joe) a.Len(sink[0], 1) a.Equal(sink[0][0].Txn.Type, protocol.HeartbeatTx) - a.Equal(sink[0][0].Txn.HeartbeatAddress, joe) + a.Equal(sink[0][0].Txn.HbAddress, joe) s.Stop() } diff --git a/ledger/apply/heartbeat.go b/ledger/apply/heartbeat.go index bce788cf68..7a2020006f 100644 --- a/ledger/apply/heartbeat.go +++ b/ledger/apply/heartbeat.go @@ -27,14 +27,14 @@ import ( // Heartbeat applies a Heartbeat transaction using the Balances interface. func Heartbeat(hb transactions.HeartbeatTxnFields, header transactions.Header, balances Balances, provider HdrProvider, round basics.Round) error { // Get the account's balance entry - account, err := balances.Get(hb.HeartbeatAddress, false) + account, err := balances.Get(hb.HbAddress, false) if err != nil { return err } sv := account.VoteID if sv.IsEmpty() { - return fmt.Errorf("heartbeat address %s has no voting keys\n", hb.HeartbeatAddress) + return fmt.Errorf("heartbeat address %s has no voting keys", hb.HbAddress) } id := basics.OneTimeIDForRound(header.LastValid, account.VoteKeyDilution) @@ -42,15 +42,18 @@ func Heartbeat(hb transactions.HeartbeatTxnFields, header transactions.Header, b if err != nil { return err } + if hdr.Seed != hb.HbSeed { + return fmt.Errorf("provided seed %v does not match round %d's seed %v", hb.HbSeed, header.FirstValid-1, hdr.Seed) + } - if !sv.Verify(id, hdr.Seed, hb.Proof) { + if !sv.Verify(id, hdr.Seed, hb.HbProof) { return errors.New("Improper heartbeat") } account.LastHeartbeat = round // Write the updated entry - err = balances.Put(hb.HeartbeatAddress, account) + err = balances.Put(hb.HbAddress, account) if err != nil { return err } diff --git a/ledger/apply/heartbeat_test.go b/ledger/apply/heartbeat_test.go index e6fe7d66a9..42f9cd4732 100644 --- a/ledger/apply/heartbeat_test.go +++ b/ledger/apply/heartbeat_test.go @@ -73,9 +73,9 @@ func TestHeartbeat(t *testing.T) { LastValid: lv, }, HeartbeatTxnFields: transactions.HeartbeatTxnFields{ - HeartbeatAddress: voter, - Proof: otss.Sign(id, seed), - Seed: seed, + HbAddress: voter, + HbProof: otss.Sign(id, seed), + HbSeed: seed, }, } diff --git a/ledger/apply/mockBalances_test.go b/ledger/apply/mockBalances_test.go index 91f918cf80..a5f636fc08 100644 --- a/ledger/apply/mockBalances_test.go +++ b/ledger/apply/mockBalances_test.go @@ -291,5 +291,5 @@ func (m mockHeaders) BlockHdr(r basics.Round) (bookkeeping.BlockHeader, error) { if hdr, ok := m.b[r]; ok { return hdr, nil } - return bookkeeping.BlockHeader{}, fmt.Errorf("round %v is not present\n", r) + return bookkeeping.BlockHeader{}, fmt.Errorf("round %v is not present", r) } diff --git a/ledger/apptxn_test.go b/ledger/apptxn_test.go index a7b3b15214..fce41b00a3 100644 --- a/ledger/apptxn_test.go +++ b/ledger/apptxn_test.go @@ -104,9 +104,9 @@ func TestPayAction(t *testing.T) { dl.t.Log("postsink", postsink, "postprop", postprop) if ver >= payoutsVer { - 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) + bonus := 10_000_000 // config/consensus.go + assert.EqualValues(t, bonus-1000, presink-postsink) // based on 50% in config/consensus.go + require.EqualValues(t, bonus+1000, postprop-preprop) } else { require.EqualValues(t, 2000, postsink-presink) // no payouts yet } diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 0014bc0044..6365a40936 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1621,7 +1621,7 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() { updates := &eval.block.ParticipationUpdates - ch := ActiveChallenge(eval.proto.Payouts, eval.Round(), eval.state) + ch := FindChallenge(eval.proto.Payouts, eval.Round(), eval.state, ChActive) for _, accountAddr := range eval.state.modifiedAccounts() { acctData, found := eval.state.mods.Accts.GetData(accountAddr) @@ -1651,7 +1651,7 @@ func (eval *BlockEvaluator) generateKnockOfflineAccountsList() { if acctData.Status == basics.Online { lastSeen := max(acctData.LastProposed, acctData.LastHeartbeat) if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, current) || - FailsChallenge(ch, accountAddr, lastSeen) { + ch.Failed(accountAddr, lastSeen) { updates.AbsentParticipationAccounts = append( updates.AbsentParticipationAccounts, accountAddr, @@ -1712,10 +1712,19 @@ type headerSource interface { BlockHdr(round basics.Round) (bookkeeping.BlockHeader, error) } -// ActiveChallenge returns details about the Challenge that was last issued if -// it is still in effect (that is, between one and two grace periods from -// issue). Otherwise it returns the zero value. -func ActiveChallenge(rules config.ProposerPayoutRules, current basics.Round, headers headerSource) challenge { +// ChallengePeriod indicates which part of the challenge period is under discussion. +type ChallengePeriod int + +const ( + // ChRisky indicates that a challenge is in effect, and the initial grace period is running out. + ChRisky ChallengePeriod = iota + // ChActive indicates that a challenege is in effect, and the grace period + // has run out, so accounts can be suspended + ChActive +) + +// FindChallenge returns the Challenge that was last issued if it's in the period requested. +func FindChallenge(rules config.ProposerPayoutRules, current basics.Round, headers headerSource, period ChallengePeriod) challenge { // are challenges active? interval := basics.Round(rules.ChallengeInterval) if rules.ChallengeInterval == 0 || current < interval { @@ -1723,9 +1732,18 @@ func ActiveChallenge(rules config.ProposerPayoutRules, current basics.Round, hea } lastChallenge := current - (current % interval) grace := basics.Round(rules.ChallengeGracePeriod) - // challenge is in effect if we're after one grace period, but before the 2nd ends. - if current <= lastChallenge+grace || current > lastChallenge+2*grace { - return challenge{} + // FindChallenge is structured this way, instead of returning the challenge + // and letting the caller determine the period it cares about, to avoid + // using BlockHdr unnecessarily. + switch period { + case ChRisky: + if current <= lastChallenge+grace/2 || current > lastChallenge+grace { + return challenge{} + } + case ChActive: + if current <= lastChallenge+grace || current > lastChallenge+2*grace { + return challenge{} + } } challengeHdr, err := headers.BlockHdr(lastChallenge) if err != nil { @@ -1739,9 +1757,14 @@ func ActiveChallenge(rules config.ProposerPayoutRules, current basics.Round, hea return challenge{lastChallenge, challengeHdr.Seed, rules.ChallengeBits} } -// FailsChallenge returns true iff ch is in effect, matches address, and -// lastSeen is before the challenge issue. -func FailsChallenge(ch challenge, address basics.Address, lastSeen basics.Round) bool { +// IsZero returns true if the challenge is empty (used to indicate no challenege) +func (ch challenge) IsZero() bool { + return ch == challenge{} +} + +// Failed returns true iff ch is in effect, matches address, and lastSeen is +// before the challenge issue. +func (ch challenge) Failed(address basics.Address, lastSeen basics.Round) bool { return ch.round != 0 && bitsMatch(ch.seed[:], address[:], ch.bits) && lastSeen < ch.round } @@ -1812,7 +1835,7 @@ func (eval *BlockEvaluator) validateAbsentOnlineAccounts() error { // For consistency with expired account handling, we preclude duplicates addressSet := make(map[basics.Address]bool, suspensionCount) - ch := ActiveChallenge(eval.proto.Payouts, eval.Round(), eval.state) + ch := FindChallenge(eval.proto.Payouts, eval.Round(), eval.state, ChActive) for _, accountAddr := range eval.block.ParticipationUpdates.AbsentParticipationAccounts { if _, exists := addressSet[accountAddr]; exists { @@ -1833,7 +1856,7 @@ func (eval *BlockEvaluator) validateAbsentOnlineAccounts() error { if isAbsent(eval.state.prevTotals.Online.Money, acctData.MicroAlgos, lastSeen, eval.Round()) { continue // ok. it's "normal absent" } - if FailsChallenge(ch, accountAddr, lastSeen) { + if ch.Failed(accountAddr, lastSeen) { continue // ok. it's "challenge absent" } return fmt.Errorf("proposed absent account %v is not absent in %d, %d", diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index d2cf2e2e6e..f0ba85102c 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -1644,16 +1644,16 @@ func TestFailsChallenge(t *testing.T) { a := assert.New(t) // a valid challenge, with 4 matching bits, and an old last seen - a.True(FailsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 10)) + a.True(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}.Failed(basics.Address{0xbf, 0x34}, 10)) // challenge isn't "on" - a.False(FailsChallenge(challenge{round: 0, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 10)) + a.False(challenge{round: 0, seed: [32]byte{0xb0, 0xb4}, bits: 4}.Failed(basics.Address{0xbf, 0x34}, 10)) // node has appeared more recently - a.False(FailsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xbf, 0x34}, 12)) + a.False(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}.Failed(basics.Address{0xbf, 0x34}, 12)) // bits don't match - a.False(FailsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}, basics.Address{0xcf, 0x34}, 10)) + a.False(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 4}.Failed(basics.Address{0xcf, 0x34}, 10)) // no enough bits match - a.False(FailsChallenge(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 5}, basics.Address{0xbf, 0x34}, 10)) + a.False(challenge{round: 11, seed: [32]byte{0xb0, 0xb4}, bits: 5}.Failed(basics.Address{0xbf, 0x34}, 10)) } type singleSource bookkeeping.BlockHeader @@ -1677,23 +1677,23 @@ func TestActiveChallenge(t *testing.T) { // simplest test. when interval=X and grace=G, X+G+1 is a challenge inChallenge := basics.Round(rules.ChallengeInterval + rules.ChallengeGracePeriod + 1) - ch := ActiveChallenge(rules, inChallenge, singleSource(nowHeader)) + ch := FindChallenge(rules, inChallenge, singleSource(nowHeader), ChActive) a.NotZero(ch.round) // all rounds before that have no challenge for r := basics.Round(1); r < inChallenge; r++ { - ch := ActiveChallenge(rules, r, singleSource(nowHeader)) + ch := FindChallenge(rules, r, singleSource(nowHeader), ChActive) a.Zero(ch.round, r) } // ChallengeGracePeriod rounds allow challenges starting with inChallenge for r := inChallenge; r < inChallenge+basics.Round(rules.ChallengeGracePeriod); r++ { - ch := ActiveChallenge(rules, r, singleSource(nowHeader)) + ch := FindChallenge(rules, r, singleSource(nowHeader), ChActive) a.EqualValues(ch.round, rules.ChallengeInterval) } // And the next round is again challenge-less - ch = ActiveChallenge(rules, inChallenge+basics.Round(rules.ChallengeGracePeriod), singleSource(nowHeader)) + ch = FindChallenge(rules, inChallenge+basics.Round(rules.ChallengeGracePeriod), singleSource(nowHeader), ChActive) a.Zero(ch.round) // ignore challenge if upgrade happened @@ -1703,6 +1703,6 @@ func TestActiveChallenge(t *testing.T) { CurrentProtocol: protocol.ConsensusV39, }, } - ch = ActiveChallenge(rules, inChallenge, singleSource(oldHeader)) + ch = FindChallenge(rules, inChallenge, singleSource(oldHeader), ChActive) a.Zero(ch.round) }