From ab59f64f57c59ef26cbe328c518c2eaecbd74d44 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Mar 2018 13:13:19 +0300 Subject: [PATCH] test we record votes and block parts Refs #1317 --- consensus/reactor.go | 22 +++++++- consensus/reactor_test.go | 109 ++++++++++++++++++++++++++++++++++++++ p2p/dummy/peer.go | 72 +++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 p2p/dummy/peer.go diff --git a/consensus/reactor.go b/consensus/reactor.go index 5c4446bf..60ac3d9c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -852,6 +852,10 @@ type peerStateStats struct { blockParts int } +func (pss peerStateStats) String() string { + return fmt.Sprintf("peerStateStats{votes: %d, blockParts: %d}", pss.votes, pss.blockParts) +} + // NewPeerState returns a new PeerState for the given Peer func NewPeerState(peer p2p.Peer) *PeerState { return &PeerState{ @@ -1087,6 +1091,12 @@ func (ps *PeerState) RecordVote(vote *types.Vote) int { return ps.stats.votes } +// VotesSent returns the number of blocks for which peer has been sending us +// votes. +func (ps *PeerState) VotesSent() int { + return ps.stats.votes +} + // RecordVote updates internal statistics for this peer by recording the block part. // It returns the total number of block parts (1 per block). This essentially means // the number of blocks for which peer has been sending us block parts. @@ -1100,6 +1110,12 @@ func (ps *PeerState) RecordBlockPart(bp *BlockPartMessage) int { return ps.stats.blockParts } +// BlockPartsSent returns the number of blocks for which peer has been sending +// us block parts. +func (ps *PeerState) BlockPartsSent() int { + return ps.stats.blockParts +} + // SetHasVote sets the given vote as known by the peer func (ps *PeerState) SetHasVote(vote *types.Vote) { ps.mtx.Lock() @@ -1246,11 +1262,13 @@ func (ps *PeerState) StringIndented(indent string) string { ps.mtx.Lock() defer ps.mtx.Unlock() return fmt.Sprintf(`PeerState{ -%s Key %v -%s PRS %v +%s Key %v +%s PRS %v +%s Stats %v %s}`, indent, ps.Peer.ID(), indent, ps.PeerRoundState.StringIndented(indent+" "), + indent, ps.stats, indent) } diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 8e96de2b..26fc7e17 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -11,10 +11,13 @@ import ( "time" "github.com/tendermint/abci/example/kvstore" + wire "github.com/tendermint/tendermint/wire" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" + p2pdummy "github.com/tendermint/tendermint/p2p/dummy" "github.com/tendermint/tendermint/types" "github.com/stretchr/testify/assert" @@ -121,6 +124,112 @@ func TestReactorProposalHeartbeats(t *testing.T) { }, css) } +// Test we record block parts from other peers +func TestReactorRecordsBlockParts(t *testing.T) { + // create dummy peer + peer := p2pdummy.NewPeer() + ps := NewPeerState(peer).SetLogger(log.TestingLogger()) + peer.Set(types.PeerStateKey, ps) + + // create reactor + css := randConsensusNet(1, "consensus_reactor_records_block_parts_test", newMockTickerFunc(true), newPersistentKVStore) + reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states + reactor.SetEventBus(css[0].eventBus) + reactor.SetLogger(log.TestingLogger()) + sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw }) + reactor.SetSwitch(sw) + err := reactor.Start() + require.NoError(t, err) + defer reactor.Stop() + + // 1) new block part + parts := types.NewPartSetFromData(cmn.RandBytes(100), 10) + msg := &BlockPartMessage{ + Height: 2, + Round: 0, + Part: parts.GetPart(0), + } + bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{msg}) + require.NoError(t, err) + + reactor.Receive(DataChannel, peer, bz) + assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should have increased by 1") + + // 2) block part with the same height, but different round + msg.Round = 1 + + bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg}) + require.NoError(t, err) + + reactor.Receive(DataChannel, peer, bz) + assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same") + + // 3) block part from earlier height + msg.Height = 1 + msg.Round = 0 + + bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{msg}) + require.NoError(t, err) + + reactor.Receive(DataChannel, peer, bz) + assert.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same") +} + +// Test we record votes from other peers +func TestReactorRecordsVotes(t *testing.T) { + // create dummy peer + peer := p2pdummy.NewPeer() + ps := NewPeerState(peer).SetLogger(log.TestingLogger()) + peer.Set(types.PeerStateKey, ps) + + // create reactor + css := randConsensusNet(1, "consensus_reactor_records_votes_test", newMockTickerFunc(true), newPersistentKVStore) + reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states + reactor.SetEventBus(css[0].eventBus) + reactor.SetLogger(log.TestingLogger()) + sw := p2p.MakeSwitch(cfg.DefaultP2PConfig(), 1, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { return sw }) + reactor.SetSwitch(sw) + err := reactor.Start() + require.NoError(t, err) + defer reactor.Stop() + _, val := css[0].state.Validators.GetByIndex(0) + + // 1) new vote + vote := &types.Vote{ + ValidatorIndex: 0, + ValidatorAddress: val.Address, + Height: 2, + Round: 0, + Timestamp: time.Now().UTC(), + Type: types.VoteTypePrevote, + BlockID: types.BlockID{}, + } + bz, err := wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}}) + require.NoError(t, err) + + reactor.Receive(VoteChannel, peer, bz) + assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should have increased by 1") + + // 2) vote with the same height, but different round + vote.Round = 1 + + bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}}) + require.NoError(t, err) + + reactor.Receive(VoteChannel, peer, bz) + assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same") + + // 3) vote from earlier height + vote.Height = 1 + vote.Round = 0 + + bz, err = wire.MarshalBinary(struct{ ConsensusMessage }{&VoteMessage{vote}}) + require.NoError(t, err) + + reactor.Receive(VoteChannel, peer, bz) + assert.Equal(t, 1, ps.VotesSent(), "number of votes sent should stay the same") +} + //------------------------------------------------------------- // ensure we can make blocks despite cycling a validator set diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go new file mode 100644 index 00000000..61c3a8ac --- /dev/null +++ b/p2p/dummy/peer.go @@ -0,0 +1,72 @@ +package dummy + +import ( + p2p "github.com/tendermint/tendermint/p2p" + tmconn "github.com/tendermint/tendermint/p2p/conn" + cmn "github.com/tendermint/tmlibs/common" +) + +type peer struct { + cmn.BaseService + kv map[string]interface{} +} + +var _ p2p.Peer = (*peer)(nil) + +// NewPeer creates new dummy peer. +func NewPeer() *peer { + p := &peer{ + kv: make(map[string]interface{}), + } + p.BaseService = *cmn.NewBaseService(nil, "peer", p) + return p +} + +// ID always returns dummy. +func (p *peer) ID() p2p.ID { + return p2p.ID("dummy") +} + +// IsOutbound always returns false. +func (p *peer) IsOutbound() bool { + return false +} + +// IsPersistent always returns false. +func (p *peer) IsPersistent() bool { + return false +} + +// NodeInfo always returns empty node info. +func (p *peer) NodeInfo() p2p.NodeInfo { + return p2p.NodeInfo{} +} + +// Status always returns empry connection status. +func (p *peer) Status() tmconn.ConnectionStatus { + return tmconn.ConnectionStatus{} +} + +// Send does not do anything and just returns true. +func (p *peer) Send(byte, interface{}) bool { + return true +} + +// TrySend does not do anything and just returns true. +func (p *peer) TrySend(byte, interface{}) bool { + return true +} + +// Set records value under key specified in the map. +func (p *peer) Set(key string, value interface{}) { + p.kv[key] = value +} + +// Get returns a value associated with the key. Nil is returned if no value +// found. +func (p *peer) Get(key string) interface{} { + if value, ok := p.kv[key]; ok { + return value + } + return nil +}