Compare commits

..

4 Commits

Author SHA1 Message Date
William Banfield
9e19803ded wip 2021-11-14 17:03:55 -05:00
William Banfield
671c22b5d1 initial proposalTimeout implementation 2021-11-14 16:44:28 -05:00
William Banfield
0d2a5de203 state: add an 'IsTimely' method to implement the 'timely' check for proposer-based timestamps (#7170)
* state: add an IsTimely function to implement the check for timely in proposer-based timestamps

* move time checks into block.go and add time source mechanism

* timestamp params comment

* add todo related to pbts spec and timestamp params

* remove old istimely

* switch to using built in before function

* lint++

* wip

* move into proposal and create a default set of params

* defer using default cons params for now
2021-11-09 15:23:39 -05:00
William Banfield
fe2ed68718 consensus: update state to prevote nil when proposal block does not match locked block. (#6986)
* add failing test

* tweak comments in failing test

* failing test comment

* initial attempt at removing prevote locked block logic

* comment out broken function

* undo reset on prevotes

* fixing TestProposeValidBlock test

* update test for completed POL update

* comment updates

* further unlock testing

* update comments

* Update internal/consensus/state.go

* spacing nit

* comment cleanup

* nil check in addVote

* update unlock description

* update precommit on relock comment

* add ensure new timeout back

* rename IsZero to IsNil and replace uses of block len check with helper

* add testing.T to new assertions

* begin removing unlock condition

* fix TestStateProposerSelection2 to precommit for nil correctly

* remove erroneous sleep

* update TestStatePOL comment

* update relock test to be more clear

* add _ into test names

* rename slashing

* udpate no relock function to be cleaner

* do not relock on old proposal test cleanup

* con state name update

* remove all references to unlock

* update test comments to include new

* add relock test

* add ensureRelock to common_test

* remove all event unlock

* remove unlock checks

* no lint add space

* lint ++

* add test for nil prevote on different proposal

* fix prevote nil condition

* fix defaultDoPrevote

* state_test.go fixes to accomodate prevoting for nil

* add failing test for POL from previous round case

* update prevote logic to prevote POL from previous round

* state.go comment fixes

* update validatePrevotes to correctly look for nil

* update new test name and comment

* update POLFromPreviousRound test

* fixes post merge

* fix spacing

* make the linter happy

* change prevote log message

* update prevote nil debug line

* update enterPrevote comment

* lint

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* add english description of alg rules

* Update internal/consensus/state.go

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>

* comment fixes from review

* fix comment

* fix comment

Co-authored-by: Dev Ojha <ValarDragon@users.noreply.github.com>
2021-10-23 16:12:11 +02:00
9 changed files with 649 additions and 503 deletions

View File

@@ -1265,8 +1265,11 @@ func (cs *State) createProposalBlock() (block *types.Block, blockParts *types.Pa
// Enter: `timeoutPropose` after entering Propose.
// Enter: proposal block and POL is ready.
// Prevote for LockedBlock if we're locked, or ProposalBlock if valid.
// Otherwise vote nil.
// If we received a valid proposal within this round and we are not locked on a block,
// we will prevote for block.
// Otherwise, if we receive a valid proposal that matches the block we are
// locked on or matches a block that received a POL in a round later than our
// locked round, prevote for the proposal, otherwise vote nil.
func (cs *State) enterPrevote(height int64, round int32) {
logger := cs.Logger.With("height", height, "round", round)
@@ -1296,14 +1299,7 @@ func (cs *State) enterPrevote(height int64, round int32) {
func (cs *State) defaultDoPrevote(height int64, round int32) {
logger := cs.Logger.With("height", height, "round", round)
// If a block is locked, prevote that.
if cs.LockedBlock != nil {
logger.Debug("prevote step; already locked on a block; prevoting locked block")
cs.signAddVote(tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header())
return
}
// If ProposalBlock is nil, prevote nil.
// We did not receive a proposal within this round. (and thus executing this from a timeout)
if cs.ProposalBlock == nil {
logger.Debug("prevote step: ProposalBlock is nil")
cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
@@ -1319,11 +1315,67 @@ func (cs *State) defaultDoPrevote(height int64, round int32) {
return
}
// Prevote cs.ProposalBlock
// NOTE: the proposal signature is validated when it is received,
// and the proposal block parts are validated as they are received (against the merkle hash in the proposal)
logger.Debug("prevote step: ProposalBlock is valid")
cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
/*
22: upon <PROPOSAL, h_p, round_p, v, 1> from proposer(h_p, round_p) while step_p = propose do
23: if valid(v) && (lockedRound_p = 1 || lockedValue_p = v) then
24: broadcast <PREVOTE, h_p, round_p, id(v)>
Here, cs.Proposal.POLRound corresponds to the -1 in the above algorithm rule.
This means that the proposer is producing a new proposal that has not previously
seen a 2/3 majority by the network.
If we have already locked on a different value that is different from the proposed value,
we prevote nil since we are locked on a different value. Otherwise, if we're not locked on a block
or the proposal matches our locked block, we prevote the proposal.
*/
if cs.Proposal.POLRound == -1 {
if cs.LockedRound == -1 {
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
}
if cs.ProposalBlock.HashesTo(cs.LockedBlock.Hash()) {
logger.Debug("prevote step: ProposalBlock is valid and matches our locked block; prevoting the proposal")
cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
return
}
}
/*
28: upon <PROPOSAL, h_p, round_p, v, v_r> from proposer(h_p, round_p) AND 2f + 1 <PREVOTE, h_p, v_r, id(v)> while
step_p = propose && (v_r ≥ 0 && v_r < round_p) do
29: if valid(v) && (lockedRound_p ≤ v_r || lockedValue_p = v) then
30: broadcast <PREVOTE, h_p, round_p, id(v)>
This rule is a bit confusing but breaks down as follows:
If we see a proposal in the current round for value 'v' that lists its valid round as 'v_r'
AND this validator saw a 2/3 majority of the voting power prevote 'v' in round 'v_r', then we will
issue a prevote for 'v' in this round if 'v' is valid and either matches our locked value OR
'v_r' is a round greater than or equal to our current locked round.
'v_r' can be a round greater than to our current locked round if a 2/3 majority of
the network prevoted a value in round 'v_r' but we did not lock on it, possibly because we
missed the proposal in round 'v_r'.
*/
blockID, ok := cs.Votes.Prevotes(cs.Proposal.POLRound).TwoThirdsMajority()
if ok && cs.ProposalBlock.HashesTo(blockID.Hash) && cs.Proposal.POLRound >= 0 && cs.Proposal.POLRound < cs.Round {
if cs.LockedRound <= cs.Proposal.POLRound {
logger.Debug("prevote step: ProposalBlock is valid and received a 2/3" +
"majority in a round later than the locked round; prevoting the proposal")
cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
return
}
if cs.ProposalBlock.HashesTo(cs.LockedBlock.Hash()) {
logger.Debug("prevote step: ProposalBlock is valid and matches our locked block; prevoting the proposal")
cs.signAddVote(tmproto.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header())
return
}
}
logger.Debug("prevote step: ProposalBlock is valid but was not our locked block or" +
"did not receive a more recent majority; prevoting nil")
cs.signAddVote(tmproto.PrevoteType, nil, types.PartSetHeader{})
}
// Enter: any +2/3 prevotes at next round.
@@ -2360,3 +2412,13 @@ func repairWalFile(src, dst string) error {
return nil
}
// proposalTimeout Header.Time + 2*ACCURACY + MSGDELAY
func proposalWaitingTime(lt tmtime.Source, h types.Header, tp types.TimestampParams) time.Duration {
t := lt.Now()
wt := h.Time.Add(2 * tp.Accuracy).Add(tp.MsgDelay)
if t.After(wt) {
return 0
}
return wt.Sub(t)
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
)
@@ -46,6 +47,7 @@ x * TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock - 4 vals, 1 misses
x * TestStateLock_MissingProposalWhenPOLSeenDoesNotUnlock - 4 vals, 1 misses proposal but sees POL.
x * TestStateLock_POLSafety1 - 4 vals. We shouldn't change lock based on polka at earlier round
x * TestStateLock_POLSafety2 - 4 vals. After unlocking, we shouldn't relock based on polka at earlier round
x_*_TestState_PrevotePOLFromPreviousRound 4 vals, prevote a proposal if a POL was seen for it in a previous round.
* TestNetworkLock - once +1/3 precommits, network should be locked
* TestNetworkLockPOL - once +1/3 precommits, the block with more recent polka is committed
SlashingSuite
@@ -459,8 +461,8 @@ func TestStateLock_NoPOL(t *testing.T) {
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, thePartSetHeader, vs2)
ensurePrevote(t, voteCh, height, round) // prevote
ensurePrecommit(t, voteCh, height, round) // precommit
// the proposed block should now be locked and our precommit added
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
// we should now be stuck in limbo forever, waiting for more precommits
@@ -492,13 +494,12 @@ func TestStateLock_NoPOL(t *testing.T) {
rs := cs1.GetRoundState()
if rs.ProposalBlock != nil {
panic("Expected proposal block to be nil")
t.Fatal("Expected proposal block to be nil")
}
// wait to finish prevote
// we should have prevoted nil since we did not see a proposal in the round.
ensurePrevote(t, voteCh, height, round)
// we should have prevoted our locked block
validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash())
validatePrevote(t, cs1, round, vss[0], nil)
// add a conflicting prevote from the other validator
signAddVotes(config, cs1, tmproto.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2)
@@ -508,9 +509,9 @@ func TestStateLock_NoPOL(t *testing.T) {
// and then prevote wait, which should timeout. then wait for precommit
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds())
ensurePrecommit(t, voteCh, height, round) // precommit
// the proposed block should still be locked and our precommit added
// we should precommit nil and be locked on the proposal
// the proposed block should still be locked block.
// we should precommit nil and be locked on the proposal.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
// add conflicting precommit from vs2
@@ -586,9 +587,10 @@ func TestStateLock_NoPOL(t *testing.T) {
}
ensureNewProposal(t, proposalCh, height, round)
ensurePrevote(t, voteCh, height, round) // prevote
// prevote for locked block (not proposal)
validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash())
// prevote for nil since we did not see a proposal for our locked block in the round.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, 3, vss[0], nil)
// prevote for proposed block
signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2)
@@ -693,11 +695,9 @@ func TestStateLock_POLUpdateLock(t *testing.T) {
// ensure that the validator receives the proposal.
ensureNewProposal(t, proposalCh, height, round)
// Prevote our locked block.
// TODO: Ensure we prevote for the proposal if it is valid and from a round greater than
// the valid round: https://github.com/tendermint/tendermint/issues/6850.
// Prevote our nil since the proposal does not match our locked block.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], theBlockHash)
validatePrevote(t, cs1, round, vss[0], nil)
// Add prevotes from the remainder of the validators for the new locked block.
signAddVotes(config, cs1, tmproto.PrevoteType, propBlockR1Hash, propBlockR1Parts.Header(), vs2, vs3, vs4)
@@ -795,9 +795,7 @@ func TestStateLock_POLRelock(t *testing.T) {
// ensure that the validator receives the proposal.
ensureNewProposal(t, proposalCh, height, round)
// Prevote our locked block.
// TODO: Ensure we prevote for the proposal if it is valid and from a round greater than
// the valid round: https://github.com/tendermint/tendermint/issues/6850.
// Prevote our locked block since it matches the propsal seen in this round.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], theBlockHash)
@@ -813,6 +811,178 @@ func TestStateLock_POLRelock(t *testing.T) {
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
}
// TestStateLock_PrevoteNilWhenLockedAndMissProposal tests that a validator prevotes nil
// if it is locked on a block and misses the proposal in a round.
func TestStateLock_PrevoteNilWhenLockedAndMissProposal(t *testing.T) {
config := configSetup(t)
cs1, vss := randState(config, 4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
pv1, err := cs1.privValidator.GetPubKey(context.Background())
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(cs1, addr)
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
/*
Round 0:
cs1 creates a proposal for block B.
Send a prevote for B from each of the validators to cs1.
Send a precommit for nil from all of the validators to cs1.
This ensures that cs1 will lock on B in this round but not precommit it.
*/
t.Log("### Starting Round 0")
startTestRound(cs1, height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
theBlockHash := rs.ProposalBlock.Hash()
theBlockParts := rs.ProposalBlockParts
ensurePrevote(t, voteCh, height, round)
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts.Header(), vs2, vs3, vs4)
// check that the validator generates a Lock event.
ensureLock(t, lockCh, height, round)
// the proposed block should now be locked and our precommit added.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
// add precommits from the rest of the validators.
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
// timeout to new round.
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
/*
Round 1:
Send a prevote for nil from each of the validators to cs1.
Send a precommit for nil from all of the validtors to cs1.
Check that cs1 prevotes nil instead of its locked block, but ensure
that it maintains its locked block.
*/
t.Log("### Starting Round 1")
incrementRound(vs2, vs3, vs4)
round++
ensureNewRound(t, newRoundCh, height, round)
// Prevote our nil.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], nil)
// Add prevotes from the remainder of the validators nil.
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
ensurePrecommit(t, voteCh, height, round)
// We should now be locked on the same block but with an updated locked round.
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
}
// TestStateLock_PrevoteNilWhenLockedAndMissProposal tests that a validator prevotes nil
// if it is locked on a block and misses the proposal in a round.
func TestStateLock_PrevoteNilWhenLockedAndDifferentProposal(t *testing.T) {
config := configSetup(t)
/*
All of the assertions in this test occur on the `cs1` validator.
The test sends signed votes from the other validators to cs1 and
cs1's state is then examined to verify that it now matches the expected
state.
*/
cs1, vss := randState(config, 4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
pv1, err := cs1.privValidator.GetPubKey(context.Background())
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(cs1, addr)
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
/*
Round 0:
cs1 creates a proposal for block B.
Send a prevote for B from each of the validators to cs1.
Send a precommit for nil from all of the validators to cs1.
This ensures that cs1 will lock on B in this round but not precommit it.
*/
t.Log("### Starting Round 0")
startTestRound(cs1, height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
theBlockHash := rs.ProposalBlock.Hash()
theBlockParts := rs.ProposalBlockParts
ensurePrevote(t, voteCh, height, round)
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts.Header(), vs2, vs3, vs4)
// check that the validator generates a Lock event.
ensureLock(t, lockCh, height, round)
// the proposed block should now be locked and our precommit added.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
// add precommits from the rest of the validators.
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
// timeout to new round.
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
/*
Round 1:
Create a proposal for a new block.
Send a prevote for nil from each of the validators to cs1.
Send a precommit for nil from all of the validtors to cs1.
Check that cs1 prevotes nil instead of its locked block, but ensure
that it maintains its locked block.
*/
t.Log("### Starting Round 1")
incrementRound(vs2, vs3, vs4)
round++
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
propR1, propBlockR1 := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
propBlockR1Parts := propBlockR1.MakePartSet(types.BlockPartSizeBytes)
propBlockR1Hash := propBlockR1.Hash()
require.NotEqual(t, propBlockR1Hash, theBlockHash)
if err := cs1.SetProposalAndBlock(propR1, propBlockR1, propBlockR1Parts, "some peer"); err != nil {
t.Fatal(err)
}
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
// Prevote our nil.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], nil)
// Add prevotes from the remainder of the validators for nil.
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
// We should now be locked on the same block but prevote nil.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
}
// TestStateLock_POLDoesNotUnlock tests that a validator maintains its locked block
// despite receiving +2/3 nil prevotes and nil precommits from other validators.
// Tendermint used to 'unlock' its locked block when greater than 2/3 prevotes
@@ -892,8 +1062,10 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
t.Log("#### ONTO ROUND 1")
round++
incrementRound(vs2, vs3, vs4)
prop, propBlock := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round)
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
propBlockParts := propBlock.MakePartSet(types.BlockPartSizeBytes)
require.NotEqual(t, propBlock.Hash(), theBlockHash)
if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, ""); err != nil {
t.Fatal(err)
}
@@ -902,12 +1074,10 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
ensureNewProposal(t, proposalCh, height, round)
// prevote for the locked block. We do not currently prevote for the
// proposal.
// TODO: do not prevote the locked block if it does not match the proposal.
// (https://github.com/tendermint/tendermint/issues/6850)
// Prevote for nil since the proposed block does not match our locked block.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], theBlockHash)
validatePrevote(t, cs1, round, vss[0], nil)
// add >2/3 prevotes for nil from all other validators
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
@@ -928,7 +1098,8 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
t.Log("#### ONTO ROUND 2")
round++
incrementRound(vs2, vs3, vs4)
prop, propBlock = decideProposal(t, cs1, vs3, vs3.Height, vs3.Round)
cs3 := newState(cs1.state, vs2, kvstore.NewApplication())
prop, propBlock = decideProposal(t, cs3, vs3, vs3.Height, vs3.Round)
propBlockParts = propBlock.MakePartSet(types.BlockPartSizeBytes)
if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, ""); err != nil {
t.Fatal(err)
@@ -938,8 +1109,9 @@ func TestStateLock_POLDoesNotUnlock(t *testing.T) {
ensureNewProposal(t, proposalCh, height, round)
// Prevote for nil since the proposal does not match our locked block.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], theBlockHash)
validatePrevote(t, cs1, round, vss[0], nil)
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
@@ -1021,9 +1193,9 @@ func TestStateLock_MissingProposalWhenPOLSeenDoesNotUpdateLock(t *testing.T) {
ensureNewRound(t, newRoundCh, height, round)
// go to prevote, node should prevote for locked block (not the new proposal) - this is relocking
// prevote for nil since the proposal was not seen.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], firstBlockHash)
validatePrevote(t, cs1, round, vss[0], nil)
// now lets add prevotes from everyone else for the new block
signAddVotes(config, cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4)
@@ -1144,8 +1316,6 @@ func TestStateLock_POLSafety1(t *testing.T) {
propBlock.Hash(), propBlock.MakePartSet(partSize).Header(),
vs2, vs3, vs4)
t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash()))
// we do see them precommit nil
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
@@ -1154,14 +1324,13 @@ func TestStateLock_POLSafety1(t *testing.T) {
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
t.Log("### ONTO ROUND 1")
prop, propBlock := decideProposal(t, cs1, vs2, vs2.Height, vs2.Round+1)
incrementRound(vs2, vs3, vs4)
round++ // moving to the next round
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
prop, propBlock := decideProposal(t, cs2, vs2, vs2.Height, vs2.Round)
propBlockHash := propBlock.Hash()
propBlockParts := propBlock.MakePartSet(partSize)
incrementRound(vs2, vs3, vs4)
round++ // moving to the next round
ensureNewRound(t, newRoundCh, height, round)
//XXX: this isnt guaranteed to get there before the timeoutPropose ...
@@ -1178,9 +1347,8 @@ func TestStateLock_POLSafety1(t *testing.T) {
rs = cs1.GetRoundState()
if rs.LockedBlock != nil {
panic("we should not be locked!")
t.Fatalf("was not expected to be locked on a block")
}
t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash))
// go to prevote, prevote for proposal block
ensurePrevote(t, voteCh, height, round)
@@ -1212,8 +1380,8 @@ func TestStateLock_POLSafety1(t *testing.T) {
// finish prevote
ensurePrevote(t, voteCh, height, round)
// we should prevote what we're locked on
validatePrevote(t, cs1, round, vss[0], propBlockHash)
// we should prevote for nil
validatePrevote(t, cs1, round, vss[0], nil)
newStepCh := subscribe(cs1.eventBus, types.EventQueryNewRoundStep)
@@ -1221,8 +1389,6 @@ func TestStateLock_POLSafety1(t *testing.T) {
// add prevotes from the earlier round
addVotes(cs1, prevotes...)
t.Log("Done adding prevotes!")
ensureNoNewRoundStep(t, newStepCh)
}
@@ -1325,6 +1491,142 @@ func TestStateLock_POLSafety2(t *testing.T) {
}
// TestState_PrevotePOLFromPreviousRound tests that a validator will prevote
// for a block if it is locked on a different block but saw a POL for the block
// it is not locked on in a previous round.
func TestState_PrevotePOLFromPreviousRound(t *testing.T) {
config := configSetup(t)
cs1, vss := randState(config, 4)
vs2, vs3, vs4 := vss[1], vss[2], vss[3]
height, round := cs1.Height, cs1.Round
partSize := types.BlockPartSizeBytes
timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait)
proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal)
pv1, err := cs1.privValidator.GetPubKey(context.Background())
require.NoError(t, err)
addr := pv1.Address()
voteCh := subscribeToVoter(cs1, addr)
lockCh := subscribe(cs1.eventBus, types.EventQueryLock)
newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound)
/*
Round 0:
cs1 creates a proposal for block B.
Send a prevote for B from each of the validators to cs1.
Send a precommit for nil from all of the validators to cs1.
This ensures that cs1 will lock on B in this round but not precommit it.
*/
t.Log("### Starting Round 0")
startTestRound(cs1, height, round)
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
rs := cs1.GetRoundState()
theBlockHash := rs.ProposalBlock.Hash()
theBlockParts := rs.ProposalBlockParts.Header()
ensurePrevote(t, voteCh, height, round)
signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4)
// check that the validator generates a Lock event.
ensureLock(t, lockCh, height, round)
// the proposed block should now be locked and our precommit added.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash)
// add precommits from the rest of the validators.
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
// timeout to new round.
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
/*
Round 1:
Create a block, D but do not send a proposal for it to cs1.
Send a prevote for D from each of the validators to cs1 so that cs1 sees a POL.
Send a precommit for nil from all of the validtors to cs1.
cs1 has now seen greater than 2/3 of the voting power prevote D in this round
but cs1 did not see the proposal for D in this round so it will not prevote or precommit it.
*/
t.Log("### Starting Round 1")
incrementRound(vs2, vs3, vs4)
round++
// Generate a new proposal block.
cs2 := newState(cs1.state, vs2, kvstore.NewApplication())
cs2.ValidRound = 1
propR1, propBlockR1 := decideProposal(t, cs2, vs2, vs2.Height, round)
t.Log(propR1.POLRound)
propBlockR1Parts := propBlockR1.MakePartSet(partSize)
propBlockR1Hash := propBlockR1.Hash()
require.NotEqual(t, propBlockR1Hash, theBlockHash)
ensureNewRound(t, newRoundCh, height, round)
signAddVotes(config, cs1, tmproto.PrevoteType, propBlockR1Hash, propBlockR1Parts.Header(), vs2, vs3, vs4)
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], nil)
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
ensurePrecommit(t, voteCh, height, round)
// timeout to new round.
ensureNewTimeout(t, timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds())
/*
Round 2:
Create a new proposal for D, the same block from Round 1.
cs1 already saw greater than 2/3 of the voting power on the network vote for
D in a previous round, so it should prevote D once it receives a proposal for it.
cs1 does not need to receive prevotes from other validators before the proposal
in this round. It will still prevote the block.
Send cs1 prevotes for nil and check that it still prevotes its locked block
and not the block that it prevoted.
*/
t.Log("### Starting Round 2")
incrementRound(vs2, vs3, vs4)
round++
propBlockID := types.BlockID{Hash: propBlockR1Hash, PartSetHeader: propBlockR1Parts.Header()}
propR2 := types.NewProposal(height, round, 1, propBlockID)
p := propR2.ToProto()
if err := vs3.SignProposal(context.Background(), cs1.state.ChainID, p); err != nil {
t.Fatalf("error signing proposal: %s", err)
}
propR2.Signature = p.Signature
// cs1 receives a proposal for D, the block that received a POL in round 1.
if err := cs1.SetProposalAndBlock(propR2, propBlockR1, propBlockR1Parts, ""); err != nil {
t.Fatal(err)
}
ensureNewRound(t, newRoundCh, height, round)
ensureNewProposal(t, proposalCh, height, round)
// We should now prevote this block, despite being locked on the block from
// round 0.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], propBlockR1Hash)
signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
// cs1 did not receive a POL within this round, so it should remain locked
// on the block from round 0.
ensurePrecommit(t, voteCh, height, round)
validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash)
}
// 4 vals.
// polka P0 at R0 for B0. We lock B0 on P0 at R0.
@@ -1381,8 +1683,9 @@ func TestProposeValidBlock(t *testing.T) {
// timeout of propose
ensureNewTimeout(t, timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds())
// We did not see a valid proposal within this round, so prevote nil.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], propBlockHash)
validatePrevote(t, cs1, round, vss[0], nil)
signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4)
@@ -2014,17 +2317,16 @@ func TestStateHalt1(t *testing.T) {
round++ // moving to the next round
ensureNewRound(t, newRoundCh, height, round)
rs = cs1.GetRoundState()
t.Log("### ONTO ROUND 1")
/*Round2
// we timeout and prevote our lock
// we timeout and prevote
// a polka happened but we didn't see it!
*/
// go to prevote, prevote for locked block
// prevote for nil since we did not receive a proposal in this round.
ensurePrevote(t, voteCh, height, round)
validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash())
validatePrevote(t, cs1, round, vss[0], nil)
// now we receive the precommit from the previous round
addVotes(cs1, precommit4)
@@ -2139,6 +2441,49 @@ func TestSignSameVoteTwice(t *testing.T) {
require.Equal(t, vote, vote2)
}
func TestProposalTimeout(t *testing.T) {
genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
require.NoError(t, err)
testCases := []struct {
name string
blockTime time.Time
localTime time.Time
accuracy time.Duration
msgDelay time.Duration
expectedDuration time.Duration
}{
{
// this test makes no sense and needs completion
name: "waiting time in the past",
blockTime: genesisTime.Add(5 * time.Millisecond),
localTime: genesisTime.Add(3 * time.Nanosecond),
accuracy: time.Nanosecond * 2,
msgDelay: time.Nanosecond,
expectedDuration: time.Nanosecond,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
b := types.Block{
Header: types.Header{
Time: testCase.blockTime,
},
}
mockSource := new(tmtimemocks.Source)
mockSource.On("Now").Return(testCase.localTime)
tp := types.TimestampParams{
Accuracy: testCase.accuracy,
MsgDelay: testCase.msgDelay,
}
ti := proposalWaitingTime(mockSource, b.Header, tp)
assert.Equal(t, testCase.expectedTime, ti)
})
}
}
// subscribe subscribes test client to the given query and returns a channel with cap = 1.
func subscribe(eventBus *types.EventBus, q tmpubsub.Query) <-chan tmpubsub.Message {
sub, err := eventBus.Subscribe(context.Background(), testSubscriber, q)

28
libs/time/mocks/source.go Normal file
View File

@@ -0,0 +1,28 @@
// Code generated by mockery. DO NOT EDIT.
package mocks
import (
time "time"
mock "github.com/stretchr/testify/mock"
)
// Source is an autogenerated mock type for the Source type
type Source struct {
mock.Mock
}
// Now provides a mock function with given fields:
func (_m *Source) Now() time.Time {
ret := _m.Called()
var r0 time.Time
if rf, ok := ret.Get(0).(func() time.Time); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(time.Time)
}
return r0
}

