diff --git a/consensus/common_test.go b/consensus/common_test.go index 6126a3faf..23fa8fadc 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -71,7 +71,8 @@ func decideProposal(cs1 *ConsensusState, vs *validatorStub, height, round int) ( } // Make proposal - proposal = types.NewProposal(height, round, blockParts.Header(), cs1.Votes.POLRound()) + polRound, polBlockID := cs1.Votes.POLInfo() + proposal = types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) if err := vs.SignProposal(config.GetString("chain_id"), proposal); err != nil { panic(err) } diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index 36d8911fd..dadc2710f 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -133,17 +133,19 @@ func (hvs *HeightVoteSet) Precommits(round int) *types.VoteSet { return hvs.getVoteSet(round, types.VoteTypePrecommit) } -// Last round that has +2/3 prevotes for a particular block or nil. +// Last round and blockID that has +2/3 prevotes for a particular block or nil. // Returns -1 if no such round exists. -func (hvs *HeightVoteSet) POLRound() int { +func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID types.BlockID) { hvs.mtx.Lock() defer hvs.mtx.Unlock() for r := hvs.round; r >= 0; r-- { - if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() { - return r + rvs := hvs.getVoteSet(r, types.VoteTypePrevote) + polBlockID, ok := rvs.TwoThirdsMajority() + if ok { + return r, polBlockID } } - return -1 + return -1, types.BlockID{} } func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet { diff --git a/consensus/reactor.go b/consensus/reactor.go index 8d872fc4f..f6cd25484 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -391,13 +391,13 @@ OUTER_LOOP: // Send Proposal && ProposalPOL BitArray? if rs.Proposal != nil && !prs.Proposal { - // Proposal + // Proposal: share the proposal metadata with peer. { msg := &ProposalMessage{Proposal: rs.Proposal} peer.Send(DataChannel, struct{ ConsensusMessage }{msg}) ps.SetHasProposal(rs.Proposal) } - // ProposalPOL. + // ProposalPOL: lets peer know which POL votes we have so far. // Peer must receive ProposalMessage first. // rs.Proposal was validated, so rs.Proposal.POLRound <= rs.Round, // so we definitely have rs.Votes.Prevotes(rs.Proposal.POLRound). diff --git a/consensus/state.go b/consensus/state.go index 654ca8d16..67c105172 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -853,7 +853,8 @@ func (cs *ConsensusState) decideProposal(height, round int) { } // Make proposal - proposal := types.NewProposal(height, round, blockParts.Header(), cs.Votes.POLRound()) + polRound, polBlockID := cs.Votes.POLInfo() + proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) if err == nil { // Set fields @@ -1064,8 +1065,9 @@ func (cs *ConsensusState) enterPrecommit(height int, round int) { types.FireEventPolka(cs.evsw, cs.RoundStateEvent()) // the latest POLRound should be this round - if cs.Votes.POLRound() < round { - PanicSanity(Fmt("This POLRound should be %v but got %", round, cs.Votes.POLRound())) + polRound, _ := cs.Votes.POLInfo() + if polRound < round { + PanicSanity(Fmt("This POLRound should be %v but got %", round, polRound)) } // +2/3 prevoted nil. Unlock and precommit nil. diff --git a/consensus/state_test.go b/consensus/state_test.go index 44eaf080b..13a2a3fcd 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -198,7 +198,7 @@ func TestBadProposal(t *testing.T) { stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet() - proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1) + proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1, types.BlockID{}) if err := vs2.SignProposal(config.GetString("chain_id"), proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } @@ -832,6 +832,7 @@ func TestLockPOLSafety2(t *testing.T) { prop1, propBlock1 := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) propBlockHash1 := propBlock1.Hash() propBlockParts1 := propBlock1.MakePartSet() + propBlockID1 := types.BlockID{propBlockHash1, propBlockParts1.Header()} incrementRound(vs2, vs3, vs4) @@ -864,7 +865,7 @@ func TestLockPOLSafety2(t *testing.T) { <-timeoutWaitCh // in round 2 we see the polkad block from round 0 - newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0) + newProp := types.NewProposal(height, 2, propBlockParts0.Header(), 0, propBlockID1) if err := vs3.SignProposal(config.GetString("chain_id"), newProp); err != nil { t.Fatal(err) } diff --git a/types/block.go b/types/block.go index 537075182..56e85a73c 100644 --- a/types/block.go +++ b/types/block.go @@ -376,9 +376,13 @@ func (blockID BlockID) Key() string { } func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(Fmt(`{"hash":"%X","parts":`, blockID.Hash)), w, n, err) - blockID.PartsHeader.WriteSignBytes(w, n, err) - wire.WriteTo([]byte("}"), w, n, err) + if blockID.IsZero() { + wire.WriteTo([]byte("null"), w, n, err) + } else { + wire.WriteTo([]byte(Fmt(`{"hash":"%X","parts":`, blockID.Hash)), w, n, err) + blockID.PartsHeader.WriteSignBytes(w, n, err) + wire.WriteTo([]byte("}"), w, n, err) + } } func (blockID BlockID) String() string { diff --git a/types/proposal.go b/types/proposal.go index d69b60649..85ac5f061 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -19,29 +19,33 @@ type Proposal struct { Height int `json:"height"` Round int `json:"round"` BlockPartsHeader PartSetHeader `json:"block_parts_header"` - POLRound int `json:"pol_round"` // -1 if null. + POLRound int `json:"pol_round"` // -1 if null. + POLBlockID BlockID `json:"pol_block_id"` // zero if null. Signature crypto.SignatureEd25519 `json:"signature"` } // polRound: -1 if no polRound. -func NewProposal(height int, round int, blockPartsHeader PartSetHeader, polRound int) *Proposal { +func NewProposal(height int, round int, blockPartsHeader PartSetHeader, polRound int, polBlockID BlockID) *Proposal { return &Proposal{ Height: height, Round: round, BlockPartsHeader: blockPartsHeader, POLRound: polRound, + POLBlockID: polBlockID, } } func (p *Proposal) String() string { - return fmt.Sprintf("Proposal{%v/%v %v %v %v}", p.Height, p.Round, - p.BlockPartsHeader, p.POLRound, p.Signature) + return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %v}", p.Height, p.Round, + p.BlockPartsHeader, p.POLRound, p.POLBlockID, p.Signature) } func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { wire.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) wire.WriteTo([]byte(`,"proposal":{"block_parts_header":`), w, n, err) p.BlockPartsHeader.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(Fmt(`,"height":%v,"pol_round":%v`, p.Height, p.POLRound)), w, n, err) + wire.WriteTo([]byte(Fmt(`,"height":%v,"pol_block_id":`, p.Height)), w, n, err) + p.POLBlockID.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(Fmt(`,"pol_round":%v`, p.POLRound)), w, n, err) wire.WriteTo([]byte(Fmt(`,"round":%v}}`, p.Round)), w, n, err) } diff --git a/types/proposal_test.go b/types/proposal_test.go index ea3dac2b0..1d8c62051 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -14,8 +14,8 @@ func TestProposalSignable(t *testing.T) { signBytes := SignBytes("test_chain_id", proposal) signStr := string(signBytes) - expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_round":-1,"round":23456}}` + expected := `{"chain_id":"test_chain_id","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_block_id":null,"pol_round":-1,"round":23456}}` if signStr != expected { - t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) + t.Errorf("Got unexpected sign string for Proposal. Expected:\n%v\nGot:\n%v", expected, signStr) } } diff --git a/types/vote_test.go b/types/vote_test.go new file mode 100644 index 000000000..353acfaf5 --- /dev/null +++ b/types/vote_test.go @@ -0,0 +1,30 @@ +package types + +import ( + "testing" +) + +func TestVoteSignable(t *testing.T) { + vote := &Vote{ + ValidatorAddress: []byte("addr"), + ValidatorIndex: 56789, + Height: 12345, + Round: 23456, + Type: byte(2), + BlockID: BlockID{ + Hash: []byte("hash"), + PartsHeader: PartSetHeader{ + Total: 1000000, + Hash: []byte("parts_hash"), + }, + }, + } + signBytes := SignBytes("test_chain_id", vote) + signStr := string(signBytes) + + expected := `{"chain_id":"test_chain_id","vote":{"block_id":{"hash":"68617368","parts":{"hash":"70617274735F68617368","total":1000000}},"height":12345,"round":23456,"type":2}}` + if signStr != expected { + // NOTE: when this fails, you probably want to fix up consensus/replay_test too + t.Errorf("Got unexpected sign string for Vote. Expected:\n%v\nGot:\n%v", expected, signStr) + } +}