Merge branch 'master' into jmalicevic/4729-evidence-individual-processing

This commit is contained in:
Jasmina Malicevic
2022-01-26 17:11:50 +01:00
committed by GitHub
49 changed files with 3062 additions and 1286 deletions
+10 -6
View File
@@ -207,7 +207,8 @@ func TestReactor_AbruptDisconnect(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(cfg.RootDir)
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, 1, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
maxBlockHeight := int64(64)
rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0)
@@ -246,7 +247,8 @@ func TestReactor_SyncTime(t *testing.T) {
require.NoError(t, err)
defer os.RemoveAll(cfg.RootDir)
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, 1, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
maxBlockHeight := int64(101)
rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0)
@@ -271,10 +273,10 @@ func TestReactor_NoBlockResponse(t *testing.T) {
cfg, err := config.ResetTestRoot("block_sync_reactor_test")
require.NoError(t, err)
defer os.RemoveAll(cfg.RootDir)
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, 1, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
maxBlockHeight := int64(65)
rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0}, 0)
@@ -326,7 +328,8 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) {
defer os.RemoveAll(cfg.RootDir)
maxBlockHeight := int64(48)
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, 1, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, 1, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
rts := setup(ctx, t, genDoc, privVals[0], []int64{maxBlockHeight, 0, 0, 0, 0}, 1000)
@@ -360,7 +363,8 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) {
//
// XXX: This causes a potential race condition.
// See: https://github.com/tendermint/tendermint/issues/6005
otherGenDoc, otherPrivVals := factory.RandGenesisDoc(ctx, t, cfg, 1, false, 30)
valSet, otherPrivVals := factory.ValidatorSet(ctx, t, 1, 30)
otherGenDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
newNode := rts.network.MakeNode(ctx, t, p2ptest.NodeOptions{
MaxPeers: uint16(len(rts.nodes) + 1),
MaxConnected: uint16(len(rts.nodes) + 1),
+6 -4
View File
@@ -23,6 +23,7 @@ import (
"github.com/tendermint/tendermint/internal/store"
"github.com/tendermint/tendermint/internal/test/factory"
"github.com/tendermint/tendermint/libs/log"
tmtime "github.com/tendermint/tendermint/libs/time"
tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
@@ -46,7 +47,8 @@ func TestByzantinePrevoteEquivocation(t *testing.T) {
tickerFunc := newMockTickerFunc(true)
appFunc := newKVStore
genDoc, privVals := factory.RandGenesisDoc(ctx, t, config, nValidators, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, 30)
genDoc := factory.GenesisDoc(config, time.Now(), valSet.Validators, nil)
states := make([]*State, nValidators)
for i := 0; i < nValidators; i++ {
@@ -215,18 +217,18 @@ 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
// send proposal and block parts on internal msg queue
lazyNodeState.sendInternalMessage(ctx, msgInfo{&ProposalMessage{proposal}, ""})
lazyNodeState.sendInternalMessage(ctx, msgInfo{&ProposalMessage{proposal}, "", tmtime.Now()})
for i := 0; i < int(blockParts.Total()); i++ {
part := blockParts.GetPart(i)
lazyNodeState.sendInternalMessage(ctx, msgInfo{&BlockPartMessage{
lazyNodeState.Height, lazyNodeState.Round, part,
}, ""})
}, "", tmtime.Now()})
}
} else if !lazyNodeState.replayMode {
lazyNodeState.logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err)
+159 -150
View File
@@ -3,6 +3,7 @@ package consensus
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
@@ -88,6 +89,7 @@ type validatorStub struct {
Index int32 // Validator index. NOTE: we don't assume validator set changes.
Height int64
Round int32
clock tmtime.Source
types.PrivValidator
VotingPower int64
lastVote *types.Vote
@@ -100,16 +102,15 @@ func newValidatorStub(privValidator types.PrivValidator, valIndex int32) *valida
Index: valIndex,
PrivValidator: privValidator,
VotingPower: testMinPower,
clock: tmtime.DefaultSource{},
}
}
func (vs *validatorStub) signVote(
ctx context.Context,
cfg *config.Config,
voteType tmproto.SignedMsgType,
hash []byte,
header types.PartSetHeader,
) (*types.Vote, error) {
chainID string,
blockID types.BlockID) (*types.Vote, error) {
pubKey, err := vs.PrivValidator.GetPubKey(ctx)
if err != nil {
@@ -121,12 +122,12 @@ func (vs *validatorStub) signVote(
ValidatorAddress: pubKey.Address(),
Height: vs.Height,
Round: vs.Round,
Timestamp: tmtime.Now(),
Timestamp: vs.clock.Now(),
Type: voteType,
BlockID: types.BlockID{Hash: hash, PartSetHeader: header},
BlockID: blockID,
}
v := vote.ToProto()
if err := vs.PrivValidator.SignVote(ctx, cfg.ChainID(), v); err != nil {
if err := vs.PrivValidator.SignVote(ctx, chainID, v); err != nil {
return nil, fmt.Errorf("sign vote failed: %w", err)
}
@@ -147,13 +148,11 @@ func signVote(
ctx context.Context,
t *testing.T,
vs *validatorStub,
cfg *config.Config,
voteType tmproto.SignedMsgType,
hash []byte,
header types.PartSetHeader,
) *types.Vote {
chainID string,
blockID types.BlockID) *types.Vote {
v, err := vs.signVote(ctx, cfg, voteType, hash, header)
v, err := vs.signVote(ctx, voteType, chainID, blockID)
require.NoError(t, err, "failed to sign vote")
vs.lastVote = v
@@ -164,15 +163,14 @@ func signVote(
func signVotes(
ctx context.Context,
t *testing.T,
cfg *config.Config,
voteType tmproto.SignedMsgType,
hash []byte,
header types.PartSetHeader,
chainID string,
blockID types.BlockID,
vss ...*validatorStub,
) []*types.Vote {
votes := make([]*types.Vote, len(vss))
for i, vs := range vss {
votes[i] = signVote(ctx, t, vs, cfg, voteType, hash, header)
votes[i] = signVote(ctx, t, vs, voteType, chainID, blockID)
}
return votes
}
@@ -247,7 +245,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()
require.NoError(t, vs.SignProposal(ctx, chainID, p))
@@ -265,14 +263,13 @@ func addVotes(to *State, votes ...*types.Vote) {
func signAddVotes(
ctx context.Context,
t *testing.T,
cfg *config.Config,
to *State,
voteType tmproto.SignedMsgType,
hash []byte,
header types.PartSetHeader,
chainID string,
blockID types.BlockID,
vss ...*validatorStub,
) {
addVotes(to, signVotes(ctx, t, cfg, voteType, hash, header, vss...)...)
addVotes(to, signVotes(ctx, t, voteType, chainID, blockID, vss...)...)
}
func validatePrevote(
@@ -378,6 +375,35 @@ func subscribeToVoter(ctx context.Context, t *testing.T, cs *State, addr []byte)
return ch
}
func subscribeToVoterBuffered(ctx context.Context, t *testing.T, cs *State, addr []byte) <-chan tmpubsub.Message {
t.Helper()
votesSub, err := cs.eventBus.SubscribeWithArgs(ctx, tmpubsub.SubscribeArgs{
ClientID: testSubscriber,
Query: types.EventQueryVote,
Limit: 10})
if err != nil {
t.Fatalf("failed to subscribe %s to %v", testSubscriber, types.EventQueryVote)
}
ch := make(chan tmpubsub.Message, 10)
go func() {
for {
msg, err := votesSub.Next(ctx)
if err != nil {
if !errors.Is(err, tmpubsub.ErrTerminated) && !errors.Is(err, context.Canceled) {
t.Errorf("error terminating pubsub %s", err)
}
return
}
vote := msg.Data().(types.EventDataVote)
// we only fire for our own votes
if bytes.Equal(addr, vote.Vote.ValidatorAddress) {
ch <- msg
}
}
}()
return ch
}
//-------------------------------------------------------------------------------
// consensus states
@@ -477,17 +503,12 @@ func loadPrivValidator(t *testing.T, cfg *config.Config) *privval.FilePV {
return privValidator
}
func randState(
ctx context.Context,
t *testing.T,
cfg *config.Config,
logger log.Logger,
nValidators int,
) (*State, []*validatorStub) {
func makeState(ctx context.Context, t *testing.T, cfg *config.Config, logger log.Logger, nValidators int) (*State, []*validatorStub) {
t.Helper()
// Get State
state, privVals := randGenesisState(ctx, t, cfg, nValidators, false, 10)
state, privVals := makeGenesisState(ctx, t, cfg, genesisStateArgs{
Validators: nValidators,
})
vss := make([]*validatorStub, nValidators)
@@ -504,7 +525,8 @@ func randState(
//-------------------------------------------------------------------------------
func ensureNoNewEvent(t *testing.T, ch <-chan tmpubsub.Message, timeout time.Duration, errorMessage string) {
func ensureNoMessageBeforeTimeout(t *testing.T, ch <-chan tmpubsub.Message, timeout time.Duration,
errorMessage string) {
t.Helper()
select {
case <-time.After(timeout):
@@ -516,7 +538,8 @@ func ensureNoNewEvent(t *testing.T, ch <-chan tmpubsub.Message, timeout time.Dur
func ensureNoNewEventOnChannel(t *testing.T, ch <-chan tmpubsub.Message) {
t.Helper()
ensureNoNewEvent(t,
ensureNoMessageBeforeTimeout(
t,
ch,
ensureTimeout,
"We should be stuck waiting, not receiving new event on the channel")
@@ -524,137 +547,112 @@ func ensureNoNewEventOnChannel(t *testing.T, ch <-chan tmpubsub.Message) {
func ensureNoNewRoundStep(t *testing.T, stepCh <-chan tmpubsub.Message) {
t.Helper()
ensureNoNewEvent(
ensureNoMessageBeforeTimeout(
t,
stepCh,
ensureTimeout,
"We should be stuck waiting, not receiving NewRoundStep event")
}
func ensureNoNewUnlock(t *testing.T, unlockCh <-chan tmpubsub.Message) {
t.Helper()
ensureNoNewEvent(t,
unlockCh,
ensureTimeout,
"We should be stuck waiting, not receiving Unlock event")
}
func ensureNoNewTimeout(t *testing.T, stepCh <-chan tmpubsub.Message, timeout int64) {
t.Helper()
timeoutDuration := time.Duration(timeout*10) * time.Nanosecond
ensureNoNewEvent(t,
ensureNoMessageBeforeTimeout(
t,
stepCh,
timeoutDuration,
"We should be stuck waiting, not receiving NewTimeout event")
}
func ensureNewEvent(t *testing.T, ch <-chan tmpubsub.Message, height int64, round int32, timeout time.Duration, errorMessage string) {
func ensureNewEvent(t *testing.T, ch <-chan tmpubsub.Message, height int64, round int32, timeout time.Duration) {
t.Helper()
select {
case <-time.After(timeout):
t.Fatal(errorMessage)
case msg := <-ch:
roundStateEvent, ok := msg.Data().(types.EventDataRoundState)
require.True(t, ok,
"expected a EventDataRoundState, got %T. Wrong subscription channel?",
msg.Data())
msg := ensureMessageBeforeTimeout(t, ch, ensureTimeout)
roundStateEvent, ok := msg.Data().(types.EventDataRoundState)
require.True(t, ok,
"expected a EventDataRoundState, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, roundStateEvent.Height)
require.Equal(t, round, roundStateEvent.Round)
// TODO: We could check also for a step at this point!
}
require.Equal(t, height, roundStateEvent.Height)
require.Equal(t, round, roundStateEvent.Round)
// TODO: We could check also for a step at this point!
}
func ensureNewRound(t *testing.T, roundCh <-chan tmpubsub.Message, height int64, round int32) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewRound event")
case msg := <-roundCh:
newRoundEvent, ok := msg.Data().(types.EventDataNewRound)
require.True(t, ok, "expected a EventDataNewRound, got %T. Wrong subscription channel?",
msg.Data())
msg := ensureMessageBeforeTimeout(t, roundCh, ensureTimeout)
newRoundEvent, ok := msg.Data().(types.EventDataNewRound)
require.True(t, ok, "expected a EventDataNewRound, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, newRoundEvent.Height)
require.Equal(t, round, newRoundEvent.Round)
}
require.Equal(t, height, newRoundEvent.Height)
require.Equal(t, round, newRoundEvent.Round)
}
func ensureNewTimeout(t *testing.T, timeoutCh <-chan tmpubsub.Message, height int64, round int32, timeout int64) {
t.Helper()
timeoutDuration := time.Duration(timeout*10) * time.Nanosecond
ensureNewEvent(t, timeoutCh, height, round, timeoutDuration,
"Timeout expired while waiting for NewTimeout event")
ensureNewEvent(t, timeoutCh, height, round, timeoutDuration)
}
func ensureNewProposal(t *testing.T, proposalCh <-chan tmpubsub.Message, height int64, round int32) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewProposal event")
case msg := <-proposalCh:
proposalEvent, ok := msg.Data().(types.EventDataCompleteProposal)
require.True(t, ok, "expected a EventDataCompleteProposal, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, proposalEvent.Height)
require.Equal(t, round, proposalEvent.Round)
}
msg := ensureMessageBeforeTimeout(t, proposalCh, ensureTimeout)
proposalEvent, ok := msg.Data().(types.EventDataCompleteProposal)
require.True(t, ok, "expected a EventDataCompleteProposal, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, proposalEvent.Height)
require.Equal(t, round, proposalEvent.Round)
}
func ensureNewValidBlock(t *testing.T, validBlockCh <-chan tmpubsub.Message, height int64, round int32) {
t.Helper()
ensureNewEvent(t, validBlockCh, height, round, ensureTimeout,
"Timeout expired while waiting for NewValidBlock event")
ensureNewEvent(t, validBlockCh, height, round, ensureTimeout)
}
func ensureNewBlock(t *testing.T, blockCh <-chan tmpubsub.Message, height int64) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewBlock event")
case msg := <-blockCh:
blockEvent, ok := msg.Data().(types.EventDataNewBlock)
require.True(t, ok, "expected a EventDataNewBlock, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, blockEvent.Block.Height)
}
msg := ensureMessageBeforeTimeout(t, blockCh, ensureTimeout)
blockEvent, ok := msg.Data().(types.EventDataNewBlock)
require.True(t, ok, "expected a EventDataNewBlock, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, blockEvent.Block.Height)
}
func ensureNewBlockHeader(t *testing.T, blockCh <-chan tmpubsub.Message, height int64, blockHash tmbytes.HexBytes) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewBlockHeader event")
case msg := <-blockCh:
blockHeaderEvent, ok := msg.Data().(types.EventDataNewBlockHeader)
require.True(t, ok, "expected a EventDataNewBlockHeader, got %T. Wrong subscription channel?",
msg.Data())
msg := ensureMessageBeforeTimeout(t, blockCh, ensureTimeout)
blockHeaderEvent, ok := msg.Data().(types.EventDataNewBlockHeader)
require.True(t, ok, "expected a EventDataNewBlockHeader, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, blockHeaderEvent.Header.Height)
require.True(t, bytes.Equal(blockHeaderEvent.Header.Hash(), blockHash))
}
require.Equal(t, height, blockHeaderEvent.Header.Height)
require.True(t, bytes.Equal(blockHeaderEvent.Header.Hash(), blockHash))
}
func ensureNewUnlock(t *testing.T, unlockCh <-chan tmpubsub.Message, height int64, round int32) {
func ensureLock(t *testing.T, lockCh <-chan tmpubsub.Message, height int64, round int32) {
t.Helper()
ensureNewEvent(t, unlockCh, height, round, ensureTimeout,
"Timeout expired while waiting for NewUnlock event")
ensureNewEvent(t, lockCh, height, round, ensureTimeout)
}
func ensureRelock(t *testing.T, relockCh <-chan tmpubsub.Message, height int64, round int32) {
t.Helper()
ensureNewEvent(t, relockCh, height, round, ensureTimeout)
}
func ensureProposal(t *testing.T, proposalCh <-chan tmpubsub.Message, height int64, round int32, propID types.BlockID) {
ensureProposalWithTimeout(t, proposalCh, height, round, &propID, ensureTimeout)
}
func ensureProposalWithTimeout(t *testing.T, proposalCh <-chan tmpubsub.Message, height int64, round int32, propID *types.BlockID, timeout time.Duration) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewProposal event")
case msg := <-proposalCh:
proposalEvent, ok := msg.Data().(types.EventDataCompleteProposal)
require.True(t, ok, "expected a EventDataCompleteProposal, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, proposalEvent.Height)
require.Equal(t, round, proposalEvent.Round)
require.True(t, proposalEvent.BlockID.Equals(propID),
msg := ensureMessageBeforeTimeout(t, proposalCh, timeout)
proposalEvent, ok := msg.Data().(types.EventDataCompleteProposal)
require.True(t, ok, "expected a EventDataCompleteProposal, got %T. Wrong subscription channel?",
msg.Data())
require.Equal(t, height, proposalEvent.Height)
require.Equal(t, round, proposalEvent.Round)
if propID != nil {
require.True(t, proposalEvent.BlockID.Equals(*propID),
"Proposed block does not match expected block (%v != %v)", proposalEvent.BlockID, propID)
}
}
@@ -703,38 +701,32 @@ func ensureVoteMatch(t *testing.T, voteCh <-chan tmpubsub.Message, height int64,
}
func ensureVote(t *testing.T, voteCh <-chan tmpubsub.Message, height int64, round int32, voteType tmproto.SignedMsgType) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for NewVote event")
case msg := <-voteCh:
voteEvent, ok := msg.Data().(types.EventDataVote)
require.True(t, ok, "expected a EventDataVote, got %T. Wrong subscription channel?",
msg.Data())
msg := ensureMessageBeforeTimeout(t, voteCh, ensureTimeout)
voteEvent, ok := msg.Data().(types.EventDataVote)
require.True(t, ok, "expected a EventDataVote, got %T. Wrong subscription channel?",
msg.Data())
vote := voteEvent.Vote
require.Equal(t, height, vote.Height)
require.Equal(t, round, vote.Round)
vote := voteEvent.Vote
require.Equal(t, height, vote.Height)
require.Equal(t, round, vote.Round)
require.Equal(t, voteType, vote.Type)
}
}
func ensurePrecommitTimeout(t *testing.T, ch <-chan tmpubsub.Message) {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for the Precommit to Timeout")
case <-ch:
}
require.Equal(t, voteType, vote.Type)
}
func ensureNewEventOnChannel(t *testing.T, ch <-chan tmpubsub.Message) {
t.Helper()
ensureMessageBeforeTimeout(t, ch, ensureTimeout)
}
func ensureMessageBeforeTimeout(t *testing.T, ch <-chan tmpubsub.Message, to time.Duration) tmpubsub.Message {
t.Helper()
select {
case <-time.After(ensureTimeout):
t.Fatal("Timeout expired while waiting for new activity on the channel")
case <-ch:
case <-time.After(to):
t.Fatalf("Timeout expired while waiting for message")
case msg := <-ch:
return msg
}
panic("unreachable")
}
//-------------------------------------------------------------------------------
@@ -746,7 +738,7 @@ func consensusLogger() log.Logger {
return log.TestingLogger().With("module", "consensus")
}
func randConsensusState(
func makeConsensusState(
ctx context.Context,
t *testing.T,
cfg *config.Config,
@@ -756,8 +748,10 @@ func randConsensusState(
appFunc func(t *testing.T, logger log.Logger) abci.Application,
configOpts ...func(*config.Config),
) ([]*State, cleanupFunc) {
t.Helper()
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, nValidators, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
css := make([]*State, nValidators)
logger := consensusLogger()
@@ -817,8 +811,10 @@ func randConsensusNetWithPeers(
) ([]*State, *types.GenesisDoc, *config.Config, cleanupFunc) {
t.Helper()
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, nValidators, false, testMinPower)
valSet, privVals := factory.ValidatorSet(ctx, t, nValidators, testMinPower)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
css := make([]*State, nPeers)
t.Helper()
logger := consensusLogger()
var peer0Config *config.Config
@@ -866,16 +862,29 @@ func randConsensusNetWithPeers(
}
}
func randGenesisState(
ctx context.Context,
t *testing.T,
cfg *config.Config,
numValidators int,
randPower bool,
minPower int64,
) (sm.State, []types.PrivValidator) {
type genesisStateArgs struct {
Validators int
Power int64
Params *types.ConsensusParams
Time time.Time
}
genDoc, privValidators := factory.RandGenesisDoc(ctx, t, cfg, numValidators, randPower, minPower)
func makeGenesisState(ctx context.Context, t *testing.T, cfg *config.Config, args genesisStateArgs) (sm.State, []types.PrivValidator) {
t.Helper()
if args.Power == 0 {
args.Power = 1
}
if args.Validators == 0 {
args.Power = 4
}
valSet, privValidators := factory.ValidatorSet(ctx, t, args.Validators, args.Power)
if args.Params == nil {
args.Params = types.DefaultConsensusParams()
}
if args.Time.IsZero() {
args.Time = time.Now()
}
genDoc := factory.GenesisDoc(cfg, args.Time, valSet.Validators, args.Params)
s0, err := sm.MakeGenesisState(genDoc)
require.NoError(t, err)
return s0, privValidators
@@ -884,7 +893,7 @@ func randGenesisState(
func newMockTickerFunc(onlyOnce bool) func() TimeoutTicker {
return func() TimeoutTicker {
return &mockTicker{
c: make(chan timeoutInfo, 10),
c: make(chan timeoutInfo, 100),
onlyOnce: onlyOnce,
}
}
+3 -2
View File
@@ -11,6 +11,7 @@ import (
"github.com/tendermint/tendermint/internal/p2p"
"github.com/tendermint/tendermint/libs/bytes"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtime "github.com/tendermint/tendermint/libs/time"
tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
@@ -23,7 +24,7 @@ func TestReactorInvalidPrecommit(t *testing.T) {
config := configSetup(t)
n := 4
states, cleanup := randConsensusState(ctx, t,
states, cleanup := makeConsensusState(ctx, t,
config, n, "consensus_reactor_test",
newMockTickerFunc(true), newKVStore)
t.Cleanup(cleanup)
@@ -107,7 +108,7 @@ func invalidDoPrevoteFunc(
ValidatorIndex: valIndex,
Height: cs.Height,
Round: cs.Round,
Timestamp: cs.voteTime(),
Timestamp: tmtime.Now(),
Type: tmproto.PrecommitType,
BlockID: types.BlockID{
Hash: blockHash,
+17 -7
View File
@@ -40,7 +40,9 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) {
t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
config.Consensus.CreateEmptyBlocks = false
state, privVals := randGenesisState(ctx, t, baseConfig, 1, false, 10)
state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{
Validators: 1,
Power: 10})
cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication())
assertMempool(t, cs.txNotifier).EnableTxsAvailable()
height, round := cs.Height, cs.Round
@@ -65,7 +67,9 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) {
t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
config.Consensus.CreateEmptyBlocksInterval = ensureTimeout
state, privVals := randGenesisState(ctx, t, baseConfig, 1, false, 10)
state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{
Validators: 1,
Power: 10})
cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication())
assertMempool(t, cs.txNotifier).EnableTxsAvailable()
@@ -88,20 +92,22 @@ func TestMempoolProgressInHigherRound(t *testing.T) {
t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) })
config.Consensus.CreateEmptyBlocks = false
state, privVals := randGenesisState(ctx, t, baseConfig, 1, false, 10)
state, privVals := makeGenesisState(ctx, t, baseConfig, genesisStateArgs{
Validators: 1,
Power: 10})
cs := newStateWithConfig(ctx, t, log.TestingLogger(), config, state, privVals[0], NewCounterApplication())
assertMempool(t, cs.txNotifier).EnableTxsAvailable()
height, round := cs.Height, cs.Round
newBlockCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlock)
newRoundCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewRound)
timeoutCh := subscribe(ctx, t, cs.eventBus, types.EventQueryTimeoutPropose)
cs.setProposal = func(proposal *types.Proposal) error {
cs.setProposal = func(proposal *types.Proposal, recvTime time.Time) error {
if cs.Height == 2 && cs.Round == 0 {
// dont set the proposal in round 0 so we timeout and
// go to next round
return nil
}
return cs.defaultSetProposal(proposal)
return cs.defaultSetProposal(proposal, recvTime)
}
startTestRound(ctx, cs, height, round)
@@ -137,7 +143,9 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) {
config := configSetup(t)
logger := log.TestingLogger()
state, privVals := randGenesisState(ctx, t, config, 1, false, 10)
state, privVals := makeGenesisState(ctx, t, config, genesisStateArgs{
Validators: 1,
Power: 10})
stateStore := sm.NewStore(dbm.NewMemDB())
blockStore := store.NewBlockStore(dbm.NewMemDB())
@@ -170,7 +178,9 @@ func TestMempoolRmBadTx(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
state, privVals := randGenesisState(ctx, t, config, 1, false, 10)
state, privVals := makeGenesisState(ctx, t, config, genesisStateArgs{
Validators: 1,
Power: 10})
app := NewCounterApplication()
stateStore := sm.NewStore(dbm.NewMemDB())
blockStore := store.NewBlockStore(dbm.NewMemDB())
+489
View File
@@ -0,0 +1,489 @@
package consensus
import (
"bytes"
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/abci/example/kvstore"
"github.com/tendermint/tendermint/internal/eventbus"
tmpubsub "github.com/tendermint/tendermint/internal/pubsub"
"github.com/tendermint/tendermint/libs/log"
tmtimemocks "github.com/tendermint/tendermint/libs/time/mocks"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
)
const (
// blockTimeIota is used in the test harness as the time between
// blocks when not otherwise specified.
blockTimeIota = time.Millisecond
)
// pbtsTestHarness constructs a Tendermint network that can be used for testing the
// implementation of the Proposer-Based timestamps algorithm.
// It runs a series of consensus heights and captures timing of votes and events.
type pbtsTestHarness struct {
// configuration options set by the user of the test harness.
pbtsTestConfiguration
// The Tendermint consensus state machine being run during
// a run of the pbtsTestHarness.
observedState *State
// A stub for signing votes and messages using the key
// from the observedState.
observedValidator *validatorStub
// A list of simulated validators that interact with the observedState and are
// fully controlled by the test harness.
otherValidators []*validatorStub
// The mock time source used by all of the validator stubs in the test harness.
// This mock clock allows the test harness to produce votes and blocks with arbitrary
// timestamps.
validatorClock *tmtimemocks.Source
chainID string
// channels for verifying that the observed validator completes certain actions.
ensureProposalCh, roundCh, blockCh, ensureVoteCh <-chan tmpubsub.Message
// channel of events from the observed validator annotated with the timestamp
// the event was received.
eventCh <-chan timestampedEvent
currentHeight int64
currentRound int32
}
type pbtsTestConfiguration struct {
// The timestamp consensus parameters to be used by the state machine under test.
synchronyParams types.SynchronyParams
// The setting to use for the TimeoutPropose configuration parameter.
timeoutPropose time.Duration
// The timestamp of the first block produced by the network.
genesisTime time.Time
// The time at which the proposal at height 2 should be delivered.
height2ProposalDeliverTime time.Time
// The timestamp of the block proposed at height 2.
height2ProposedBlockTime time.Time
// The timestamp of the block proposed at height 4.
// At height 4, the proposed block time and the deliver time are the same so
// that timely-ness does not affect height 4.
height4ProposedBlockTime time.Time
}
func newPBTSTestHarness(ctx context.Context, t *testing.T, tc pbtsTestConfiguration) pbtsTestHarness {
t.Helper()
const validators = 4
cfg := configSetup(t)
clock := new(tmtimemocks.Source)
if tc.height4ProposedBlockTime.IsZero() {
// Set a default height4ProposedBlockTime.
// Use a proposed block time that is greater than the time that the
// block at height 2 was delivered. Height 3 is not relevant for testing
// and always occurs blockTimeIota before height 4. If not otherwise specified,
// height 4 therefore occurs 2*blockTimeIota after height 2.
tc.height4ProposedBlockTime = tc.height2ProposalDeliverTime.Add(2 * blockTimeIota)
}
cfg.Consensus.TimeoutPropose = tc.timeoutPropose
consensusParams := types.DefaultConsensusParams()
consensusParams.Synchrony = tc.synchronyParams
state, privVals := makeGenesisState(ctx, t, cfg, genesisStateArgs{
Params: consensusParams,
Time: tc.genesisTime,
Validators: validators,
})
cs := newState(ctx, t, log.TestingLogger(), state, privVals[0], kvstore.NewApplication())
vss := make([]*validatorStub, validators)
for i := 0; i < validators; i++ {
vss[i] = newValidatorStub(privVals[i], int32(i))
}
incrementHeight(vss[1:]...)
for _, vs := range vss {
vs.clock = clock
}
pubKey, err := vss[0].PrivValidator.GetPubKey(ctx)
require.NoError(t, err)
eventCh := timestampedCollector(ctx, t, cs.eventBus)
return pbtsTestHarness{
pbtsTestConfiguration: tc,
observedValidator: vss[0],
observedState: cs,
otherValidators: vss[1:],
validatorClock: clock,
currentHeight: 1,
chainID: cfg.ChainID(),
roundCh: subscribe(ctx, t, cs.eventBus, types.EventQueryNewRound),
ensureProposalCh: subscribe(ctx, t, cs.eventBus, types.EventQueryCompleteProposal),
blockCh: subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlock),
ensureVoteCh: subscribeToVoterBuffered(ctx, t, cs, pubKey.Address()),
eventCh: eventCh,
}
}
func (p *pbtsTestHarness) observedValidatorProposerHeight(ctx context.Context, t *testing.T, previousBlockTime time.Time) heightResult {
p.validatorClock.On("Now").Return(p.height2ProposedBlockTime).Times(6)
ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound)
timeout := time.Until(previousBlockTime.Add(ensureTimeout))
ensureProposalWithTimeout(t, p.ensureProposalCh, p.currentHeight, p.currentRound, nil, timeout)
rs := p.observedState.GetRoundState()
bid := types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()}
ensurePrevote(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
signAddVotes(ctx, t, p.observedState, tmproto.PrevoteType, p.chainID, bid, p.otherValidators...)
signAddVotes(ctx, t, p.observedState, tmproto.PrecommitType, p.chainID, bid, p.otherValidators...)
ensurePrecommit(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
ensureNewBlock(t, p.blockCh, p.currentHeight)
vk, err := p.observedValidator.GetPubKey(ctx)
require.NoError(t, err)
res := collectHeightResults(ctx, t, p.eventCh, p.currentHeight, vk.Address())
p.currentHeight++
incrementHeight(p.otherValidators...)
return res
}
func (p *pbtsTestHarness) height2(ctx context.Context, t *testing.T) heightResult {
signer := p.otherValidators[0].PrivValidator
height3BlockTime := p.height2ProposedBlockTime.Add(-blockTimeIota)
return p.nextHeight(ctx, t, signer, p.height2ProposalDeliverTime, p.height2ProposedBlockTime, height3BlockTime)
}
func (p *pbtsTestHarness) intermediateHeights(ctx context.Context, t *testing.T) {
signer := p.otherValidators[1].PrivValidator
blockTimeHeight3 := p.height4ProposedBlockTime.Add(-blockTimeIota)
p.nextHeight(ctx, t, signer, blockTimeHeight3, blockTimeHeight3, p.height4ProposedBlockTime)
signer = p.otherValidators[2].PrivValidator
p.nextHeight(ctx, t, signer, p.height4ProposedBlockTime, p.height4ProposedBlockTime, time.Now())
}
func (p *pbtsTestHarness) height5(ctx context.Context, t *testing.T) heightResult {
return p.observedValidatorProposerHeight(ctx, t, p.height4ProposedBlockTime)
}
func (p *pbtsTestHarness) nextHeight(ctx context.Context, t *testing.T, proposer types.PrivValidator, deliverTime, proposedTime, nextProposedTime time.Time) heightResult {
p.validatorClock.On("Now").Return(nextProposedTime).Times(6)
ensureNewRound(t, p.roundCh, p.currentHeight, p.currentRound)
b, _, err := p.observedState.createProposalBlock()
require.NoError(t, err)
b.Height = p.currentHeight
b.Header.Height = p.currentHeight
b.Header.Time = proposedTime
k, err := proposer.GetPubKey(ctx)
require.NoError(t, err)
b.Header.ProposerAddress = k.Address()
ps, err := b.MakePartSet(types.BlockPartSizeBytes)
require.NoError(t, err)
bid := types.BlockID{Hash: b.Hash(), PartSetHeader: ps.Header()}
prop := types.NewProposal(p.currentHeight, 0, -1, bid, proposedTime)
tp := prop.ToProto()
if err := proposer.SignProposal(ctx, p.observedState.state.ChainID, tp); err != nil {
t.Fatalf("error signing proposal: %s", err)
}
time.Sleep(time.Until(deliverTime))
prop.Signature = tp.Signature
if err := p.observedState.SetProposalAndBlock(ctx, prop, b, ps, "peerID"); err != nil {
t.Fatal(err)
}
ensureProposal(t, p.ensureProposalCh, p.currentHeight, 0, bid)
ensurePrevote(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
signAddVotes(ctx, t, p.observedState, tmproto.PrevoteType, p.chainID, bid, p.otherValidators...)
signAddVotes(ctx, t, p.observedState, tmproto.PrecommitType, p.chainID, bid, p.otherValidators...)
ensurePrecommit(t, p.ensureVoteCh, p.currentHeight, p.currentRound)
vk, err := p.observedValidator.GetPubKey(ctx)
require.NoError(t, err)
res := collectHeightResults(ctx, t, p.eventCh, p.currentHeight, vk.Address())
ensureNewBlock(t, p.blockCh, p.currentHeight)
p.currentHeight++
incrementHeight(p.otherValidators...)
return res
}
func timestampedCollector(ctx context.Context, t *testing.T, eb *eventbus.EventBus) <-chan timestampedEvent {
t.Helper()
// Since eventCh is not read until the end of each height, it must be large
// enough to hold all of the events produced during a single height.
eventCh := make(chan timestampedEvent, 100)
if err := eb.Observe(ctx, func(msg tmpubsub.Message) error {
eventCh <- timestampedEvent{
ts: time.Now(),
m: msg,
}
return nil
}, types.EventQueryVote, types.EventQueryCompleteProposal); err != nil {
t.Fatalf("Failed to observe query %v: %v", types.EventQueryVote, err)
}
return eventCh
}
func collectHeightResults(ctx context.Context, t *testing.T, eventCh <-chan timestampedEvent, height int64, address []byte) heightResult {
t.Helper()
var res heightResult
for event := range eventCh {
switch v := event.m.Data().(type) {
case types.EventDataVote:
if v.Vote.Height > height {
t.Fatalf("received prevote from unexpected height, expected: %d, saw: %d", height, v.Vote.Height)
}
if !bytes.Equal(address, v.Vote.ValidatorAddress) {
continue
}
if v.Vote.Type != tmproto.PrevoteType {
continue
}
res.prevote = v.Vote
res.prevoteIssuedAt = event.ts
case types.EventDataCompleteProposal:
if v.Height > height {
t.Fatalf("received proposal from unexpected height, expected: %d, saw: %d", height, v.Height)
}
res.proposalIssuedAt = event.ts
}
if res.isComplete() {
return res
}
}
t.Fatalf("complete height result never seen for height %d", height)
panic("unreachable")
}
type timestampedEvent struct {
ts time.Time
m tmpubsub.Message
}
func (p *pbtsTestHarness) run(ctx context.Context, t *testing.T) resultSet {
startTestRound(ctx, p.observedState, p.currentHeight, p.currentRound)
r1 := p.observedValidatorProposerHeight(ctx, t, p.genesisTime)
r2 := p.height2(ctx, t)
p.intermediateHeights(ctx, t)
r5 := p.height5(ctx, t)
return resultSet{
genesisHeight: r1,
height2: r2,
height5: r5,
}
}
type resultSet struct {
genesisHeight heightResult
height2 heightResult
height5 heightResult
}
type heightResult struct {
proposalIssuedAt time.Time
prevote *types.Vote
prevoteIssuedAt time.Time
}
func (hr heightResult) isComplete() bool {
return !hr.proposalIssuedAt.IsZero() && !hr.prevoteIssuedAt.IsZero() && hr.prevote != nil
}
// TestProposerWaitsForGenesisTime tests that a proposer will not propose a block
// until after the genesis time has passed. The test sets the genesis time in the
// future and then ensures that the observed validator waits to propose a block.
func TestProposerWaitsForGenesisTime(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// create a genesis time far (enough) in the future.
initialTime := time.Now().Add(800 * time.Millisecond)
cfg := pbtsTestConfiguration{
synchronyParams: types.SynchronyParams{
Precision: 10 * time.Millisecond,
MessageDelay: 10 * time.Millisecond,
},
timeoutPropose: 10 * time.Millisecond,
genesisTime: initialTime,
height2ProposalDeliverTime: initialTime.Add(10 * time.Millisecond),
height2ProposedBlockTime: initialTime.Add(10 * time.Millisecond),
}
pbtsTest := newPBTSTestHarness(ctx, t, cfg)
results := pbtsTest.run(ctx, t)
// ensure that the proposal was issued after the genesis time.
assert.True(t, results.genesisHeight.proposalIssuedAt.After(cfg.genesisTime))
}
// TestProposerWaitsForPreviousBlock tests that the proposer of a block waits until
// the block time of the previous height has passed to propose the next block.
// The test harness ensures that the observed validator will be the proposer at
// height 1 and height 5. The test sets the block time of height 4 in the future
// and then verifies that the observed validator waits until after the block time
// of height 4 to propose a block at height 5.
func TestProposerWaitsForPreviousBlock(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
initialTime := time.Now().Add(time.Millisecond * 50)
cfg := pbtsTestConfiguration{
synchronyParams: types.SynchronyParams{
Precision: 100 * time.Millisecond,
MessageDelay: 500 * time.Millisecond,
},
timeoutPropose: 50 * time.Millisecond,
genesisTime: initialTime,
height2ProposalDeliverTime: initialTime.Add(150 * time.Millisecond),
height2ProposedBlockTime: initialTime.Add(100 * time.Millisecond),
height4ProposedBlockTime: initialTime.Add(800 * time.Millisecond),
}
pbtsTest := newPBTSTestHarness(ctx, t, cfg)
results := pbtsTest.run(ctx, t)
// the observed validator is the proposer at height 5.
// ensure that the observed validator did not propose a block until after
// the time configured for height 4.
assert.True(t, results.height5.proposalIssuedAt.After(cfg.height4ProposedBlockTime))
// Ensure that the validator issued a prevote for a non-nil block.
assert.NotNil(t, results.height5.prevote.BlockID.Hash)
}
func TestProposerWaitTime(t *testing.T) {
genesisTime, err := time.Parse(time.RFC3339, "2019-03-13T23:00:00Z")
require.NoError(t, err)
testCases := []struct {
name string
previousBlockTime time.Time
localTime time.Time
expectedWait time.Duration
}{
{
name: "block time greater than local time",
previousBlockTime: genesisTime.Add(5 * time.Nanosecond),
localTime: genesisTime.Add(1 * time.Nanosecond),
expectedWait: 4 * time.Nanosecond,
},
{
name: "local time greater than block time",
previousBlockTime: genesisTime.Add(1 * time.Nanosecond),
localTime: genesisTime.Add(5 * time.Nanosecond),
expectedWait: 0,
},
{
name: "both times equal",
previousBlockTime: genesisTime.Add(5 * time.Nanosecond),
localTime: genesisTime.Add(5 * time.Nanosecond),
expectedWait: 0,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
mockSource := new(tmtimemocks.Source)
mockSource.On("Now").Return(testCase.localTime)
ti := proposerWaitTime(mockSource, testCase.previousBlockTime)
assert.Equal(t, testCase.expectedWait, ti)
})
}
}
func TestTimelyProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
initialTime := time.Now()
cfg := pbtsTestConfiguration{
synchronyParams: types.SynchronyParams{
Precision: 10 * time.Millisecond,
MessageDelay: 140 * time.Millisecond,
},
timeoutPropose: 40 * time.Millisecond,
genesisTime: initialTime,
height2ProposedBlockTime: initialTime.Add(10 * time.Millisecond),
height2ProposalDeliverTime: initialTime.Add(30 * time.Millisecond),
}
pbtsTest := newPBTSTestHarness(ctx, t, cfg)
results := pbtsTest.run(ctx, t)
require.NotNil(t, results.height2.prevote.BlockID.Hash)
}
func TestTooFarInThePastProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
initialTime := time.Now()
// localtime > proposedBlockTime + MsgDelay + Precision
cfg := pbtsTestConfiguration{
synchronyParams: types.SynchronyParams{
Precision: 1 * time.Millisecond,
MessageDelay: 10 * time.Millisecond,
},
timeoutPropose: 50 * time.Millisecond,
genesisTime: initialTime,
height2ProposedBlockTime: initialTime.Add(10 * time.Millisecond),
height2ProposalDeliverTime: initialTime.Add(21 * time.Millisecond),
}
pbtsTest := newPBTSTestHarness(ctx, t, cfg)
results := pbtsTest.run(ctx, t)
require.Nil(t, results.height2.prevote.BlockID.Hash)
}
func TestTooFarInTheFutureProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
initialTime := time.Now()
// localtime < proposedBlockTime - Precision
cfg := pbtsTestConfiguration{
synchronyParams: types.SynchronyParams{
Precision: 1 * time.Millisecond,
MessageDelay: 10 * time.Millisecond,
},
timeoutPropose: 50 * time.Millisecond,
genesisTime: initialTime,
height2ProposedBlockTime: initialTime.Add(100 * time.Millisecond),
height2ProposalDeliverTime: initialTime.Add(10 * time.Millisecond),
height4ProposedBlockTime: initialTime.Add(150 * time.Millisecond),
}
pbtsTest := newPBTSTestHarness(ctx, t, cfg)
results := pbtsTest.run(ctx, t)
require.Nil(t, results.height2.prevote.BlockID.Hash)
}
+4 -3
View File
@@ -16,6 +16,7 @@ import (
tmevents "github.com/tendermint/tendermint/libs/events"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
tmtime "github.com/tendermint/tendermint/libs/time"
tmcons "github.com/tendermint/tendermint/proto/tendermint/consensus"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
@@ -1184,7 +1185,7 @@ func (r *Reactor) handleDataMessage(ctx context.Context, envelope *p2p.Envelope,
select {
case <-ctx.Done():
return ctx.Err()
case r.state.peerMsgQueue <- msgInfo{pMsg, envelope.From}:
case r.state.peerMsgQueue <- msgInfo{pMsg, envelope.From, tmtime.Now()}:
}
case *tmcons.ProposalPOL:
ps.ApplyProposalPOLMessage(msgI.(*ProposalPOLMessage))
@@ -1194,7 +1195,7 @@ func (r *Reactor) handleDataMessage(ctx context.Context, envelope *p2p.Envelope,
ps.SetHasProposalBlockPart(bpMsg.Height, bpMsg.Round, int(bpMsg.Part.Index))
r.Metrics.BlockParts.With("peer_id", string(envelope.From)).Add(1)
select {
case r.state.peerMsgQueue <- msgInfo{bpMsg, envelope.From}:
case r.state.peerMsgQueue <- msgInfo{bpMsg, envelope.From, tmtime.Now()}:
return nil
case <-ctx.Done():
return ctx.Err()
@@ -1238,7 +1239,7 @@ func (r *Reactor) handleVoteMessage(ctx context.Context, envelope *p2p.Envelope,
ps.SetHasVote(vMsg.Vote)
select {
case r.state.peerMsgQueue <- msgInfo{vMsg, envelope.From}:
case r.state.peerMsgQueue <- msgInfo{vMsg, envelope.From, tmtime.Now()}:
return nil
case <-ctx.Done():
return ctx.Err()
+6 -7
View File
@@ -324,7 +324,7 @@ func TestReactorBasic(t *testing.T) {
cfg := configSetup(t)
n := 4
states, cleanup := randConsensusState(ctx, t,
states, cleanup := makeConsensusState(ctx, t,
cfg, n, "consensus_reactor_test",
newMockTickerFunc(true), newKVStore)
t.Cleanup(cleanup)
@@ -381,7 +381,8 @@ func TestReactorWithEvidence(t *testing.T) {
tickerFunc := newMockTickerFunc(true)
appFunc := newKVStore
genDoc, privVals := factory.RandGenesisDoc(ctx, t, cfg, n, false, 30)
valSet, privVals := factory.ValidatorSet(ctx, t, n, 30)
genDoc := factory.GenesisDoc(cfg, time.Now(), valSet.Validators, nil)
states := make([]*State, n)
logger := consensusLogger()
@@ -484,8 +485,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) {
cfg := configSetup(t)
n := 4
states, cleanup := randConsensusState(
ctx,
states, cleanup := makeConsensusState(ctx,
t,
cfg,
n,
@@ -541,7 +541,7 @@ func TestReactorRecordsVotesAndBlockParts(t *testing.T) {
cfg := configSetup(t)
n := 4
states, cleanup := randConsensusState(ctx, t,
states, cleanup := makeConsensusState(ctx, t,
cfg, n, "consensus_reactor_test",
newMockTickerFunc(true), newKVStore)
t.Cleanup(cleanup)
@@ -606,8 +606,7 @@ func TestReactorVotingPowerChange(t *testing.T) {
cfg := configSetup(t)
n := 4
states, cleanup := randConsensusState(
ctx,
states, cleanup := makeConsensusState(ctx,
t,
cfg,
n,
+29 -24
View File
@@ -367,8 +367,8 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
ensureNewProposal(t, proposalCh, height, round)
rs := css[0].GetRoundState()
signAddVotes(ctx, t, sim.Config, css[0], tmproto.PrecommitType,
rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(),
signAddVotes(ctx, t, css[0], tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
vss[1:nVals]...)
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -389,7 +389,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
require.NoError(t, err)
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)
@@ -402,8 +402,8 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
}
ensureNewProposal(t, proposalCh, height, round)
rs = css[0].GetRoundState()
signAddVotes(ctx, t, sim.Config, css[0], tmproto.PrecommitType,
rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(),
signAddVotes(ctx, t, css[0], tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
vss[1:nVals]...)
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -423,7 +423,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
require.NoError(t, err)
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)
@@ -436,8 +436,8 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
}
ensureNewProposal(t, proposalCh, height, round)
rs = css[0].GetRoundState()
signAddVotes(ctx, t, sim.Config, css[0], tmproto.PrecommitType,
rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(),
signAddVotes(ctx, t, css[0], tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
vss[1:nVals]...)
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -486,7 +486,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
selfIndex := valIndexFn(0)
require.NotEqual(t, -1, selfIndex)
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)
@@ -508,9 +508,10 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
if i == selfIndex {
continue
}
signAddVotes(ctx, t, sim.Config, css[0],
tmproto.PrecommitType, rs.ProposalBlock.Hash(),
rs.ProposalBlockParts.Header(), newVss[i])
signAddVotes(ctx, t, css[0],
tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
newVss[i])
}
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -532,9 +533,10 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
if i == selfIndex {
continue
}
signAddVotes(ctx, t, sim.Config, css[0],
tmproto.PrecommitType, rs.ProposalBlock.Hash(),
rs.ProposalBlockParts.Header(), newVss[i])
signAddVotes(ctx, t, css[0],
tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
newVss[i])
}
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -555,8 +557,7 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
selfIndex = valIndexFn(0)
require.NotEqual(t, -1, selfIndex)
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)
@@ -573,9 +574,10 @@ func setupSimulator(ctx context.Context, t *testing.T) *simulatorTestSuite {
if i == selfIndex {
continue
}
signAddVotes(ctx, t, sim.Config, css[0],
tmproto.PrecommitType, rs.ProposalBlock.Hash(),
rs.ProposalBlockParts.Header(), newVss[i])
signAddVotes(ctx, t, css[0],
tmproto.PrecommitType, sim.Config.ChainID(),
types.BlockID{Hash: rs.ProposalBlock.Hash(), PartSetHeader: rs.ProposalBlockParts.Header()},
newVss[i])
}
ensureNewRound(t, newRoundCh, height+1, 0)
@@ -823,14 +825,15 @@ func testHandshakeReplay(
}
// now start the app using the handshake - it should sync
genDoc, _ := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
require.NoError(t, err)
handshaker := NewHandshaker(logger, stateStore, state, store, eventbus.NopEventBus{}, genDoc)
proxyApp := proxy.NewAppConns(clientCreator2, logger, proxy.NopMetrics())
require.NoError(t, proxyApp.Start(ctx), "Error starting proxy app connections")
t.Cleanup(func() { cancel(); proxyApp.Wait() })
err := handshaker.Handshake(ctx, proxyApp)
err = handshaker.Handshake(ctx, proxyApp)
if expectError {
require.Error(t, err)
return
@@ -1011,7 +1014,8 @@ func TestHandshakePanicsIfAppReturnsWrongAppHash(t *testing.T) {
require.NoError(t, err)
stateDB, state, store := stateAndStore(t, cfg, pubKey, appVersion)
stateStore := sm.NewStore(stateDB)
genDoc, _ := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
genDoc, err := sm.MakeGenesisDocFromFile(cfg.GenesisFile())
require.NoError(t, err)
state.LastValidators = state.Validators.Copy()
// mode = 0 for committing all the blocks
blocks := sf.MakeBlocks(ctx, t, 3, &state, privVal)
@@ -1277,7 +1281,8 @@ func TestHandshakeUpdatesValidators(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
val, _, err := factory.RandValidator(ctx, true, 10)
votePower := 10 + int64(rand.Uint32())
val, _, err := factory.Validator(ctx, votePower)
require.NoError(t, err)
vals := types.NewValidatorSet([]*types.Validator{val})
app := &initChainApp{vals: types.TM2PB.ValidatorUpdates(vals)}
+197 -131
View File
@@ -47,15 +47,17 @@ var msgQueueSize = 1000
// msgs from the reactor which may update the state
type msgInfo struct {
Msg Message
PeerID types.NodeID
Msg Message
PeerID types.NodeID
ReceiveTime time.Time
}
func (msgInfo) TypeTag() string { return "tendermint/wal/MsgInfo" }
type msgInfoJSON struct {
Msg json.RawMessage `json:"msg"`
PeerID types.NodeID `json:"peer_key"`
Msg json.RawMessage `json:"msg"`
PeerID types.NodeID `json:"peer_key"`
ReceiveTime time.Time `json:"receive_time"`
}
func (m msgInfo) MarshalJSON() ([]byte, error) {
@@ -63,7 +65,7 @@ func (m msgInfo) MarshalJSON() ([]byte, error) {
if err != nil {
return nil, err
}
return json.Marshal(msgInfoJSON{Msg: msg, PeerID: m.PeerID})
return json.Marshal(msgInfoJSON{Msg: msg, PeerID: m.PeerID, ReceiveTime: m.ReceiveTime})
}
func (m *msgInfo) UnmarshalJSON(data []byte) error {
@@ -163,7 +165,7 @@ type State struct {
// some functions can be overwritten for testing
decideProposal func(ctx context.Context, height int64, round int32)
doPrevote func(ctx context.Context, height int64, round int32)
setProposal func(proposal *types.Proposal) error
setProposal func(proposal *types.Proposal, t time.Time) error
// closed when we finish shutting down
done chan struct{}
@@ -546,14 +548,14 @@ func (cs *State) AddVote(ctx context.Context, vote *types.Vote, peerID types.Nod
select {
case <-ctx.Done():
return ctx.Err()
case cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, ""}:
case cs.internalMsgQueue <- msgInfo{&VoteMessage{vote}, "", tmtime.Now()}:
return nil
}
} else {
select {
case <-ctx.Done():
return ctx.Err()
case cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID}:
case cs.peerMsgQueue <- msgInfo{&VoteMessage{vote}, peerID, tmtime.Now()}:
return nil
}
}
@@ -568,14 +570,14 @@ func (cs *State) SetProposal(ctx context.Context, proposal *types.Proposal, peer
select {
case <-ctx.Done():
return ctx.Err()
case cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, ""}:
case cs.internalMsgQueue <- msgInfo{&ProposalMessage{proposal}, "", tmtime.Now()}:
return nil
}
} else {
select {
case <-ctx.Done():
return ctx.Err()
case cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID}:
case cs.peerMsgQueue <- msgInfo{&ProposalMessage{proposal}, peerID, tmtime.Now()}:
return nil
}
}
@@ -589,14 +591,14 @@ func (cs *State) AddProposalBlockPart(ctx context.Context, height int64, round i
select {
case <-ctx.Done():
return ctx.Err()
case cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, ""}:
case cs.internalMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, "", tmtime.Now()}:
return nil
}
} else {
select {
case <-ctx.Done():
return ctx.Err()
case cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID}:
case cs.peerMsgQueue <- msgInfo{&BlockPartMessage{height, round, part}, peerID, tmtime.Now()}:
return nil
}
}
@@ -785,6 +787,7 @@ func (cs *State) updateToState(ctx context.Context, state sm.State) {
cs.Validators = validators
cs.Proposal = nil
cs.ProposalReceiveTime = time.Time{}
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
cs.LockedRound = -1
@@ -933,7 +936,7 @@ func (cs *State) handleMsg(ctx context.Context, mi msgInfo) {
case *ProposalMessage:
// will not cause transition.
// once proposal is set, we can receive block parts
err = cs.setProposal(msg.Proposal)
err = cs.setProposal(msg.Proposal, mi.ReceiveTime)
case *BlockPartMessage:
// if the proposal is complete, we'll enterPrevote or tryFinalizeCommit
@@ -1125,6 +1128,7 @@ func (cs *State) enterNewRound(ctx context.Context, height int64, round int32) {
} else {
logger.Debug("resetting proposal info")
cs.Proposal = nil
cs.ProposalReceiveTime = time.Time{}
cs.ProposalBlock = nil
cs.ProposalBlockParts = nil
}
@@ -1147,9 +1151,10 @@ func (cs *State) enterNewRound(ctx context.Context, height int64, round int32) {
cs.scheduleTimeout(cs.config.CreateEmptyBlocksInterval, height, round,
cstypes.RoundStepNewRound)
}
} else {
cs.enterPropose(ctx, height, round)
return
}
cs.enterPropose(ctx, height, round)
}
// needProofBlock returns true on the first height (so the genesis app hash is signed right away)
@@ -1182,6 +1187,16 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
return
}
// If this validator is the proposer of this round, and the previous block time is later than
// our local clock time, wait to propose until our local clock time has passed the block time.
if cs.privValidatorPubKey != nil && cs.isProposer(cs.privValidatorPubKey.Address()) {
proposerWaitTime := proposerWaitTime(tmtime.DefaultSource{}, cs.state.LastBlockTime)
if proposerWaitTime > 0 {
cs.scheduleTimeout(proposerWaitTime, height, round, cstypes.RoundStepNewRound)
return
}
}
logger.Debug("entering propose step", "current", fmt.Sprintf("%v/%v/%v", cs.Height, cs.Round, cs.Step))
defer func() {
@@ -1202,12 +1217,10 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
// Nothing more to do if we're not a validator
if cs.privValidator == nil {
logger.Debug("node is not a validator")
logger.Debug("propose step; not proposing since node is not a validator")
return
}
logger.Debug("node is a validator")
if cs.privValidatorPubKey == nil {
// If this node is a validator & proposer in the current round, it will
// miss the opportunity to create a block.
@@ -1215,18 +1228,20 @@ func (cs *State) enterPropose(ctx context.Context, height int64, round int32) {
return
}
address := cs.privValidatorPubKey.Address()
addr := cs.privValidatorPubKey.Address()
// if not a validator, we're done
if !cs.Validators.HasAddress(address) {
logger.Debug("node is not a validator", "addr", address, "vals", cs.Validators)
if !cs.Validators.HasAddress(addr) {
logger.Debug("propose step; not proposing since node is not in the validator set",
"addr", addr,
"vals", cs.Validators)
return
}
if cs.isProposer(address) {
if cs.isProposer(addr) {
logger.Debug(
"propose step; our turn to propose",
"proposer", address,
"proposer", addr,
)
cs.decideProposal(ctx, height, round)
@@ -1267,7 +1282,7 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round
// 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
@@ -1277,11 +1292,11 @@ func (cs *State) defaultDecideProposal(ctx context.Context, height int64, round
proposal.Signature = p.Signature
// send proposal and block parts on internal msg queue
cs.sendInternalMessage(ctx, msgInfo{&ProposalMessage{proposal}, ""})
cs.sendInternalMessage(ctx, msgInfo{&ProposalMessage{proposal}, "", tmtime.Now()})
for i := 0; i < int(blockParts.Total()); i++ {
part := blockParts.GetPart(i)
cs.sendInternalMessage(ctx, msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""})
cs.sendInternalMessage(ctx, msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, "", tmtime.Now()})
}
cs.logger.Debug("signed proposal", "height", height, "round", round, "proposal", proposal)
@@ -1348,8 +1363,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(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
@@ -1376,19 +1394,47 @@ func (cs *State) enterPrevote(ctx context.Context, height int64, round int32) {
// (so we have more time to try and collect +2/3 prevotes for a single block)
}
func (cs *State) proposalIsTimely() bool {
sp := types.SynchronyParams{
Precision: cs.state.ConsensusParams.Synchrony.Precision,
MessageDelay: cs.state.ConsensusParams.Synchrony.MessageDelay,
}
return cs.Proposal.IsTimely(cs.ProposalReceiveTime, sp, cs.state.InitialHeight)
}
func (cs *State) defaultDoPrevote(ctx context.Context, 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(ctx, tmproto.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header())
// Check that a proposed block was not received within this round (and thus executing this from a timeout).
if cs.ProposalBlock == nil {
logger.Debug("prevote step: ProposalBlock is nil; prevoting nil")
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
// If ProposalBlock is nil, prevote nil.
if cs.ProposalBlock == nil {
logger.Debug("prevote step: ProposalBlock is nil")
if cs.Proposal == nil {
logger.Debug("prevote step: did not receive proposal; prevoting nil")
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
if !cs.Proposal.Timestamp.Equal(cs.ProposalBlock.Header.Time) {
logger.Debug("prevote step: proposal timestamp not equal; prevoting nil")
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
if cs.Proposal.POLRound == -1 && cs.LockedRound == -1 && !cs.proposalIsTimely() {
logger.Debug("prevote step: Proposal is not timely; prevoting nil",
"proposed",
tmtime.Canonical(cs.Proposal.Timestamp).Format(time.RFC3339Nano),
"received",
tmtime.Canonical(cs.ProposalReceiveTime).Format(time.RFC3339Nano),
"msg_delay",
cs.state.ConsensusParams.Synchrony.MessageDelay,
"precision",
cs.state.ConsensusParams.Synchrony.Precision)
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
return
}
@@ -1397,16 +1443,72 @@ func (cs *State) defaultDoPrevote(ctx context.Context, height int64, round int32
err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock)
if err != nil {
// ProposalBlock is invalid, prevote nil.
logger.Error("prevote step: ProposalBlock is invalid", "err", err)
logger.Error("prevote step: ProposalBlock is invalid; prevoting nil", "err", err)
cs.signAddVote(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
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(ctx, 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(ctx, 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(ctx, 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(ctx, 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(ctx, 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(ctx, tmproto.PrevoteType, nil, types.PartSetHeader{})
}
// Enter: any +2/3 prevotes at next round.
@@ -1444,7 +1546,6 @@ func (cs *State) enterPrevoteWait(ctx context.Context, height int64, round int32
// Enter: `timeoutPrecommit` after any +2/3 precommits.
// Enter: +2/3 precomits for block or nil.
// Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round)
// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil,
// else, precommit nil otherwise.
func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32) {
logger := cs.logger.With("height", height, "round", round)
@@ -1491,47 +1592,50 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32)
panic(fmt.Sprintf("this POLRound should be %v but got %v", round, polRound))
}
// +2/3 prevoted nil. Unlock and precommit nil.
if len(blockID.Hash) == 0 {
if cs.LockedBlock == nil {
logger.Debug("precommit step; +2/3 prevoted for nil")
} else {
logger.Debug("precommit step; +2/3 prevoted for nil; unlocking")
cs.LockedRound = -1
cs.LockedBlock = nil
cs.LockedBlockParts = nil
if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil {
logger.Error("failed publishing event unlock", "err", err)
}
}
// +2/3 prevoted nil. Precommit nil.
if blockID.IsNil() {
logger.Debug("precommit step: +2/3 prevoted for nil; precommitting nil")
cs.signAddVote(ctx, tmproto.PrecommitType, nil, types.PartSetHeader{})
return
}
// At this point, +2/3 prevoted for a particular block.
// If we never received a proposal for this block, we must precommit nil
if cs.Proposal == nil || cs.ProposalBlock == nil {
logger.Debug("precommit step; did not receive proposal, precommitting nil")
cs.signAddVote(ctx, 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("precommit step: proposal timestamp not equal; precommitting nil")
cs.signAddVote(ctx, 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")
logger.Debug("precommit step: +2/3 prevoted locked block; relocking")
cs.LockedRound = round
if err := cs.eventBus.PublishEventRelock(ctx, cs.RoundStateEvent()); err != nil {
logger.Error("failed publishing event relock", "err", err)
logger.Error("precommit step: failed publishing event relock", "err", err)
}
cs.signAddVote(ctx, tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader)
return
}
// If +2/3 prevoted for proposal block, stage and precommit it
// If greater than 2/3 of the voting power on the network prevoted for
// the proposed block, update our locked block to this block and issue a
// precommit vote for it.
if cs.ProposalBlock.HashesTo(blockID.Hash) {
logger.Debug("precommit step; +2/3 prevoted proposal block; locking", "hash", blockID.Hash)
logger.Debug("precommit step: +2/3 prevoted proposal block; locking", "hash", blockID.Hash)
// Validate the block.
if err := cs.blockExec.ValidateBlock(cs.state, cs.ProposalBlock); err != nil {
panic(fmt.Errorf("precommit step; +2/3 prevoted for an invalid block: %w", err))
panic(fmt.Sprintf("precommit step: +2/3 prevoted for an invalid block %v; relocking", err))
}
cs.LockedRound = round
@@ -1539,7 +1643,7 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32)
cs.LockedBlockParts = cs.ProposalBlockParts
if err := cs.eventBus.PublishEventLock(ctx, cs.RoundStateEvent()); err != nil {
logger.Error("failed publishing event lock", "err", err)
logger.Error("precommit step: failed publishing event lock", "err", err)
}
cs.signAddVote(ctx, tmproto.PrecommitType, blockID.Hash, blockID.PartSetHeader)
@@ -1547,23 +1651,14 @@ func (cs *State) enterPrecommit(ctx context.Context, height int64, round int32)
}
// There was a polka in this round for a block we don't have.
// Fetch that block, unlock, and precommit nil.
// The +2/3 prevotes for this round is the POL for our unlock.
logger.Debug("precommit step; +2/3 prevotes for a block we do not have; voting nil", "block_id", blockID)
cs.LockedRound = -1
cs.LockedBlock = nil
cs.LockedBlockParts = nil
// Fetch that block, and precommit nil.
logger.Debug("precommit step: +2/3 prevotes for a block we do not have; voting nil", "block_id", blockID)
if !cs.ProposalBlockParts.HasHeader(blockID.PartSetHeader) {
cs.ProposalBlock = nil
cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartSetHeader)
}
if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil {
logger.Error("failed publishing event unlock", "err", err)
}
cs.signAddVote(ctx, tmproto.PrecommitType, nil, types.PartSetHeader{})
}
@@ -1671,7 +1766,7 @@ func (cs *State) tryFinalizeCommit(ctx context.Context, height int64) {
}
blockID, ok := cs.Votes.Precommits(cs.CommitRound).TwoThirdsMajority()
if !ok || len(blockID.Hash) == 0 {
if !ok || blockID.IsNil() {
logger.Error("failed attempt to finalize commit; there was no +2/3 majority or +2/3 was for nil")
return
}
@@ -1893,10 +1988,10 @@ func (cs *State) RecordMetrics(height int64, block *types.Block) {
//-----------------------------------------------------------------------------
func (cs *State) defaultSetProposal(proposal *types.Proposal) error {
func (cs *State) defaultSetProposal(proposal *types.Proposal, recvTime time.Time) error {
// Already have one
// TODO: possibly catch double proposals
if cs.Proposal != nil {
if cs.Proposal != nil || proposal == nil {
return nil
}
@@ -1921,6 +2016,7 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error {
proposal.Signature = p.Signature
cs.Proposal = proposal
cs.ProposalReceiveTime = recvTime
// We don't update cs.ProposalBlockParts if it is already set.
// This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round.
// TODO: We can check if Proposal is for a different block as this is a sign of misbehavior!
@@ -2000,7 +2096,7 @@ func (cs *State) addProposalBlockPart(
// Update Valid* if we can.
prevotes := cs.Votes.Prevotes(cs.Round)
blockID, hasTwoThirds := prevotes.TwoThirdsMajority()
if hasTwoThirds && !blockID.IsZero() && (cs.ValidRound < cs.Round) {
if hasTwoThirds && !blockID.IsNil() && (cs.ValidRound < cs.Round) {
if cs.ProposalBlock.HashesTo(blockID.Hash) {
cs.logger.Debug(
"updating valid block to new proposal block",
@@ -2153,33 +2249,13 @@ func (cs *State) addVote(
prevotes := cs.Votes.Prevotes(vote.Round)
cs.logger.Debug("added vote to prevote", "vote", vote, "prevotes", prevotes.StringShort())
// If +2/3 prevotes for a block or nil for *any* round:
if blockID, ok := prevotes.TwoThirdsMajority(); ok {
// There was a polka!
// If we're locked but this is a recent polka, unlock.
// If it matches our ProposalBlock, update the ValidBlock
// Unlock if `cs.LockedRound < vote.Round <= cs.Round`
// NOTE: If vote.Round > cs.Round, we'll deal with it when we get to vote.Round
if (cs.LockedBlock != nil) &&
(cs.LockedRound < vote.Round) &&
(vote.Round <= cs.Round) &&
!cs.LockedBlock.HashesTo(blockID.Hash) {
cs.logger.Debug("unlocking because of POL", "locked_round", cs.LockedRound, "pol_round", vote.Round)
cs.LockedRound = -1
cs.LockedBlock = nil
cs.LockedBlockParts = nil
if err := cs.eventBus.PublishEventUnlock(ctx, cs.RoundStateEvent()); err != nil {
return added, err
}
}
// Check to see if >2/3 of the voting power on the network voted for any non-nil block.
if blockID, ok := prevotes.TwoThirdsMajority(); ok && !blockID.IsNil() {
// Greater than 2/3 of the voting power on the network voted for some
// non-nil block
// Update Valid* if we can.
// NOTE: our proposal block may be nil or not what received a polka..
if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) {
if cs.ValidRound < vote.Round && vote.Round == cs.Round {
if cs.ProposalBlock.HashesTo(blockID.Hash) {
cs.logger.Debug("updating valid block because of POL", "valid_round", cs.ValidRound, "pol_round", vote.Round)
cs.ValidRound = vote.Round
@@ -2215,7 +2291,7 @@ func (cs *State) addVote(
case cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step: // current round
blockID, ok := prevotes.TwoThirdsMajority()
if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) {
if ok && (cs.isProposalComplete() || blockID.IsNil()) {
cs.enterPrecommit(ctx, height, vote.Round)
} else if prevotes.HasTwoThirdsAny() {
cs.enterPrevoteWait(ctx, height, vote.Round)
@@ -2243,7 +2319,7 @@ func (cs *State) addVote(
cs.enterNewRound(ctx, height, vote.Round)
cs.enterPrecommit(ctx, height, vote.Round)
if len(blockID.Hash) != 0 {
if !blockID.IsNil() {
cs.enterCommit(ctx, height, vote.Round)
if cs.config.SkipTimeoutCommit && precommits.HasAll() {
cs.enterNewRound(ctx, cs.Height, 0)
@@ -2288,7 +2364,7 @@ func (cs *State) signVote(
ValidatorIndex: valIdx,
Height: cs.Height,
Round: cs.Round,
Timestamp: cs.voteTime(),
Timestamp: tmtime.Now(),
Type: msgType,
BlockID: types.BlockID{Hash: hash, PartSetHeader: header},
}
@@ -2318,30 +2394,6 @@ func (cs *State) signVote(
return vote, err
}
// voteTime ensures monotonicity of the time a validator votes on.
// It ensures that for a prior block with a BFT-timestamp of T,
// any vote from this validator will have time at least time T + 1ms.
// This is needed, as monotonicity of time is a guarantee that BFT time provides.
func (cs *State) voteTime() time.Time {
now := tmtime.Now()
minVoteTime := now
// Minimum time increment between blocks
const timeIota = time.Millisecond
// TODO: We should remove next line in case we don't vote for v in case cs.ProposalBlock == nil,
// even if cs.LockedBlock != nil. See https://docs.tendermint.com/master/spec/.
if cs.LockedBlock != nil {
// See the BFT time spec https://docs.tendermint.com/master/spec/consensus/bft-time.html
minVoteTime = cs.LockedBlock.Time.Add(timeIota)
} else if cs.ProposalBlock != nil {
minVoteTime = cs.ProposalBlock.Time.Add(timeIota)
}
if now.After(minVoteTime) {
return now
}
return minVoteTime
}
// sign the vote and publish on internalMsgQueue
func (cs *State) signAddVote(ctx context.Context, msgType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
if cs.privValidator == nil { // the node does not have a key
@@ -2362,7 +2414,7 @@ func (cs *State) signAddVote(ctx context.Context, msgType tmproto.SignedMsgType,
// TODO: pass pubKey to signVote
vote, err := cs.signVote(ctx, msgType, hash, header)
if err == nil {
cs.sendInternalMessage(ctx, msgInfo{&VoteMessage{vote}, ""})
cs.sendInternalMessage(ctx, msgInfo{&VoteMessage{vote}, "", tmtime.Now()})
cs.logger.Debug("signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote)
return vote
}
@@ -2509,3 +2561,17 @@ func repairWalFile(src, dst string) error {
return nil
}
// proposerWaitTime determines how long the proposer should wait to propose its next block.
// If the result is zero, a block can be proposed immediately.
//
// Block times must be monotonically increasing, so if the block time of the previous
// block is larger than the proposer's current time, then the proposer will sleep
// until its local clock exceeds the previous block time.
func proposerWaitTime(lt tmtime.Source, bt time.Time) time.Duration {
t := lt.Now()
if bt.After(t) {
return bt.Sub(t)
}
return 0
}
File diff suppressed because it is too large Load Diff
@@ -33,7 +33,7 @@ func TestPeerCatchupRounds(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
valSet, privVals := factory.RandValidatorSet(ctx, t, 10, 1)
valSet, privVals := factory.ValidatorSet(ctx, t, 10, 1)
hvs := NewHeightVoteSet(cfg.ChainID(), 1, valSet)
+9 -8
View File
@@ -71,14 +71,15 @@ type RoundState struct {
StartTime time.Time `json:"start_time"`
// Subjective time when +2/3 precommits for Block at Round were found
CommitTime time.Time `json:"commit_time"`
Validators *types.ValidatorSet `json:"validators"`
Proposal *types.Proposal `json:"proposal"`
ProposalBlock *types.Block `json:"proposal_block"`
ProposalBlockParts *types.PartSet `json:"proposal_block_parts"`
LockedRound int32 `json:"locked_round"`
LockedBlock *types.Block `json:"locked_block"`
LockedBlockParts *types.PartSet `json:"locked_block_parts"`
CommitTime time.Time `json:"commit_time"`
Validators *types.ValidatorSet `json:"validators"`
Proposal *types.Proposal `json:"proposal"`
ProposalReceiveTime time.Time `json:"proposal_receive_time"`
ProposalBlock *types.Block `json:"proposal_block"`
ProposalBlockParts *types.PartSet `json:"proposal_block_parts"`
LockedRound int32 `json:"locked_round"`
LockedBlock *types.Block `json:"locked_block"`
LockedBlockParts *types.PartSet `json:"locked_block_parts"`
// Last known round with POL for non-nil valid block.
ValidRound int32 `json:"valid_round"`
-4
View File
@@ -186,10 +186,6 @@ func (b *EventBus) PublishEventPolka(ctx context.Context, data types.EventDataRo
return b.Publish(ctx, types.EventPolkaValue, data)
}
func (b *EventBus) PublishEventUnlock(ctx context.Context, data types.EventDataRoundState) error {
return b.Publish(ctx, types.EventUnlockValue, data)
}
func (b *EventBus) PublishEventRelock(ctx context.Context, data types.EventDataRoundState) error {
return b.Publish(ctx, types.EventRelockValue, data)
}
-3
View File
@@ -385,7 +385,6 @@ func TestEventBusPublish(t *testing.T) {
require.NoError(t, eventBus.PublishEventNewRound(ctx, types.EventDataNewRound{}))
require.NoError(t, eventBus.PublishEventCompleteProposal(ctx, types.EventDataCompleteProposal{}))
require.NoError(t, eventBus.PublishEventPolka(ctx, types.EventDataRoundState{}))
require.NoError(t, eventBus.PublishEventUnlock(ctx, types.EventDataRoundState{}))
require.NoError(t, eventBus.PublishEventRelock(ctx, types.EventDataRoundState{}))
require.NoError(t, eventBus.PublishEventLock(ctx, types.EventDataRoundState{}))
require.NoError(t, eventBus.PublishEventValidatorSetUpdates(ctx, types.EventDataValidatorSetUpdates{}))
@@ -487,7 +486,6 @@ var events = []string{
types.EventTimeoutProposeValue,
types.EventCompleteProposalValue,
types.EventPolkaValue,
types.EventUnlockValue,
types.EventLockValue,
types.EventRelockValue,
types.EventTimeoutWaitValue,
@@ -508,7 +506,6 @@ var queries = []*tmquery.Query{
types.EventQueryTimeoutPropose,
types.EventQueryCompleteProposal,
types.EventQueryPolka,
types.EventQueryUnlock,
types.EventQueryLock,
types.EventQueryRelock,
types.EventQueryTimeoutWait,
+1 -2
View File
@@ -41,8 +41,7 @@ func TestEvidencePoolBasic(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
valSet, privVals := factory.RandValidatorSet(ctx, t, 1, 10)
valSet, privVals := factory.ValidatorSet(ctx, t, 1, 10)
blockStore.On("LoadBlockMeta", mock.AnythingOfType("int64")).Return(
&types.BlockMeta{Header: types.Header{Time: defaultEvidenceTime}},
)
+6 -8
View File
@@ -201,7 +201,7 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conflictingVals, conflictingPrivVals := factory.RandValidatorSet(ctx, t, 5, 10)
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
conflictingHeader := factory.MakeHeader(t, &types.Header{
ChainID: evidenceChainID,
@@ -294,12 +294,10 @@ func TestVerifyLightClientAttack_Equivocation(t *testing.T) {
}
func TestVerifyLightClientAttack_Amnesia(t *testing.T) {
var height int64 = 10
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conflictingVals, conflictingPrivVals := factory.RandValidatorSet(ctx, t, 5, 10)
var height int64 = 10
conflictingVals, conflictingPrivVals := factory.ValidatorSet(ctx, t, 5, 10)
conflictingHeader := factory.MakeHeader(t, &types.Header{
ChainID: evidenceChainID,
@@ -496,14 +494,14 @@ func makeLunaticEvidence(
) (ev *types.LightClientAttackEvidence, trusted *types.LightBlock, common *types.LightBlock) {
t.Helper()
commonValSet, commonPrivVals := factory.RandValidatorSet(ctx, t, totalVals, defaultVotingPower)
commonValSet, commonPrivVals := factory.ValidatorSet(ctx, t, totalVals, defaultVotingPower)
require.Greater(t, totalVals, byzVals)
// extract out the subset of byzantine validators in the common validator set
byzValSet, byzPrivVals := commonValSet.Validators[:byzVals], commonPrivVals[:byzVals]
phantomValSet, phantomPrivVals := factory.RandValidatorSet(ctx, t, phantomVals, defaultVotingPower)
phantomValSet, phantomPrivVals := factory.ValidatorSet(ctx, t, phantomVals, defaultVotingPower)
conflictingVals := phantomValSet.Copy()
require.NoError(t, conflictingVals.UpdateWithChangeSet(byzValSet))
@@ -558,7 +556,7 @@ func makeLunaticEvidence(
ValidatorSet: commonValSet,
}
trustedBlockID := factory.MakeBlockIDWithHash(trustedHeader.Hash())
trustedVals, privVals := factory.RandValidatorSet(ctx, t, totalVals, defaultVotingPower)
trustedVals, privVals := factory.ValidatorSet(ctx, t, totalVals, defaultVotingPower)
trustedVoteSet := types.NewVoteSet(evidenceChainID, height, 1, tmproto.SignedMsgType(2), trustedVals)
trustedCommit, err := factory.MakeCommit(ctx, trustedBlockID, height, 1, trustedVoteSet, privVals, defaultEvidenceTime)
require.NoError(t, err)
+2 -2
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"math/rand"
"testing"
"time"
@@ -258,7 +259,6 @@ func makeRandomStateFromValidatorSet(
InitialHeight: 1,
}
}
func makeRandomStateFromConsensusParams(
ctx context.Context,
t *testing.T,
@@ -267,7 +267,7 @@ func makeRandomStateFromConsensusParams(
lastHeightConsensusParamsChanged int64,
) sm.State {
t.Helper()
val, _, err := factory.RandValidator(ctx, true, 10)
val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
valSet := types.NewValidatorSet([]*types.Validator{val})
return sm.State{
+7 -14
View File
@@ -19,11 +19,9 @@ func TestRollback(t *testing.T) {
height int64 = 100
nextHeight int64 = 101
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
blockStore := &mocks.BlockStore{}
stateStore := setupStateStore(ctx, t, height)
stateStore := setupStateStore(t, height)
initialState, err := stateStore.Load()
require.NoError(t, err)
@@ -83,10 +81,7 @@ func TestRollbackNoState(t *testing.T) {
func TestRollbackNoBlocks(t *testing.T) {
const height = int64(100)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stateStore := setupStateStore(ctx, t, height)
stateStore := setupStateStore(t, height)
blockStore := &mocks.BlockStore{}
blockStore.On("Height").Return(height)
blockStore.On("LoadBlockMeta", height-1).Return(nil)
@@ -98,11 +93,7 @@ func TestRollbackNoBlocks(t *testing.T) {
func TestRollbackDifferentStateHeight(t *testing.T) {
const height = int64(100)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stateStore := setupStateStore(ctx, t, height)
stateStore := setupStateStore(t, height)
blockStore := &mocks.BlockStore{}
blockStore.On("Height").Return(height + 2)
@@ -111,9 +102,11 @@ func TestRollbackDifferentStateHeight(t *testing.T) {
require.Equal(t, err.Error(), "statestore height (100) is not one below or equal to blockstore height (102)")
}
func setupStateStore(ctx context.Context, t *testing.T, height int64) state.Store {
func setupStateStore(t *testing.T, height int64) state.Store {
stateStore := state.NewStore(dbm.NewMemDB())
valSet, _ := factory.RandValidatorSet(ctx, t, 5, 10)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
valSet, _ := factory.ValidatorSet(ctx, t, 5, 10)
params := types.DefaultConsensusParams()
params.Version.AppVersion = 10
+1 -24
View File
@@ -268,7 +268,7 @@ func (state State) MakeBlock(
if height == state.InitialHeight {
timestamp = state.LastBlockTime // genesis time
} else {
timestamp = MedianTime(commit, state.LastValidators)
timestamp = time.Now()
}
// Fill rest of header with state data.
@@ -288,29 +288,6 @@ func (state State) MakeBlock(
return block, bps, nil
}
// MedianTime computes a median time for a given Commit (based on Timestamp field of votes messages) and the
// corresponding validator set. The computed time is always between timestamps of
// the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the
// computed value.
func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time {
weightedTimes := make([]*weightedTime, len(commit.Signatures))
totalVotingPower := int64(0)
for i, commitSig := range commit.Signatures {
if commitSig.Absent() {
continue
}
_, validator := validators.GetByAddress(commitSig.ValidatorAddress)
// If there's no condition, TestValidateBlockCommit panics; not needed normally.
if validator != nil {
totalVotingPower += validator.VotingPower
weightedTimes[i] = newWeightedTime(commitSig.Timestamp, validator.VotingPower)
}
}
return weightedMedian(weightedTimes, totalVotingPower)
}
//------------------------------------------------------------------------
// Genesis
+11 -12
View File
@@ -3,6 +3,7 @@ package state_test
import (
"context"
"fmt"
"math/rand"
"os"
"testing"
@@ -27,16 +28,15 @@ const (
)
func TestStoreBootstrap(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stateDB := dbm.NewMemDB()
stateStore := sm.NewStore(stateDB)
val, _, err := factory.RandValidator(ctx, true, 10)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val2, _, err := factory.RandValidator(ctx, true, 10)
val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val3, _, err := factory.RandValidator(ctx, true, 10)
val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
bootstrapState := makeRandomStateFromValidatorSet(vals, 100, 100)
@@ -58,16 +58,15 @@ func TestStoreBootstrap(t *testing.T) {
}
func TestStoreLoadValidators(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
stateDB := dbm.NewMemDB()
stateStore := sm.NewStore(stateDB)
val, _, err := factory.RandValidator(ctx, true, 10)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
val, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val2, _, err := factory.RandValidator(ctx, true, 10)
val2, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
val3, _, err := factory.RandValidator(ctx, true, 10)
val3, _, err := factory.Validator(ctx, 10+int64(rand.Uint32()))
require.NoError(t, err)
vals := types.NewValidatorSet([]*types.Validator{val, val2, val3})
-7
View File
@@ -114,13 +114,6 @@ func validateBlock(state State, block *types.Block) error {
state.LastBlockTime,
)
}
medianTime := MedianTime(block.LastCommit, state.LastValidators)
if !block.Time.Equal(medianTime) {
return fmt.Errorf("invalid block time. Expected %v, got %v",
medianTime,
block.Time,
)
}
case block.Height == state.InitialHeight:
genesisTime := state.LastBlockTime
-1
View File
@@ -64,7 +64,6 @@ func TestValidateBlockHeader(t *testing.T) {
{"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }},
{"Height wrong", func(block *types.Block) { block.Height += 10 }},
{"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 1) }},
{"Time wrong 2", func(block *types.Block) { block.Time = block.Time.Add(time.Second * 1) }},
{"LastBlockID wrong", func(block *types.Block) { block.LastBlockID.PartSetHeader.Total += 10 }},
{"LastCommitHash wrong", func(block *types.Block) { block.LastCommitHash = wrongHash }},
+1 -1
View File
@@ -294,7 +294,7 @@ loop:
func mockLBResp(ctx context.Context, t *testing.T, peer types.NodeID, height int64, time time.Time) lightBlockResponse {
t.Helper()
vals, pv := factory.RandValidatorSet(ctx, t, 3, 10)
vals, pv := factory.ValidatorSet(ctx, t, 3, 10)
_, _, lb := mockLB(ctx, t, height, time, factory.MakeBlockID(), vals, pv)
return lightBlockResponse{
block: lb,
+4 -4
View File
@@ -442,7 +442,7 @@ func TestReactor_LightBlockResponse(t *testing.T) {
h := factory.MakeHeader(t, &types.Header{})
h.Height = height
blockID := factory.MakeBlockIDWithHash(h.Hash())
vals, pv := factory.RandValidatorSet(ctx, t, 1, 10)
vals, pv := factory.ValidatorSet(ctx, t, 1, 10)
vote, err := factory.MakeVote(ctx, pv[0], h.ChainID, 0, h.Height, 0, 2,
blockID, factory.DefaultTestTime)
require.NoError(t, err)
@@ -733,7 +733,7 @@ func handleLightBlockRequests(
} else {
switch errorCount % 3 {
case 0: // send a different block
vals, pv := factory.RandValidatorSet(ctx, t, 3, 10)
vals, pv := factory.ValidatorSet(ctx, t, 3, 10)
_, _, lb := mockLB(ctx, t, int64(msg.Height), factory.DefaultTestTime, factory.MakeBlockID(), vals, pv)
differntLB, err := lb.ToProto()
require.NoError(t, err)
@@ -802,7 +802,7 @@ func buildLightBlockChain(ctx context.Context, t *testing.T, fromHeight, toHeigh
chain := make(map[int64]*types.LightBlock, toHeight-fromHeight)
lastBlockID := factory.MakeBlockID()
blockTime := startTime.Add(time.Duration(fromHeight-toHeight) * time.Minute)
vals, pv := factory.RandValidatorSet(ctx, t, 3, 10)
vals, pv := factory.ValidatorSet(ctx, t, 3, 10)
for height := fromHeight; height < toHeight; height++ {
vals, pv, chain[height] = mockLB(ctx, t, height, blockTime, lastBlockID, vals, pv)
lastBlockID = factory.MakeBlockIDWithHash(chain[height].Header.Hash())
@@ -822,7 +822,7 @@ func mockLB(ctx context.Context, t *testing.T, height int64, time time.Time, las
})
header.Version.App = testAppVersion
nextVals, nextPrivVals := factory.RandValidatorSet(ctx, t, 3, 10)
nextVals, nextPrivVals := factory.ValidatorSet(ctx, t, 3, 10)
header.ValidatorsHash = currentVals.Hash()
header.NextValidatorsHash = nextVals.Hash()
header.ConsensusHash = types.DefaultConsensusParams().HashConsensusParams()
+1 -1
View File
@@ -51,7 +51,7 @@ func MakeHeader(t *testing.T, h *types.Header) *types.Header {
if h.Height == 0 {
h.Height = 1
}
if h.LastBlockID.IsZero() {
if h.LastBlockID.IsNil() {
h.LastBlockID = MakeBlockID()
}
if h.ChainID == "" {
+20 -23
View File
@@ -1,36 +1,33 @@
package factory
import (
"context"
"sort"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/config"
tmtime "github.com/tendermint/tendermint/libs/time"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/types"
)
func RandGenesisDoc(ctx context.Context, t *testing.T, cfg *config.Config, numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) {
t.Helper()
func GenesisDoc(
config *cfg.Config,
time time.Time,
validators []*types.Validator,
consensusParams *types.ConsensusParams,
) *types.GenesisDoc {
validators := make([]types.GenesisValidator, numValidators)
privValidators := make([]types.PrivValidator, numValidators)
for i := 0; i < numValidators; i++ {
val, privVal, err := RandValidator(ctx, randPower, minPower)
require.NoError(t, err)
validators[i] = types.GenesisValidator{
PubKey: val.PubKey,
Power: val.VotingPower,
genesisValidators := make([]types.GenesisValidator, len(validators))
for i := range validators {
genesisValidators[i] = types.GenesisValidator{
Power: validators[i].VotingPower,
PubKey: validators[i].PubKey,
}
privValidators[i] = privVal
}
sort.Sort(types.PrivValidatorsByAddress(privValidators))
return &types.GenesisDoc{
GenesisTime: tmtime.Now(),
InitialHeight: 1,
ChainID: cfg.ChainID(),
Validators: validators,
}, privValidators
GenesisTime: time,
InitialHeight: 1,
ChainID: config.ChainID(),
Validators: genesisValidators,
ConsensusParams: consensusParams,
}
}
+6 -13
View File
@@ -2,8 +2,6 @@ package factory
import (
"context"
"fmt"
"math/rand"
"sort"
"testing"
@@ -11,23 +9,18 @@ import (
"github.com/tendermint/tendermint/types"
)
func RandValidator(ctx context.Context, randPower bool, minPower int64) (*types.Validator, types.PrivValidator, error) {
func Validator(ctx context.Context, votingPower int64) (*types.Validator, types.PrivValidator, error) {
privVal := types.NewMockPV()
votePower := minPower
if randPower {
// nolint:gosec // G404: Use of weak random number generator
votePower += int64(rand.Uint32())
}
pubKey, err := privVal.GetPubKey(ctx)
if err != nil {
return nil, nil, fmt.Errorf("could not retrieve public key: %w", err)
return nil, nil, err
}
val := types.NewValidator(pubKey, votePower)
return val, privVal, err
val := types.NewValidator(pubKey, votingPower)
return val, privVal, nil
}
func RandValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) {
func ValidatorSet(ctx context.Context, t *testing.T, numValidators int, votingPower int64) (*types.ValidatorSet, []types.PrivValidator) {
var (
valz = make([]*types.Validator, numValidators)
privValidators = make([]types.PrivValidator, numValidators)
@@ -35,7 +28,7 @@ func RandValidatorSet(ctx context.Context, t *testing.T, numValidators int, voti
t.Helper()
for i := 0; i < numValidators; i++ {
val, privValidator, err := RandValidator(ctx, false, votingPower)
val, privValidator, err := Validator(ctx, votingPower)
require.NoError(t, err)
valz[i] = val
privValidators[i] = privValidator