View File

@@ -15,3 +15,17 @@ func Now() time.Time {
func Canonical(t time.Time) time.Time {
return t.Round(0).UTC()
}
//go:generate ../../scripts/mockery_generate.sh Source
// Source is an interface that defines a way to fetch the current time.
type Source interface {
Now() time.Time
}
// DefaultSource implements the Source interface using the system clock provided by the standard library.
type DefaultSource struct{}
func (DefaultSource) Now() time.Time {
return Now()
}

View File

@@ -34,7 +34,6 @@ type ConsensusParams struct {
Evidence *EvidenceParams `protobuf:"bytes,2,opt,name=evidence,proto3" json:"evidence,omitempty"`
Validator *ValidatorParams `protobuf:"bytes,3,opt,name=validator,proto3" json:"validator,omitempty"`
Version *VersionParams `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
Timestamp *TimestampParams `protobuf:"bytes,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
}
func (m *ConsensusParams) Reset() { *m = ConsensusParams{} }
@@ -98,13 +97,6 @@ func (m *ConsensusParams) GetVersion() *VersionParams {
return nil
}
func (m *ConsensusParams) GetTimestamp() *TimestampParams {
if m != nil {
return m.Timestamp
}
return nil
}
// BlockParams contains limits on the block size.
type BlockParams struct {
// Max block size, in bytes.
@@ -326,66 +318,6 @@ func (m *VersionParams) GetAppVersion() uint64 {
return 0
}
type TimestampParams struct {
Accuracy time.Duration `protobuf:"bytes,1,opt,name=accuracy,proto3,stdduration" json:"accuracy"`
Precision time.Duration `protobuf:"bytes,2,opt,name=precision,proto3,stdduration" json:"precision"`
MessageDelay time.Duration `protobuf:"bytes,3,opt,name=message_delay,json=messageDelay,proto3,stdduration" json:"message_delay"`
}
func (m *TimestampParams) Reset() { *m = TimestampParams{} }
func (m *TimestampParams) String() string { return proto.CompactTextString(m) }
func (*TimestampParams) ProtoMessage() {}
func (*TimestampParams) Descriptor() ([]byte, []int) {
return fileDescriptor_e12598271a686f57, []int{5}
}
func (m *TimestampParams) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *TimestampParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_TimestampParams.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *TimestampParams) XXX_Merge(src proto.Message) {
xxx_messageInfo_TimestampParams.Merge(m, src)
}
func (m *TimestampParams) XXX_Size() int {
return m.Size()
}
func (m *TimestampParams) XXX_DiscardUnknown() {
xxx_messageInfo_TimestampParams.DiscardUnknown(m)
}
var xxx_messageInfo_TimestampParams proto.InternalMessageInfo
func (m *TimestampParams) GetAccuracy() time.Duration {
if m != nil {
return m.Accuracy
}
return 0
}
func (m *TimestampParams) GetPrecision() time.Duration {
if m != nil {
return m.Precision
}
return 0
}
func (m *TimestampParams) GetMessageDelay() time.Duration {
if m != nil {
return m.MessageDelay
}
return 0
}
// HashedParams is a subset of ConsensusParams.
//
// It is hashed into the Header.ConsensusHash.
@@ -398,7 +330,7 @@ func (m *HashedParams) Reset() { *m = HashedParams{} }
func (m *HashedParams) String() string { return proto.CompactTextString(m) }
func (*HashedParams) ProtoMessage() {}
func (*HashedParams) Descriptor() ([]byte, []int) {
return fileDescriptor_e12598271a686f57, []int{6}
return fileDescriptor_e12598271a686f57, []int{5}
}
func (m *HashedParams) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@@ -447,51 +379,45 @@ func init() {
proto.RegisterType((*EvidenceParams)(nil), "tendermint.types.EvidenceParams")
proto.RegisterType((*ValidatorParams)(nil), "tendermint.types.ValidatorParams")
proto.RegisterType((*VersionParams)(nil), "tendermint.types.VersionParams")
proto.RegisterType((*TimestampParams)(nil), "tendermint.types.TimestampParams")
proto.RegisterType((*HashedParams)(nil), "tendermint.types.HashedParams")
}
func init() { proto.RegisterFile("tendermint/types/params.proto", fileDescriptor_e12598271a686f57) }
var fileDescriptor_e12598271a686f57 = []byte{
// 577 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x6b, 0xd4, 0x40,
0x18, 0xc6, 0x37, 0xdd, 0xfe, 0xd9, 0x7d, 0xb7, 0xdb, 0x2d, 0x83, 0x60, 0xac, 0x34, 0x5b, 0x73,
0x90, 0x82, 0x90, 0x88, 0x45, 0x44, 0x10, 0x4a, 0xb7, 0x95, 0x16, 0xa4, 0x22, 0xa1, 0x7a, 0xe8,
0x25, 0x4c, 0xb2, 0x63, 0x1a, 0xba, 0x93, 0x19, 0x32, 0x49, 0xd9, 0x7c, 0x0b, 0x8f, 0x7e, 0x04,
0xfd, 0x18, 0xde, 0x7a, 0xec, 0xd1, 0x93, 0x95, 0xed, 0x17, 0x91, 0x4c, 0x66, 0x36, 0xdd, 0xad,
0x42, 0xbd, 0x25, 0xf3, 0x3e, 0xbf, 0x79, 0x79, 0x9f, 0xf7, 0x61, 0x60, 0x33, 0x23, 0xc9, 0x90,
0xa4, 0x34, 0x4e, 0x32, 0x37, 0x2b, 0x38, 0x11, 0x2e, 0xc7, 0x29, 0xa6, 0xc2, 0xe1, 0x29, 0xcb,
0x18, 0x5a, 0xaf, 0xcb, 0x8e, 0x2c, 0x6f, 0x3c, 0x88, 0x58, 0xc4, 0x64, 0xd1, 0x2d, 0xbf, 0x2a,
0xdd, 0x86, 0x15, 0x31, 0x16, 0x8d, 0x88, 0x2b, 0xff, 0x82, 0xfc, 0xb3, 0x3b, 0xcc, 0x53, 0x9c,
0xc5, 0x2c, 0xa9, 0xea, 0xf6, 0x8f, 0x05, 0xe8, 0xed, 0xb3, 0x44, 0x90, 0x44, 0xe4, 0xe2, 0x83,
0xec, 0x80, 0x76, 0x60, 0x29, 0x18, 0xb1, 0xf0, 0xdc, 0x34, 0xb6, 0x8c, 0xed, 0xce, 0x8b, 0x4d,
0x67, 0xbe, 0x97, 0x33, 0x28, 0xcb, 0x95, 0xda, 0xab, 0xb4, 0xe8, 0x0d, 0xb4, 0xc8, 0x45, 0x3c,
0x24, 0x49, 0x48, 0xcc, 0x05, 0xc9, 0x6d, 0xdd, 0xe5, 0xde, 0x2a, 0x85, 0x42, 0xa7, 0x04, 0xda,
0x85, 0xf6, 0x05, 0x1e, 0xc5, 0x43, 0x9c, 0xb1, 0xd4, 0x6c, 0x4a, 0xfc, 0xc9, 0x5d, 0xfc, 0x93,
0x96, 0x28, 0xbe, 0x66, 0xd0, 0x6b, 0x58, 0xb9, 0x20, 0xa9, 0x88, 0x59, 0x62, 0x2e, 0x4a, 0xbc,
0xff, 0x17, 0xbc, 0x12, 0x28, 0x58, 0xeb, 0xcb, 0xde, 0x59, 0x4c, 0x89, 0xc8, 0x30, 0xe5, 0xe6,
0xd2, 0xbf, 0x7a, 0x9f, 0x68, 0x89, 0xee, 0x3d, 0x65, 0xec, 0x7d, 0xe8, 0xdc, 0x32, 0x04, 0x3d,
0x86, 0x36, 0xc5, 0x63, 0x3f, 0x28, 0x32, 0x22, 0xa4, 0x85, 0x4d, 0xaf, 0x45, 0xf1, 0x78, 0x50,
0xfe, 0xa3, 0x87, 0xb0, 0x52, 0x16, 0x23, 0x2c, 0xa4, 0x4b, 0x4d, 0x6f, 0x99, 0xe2, 0xf1, 0x21,
0x16, 0xf6, 0x77, 0x03, 0xd6, 0x66, 0xed, 0x41, 0xcf, 0x00, 0x95, 0x5a, 0x1c, 0x11, 0x3f, 0xc9,
0xa9, 0x2f, 0x7d, 0xd6, 0x37, 0xf6, 0x28, 0x1e, 0xef, 0x45, 0xe4, 0x7d, 0x4e, 0x65, 0x6b, 0x81,
0x8e, 0x61, 0x5d, 0x8b, 0xf5, 0x8a, 0xd5, 0x1e, 0x1e, 0x39, 0x55, 0x06, 0x1c, 0x9d, 0x01, 0xe7,
0x40, 0x09, 0x06, 0xad, 0xcb, 0x5f, 0xfd, 0xc6, 0xd7, 0xeb, 0xbe, 0xe1, 0xad, 0x55, 0xf7, 0xe9,
0xca, 0xec, 0x10, 0xcd, 0xd9, 0x21, 0xec, 0x97, 0xd0, 0x9b, 0x5b, 0x05, 0xb2, 0xa1, 0xcb, 0xf3,
0xc0, 0x3f, 0x27, 0x85, 0x2f, 0xfd, 0x32, 0x8d, 0xad, 0xe6, 0x76, 0xdb, 0xeb, 0xf0, 0x3c, 0x78,
0x47, 0x8a, 0x93, 0xf2, 0xc8, 0x7e, 0x0e, 0xdd, 0x99, 0x15, 0xa0, 0x3e, 0x74, 0x30, 0xe7, 0xbe,
0x5e, 0x5c, 0x39, 0xd9, 0xa2, 0x07, 0x98, 0x73, 0x25, 0xb3, 0xaf, 0x0d, 0xe8, 0xcd, 0x19, 0x8f,
0x76, 0xa1, 0x85, 0xc3, 0x30, 0x4f, 0x71, 0x58, 0xa8, 0x80, 0xde, 0x6b, 0xc0, 0x29, 0x84, 0xf6,
0xa0, 0xcd, 0x53, 0x12, 0xc6, 0xe2, 0x3f, 0x2d, 0xaa, 0x29, 0x74, 0x04, 0x5d, 0x4a, 0x84, 0x90,
0x66, 0x93, 0x11, 0x2e, 0x54, 0x64, 0xef, 0x75, 0xcd, 0xaa, 0x22, 0x0f, 0x4a, 0xd0, 0x3e, 0x85,
0xd5, 0x23, 0x2c, 0xce, 0xc8, 0x50, 0x4d, 0xf7, 0x14, 0x7a, 0x72, 0xcf, 0xfe, 0x7c, 0x84, 0xba,
0xf2, 0xf8, 0x58, 0xe7, 0xc8, 0x86, 0x6e, 0xad, 0xab, 0xd3, 0xd4, 0xd1, 0xaa, 0x43, 0x2c, 0x06,
0x1f, 0xbf, 0x4d, 0x2c, 0xe3, 0x72, 0x62, 0x19, 0x57, 0x13, 0xcb, 0xf8, 0x3d, 0xb1, 0x8c, 0x2f,
0x37, 0x56, 0xe3, 0xea, 0xc6, 0x6a, 0xfc, 0xbc, 0xb1, 0x1a, 0xa7, 0xaf, 0xa2, 0x38, 0x3b, 0xcb,
0x03, 0x27, 0x64, 0xd4, 0xbd, 0xfd, 0xd6, 0xd4, 0x9f, 0xd5, 0x63, 0x32, 0xff, 0x0e, 0x05, 0xcb,
0xf2, 0x7c, 0xe7, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x64, 0xc1, 0x5d, 0xa2, 0x04, 0x00,
0x00,
// 498 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0xc1, 0x6a, 0xd4, 0x40,
0x1c, 0xc6, 0x77, 0x9a, 0xda, 0xee, 0xfe, 0xe3, 0x76, 0xcb, 0x20, 0x18, 0x2b, 0xcd, 0xae, 0x39,
0x48, 0x41, 0x48, 0xc4, 0x22, 0x22, 0x08, 0xe2, 0x56, 0xa9, 0x20, 0x15, 0x09, 0xea, 0xa1, 0x97,
0x30, 0xd9, 0x8c, 0x69, 0xe8, 0x4e, 0x66, 0xc8, 0x24, 0xcb, 0xee, 0xcd, 0x47, 0xf0, 0xe8, 0x23,
0xe8, 0x9b, 0xf4, 0xd8, 0xa3, 0x27, 0x95, 0xdd, 0x17, 0x91, 0x4c, 0x32, 0xa6, 0x9b, 0xf6, 0x36,
0x33, 0xdf, 0xef, 0x9b, 0xe1, 0xfb, 0x86, 0x3f, 0xec, 0xe7, 0x34, 0x8d, 0x68, 0xc6, 0x92, 0x34,
0xf7, 0xf2, 0x85, 0xa0, 0xd2, 0x13, 0x24, 0x23, 0x4c, 0xba, 0x22, 0xe3, 0x39, 0xc7, 0xbb, 0x8d,
0xec, 0x2a, 0x79, 0xef, 0x4e, 0xcc, 0x63, 0xae, 0x44, 0xaf, 0x5c, 0x55, 0xdc, 0x9e, 0x1d, 0x73,
0x1e, 0x4f, 0xa9, 0xa7, 0x76, 0x61, 0xf1, 0xc5, 0x8b, 0x8a, 0x8c, 0xe4, 0x09, 0x4f, 0x2b, 0xdd,
0xf9, 0xba, 0x01, 0x83, 0x23, 0x9e, 0x4a, 0x9a, 0xca, 0x42, 0x7e, 0x50, 0x2f, 0xe0, 0x43, 0xb8,
0x15, 0x4e, 0xf9, 0xe4, 0xdc, 0x42, 0x23, 0x74, 0x60, 0x3e, 0xd9, 0x77, 0xdb, 0x6f, 0xb9, 0xe3,
0x52, 0xae, 0x68, 0xbf, 0x62, 0xf1, 0x0b, 0xe8, 0xd2, 0x59, 0x12, 0xd1, 0x74, 0x42, 0xad, 0x0d,
0xe5, 0x1b, 0x5d, 0xf7, 0xbd, 0xa9, 0x89, 0xda, 0xfa, 0xdf, 0x81, 0x5f, 0x42, 0x6f, 0x46, 0xa6,
0x49, 0x44, 0x72, 0x9e, 0x59, 0x86, 0xb2, 0x3f, 0xb8, 0x6e, 0xff, 0xac, 0x91, 0xda, 0xdf, 0x78,
0xf0, 0x73, 0xd8, 0x9e, 0xd1, 0x4c, 0x26, 0x3c, 0xb5, 0x36, 0x95, 0x7d, 0x78, 0x83, 0xbd, 0x02,
0x6a, 0xb3, 0xe6, 0x9d, 0x23, 0x30, 0xaf, 0xe4, 0xc1, 0xf7, 0xa1, 0xc7, 0xc8, 0x3c, 0x08, 0x17,
0x39, 0x95, 0xaa, 0x01, 0xc3, 0xef, 0x32, 0x32, 0x1f, 0x97, 0x7b, 0x7c, 0x17, 0xb6, 0x4b, 0x31,
0x26, 0x52, 0x85, 0x34, 0xfc, 0x2d, 0x46, 0xe6, 0xc7, 0x44, 0x3a, 0x3f, 0x11, 0xec, 0xac, 0xa7,
0xc3, 0x8f, 0x00, 0x97, 0x2c, 0x89, 0x69, 0x90, 0x16, 0x2c, 0x50, 0x35, 0xe9, 0x1b, 0x07, 0x8c,
0xcc, 0x5f, 0xc5, 0xf4, 0x7d, 0xc1, 0xd4, 0xd3, 0x12, 0x9f, 0xc0, 0xae, 0x86, 0xf5, 0x0f, 0xd5,
0x35, 0xde, 0x73, 0xab, 0x2f, 0x74, 0xf5, 0x17, 0xba, 0xaf, 0x6b, 0x60, 0xdc, 0xbd, 0xf8, 0x3d,
0xec, 0x7c, 0xff, 0x33, 0x44, 0xfe, 0x4e, 0x75, 0x9f, 0x56, 0xd6, 0x43, 0x18, 0xeb, 0x21, 0x9c,
0xa7, 0x30, 0x68, 0x35, 0x89, 0x1d, 0xe8, 0x8b, 0x22, 0x0c, 0xce, 0xe9, 0x22, 0x50, 0x5d, 0x59,
0x68, 0x64, 0x1c, 0xf4, 0x7c, 0x53, 0x14, 0xe1, 0x3b, 0xba, 0xf8, 0x58, 0x1e, 0x39, 0x8f, 0xa1,
0xbf, 0xd6, 0x20, 0x1e, 0x82, 0x49, 0x84, 0x08, 0x74, 0xef, 0x65, 0xb2, 0x4d, 0x1f, 0x88, 0x10,
0x35, 0xe6, 0x9c, 0xc2, 0xed, 0xb7, 0x44, 0x9e, 0xd1, 0xa8, 0x36, 0x3c, 0x84, 0x81, 0x6a, 0x21,
0x68, 0x17, 0xdc, 0x57, 0xc7, 0x27, 0xba, 0x65, 0x07, 0xfa, 0x0d, 0xd7, 0x74, 0x6d, 0x6a, 0xea,
0x98, 0xc8, 0xf1, 0xa7, 0x1f, 0x4b, 0x1b, 0x5d, 0x2c, 0x6d, 0x74, 0xb9, 0xb4, 0xd1, 0xdf, 0xa5,
0x8d, 0xbe, 0xad, 0xec, 0xce, 0xe5, 0xca, 0xee, 0xfc, 0x5a, 0xd9, 0x9d, 0xd3, 0x67, 0x71, 0x92,
0x9f, 0x15, 0xa1, 0x3b, 0xe1, 0xcc, 0xbb, 0x3a, 0x48, 0xcd, 0xb2, 0x9a, 0x94, 0xf6, 0x90, 0x85,
0x5b, 0xea, 0xfc, 0xf0, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x54, 0x4f, 0xe1, 0x7f, 0x03,
0x00, 0x00,
}
func (this *ConsensusParams) Equal(that interface{}) bool {
@@ -525,9 +451,6 @@ func (this *ConsensusParams) Equal(that interface{}) bool {
if !this.Version.Equal(that1.Version) {
return false
}
if !this.Timestamp.Equal(that1.Timestamp) {
return false
}
return true
}
func (this *BlockParams) Equal(that interface{}) bool {
@@ -640,36 +563,6 @@ func (this *VersionParams) Equal(that interface{}) bool {
}
return true
}
func (this *TimestampParams) Equal(that interface{}) bool {
if that == nil {
return this == nil
}
that1, ok := that.(*TimestampParams)
if !ok {
that2, ok := that.(TimestampParams)
if ok {
that1 = &that2
} else {
return false
}
}
if that1 == nil {
return this == nil
} else if this == nil {
return false
}
if this.Accuracy != that1.Accuracy {
return false
}
if this.Precision != that1.Precision {
return false
}
if this.MessageDelay != that1.MessageDelay {
return false
}
return true
}
func (this *HashedParams) Equal(that interface{}) bool {
if that == nil {
return this == nil
@@ -717,18 +610,6 @@ func (m *ConsensusParams) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.Timestamp != nil {
{
size, err := m.Timestamp.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintParams(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x2a
}
if m.Version != nil {
{
size, err := m.Version.MarshalToSizedBuffer(dAtA[:i])
@@ -838,12 +719,12 @@ func (m *EvidenceParams) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x18
}
n6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxAgeDuration):])
if err6 != nil {
return 0, err6
n5, err5 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MaxAgeDuration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MaxAgeDuration):])
if err5 != nil {
return 0, err5
}
i -= n6
i = encodeVarintParams(dAtA, i, uint64(n6))
i -= n5
i = encodeVarintParams(dAtA, i, uint64(n5))
i--
dAtA[i] = 0x12
if m.MaxAgeNumBlocks != 0 {
@@ -914,53 +795,6 @@ func (m *VersionParams) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *TimestampParams) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *TimestampParams) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *TimestampParams) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
n7, err7 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.MessageDelay, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.MessageDelay):])
if err7 != nil {
return 0, err7
}
i -= n7
i = encodeVarintParams(dAtA, i, uint64(n7))
i--
dAtA[i] = 0x1a
n8, err8 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Precision, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Precision):])
if err8 != nil {
return 0, err8
}
i -= n8
i = encodeVarintParams(dAtA, i, uint64(n8))
i--
dAtA[i] = 0x12
n9, err9 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Accuracy, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Accuracy):])
if err9 != nil {
return 0, err9
}
i -= n9
i = encodeVarintParams(dAtA, i, uint64(n9))
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func (m *HashedParams) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@@ -1027,10 +861,6 @@ func (m *ConsensusParams) Size() (n int) {
l = m.Version.Size()
n += 1 + l + sovParams(uint64(l))
}
if m.Timestamp != nil {
l = m.Timestamp.Size()
n += 1 + l + sovParams(uint64(l))
}
return n
}
@@ -1093,21 +923,6 @@ func (m *VersionParams) Size() (n int) {
return n
}
func (m *TimestampParams) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Accuracy)
n += 1 + l + sovParams(uint64(l))
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Precision)
n += 1 + l + sovParams(uint64(l))
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.MessageDelay)
n += 1 + l + sovParams(uint64(l))
return n
}
func (m *HashedParams) Size() (n int) {
if m == nil {
return 0
@@ -1302,42 +1117,6 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowParams
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthParams
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthParams
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Timestamp == nil {
m.Timestamp = &TimestampParams{}
}
if err := m.Timestamp.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipParams(dAtA[iNdEx:])
@@ -1719,155 +1498,6 @@ func (m *VersionParams) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *TimestampParams) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowParams
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: TimestampParams: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: TimestampParams: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Accuracy", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowParams
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthParams
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthParams
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Accuracy, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Precision", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowParams
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthParams
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthParams
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Precision, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field MessageDelay", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowParams
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthParams
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthParams
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.MessageDelay, dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipParams(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthParams
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HashedParams) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0

View File

@@ -15,7 +15,6 @@ message ConsensusParams {
EvidenceParams evidence = 2;
ValidatorParams validator = 3;
VersionParams version = 4;
TimestampParams timestamp = 5;
}
// BlockParams contains limits on the block size.
@@ -61,15 +60,6 @@ message VersionParams {
uint64 app_version = 1;
}
message TimestampParams {
google.protobuf.Duration accuracy = 1
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
google.protobuf.Duration precision = 2
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
google.protobuf.Duration message_delay = 3
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true];
}
// HashedParams is a subset of ConsensusParams.
//
// It is hashed into the Header.ConsensusHash.

