diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index fb709ef7f..373e9b7f1 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -180,7 +180,10 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { } // omit the last signature in the commit - commit.Signatures[len(commit.Signatures)-1] = types.NewCommitSigAbsent() + // except a proposal for the first block + if commit.Signatures != nil { + commit.Signatures[len(commit.Signatures)-1] = types.NewCommitSigAbsent() + } if lazyProposer.privValidatorPubKey == nil { // If this node is a validator & proposer in the current round, it will diff --git a/consensus/common_test.go b/consensus/common_test.go index 2f711fdd5..55276810d 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -4,7 +4,9 @@ import ( "bytes" "context" "fmt" + "github.com/tendermint/tendermint/crypto/vrf" "io/ioutil" + "math" "os" "path/filepath" "sort" @@ -436,6 +438,46 @@ func randState(nValidators int) (*State, []*validatorStub) { return cs, vss } +func theOthers(index int) int { + const theOtherIndex = math.MaxInt32 + return theOtherIndex - index +} + +func forceProposer(cs *State, vals []*validatorStub, index []int, height []int64, round []int32) { + for i := 0; i < 5000; i++ { + allMatch := true + firstHash := []byte{byte(i)} + currentHash := firstHash + for j := 0; j < len(index); j++ { + var curVal *validatorStub + var mustBe bool + if index[j] < len(vals) { + curVal = vals[index[j]] + mustBe = true + } else { + curVal = vals[theOthers(index[j])] + mustBe = false + } + curValPubKey, _ := curVal.GetPubKey() + cs.Validators.ResetProposerAtRandom(currentHash) + if curValPubKey.Equals(cs.Validators.GetProposer().PubKey) != mustBe { + allMatch = false + break + } + if j+1 < len(height) && height[j+1] > height[j] { + message := currentHash + proof, _ := curVal.PrivValidator.GenerateVRFProof(message) + currentHash, _ = vrf.ProofToHash(proof) + } + } + if allMatch { + cs.state.LastProofHash = firstHash + return + } + } + panic("no such LastProofHash making index validator to be proposer") +} + //------------------------------------------------------------------------------- func ensureNoNewEvent(ch <-chan tmpubsub.Message, timeout time.Duration, @@ -480,7 +522,7 @@ func ensureNoNewTimeout(stepCh <-chan tmpubsub.Message, timeout int64) { func ensureNewEvent(ch <-chan tmpubsub.Message, height int64, round int32, timeout time.Duration, errorMessage string) { select { case <-time.After(timeout): - panic(errorMessage) + panic(fmt.Sprintf("%s: %d nsec", errorMessage, timeout)) case msg := <-ch: roundStateEvent, ok := msg.Data().(types.EventDataRoundState) if !ok { diff --git a/consensus/state.go b/consensus/state.go index 24d260e06..0cebc799d 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1826,6 +1826,9 @@ func (cs *State) defaultSetProposal(proposal *types.Proposal) error { if !cs.Validators.GetProposer().PubKey.VerifySignature( types.ProposalSignBytes(cs.state.ChainID, p), proposal.Signature, ) { + cs.Logger.Error(fmt.Sprintf("proposal signature verification failed: proposer=%X, bytes=%X, signature=%X", + cs.Validators.GetProposer().Address, types.ProposalSignBytes(cs.state.ChainID, p), + proposal.Signature)) return ErrInvalidProposalSignature } diff --git a/consensus/state_test.go b/consensus/state_test.go index 4fe449318..b240888a0 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -254,6 +254,8 @@ func TestStateOversizedBlock(t *testing.T) { height, round := cs1.Height, cs1.Round vs2 := vss[1] + forceProposer(cs1, vss, []int{1, 1}, []int64{height, height}, []int32{round, round}) + partSize := types.BlockPartSizeBytes timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) @@ -815,6 +817,8 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { Round0 (cs1, A) // A A A A// A nil nil nil */ + forceProposer(cs1, vss, []int{0, 1, 2}, []int64{height, height, height + 1}, []int32{round, round + 1, 0}) + // start round and wait for propose and prevote startTestRound(cs1, height, round) diff --git a/types/validator_set.go b/types/validator_set.go index 3093922bb..b4b439b61 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -156,7 +156,7 @@ func (vals *ValidatorSet) ResetProposerAtRandom(hash []byte) { } seed := hashToSeed(hash) proposer := vals.selectProposerAtRandom(seed) - proposerChanged(seed, vals.Proposer, proposer) + proposerChanged(vals, seed, hash, vals.Proposer, proposer) vals.Proposer = proposer } @@ -167,7 +167,7 @@ func hashToSeed(hash []byte) uint64 { return binary.LittleEndian.Uint64(hash[:8]) } -func proposerChanged(seed uint64, p1 *Validator, p2 *Validator) { +func proposerChanged(vals *ValidatorSet, seed uint64, hash []byte, p1 *Validator, p2 *Validator) { from := "nil" to := "nil" if p1 != nil && p1.PubKey != nil && p1.PubKey.Bytes() != nil { @@ -176,7 +176,17 @@ func proposerChanged(seed uint64, p1 *Validator, p2 *Validator) { if p2 != nil && p2.PubKey != nil && p2.PubKey.Bytes() != nil { to = hex.EncodeToString(p2.PubKey.Bytes()[:8]) } - fmt.Fprintf(os.Stderr, "******** ResetProposerAtRandom: Proposer [%d] %s -> %s\n", seed, from, to) + var i1 = -1 + var i2 = -1 + for i, val := range vals.Validators { + if p1 != nil && bytes.Equal(val.Address, p1.Address) { + i1 = i + } + if p2 != nil && bytes.Equal(val.Address, p2.Address) { + i2 = i + } + } + fmt.Fprintf(os.Stderr, "******** ResetProposerAtRandom: Proposer (seed=%d,hash=%X) [%d]%s -> [%d]%s\n", seed, hash, i1, from, i2, to) } // RescalePriorities rescales the priorities such that the distance between the maximum and minimum @@ -702,6 +712,8 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes // Reset proposer vals.ResetProposerAtRandom([]byte{}) + sort.Sort(ValidatorsByVotingPower(vals.Validators)) + return nil }