From edc2e34dc106b638a43c831bbd9f4ff0de4ff610 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 14 Nov 2023 13:54:55 -0500 Subject: [PATCH 1/2] Revert "Feature: Catchup Eval Stake Exception Round Handling (#5795)" This reverts commit c4e94feb6220e70725184bb609362670974a465b. --- ledger/eval/eval.go | 43 +++++--- ledger/eval/eval_test.go | 231 +++++++++------------------------------ 2 files changed, 75 insertions(+), 199 deletions(-) diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 66b1133457..5fc72ff54e 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1318,23 +1318,30 @@ func (eval *BlockEvaluator) endOfBlock() error { if !eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment.IsEqual(expectedVoters) { return fmt.Errorf("StateProofVotersCommitment wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofVotersCommitment, expectedVoters) } - - if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { - mainnetGenesisHash, _ := crypto.DigestFromString("YBQ4JWH4DW655UWXMBF6IVUOH5WQIGMHVQ333ZFWEC22WOJERLPQ") - if eval.genesisHash == mainnetGenesisHash { - // Handful of historic rounds where the state proof online total weight is known to be slightly different - // than expected voters weight. A consensus release (V38) addressed this/prevented the corner case from - // occurring going forward. - switch eval.block.Round() { - case 24018688, 26982912, 27009024, 27713280, 27822080, 27822848, 27929344, 28032768, - 28977920, 29822208, 30005248, 30033920: - // Don't return an error for these blocks - default: - return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) - } - } else { + if eval.proto.ExcludeExpiredCirculation { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight, expectedVotersWeight) } + } else { + if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight != expectedVotersWeight { + actualVotersWeight := eval.block.StateProofTracking[protocol.StateProofBasic].StateProofOnlineTotalWeight + var highWeight, lowWeight basics.MicroAlgos + if expectedVotersWeight.LessThan(actualVotersWeight) { + highWeight = actualVotersWeight + lowWeight = expectedVotersWeight + } else { + highWeight = expectedVotersWeight + lowWeight = actualVotersWeight + } + const stakeDiffusionFactor = 5 + allowedDelta, overflowed := basics.Muldiv(expectedVotersWeight.Raw, stakeDiffusionFactor, 100) + if overflowed { + return fmt.Errorf("StateProofOnlineTotalWeight overflow: %v != %v", actualVotersWeight, expectedVotersWeight) + } + if (highWeight.Raw - lowWeight.Raw) > allowedDelta { + return fmt.Errorf("StateProofOnlineTotalWeight wrong: %v != %v greater than %d", actualVotersWeight, expectedVotersWeight, allowedDelta) + } + } } if eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound != eval.state.GetStateProofNextRound() { return fmt.Errorf("StateProofNextRound wrong: %v != %v", eval.block.StateProofTracking[protocol.StateProofBasic].StateProofNextRound, eval.state.GetStateProofNextRound()) @@ -1708,12 +1715,12 @@ transactionGroupLoop: select { case <-ctx.Done(): return ledgercore.StateDelta{}, ctx.Err() - case err1, open := <-txvalidator.done: + case err, open := <-txvalidator.done: if !open { break } - if err1 != nil { - return ledgercore.StateDelta{}, err1 + if err != nil { + return ledgercore.StateDelta{}, err } } } diff --git a/ledger/eval/eval_test.go b/ledger/eval/eval_test.go index 94203b4bbb..b916558314 100644 --- a/ledger/eval/eval_test.go +++ b/ledger/eval/eval_test.go @@ -21,8 +21,6 @@ import ( "context" "errors" "fmt" - "github.com/algorand/go-algorand/crypto/merklearray" - "math" "math/rand" "testing" @@ -673,6 +671,56 @@ func testnetFixupExecution(t *testing.T, headerRound basics.Round, poolBonus uin require.NoError(t, err) } +// newTestGenesis creates a bunch of accounts, splits up 10B algos +// between them and the rewardspool and feesink, and gives out the +// addresses and secrets it creates to enable tests. For special +// scenarios, manipulate these return values before using newTestLedger. +func newTestGenesis() (bookkeeping.GenesisBalances, []basics.Address, []*crypto.SignatureSecrets) { + // irrelevant, but deterministic + sink, err := basics.UnmarshalChecksumAddress("YTPRLJ2KK2JRFSZZNAF57F3K5Y2KCG36FZ5OSYLW776JJGAUW5JXJBBD7Q") + if err != nil { + panic(err) + } + rewards, err := basics.UnmarshalChecksumAddress("242H5OXHUEBYCGGWB3CQ6AZAMQB5TMCWJGHCGQOZPEIVQJKOO7NZXUXDQA") + if err != nil { + panic(err) + } + + const count = 10 + addrs := make([]basics.Address, count) + secrets := make([]*crypto.SignatureSecrets, count) + accts := make(map[basics.Address]basics.AccountData) + + // 10 billion microalgos, across N accounts and pool and sink + amount := 10 * 1000000000 * 1000000 / uint64(count+2) + + for i := 0; i < count; i++ { + // Create deterministic addresses, so that output stays the same, run to run. + var seed crypto.Seed + seed[0] = byte(i) + secrets[i] = crypto.GenerateSignatureSecrets(seed) + addrs[i] = basics.Address(secrets[i].SignatureVerifier) + + adata := basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + } + accts[addrs[i]] = adata + } + + accts[sink] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + Status: basics.NotParticipating, + } + + accts[rewards] = basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: amount}, + } + + genBalances := bookkeeping.MakeGenesisBalances(accts, sink, rewards) + + return genBalances, addrs, secrets +} + type evalTestLedger struct { blocks map[basics.Round]bookkeeping.Block roundBalances map[basics.Round]map[basics.Address]basics.AccountData @@ -684,7 +732,6 @@ type evalTestLedger struct { latestTotals ledgercore.AccountTotals tracer logic.EvalTracer boxes map[string][]byte - voters map[basics.Round]*ledgercore.VotersForRound } // newTestLedger creates a in memory Ledger that is as realistic as @@ -894,9 +941,6 @@ func (ledger *evalTestLedger) BlockHdr(rnd basics.Round) (bookkeeping.BlockHeade } func (ledger *evalTestLedger) VotersForStateProof(rnd basics.Round) (*ledgercore.VotersForRound, error) { - if v, ok := ledger.voters[rnd]; ok { - return v, nil - } return nil, errors.New("untested code path") } @@ -1401,178 +1445,3 @@ func TestExpiredAccountGeneration(t *testing.T) { require.Equal(t, crypto.VRFVerifier{}, recvAcct.SelectionID) require.Equal(t, merklesignature.Verifier{}.Commitment, recvAcct.StateProofID) } - -func TestEval_EndOfBlockStake(t *testing.T) { - partitiontest.PartitionTest(t) - - proto := protocol.ConsensusFuture - mainnetDigest, digestError := crypto.DigestFromString("YBQ4JWH4DW655UWXMBF6IVUOH5WQIGMHVQ333ZFWEC22WOJERLPQ") - require.NoError(t, digestError) - - var tests = []struct { - actual basics.MicroAlgos - expected basics.MicroAlgos - round basics.Round - genesisHash crypto.Digest - err string - }{ - // Normal round mainnet, matched stake => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "", - }, - // Normal round mainnet, mismatched stake (actual error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Normal round mainnet, mismatched stake (actual>expected) => error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {100} != {99}", - }, - // Normal round mainnet, mismatched stake with max possible value for muldiv => error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "StateProofOnlineTotalWeight wrong: {100} != {18446744073709551615}", - }, - // Normal round mainnet, matched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched state (actual>expected) => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched state (actual no error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Exception round mainnet, mismatched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64 - 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(27713280), - genesisHash: mainnetDigest, - err: "", - }, - // Normal round non-mainnet, matched stake => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - err: "", - }, - // Normal round non-mainnet, mismatched stake (actual error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(config.Consensus[proto].StateProofInterval), - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Exception round non-mainnet, mismatched state (actual>expected) => no error - { - actual: basics.MicroAlgos{Raw: 100}, - expected: basics.MicroAlgos{Raw: 99}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {100} != {99}", - }, - // Exception round non-mainnet, mismatched state (actual no error - { - actual: basics.MicroAlgos{Raw: 99}, - expected: basics.MicroAlgos{Raw: 100}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {99} != {100}", - }, - // Exception round non-mainnet, mismatched stake with max possible value for muldiv => no error - { - actual: basics.MicroAlgos{Raw: math.MaxUint64 - 100}, - expected: basics.MicroAlgos{Raw: math.MaxUint64}, - round: basics.Round(27713280), - err: "StateProofOnlineTotalWeight wrong: {18446744073709551515} != {18446744073709551615}", - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - params := config.Consensus[proto] - - genesisInitState, _, _ := ledgertesting.GenesisWithProto(2, proto) - - l := newTestLedger(t, bookkeeping.GenesisBalances{ - Balances: genesisInitState.Accounts, - FeeSink: testSinkAddr, - RewardsPool: testPoolAddr, - Timestamp: 0, - }) - // If genesis hash is non-nil in test case, override the test hash here - if !test.genesisHash.IsZero() { - l.genesisHash = test.genesisHash - } - l.voters = map[basics.Round]*ledgercore.VotersForRound{ - basics.Round(uint64(test.round) - params.StateProofVotersLookback): { - Tree: &merklearray.Tree{}, - TotalWeight: test.expected, - }, - } - - genesisBlockHeader, err := l.BlockHdr(0) - require.NoError(t, err) - newBlock := bookkeeping.MakeBlock(genesisBlockHeader) - newBlock.StateProofTracking = map[protocol.StateProofType]bookkeeping.StateProofTrackingData{ - protocol.StateProofBasic: { - StateProofOnlineTotalWeight: test.actual, - }, - } - newBlock.BlockHeader.Round = test.round - - pcow := mockLedger{balanceMap: map[basics.Address]basics.AccountData{}} - mods := ledgercore.StateDelta{Hdr: &newBlock.BlockHeader} - cow := &roundCowState{ - lookupParent: &pcow, - mods: mods, - } - eval := BlockEvaluator{ - state: cow, - validate: true, - prevHeader: genesisBlockHeader, - proto: params, - genesisHash: l.genesisHash, - block: newBlock, - l: l, - } - - err = eval.endOfBlock() - if len(test.err) == 0 { - require.NoError(t, err) - } else { - require.ErrorContains(t, err, test.err) - } - }) - } - -} From 995d17947846ae5c4c2c71dc3733b1084c826115 Mon Sep 17 00:00:00 2001 From: Pavel Zbitskiy Date: Tue, 14 Nov 2023 14:02:32 -0500 Subject: [PATCH 2/2] change the diffusion factor to 1% --- ledger/eval/eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/eval/eval.go b/ledger/eval/eval.go index 5fc72ff54e..58f7fc8a4f 100644 --- a/ledger/eval/eval.go +++ b/ledger/eval/eval.go @@ -1333,7 +1333,7 @@ func (eval *BlockEvaluator) endOfBlock() error { highWeight = expectedVotersWeight lowWeight = actualVotersWeight } - const stakeDiffusionFactor = 5 + const stakeDiffusionFactor = 1 allowedDelta, overflowed := basics.Muldiv(expectedVotersWeight.Raw, stakeDiffusionFactor, 100) if overflowed { return fmt.Errorf("StateProofOnlineTotalWeight overflow: %v != %v", actualVotersWeight, expectedVotersWeight)