From e9804d76cfff47c4973f701f19e260fecc1f21cf Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 14 May 2018 10:33:31 -0400 Subject: [PATCH] fixes from review --- consensus/types/height_vote_set.go | 62 +++++++++++++++--------------- consensus/types/round_state.go | 7 +++- rpc/client/httpclient.go | 9 +++++ rpc/client/interface.go | 1 + rpc/client/localclient.go | 4 ++ rpc/client/rpc_test.go | 11 ++++++ rpc/core/consensus.go | 41 ++++++++++++++++++++ types/vote_set.go | 8 +++- 8 files changed, 110 insertions(+), 33 deletions(-) diff --git a/consensus/types/height_vote_set.go b/consensus/types/height_vote_set.go index 2013f9199..3c9867940 100644 --- a/consensus/types/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -174,6 +174,26 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet { } } +// If a peer claims that it has 2/3 majority for given blockKey, call this. +// NOTE: if there are too many peers, or too much peer churn, +// this can cause memory issues. +// TODO: implement ability to remove peers too +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + if !types.IsVoteTypeValid(type_) { + return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_) + } + voteSet := hvs.getVoteSet(round, type_) + if voteSet == nil { + return nil // something we don't know about yet + } + return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID) +} + +//--------------------------------------------------------- +// string and json + func (hvs *HeightVoteSet) String() string { return hvs.StringIndented("") } @@ -207,29 +227,20 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { indent) } -// `"__xx_xx____x:46/100:0.46"` -type roundVotes struct { - Round int `json:"round"` - Prevotes []string `json:"prevotes"` - PrevotesBitArray string `json:"prevotes_bit_array"` - Precommits []string `json:"precommits"` - PrecommitsBitArray string `json:"precommits_bit_array"` -} - func (hvs *HeightVoteSet) MarshalJSON() ([]byte, error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() - roundsVotes := hvs.toRoundVotes() - return cdc.MarshalJSON(roundsVotes) + allVotes := hvs.toAllRoundVotes() + return cdc.MarshalJSON(allVotes) } -func (hvs *HeightVoteSet) toRoundVotes() []roundVotes { +func (hvs *HeightVoteSet) toAllRoundVotes() []roundVotes { totalRounds := hvs.round + 1 - roundsVotes := make([]roundVotes, totalRounds) + allVotes := make([]roundVotes, totalRounds) // rounds 0 ~ hvs.round inclusive for round := 0; round < totalRounds; round++ { - roundsVotes[round] = roundVotes{ + allVotes[round] = roundVotes{ Round: round, Prevotes: hvs.roundVoteSets[round].Prevotes.VoteStrings(), PrevotesBitArray: hvs.roundVoteSets[round].Prevotes.BitArrayString(), @@ -238,22 +249,13 @@ func (hvs *HeightVoteSet) toRoundVotes() []roundVotes { } } // TODO: all other peer catchup rounds - return roundsVotes + return allVotes } -// If a peer claims that it has 2/3 majority for given blockKey, call this. -// NOTE: if there are too many peers, or too much peer churn, -// this can cause memory issues. -// TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error { - hvs.mtx.Lock() - defer hvs.mtx.Unlock() - if !types.IsVoteTypeValid(type_) { - return fmt.Errorf("SetPeerMaj23: Invalid vote type %v", type_) - } - voteSet := hvs.getVoteSet(round, type_) - if voteSet == nil { - return nil // something we don't know about yet - } - return voteSet.SetPeerMaj23(types.P2PID(peerID), blockID) +type roundVotes struct { + Round int `json:"round"` + Prevotes []string `json:"prevotes"` + PrevotesBitArray string `json:"prevotes_bit_array"` + Precommits []string `json:"precommits"` + PrecommitsBitArray string `json:"precommits_bit_array"` } diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index 4e1db3cd5..f69b8f39d 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -79,6 +79,7 @@ type RoundState struct { LastValidators *types.ValidatorSet `json:"last_validators"` } +// Compressed version of the RoundState for use in RPC type RoundStateSimple struct { HeightRoundStep string `json:"height/round/step"` StartTime time.Time `json:"start_time"` @@ -88,8 +89,12 @@ type RoundStateSimple struct { Votes json.RawMessage `json:"height_vote_set"` } +// Compress the RoundState to RoundStateSimple func (rs *RoundState) RoundStateSimple() RoundStateSimple { - votesJSON, _ := rs.Votes.MarshalJSON() // TODO err + votesJSON, err := rs.Votes.MarshalJSON() + if err != nil { + panic(err) + } return RoundStateSimple{ HeightRoundStep: fmt.Sprintf("%d/%d/%d", rs.Height, rs.Round, rs.Step), StartTime: rs.StartTime, diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index 83207eb7b..89a1293af 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -126,6 +126,15 @@ func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return result, nil } +func (c *HTTP) ConsensusState() (*ctypes.ResultConsensusState, error) { + result := new(ctypes.ResultConsensusState) + _, err := c.rpc.Call("consensus_state", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "ConsensusState") + } + return result, nil +} + func (c *HTTP) Health() (*ctypes.ResultHealth, error) { result := new(ctypes.ResultHealth) _, err := c.rpc.Call("health", map[string]interface{}{}, result) diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 010b6abf4..0cc9333ef 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -84,6 +84,7 @@ type Client interface { type NetworkClient interface { NetInfo() (*ctypes.ResultNetInfo, error) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) + ConsensusState() (*ctypes.ResultConsensusState, error) Health() (*ctypes.ResultHealth, error) } diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 7e2f8fedc..0c47de830 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -84,6 +84,10 @@ func (Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return core.DumpConsensusState() } +func (Local) ConsensusState() (*ctypes.ResultConsensusState, error) { + return core.ConsensusState() +} + func (Local) Health() (*ctypes.ResultHealth, error) { return core.Health() } diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 661ec717d..eb25b94ee 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -78,6 +78,17 @@ func TestDumpConsensusState(t *testing.T) { } } +func TestConsensusState(t *testing.T) { + for i, c := range GetClients() { + // FIXME: fix server so it doesn't panic on invalid input + nc, ok := c.(client.NetworkClient) + require.True(t, ok, "%d", i) + cons, err := nc.ConsensusState() + require.Nil(t, err, "%d: %+v", i, err) + assert.NotEmpty(t, cons.RoundState) + } +} + func TestHealth(t *testing.T) { for i, c := range GetClients() { nc, ok := c.(client.NetworkClient) diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 2cc26e52f..68e25c1e6 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -212,7 +212,48 @@ func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { return &ctypes.ResultDumpConsensusState{roundState, peerStates}, nil } +// ConsensusState returns a concise summary of the consensus state. // UNSTABLE +// +// ```shell +// curl 'localhost:46657/consensus_state' +// ``` +// +// ```go +// client := client.NewHTTP("tcp://0.0.0.0:46657", "/websocket") +// state, err := client.ConsensusState() +// ``` +// +// The above command returns JSON structured like this: +// +// ```json +//{ +// "jsonrpc": "2.0", +// "id": "", +// "result": { +// "round_state": { +// "height/round/step": "9336/0/1", +// "start_time": "2018-05-14T10:25:45.72595357-04:00", +// "proposal_block_hash": "", +// "locked_block_hash": "", +// "valid_block_hash": "", +// "height_vote_set": [ +// { +// "round": 0, +// "prevotes": [ +// "nil-Vote" +// ], +// "prevotes_bit_array": "BA{1:_} 0/10 = 0.00", +// "precommits": [ +// "nil-Vote" +// ], +// "precommits_bit_array": "BA{1:_} 0/10 = 0.00" +// } +// ] +// } +// } +//} +//``` func ConsensusState() (*ctypes.ResultConsensusState, error) { // Get self round state. bz, err := consensusState.GetRoundStateSimpleJSON() diff --git a/types/vote_set.go b/types/vote_set.go index cde15e19b..8908f86f2 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -418,6 +418,9 @@ func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) { return BlockID{}, false } +//-------------------------------------------------------------------------------- +// Strings and JSON + func (voteSet *VoteSet) String() string { if voteSet == nil { return "nil-VoteSet" @@ -470,8 +473,9 @@ type VoteSetJSON struct { PeerMaj23s map[P2PID]BlockID `json:"peer_maj_23s"` } -// Return the bit-array of votes including the fraction of power -// that has voted eg. "__x_xxx_:6/20 = 0.3". +// Return the bit-array of votes including +// the fraction of power that has voted like: +// "BA{29:xx__x__x_x___x__x_______xxx__} 856/1304 = 0.66" func (voteSet *VoteSet) BitArrayString() string { voteSet.mtx.Lock() defer voteSet.mtx.Unlock()