Merge branch 'wb/proposer-sets-block-time' into wb/proposer-based-timestamps

This commit is contained in:
William Banfield
2021-12-15 14:21:55 -05:00
8 changed files with 132 additions and 20 deletions
+1 -1
View File
@@ -214,7 +214,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
// Make proposal
propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()}
proposal := types.NewProposal(height, round, lazyNodeState.ValidRound, propBlockID)
proposal := types.NewProposal(height, round, lazyNodeState.ValidRound, propBlockID, block.Header.Time)
p := proposal.ToProto()
if err := lazyNodeState.privValidator.SignProposal(ctx, lazyNodeState.state.ChainID, p); err == nil {
proposal.Signature = p.Signature
+1 -1
View File
@@ -248,7 +248,7 @@ func decideProposal(
// Make proposal
polRound, propBlockID := validRound, types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()}
proposal = types.NewProposal(height, round, polRound, propBlockID)
proposal = types.NewProposal(height, round, polRound, propBlockID, block.Header.Time)
p := proposal.ToProto()
if err := vs.SignProposal(ctx, chainID, p); err != nil {
t.Fatalf("error signing proposal: %s", err)
+1 -1
View File
@@ -204,7 +204,7 @@ func (p *pbtsTestHarness) nextHeight(proposer types.PrivValidator, deliverTime,
b.Header.ProposerAddress = k.Address()
ps := b.MakePartSet(types.BlockPartSizeBytes)
bid := types.BlockID{Hash: b.Hash(), PartSetHeader: ps.Header()}
prop := types.NewProposal(p.currentHeight, 0, -1, bid)
prop := types.NewProposal(p.currentHeight, 0, -1, bid, proposedTime)
tp := prop.ToProto()
if err := proposer.SignProposal(context.Background(), p.observedState.state.ChainID, tp); err != nil {
+4 -4
View File
@@ -384,7 +384,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
propBlockParts := propBlock.MakePartSet(partSize)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
proposal := types.NewProposal(vss[1].Height, round, -1, blockID)
proposal := types.NewProposal(vss[1].Height, round, -1, blockID, propBlock.Header.Time)
p := proposal.ToProto()
if err := vss[1].SignProposal(ctx, cfg.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
@@ -416,7 +416,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
propBlockParts = propBlock.MakePartSet(partSize)
blockID = types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
proposal = types.NewProposal(vss[2].Height, round, -1, blockID)
proposal = types.NewProposal(vss[2].Height, round, -1, blockID, propBlock.Header.Time)
p = proposal.ToProto()
if err := vss[2].SignProposal(ctx, cfg.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
@@ -475,7 +475,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
selfIndex := valIndexFn(0)
proposal = types.NewProposal(vss[3].Height, round, -1, blockID)
proposal = types.NewProposal(vss[3].Height, round, -1, blockID, propBlock.Header.Time)
p = proposal.ToProto()
if err := vss[3].SignProposal(ctx, cfg.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
@@ -540,7 +540,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
sort.Sort(ValidatorStubsByPower(newVss))
selfIndex = valIndexFn(0)
proposal = types.NewProposal(vss[1].Height, round, -1, blockID)
proposal = types.NewProposal(vss[1].Height, round, -1, blockID, propBlock.Header.Time)
p = proposal.ToProto()
if err := vss[1].SignProposal(ctx, cfg.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
+15 -2
View File
@@ -1199,7 +1199,7 @@ func (cs *State) defaultDecideProposal(height int64, round int32) {
// Make proposal
propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()}
proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID)
proposal := types.NewProposal(height, round, cs.ValidRound, propBlockID, block.Header.Time)
p := proposal.ToProto()
// wait the max amount we would wait for a proposal
@@ -1321,6 +1321,12 @@ func (cs *State) defaultDoPrevote(height int64, round int32) {
return
}
if !cs.Proposal.Timestamp.Equal(cs.ProposalBlock.Header.Time) {
logger.Debug("proposal timestamp not equal, prevoting nil")
cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
// Validate proposal block
err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock)
if err != nil {
@@ -1345,6 +1351,7 @@ func (cs *State) defaultDoPrevote(height int64, round int32) {
*/
if cs.Proposal.POLRound == -1 {
if cs.LockedRound == -1 {
// TODO(@wbanfield) add check for timely here as well
logger.Debug("prevote step: ProposalBlock is valid and there is no locked block; prevoting the proposal")
cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
return
@@ -1480,9 +1487,15 @@ func (cs *State) enterPrecommit(height int64, round int32) {
cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{})
return
}
// At this point, +2/3 prevoted for a particular block.
// If the proposal time does not match the block time, precommit nil.
if !cs.Proposal.Timestamp.Equal(cs.ProposalBlock.Header.Time) {
logger.Debug("proposal timestamp not equal, precommitting nil")
cs.signAddVote(tmproto.PrecommitType, nil, types.PartSetHeader{})
return
}
// If we're already locked on that block, precommit it, and update the LockedRound
if cs.LockedBlock.HashesTo(blockID.Hash) {
logger.Debug("precommit step; +2/3 prevoted locked block; relocking")
+103 -5
View File
@@ -246,7 +246,7 @@ func TestStateBadProposal(t *testing.T) {
propBlock.AppHash = stateHash
propBlockParts := propBlock.MakePartSet(partSize)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
proposal := types.NewProposal(vs2.Height, round, -1, blockID)
proposal := types.NewProposal(vs2.Height, round, -1, blockID, propBlock.Header.Time)
p := proposal.ToProto()
if err := vs2.SignProposal(ctx, config.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
@@ -306,7 +306,7 @@ func TestStateOversizedBlock(t *testing.T) {
propBlockParts := propBlock.MakePartSet(partSize)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
proposal := types.NewProposal(height, round, -1, blockID)
proposal := types.NewProposal(height, round, -1, blockID, propBlock.Header.Time)
p := proposal.ToProto()
if err := vs2.SignProposal(ctx, config.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
@@ -856,7 +856,7 @@ func TestStateLock_POLRelock(t *testing.T) {
t.Log("### Starting Round 1")
incrementRound(vs2, vs3, vs4)
round++
propR1 := types.NewProposal(height, round, cs1.ValidRound, blockID)
propR1 := types.NewProposal(height, round, cs1.ValidRound, blockID, theBlock.Header.Time)
p := propR1.ToProto()
if err := vs2.SignProposal(ctx, cs1.state.ChainID, p); err != nil {
t.Fatalf("error signing proposal: %s", err)
@@ -1588,7 +1588,7 @@ func TestStateLock_POLSafety2(t *testing.T) {
round++ // moving to the next round
// in round 2 we see the polkad block from round 0
newProp := types.NewProposal(height, round, 0, propBlockID0)
newProp := types.NewProposal(height, round, 0, propBlockID0, propBlock0.Header.Time)
p := newProp.ToProto()
if err := vs3.SignProposal(ctx, config.ChainID(), p); err != nil {
t.Fatal(err)
@@ -1730,7 +1730,7 @@ func TestState_PrevotePOLFromPreviousRound(t *testing.T) {
t.Log("### Starting Round 2")
incrementRound(vs2, vs3, vs4)
round++
propR2 := types.NewProposal(height, round, 1, r1BlockID)
propR2 := types.NewProposal(height, round, 1, r1BlockID, propBlockR1.Header.Time)
p := propR2.ToProto()
if err := vs3.SignProposal(ctx, cs1.state.ChainID, p); err != nil {
t.Fatalf("error signing proposal: %s", err)
@@ -2649,6 +2649,104 @@ func TestSignSameVoteTwice(t *testing.T) {
require.Equal(t, vote, vote2)
}
// TestStateTimestamp_ProposalNotMatch tests that a validator does not prevote a
// proposed block if the timestamp in the block does not matche the timestamp in the
// corresponding proposal message.
func TestStateTimestamp_ProposalNotMatch(t *testing.T) {
config := configSetup(t)
logger := log.TestingLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss, err := makeState(ctx, config, logger, 4)
require.NoError(t, err)
height, round := cs1.Height, cs1.Round
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
propBlock, _ := cs1.createProposalBlock()
round++
incrementRound(vss[1:]...)
propBlockParts := propBlock.MakePartSet(types.BlockPartSizeBytes)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
// Create a proposal with a timestamp that does not match the timestamp of the block.
proposal := types.NewProposal(vs2.Height, round, -1, blockID, propBlock.Header.Time.Add(time.Millisecond))
p := proposal.ToProto()
if err := vs2.SignProposal(ctx, config.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
}
proposal.Signature = p.Signature
require.NoError(t, cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"))
startTestRound(ctx, cs1, height, round)
ensureProposal(t, proposalCh, height, round, blockID)
signAddVotes(ctx, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vs2, vs3, vs4)
// ensure that the validator prevotes nil.
ensurePrevote(t, voteCh, height, round)
validatePrevote(ctx, t, cs1, round, vss[0], nil)
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(ctx, t, cs1, round, -1, vss[0], nil, nil)
}
// TestStateTimestamp_ProposalMatch tests that a validator prevotes a
// proposed block if the timestamp in the block matches the timestamp in the
// corresponding proposal message.
func TestStateTimestamp_ProposalMatch(t *testing.T) {
config := configSetup(t)
logger := log.TestingLogger()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cs1, vss, err := makeState(ctx, config, logger, 4)
require.NoError(t, err)
height, round := cs1.Height, cs1.Round
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
proposalCh := subscribe(ctx, t, cs1.eventBus, types.EventQueryCompleteProposal)
pv1, err := cs1.privValidator.GetPubKey(ctx)
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(ctx, t, cs1, addr)
propBlock, _ := cs1.createProposalBlock()
round++
incrementRound(vss[1:]...)
propBlockParts := propBlock.MakePartSet(types.BlockPartSizeBytes)
blockID := types.BlockID{Hash: propBlock.Hash(), PartSetHeader: propBlockParts.Header()}
// Create a proposal with a timestamp that matches the timestamp of the block.
proposal := types.NewProposal(vs2.Height, round, -1, blockID, propBlock.Header.Time)
p := proposal.ToProto()
if err := vs2.SignProposal(ctx, config.ChainID(), p); err != nil {
t.Fatal("failed to sign bad proposal", err)
}
proposal.Signature = p.Signature
require.NoError(t, cs1.SetProposalAndBlock(proposal, propBlock, propBlockParts, "some peer"))
startTestRound(ctx, cs1, height, round)
ensureProposal(t, proposalCh, height, round, blockID)
signAddVotes(ctx, cs1, tmproto.PrevoteType, config.ChainID(), blockID, vs2, vs3, vs4)
// ensure that the validator prevotes the block.
ensurePrevote(t, voteCh, height, round)
validatePrevote(ctx, t, cs1, round, vss[0], propBlock.Hash())
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(ctx, t, cs1, round, 1, vss[0], propBlock.Hash(), propBlock.Hash())
}
// subscribe subscribes test client to the given query and returns a channel with cap = 1.
func subscribe(
ctx context.Context,
+2 -2
View File
@@ -34,14 +34,14 @@ type Proposal struct {
// NewProposal returns a new Proposal.
// If there is no POLRound, polRound should be -1.
func NewProposal(height int64, round int32, polRound int32, blockID BlockID) *Proposal {
func NewProposal(height int64, round int32, polRound int32, blockID BlockID, ts time.Time) *Proposal {
return &Proposal{
Type: tmproto.ProposalType,
Height: height,
Round: round,
BlockID: blockID,
POLRound: polRound,
Timestamp: tmtime.Now(),
Timestamp: tmtime.Canonical(ts),
}
}
+5 -4
View File
@@ -13,6 +13,7 @@ import (
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/tendermint/tendermint/internal/libs/protoio"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtime "github.com/tendermint/tendermint/libs/time"
tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
@@ -63,7 +64,7 @@ func TestProposalVerifySignature(t *testing.T) {
prop := NewProposal(
4, 2, 2,
BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}})
BlockID{tmrand.Bytes(tmhash.Size), PartSetHeader{777, tmrand.Bytes(tmhash.Size)}}, tmtime.Now())
p := prop.ToProto()
signBytes := ProposalSignBytes("test_chain_id", p)
@@ -154,7 +155,7 @@ func TestProposalValidateBasic(t *testing.T) {
t.Run(tc.testName, func(t *testing.T) {
prop := NewProposal(
4, 2, 2,
blockID)
blockID, tmtime.Now())
p := prop.ToProto()
err := privVal.SignProposal(context.Background(), "test_chain_id", p)
prop.Signature = p.Signature
@@ -166,9 +167,9 @@ func TestProposalValidateBasic(t *testing.T) {
}
func TestProposalProtoBuf(t *testing.T) {
proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash")))
proposal := NewProposal(1, 2, 3, makeBlockID([]byte("hash"), 2, []byte("part_set_hash")), tmtime.Now())
proposal.Signature = []byte("sig")
proposal2 := NewProposal(1, 2, 3, BlockID{})
proposal2 := NewProposal(1, 2, 3, BlockID{}, tmtime.Now())
testCases := []struct {
msg string