View File

@@ -66,14 +66,6 @@ type EvidenceParams struct {
MaxBytes int64 `json:"max_bytes"`
}
// TimestampParams define the acceptable amount of clock skew among different
// validators on a network.
type TimestampParams struct {
Accuracy time.Duration `json:"accuracy"`
Precision time.Duration `json:"precision"`
MessageDelay time.Duration `json:"message_delay"`
}
// ValidatorParams restrict the public key types validators can use.
// NOTE: uses ABCI pubkey naming, not Amino names.
type ValidatorParams struct {
@@ -84,6 +76,14 @@ type VersionParams struct {
AppVersion uint64 `json:"app_version"`
}
// TimestampParams influence the validity of block timestamps.
// TODO (@wbanfield): add link to proposer-based timestamp spec when completed.
type TimestampParams struct {
Precision time.Duration `json:"precision"`
Accuracy time.Duration `json:"accuracy"`
MsgDelay time.Duration `json:"msg_delay"`
}
// DefaultConsensusParams returns a default ConsensusParams.
func DefaultConsensusParams() *ConsensusParams {
return &ConsensusParams{
@@ -125,6 +125,16 @@ func DefaultVersionParams() VersionParams {
}
}
func DefaultTimestampParams() TimestampParams {
// TODO(@wbanfield): Determine experimental values for these defaults
// https://github.com/tendermint/tendermint/issues/7202
return TimestampParams{
Precision: 2 * time.Second,
Accuracy: 500 * time.Millisecond,
MsgDelay: 3 * time.Second,
}
}
func (val *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool {
for i := 0; i < len(val.PubKeyTypes); i++ {
if val.PubKeyTypes[i] == pubkeyType {
@@ -244,11 +254,6 @@ func (params ConsensusParams) UpdateConsensusParams(params2 *tmproto.ConsensusPa
if params2.Version != nil {
res.Version.AppVersion = params2.Version.AppVersion
}
if params2.Timestamp != nil {
res.Timestamp.Accuracy = params2.Timestamp.Accuracy
res.Timestamp.Precision = params2.Timestamp.Precision
res.Timestamp.MessageDelay = params2.Timestamp.MessageDelay
}
return res
}
@@ -269,11 +274,6 @@ func (params *ConsensusParams) ToProto() tmproto.ConsensusParams {
Version: &tmproto.VersionParams{
AppVersion: params.Version.AppVersion,
},
Timestamp: &tmproto.TimestampParams{
Accuracy: params.Timestamp.Accuracy,
Precision: params.Timestamp.Precision,
MessageDelay: params.Timestamp.MessageDelay,
},
}
}
@@ -294,10 +294,5 @@ func ConsensusParamsFromProto(pbParams tmproto.ConsensusParams) ConsensusParams
Version: VersionParams{
AppVersion: pbParams.Version.AppVersion,
},
Timestamp: TimestampParams{
Accuracy: pbParams.Timestamp.Accuracy,
Precision: pbParams.Timestamp.Precision,
MessageDelay: pbParams.Timestamp.MessageDelay,
},
}
}

View File

@@ -79,6 +79,25 @@ func (p *Proposal) ValidateBasic() error {
return nil
}
// IsTimely validates that the block timestamp is 'timely' according to the proposer-based timestamp algorithm.
// To evaluate if a block is timely, its timestamp is compared to the local time of the validator along with the
// configured Precision and MsgDelay parameters.
// Specifically, a proposed block timestamp is considered timely if it is satisfies the following inequalities:
//
// proposedBlockTime > validatorLocaltime - Precision && proposedBlockTime < validatorLocalTime + Precision + MsgDelay.
//
// For more information on the meaning of 'timely', see the proposer-based timestamp specification:
// https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp
func (p *Proposal) IsTimely(clock tmtime.Source, tp TimestampParams) bool {
lt := clock.Now()
lhs := lt.Add(-tp.Precision)
rhs := lt.Add(tp.Precision).Add(tp.MsgDelay)
if lhs.Before(p.Timestamp) && p.Timestamp.Before(rhs) {
return true
}
return false
}
// String returns a string representation of the Proposal.
//
// 1. height

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"
tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
@@ -191,3 +192,65 @@ func TestProposalProtoBuf(t *testing.T) {
}
}
}
func TestIsTimely(t *testing.T) {
genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
require.NoError(t, err)
testCases := []struct {
name string
proposalTime time.Time
localTime time.Time
precision time.Duration
msgDelay time.Duration
expectTimely bool
}{
{
// Checking that the following inequality evaluates to true:
// 1 - 2 < 0 < 1 + 2 + 1
name: "basic timely",
proposalTime: genesisTime,
localTime: genesisTime.Add(1 * time.Nanosecond),
precision: time.Nanosecond * 2,
msgDelay: time.Nanosecond,
expectTimely: true,
},
{
// Checking that the following inequality evaluates to false:
// 3 - 2 < 0 < 3 + 2 + 1
name: "local time too large",
proposalTime: genesisTime,
localTime: genesisTime.Add(3 * time.Nanosecond),
precision: time.Nanosecond * 2,
msgDelay: time.Nanosecond,
expectTimely: false,
},
{
// Checking that the following inequality evaluates to false:
// 0 - 2 < 2 < 2 + 1
name: "proposal time too large",
proposalTime: genesisTime.Add(4 * time.Nanosecond),
localTime: genesisTime,
precision: time.Nanosecond * 2,
msgDelay: time.Nanosecond,
expectTimely: false,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
p := Proposal{
Timestamp: testCase.proposalTime,
}
tp := TimestampParams{
Precision: testCase.precision,
MsgDelay: testCase.msgDelay,
}
mockSource := new(tmtimemocks.Source)
mockSource.On("Now").Return(testCase.localTime)
ti := p.IsTimely(mockSource, tp)
assert.Equal(t, testCase.expectTimely, ti)
})
}
}