From 83a90696dafa282543f4efd437ebf50dc627cfb1 Mon Sep 17 00:00:00 2001 From: Mark Rushakoff Date: Wed, 15 Jan 2025 13:54:33 -0500 Subject: [PATCH] wip: mirror confirms vote power of previous commit proof --- tm/tmengine/internal/tmmirror/mirror.go | 20 +++++++- tm/tmengine/internal/tmmirror/mirror_test.go | 54 ++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/tm/tmengine/internal/tmmirror/mirror.go b/tm/tmengine/internal/tmmirror/mirror.go index 2a64d98..c18e732 100644 --- a/tm/tmengine/internal/tmmirror/mirror.go +++ b/tm/tmengine/internal/tmmirror/mirror.go @@ -335,7 +335,25 @@ RESTART: return tmconsensus.HandleProposedHeaderBadPrevCommitProofSignature } - // TODO: confirm that we have majority voting power on the previous block hash. + if ph.Header.Height > m.initialHeight { + // Only confirm the vote power if we are beyond the genesis height, + // as the initial height does not have previous commit proofs. + var prevBlockVotePower, availableVotePower uint64 + prevVals := checkResp.PrevValidatorSet.Validators + sigBits := signBitsByHash[string(ph.Header.PrevBlockHash)] + for i, v := range prevVals { + // If we already had the total vote power, + // we could break out of this loop as soon as we cross majority power. + availableVotePower += v.Power + if sigBits.Test(uint(i)) { + prevBlockVotePower += v.Power + } + } + + if prevBlockVotePower < tmconsensus.ByzantineMajority(availableVotePower) { + return tmconsensus.HandleProposedHeaderBadPrevCommitVoteCount + } + } // The hash matches and the proposed header was signed by a validator we know, // so we can accept the message. diff --git a/tm/tmengine/internal/tmmirror/mirror_test.go b/tm/tmengine/internal/tmmirror/mirror_test.go index d43384b..3bb89af 100644 --- a/tm/tmengine/internal/tmmirror/mirror_test.go +++ b/tm/tmengine/internal/tmmirror/mirror_test.go @@ -546,6 +546,56 @@ func TestMirror_HandleProposedHeader(t *testing.T) { }) }) + t.Run("rejected when there are insufficient votes in previous commit proof", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + mfx := tmmirrortest.NewFixture(ctx, t, 4) + + m := mfx.NewMirror() + defer m.Wait() + defer cancel() + + // Proposed header and 3/4 precommits at height 1. + ph1 := mfx.Fx.NextProposedHeader([]byte("app_data_1"), 0) + mfx.Fx.SignProposal(ctx, &ph1, 0) + require.Equal(t, tmconsensus.HandleProposedHeaderAccepted, m.HandleProposedHeader(ctx, ph1)) + keyHash, _ := mfx.Fx.ValidatorHashes() + voteMap := map[string][]int{ + string(ph1.Header.Hash): {0, 1, 2}, + } + precommitProof := tmconsensus.PrecommitSparseProof{ + Height: 1, + Round: 0, + PubKeyHash: keyHash, + Proofs: mfx.Fx.SparsePrecommitProofMap(ctx, 1, 0, voteMap), + } + require.Equal(t, tmconsensus.HandleVoteProofsAccepted, m.HandlePrecommitProofs(ctx, precommitProof)) + + // Now the mirror is ready for the proposed header at height 2. + // So, if we set the previous commit proof to include a double signature for validator 3, + // the mirror should reject the proposed header on account of the double signature. + + mfx.Fx.CommitBlock( + ph1.Header, []byte("app_state_height_1"), 0, + mfx.Fx.PrecommitProofMap(ctx, 1, 0, voteMap), + ) + ph2 := mfx.Fx.NextProposedHeader([]byte("app_data_2"), 1) + + // Clip off the first precommit for ph1, + // which will cause there to only be 50% votes present. + t.Logf("%#v", ph2.Header.PrevCommitProof.Proofs) + ph2.Header.PrevCommitProof.Proofs[string(ph1.Header.Hash)] = + ph2.Header.PrevCommitProof.Proofs[string(ph1.Header.Hash)][1:] + + mfx.Fx.RecalculateHash(&ph2.Header) + mfx.Fx.SignProposal(ctx, &ph2, 0) + + require.Equal(t, tmconsensus.HandleProposedHeaderBadPrevCommitVoteCount, m.HandleProposedHeader(ctx, ph2)) + }) + t.Run("rejected when there is a double signature", func(t *testing.T) { t.Parallel() @@ -578,6 +628,10 @@ func TestMirror_HandleProposedHeader(t *testing.T) { // So, if we set the previous commit proof to include a double signature for validator 3, // the mirror should reject the proposed header on account of the double signature. + mfx.Fx.CommitBlock( + ph1.Header, []byte("app_state_height_1"), 0, + mfx.Fx.PrecommitProofMap(ctx, 1, 0, voteMap), + ) ph2 := mfx.Fx.NextProposedHeader([]byte("app_data_2"), 1) voteMap[""] = []int{3} precommitProof = tmconsensus.PrecommitSparseProof{