@ -0,0 +1,49 @@ | |||
package behavior | |||
import ( | |||
"github.com/tendermint/tendermint/p2p" | |||
) | |||
// PeerBehavior is a struct describing a behavior a peer performed. | |||
// `peerID` identifies the peer and reason characterizes the specific | |||
// behavior performed by the peer. | |||
type PeerBehavior struct { | |||
peerID p2p.NodeID | |||
reason interface{} | |||
} | |||
type badMessage struct { | |||
explanation string | |||
} | |||
// BadMessage returns a badMessage PeerBehavior. | |||
func BadMessage(peerID p2p.NodeID, explanation string) PeerBehavior { | |||
return PeerBehavior{peerID: peerID, reason: badMessage{explanation}} | |||
} | |||
type messageOutOfOrder struct { | |||
explanation string | |||
} | |||
// MessageOutOfOrder returns a messagOutOfOrder PeerBehavior. | |||
func MessageOutOfOrder(peerID p2p.NodeID, explanation string) PeerBehavior { | |||
return PeerBehavior{peerID: peerID, reason: messageOutOfOrder{explanation}} | |||
} | |||
type consensusVote struct { | |||
explanation string | |||
} | |||
// ConsensusVote returns a consensusVote PeerBehavior. | |||
func ConsensusVote(peerID p2p.NodeID, explanation string) PeerBehavior { | |||
return PeerBehavior{peerID: peerID, reason: consensusVote{explanation}} | |||
} | |||
type blockPart struct { | |||
explanation string | |||
} | |||
// BlockPart returns blockPart PeerBehavior. | |||
func BlockPart(peerID p2p.NodeID, explanation string) PeerBehavior { | |||
return PeerBehavior{peerID: peerID, reason: blockPart{explanation}} | |||
} |
@ -0,0 +1,205 @@ | |||
package behavior_test | |||
import ( | |||
"sync" | |||
"testing" | |||
bh "github.com/tendermint/tendermint/behavior" | |||
"github.com/tendermint/tendermint/p2p" | |||
) | |||
// TestMockReporter tests the MockReporter's ability to store reported | |||
// peer behavior in memory indexed by the peerID. | |||
func TestMockReporter(t *testing.T) { | |||
var peerID p2p.NodeID = "MockPeer" | |||
pr := bh.NewMockReporter() | |||
behaviors := pr.GetBehaviors(peerID) | |||
if len(behaviors) != 0 { | |||
t.Error("Expected to have no behaviors reported") | |||
} | |||
badMessage := bh.BadMessage(peerID, "bad message") | |||
if err := pr.Report(badMessage); err != nil { | |||
t.Error(err) | |||
} | |||
behaviors = pr.GetBehaviors(peerID) | |||
if len(behaviors) != 1 { | |||
t.Error("Expected the peer have one reported behavior") | |||
} | |||
if behaviors[0] != badMessage { | |||
t.Error("Expected Bad Message to have been reported") | |||
} | |||
} | |||
type scriptItem struct { | |||
peerID p2p.NodeID | |||
behavior bh.PeerBehavior | |||
} | |||
// equalBehaviors returns true if a and b contain the same PeerBehaviors with | |||
// the same freequencies and otherwise false. | |||
func equalBehaviors(a []bh.PeerBehavior, b []bh.PeerBehavior) bool { | |||
aHistogram := map[bh.PeerBehavior]int{} | |||
bHistogram := map[bh.PeerBehavior]int{} | |||
for _, behavior := range a { | |||
aHistogram[behavior]++ | |||
} | |||
for _, behavior := range b { | |||
bHistogram[behavior]++ | |||
} | |||
if len(aHistogram) != len(bHistogram) { | |||
return false | |||
} | |||
for _, behavior := range a { | |||
if aHistogram[behavior] != bHistogram[behavior] { | |||
return false | |||
} | |||
} | |||
for _, behavior := range b { | |||
if bHistogram[behavior] != aHistogram[behavior] { | |||
return false | |||
} | |||
} | |||
return true | |||
} | |||
// TestEqualPeerBehaviors tests that equalBehaviors can tell that two slices | |||
// of peer behaviors can be compared for the behaviors they contain and the | |||
// freequencies that those behaviors occur. | |||
func TestEqualPeerBehaviors(t *testing.T) { | |||
var ( | |||
peerID p2p.NodeID = "MockPeer" | |||
consensusVote = bh.ConsensusVote(peerID, "voted") | |||
blockPart = bh.BlockPart(peerID, "blocked") | |||
equals = []struct { | |||
left []bh.PeerBehavior | |||
right []bh.PeerBehavior | |||
}{ | |||
// Empty sets | |||
{[]bh.PeerBehavior{}, []bh.PeerBehavior{}}, | |||
// Single behaviors | |||
{[]bh.PeerBehavior{consensusVote}, []bh.PeerBehavior{consensusVote}}, | |||
// Equal Frequencies | |||
{[]bh.PeerBehavior{consensusVote, consensusVote}, | |||
[]bh.PeerBehavior{consensusVote, consensusVote}}, | |||
// Equal frequencies different orders | |||
{[]bh.PeerBehavior{consensusVote, blockPart}, | |||
[]bh.PeerBehavior{blockPart, consensusVote}}, | |||
} | |||
unequals = []struct { | |||
left []bh.PeerBehavior | |||
right []bh.PeerBehavior | |||
}{ | |||
// Comparing empty sets to non empty sets | |||
{[]bh.PeerBehavior{}, []bh.PeerBehavior{consensusVote}}, | |||
// Different behaviors | |||
{[]bh.PeerBehavior{consensusVote}, []bh.PeerBehavior{blockPart}}, | |||
// Same behavior with different frequencies | |||
{[]bh.PeerBehavior{consensusVote}, | |||
[]bh.PeerBehavior{consensusVote, consensusVote}}, | |||
} | |||
) | |||
for _, test := range equals { | |||
if !equalBehaviors(test.left, test.right) { | |||
t.Errorf("expected %#v and %#v to be equal", test.left, test.right) | |||
} | |||
} | |||
for _, test := range unequals { | |||
if equalBehaviors(test.left, test.right) { | |||
t.Errorf("expected %#v and %#v to be unequal", test.left, test.right) | |||
} | |||
} | |||
} | |||
// TestPeerBehaviorConcurrency constructs a scenario in which | |||
// multiple goroutines are using the same MockReporter instance. | |||
// This test reproduces the conditions in which MockReporter will | |||
// be used within a Reactor `Receive` method tests to ensure thread safety. | |||
func TestMockPeerBehaviorReporterConcurrency(t *testing.T) { | |||
var ( | |||
behaviorScript = []struct { | |||
peerID p2p.NodeID | |||
behaviors []bh.PeerBehavior | |||
}{ | |||
{"1", []bh.PeerBehavior{bh.ConsensusVote("1", "")}}, | |||
{"2", []bh.PeerBehavior{bh.ConsensusVote("2", ""), bh.ConsensusVote("2", ""), bh.ConsensusVote("2", "")}}, | |||
{ | |||
"3", | |||
[]bh.PeerBehavior{bh.BlockPart("3", ""), | |||
bh.ConsensusVote("3", ""), | |||
bh.BlockPart("3", ""), | |||
bh.ConsensusVote("3", "")}}, | |||
{ | |||
"4", | |||
[]bh.PeerBehavior{bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", "")}}, | |||
{ | |||
"5", | |||
[]bh.PeerBehavior{bh.BlockPart("5", ""), | |||
bh.ConsensusVote("5", ""), | |||
bh.BlockPart("5", ""), | |||
bh.ConsensusVote("5", "")}}, | |||
} | |||
) | |||
var receiveWg sync.WaitGroup | |||
pr := bh.NewMockReporter() | |||
scriptItems := make(chan scriptItem) | |||
done := make(chan int) | |||
numConsumers := 3 | |||
for i := 0; i < numConsumers; i++ { | |||
receiveWg.Add(1) | |||
go func() { | |||
defer receiveWg.Done() | |||
for { | |||
select { | |||
case pb := <-scriptItems: | |||
if err := pr.Report(pb.behavior); err != nil { | |||
t.Error(err) | |||
} | |||
case <-done: | |||
return | |||
} | |||
} | |||
}() | |||
} | |||
var sendingWg sync.WaitGroup | |||
sendingWg.Add(1) | |||
go func() { | |||
defer sendingWg.Done() | |||
for _, item := range behaviorScript { | |||
for _, reason := range item.behaviors { | |||
scriptItems <- scriptItem{item.peerID, reason} | |||
} | |||
} | |||
}() | |||
sendingWg.Wait() | |||
for i := 0; i < numConsumers; i++ { | |||
done <- 1 | |||
} | |||
receiveWg.Wait() | |||
for _, items := range behaviorScript { | |||
reported := pr.GetBehaviors(items.peerID) | |||
if !equalBehaviors(reported, items.behaviors) { | |||
t.Errorf("expected peer %s to have behaved \nExpected: %#v \nGot %#v \n", | |||
items.peerID, items.behaviors, reported) | |||
} | |||
} | |||
} |
@ -1,49 +0,0 @@ | |||
package behaviour | |||
import ( | |||
"github.com/tendermint/tendermint/p2p" | |||
) | |||
// PeerBehaviour is a struct describing a behaviour a peer performed. | |||
// `peerID` identifies the peer and reason characterizes the specific | |||
// behaviour performed by the peer. | |||
type PeerBehaviour struct { | |||
peerID p2p.NodeID | |||
reason interface{} | |||
} | |||
type badMessage struct { | |||
explanation string | |||
} | |||
// BadMessage returns a badMessage PeerBehaviour. | |||
func BadMessage(peerID p2p.NodeID, explanation string) PeerBehaviour { | |||
return PeerBehaviour{peerID: peerID, reason: badMessage{explanation}} | |||
} | |||
type messageOutOfOrder struct { | |||
explanation string | |||
} | |||
// MessageOutOfOrder returns a messagOutOfOrder PeerBehaviour. | |||
func MessageOutOfOrder(peerID p2p.NodeID, explanation string) PeerBehaviour { | |||
return PeerBehaviour{peerID: peerID, reason: messageOutOfOrder{explanation}} | |||
} | |||
type consensusVote struct { | |||
explanation string | |||
} | |||
// ConsensusVote returns a consensusVote PeerBehaviour. | |||
func ConsensusVote(peerID p2p.NodeID, explanation string) PeerBehaviour { | |||
return PeerBehaviour{peerID: peerID, reason: consensusVote{explanation}} | |||
} | |||
type blockPart struct { | |||
explanation string | |||
} | |||
// BlockPart returns blockPart PeerBehaviour. | |||
func BlockPart(peerID p2p.NodeID, explanation string) PeerBehaviour { | |||
return PeerBehaviour{peerID: peerID, reason: blockPart{explanation}} | |||
} |
@ -1,205 +0,0 @@ | |||
package behaviour_test | |||
import ( | |||
"sync" | |||
"testing" | |||
bh "github.com/tendermint/tendermint/behaviour" | |||
"github.com/tendermint/tendermint/p2p" | |||
) | |||
// TestMockReporter tests the MockReporter's ability to store reported | |||
// peer behaviour in memory indexed by the peerID. | |||
func TestMockReporter(t *testing.T) { | |||
var peerID p2p.NodeID = "MockPeer" | |||
pr := bh.NewMockReporter() | |||
behaviours := pr.GetBehaviours(peerID) | |||
if len(behaviours) != 0 { | |||
t.Error("Expected to have no behaviours reported") | |||
} | |||
badMessage := bh.BadMessage(peerID, "bad message") | |||
if err := pr.Report(badMessage); err != nil { | |||
t.Error(err) | |||
} | |||
behaviours = pr.GetBehaviours(peerID) | |||
if len(behaviours) != 1 { | |||
t.Error("Expected the peer have one reported behaviour") | |||
} | |||
if behaviours[0] != badMessage { | |||
t.Error("Expected Bad Message to have been reported") | |||
} | |||
} | |||
type scriptItem struct { | |||
peerID p2p.NodeID | |||
behaviour bh.PeerBehaviour | |||
} | |||
// equalBehaviours returns true if a and b contain the same PeerBehaviours with | |||
// the same freequencies and otherwise false. | |||
func equalBehaviours(a []bh.PeerBehaviour, b []bh.PeerBehaviour) bool { | |||
aHistogram := map[bh.PeerBehaviour]int{} | |||
bHistogram := map[bh.PeerBehaviour]int{} | |||
for _, behaviour := range a { | |||
aHistogram[behaviour]++ | |||
} | |||
for _, behaviour := range b { | |||
bHistogram[behaviour]++ | |||
} | |||
if len(aHistogram) != len(bHistogram) { | |||
return false | |||
} | |||
for _, behaviour := range a { | |||
if aHistogram[behaviour] != bHistogram[behaviour] { | |||
return false | |||
} | |||
} | |||
for _, behaviour := range b { | |||
if bHistogram[behaviour] != aHistogram[behaviour] { | |||
return false | |||
} | |||
} | |||
return true | |||
} | |||
// TestEqualPeerBehaviours tests that equalBehaviours can tell that two slices | |||
// of peer behaviours can be compared for the behaviours they contain and the | |||
// freequencies that those behaviours occur. | |||
func TestEqualPeerBehaviours(t *testing.T) { | |||
var ( | |||
peerID p2p.NodeID = "MockPeer" | |||
consensusVote = bh.ConsensusVote(peerID, "voted") | |||
blockPart = bh.BlockPart(peerID, "blocked") | |||
equals = []struct { | |||
left []bh.PeerBehaviour | |||
right []bh.PeerBehaviour | |||
}{ | |||
// Empty sets | |||
{[]bh.PeerBehaviour{}, []bh.PeerBehaviour{}}, | |||
// Single behaviours | |||
{[]bh.PeerBehaviour{consensusVote}, []bh.PeerBehaviour{consensusVote}}, | |||
// Equal Frequencies | |||
{[]bh.PeerBehaviour{consensusVote, consensusVote}, | |||
[]bh.PeerBehaviour{consensusVote, consensusVote}}, | |||
// Equal frequencies different orders | |||
{[]bh.PeerBehaviour{consensusVote, blockPart}, | |||
[]bh.PeerBehaviour{blockPart, consensusVote}}, | |||
} | |||
unequals = []struct { | |||
left []bh.PeerBehaviour | |||
right []bh.PeerBehaviour | |||
}{ | |||
// Comparing empty sets to non empty sets | |||
{[]bh.PeerBehaviour{}, []bh.PeerBehaviour{consensusVote}}, | |||
// Different behaviours | |||
{[]bh.PeerBehaviour{consensusVote}, []bh.PeerBehaviour{blockPart}}, | |||
// Same behaviour with different frequencies | |||
{[]bh.PeerBehaviour{consensusVote}, | |||
[]bh.PeerBehaviour{consensusVote, consensusVote}}, | |||
} | |||
) | |||
for _, test := range equals { | |||
if !equalBehaviours(test.left, test.right) { | |||
t.Errorf("expected %#v and %#v to be equal", test.left, test.right) | |||
} | |||
} | |||
for _, test := range unequals { | |||
if equalBehaviours(test.left, test.right) { | |||
t.Errorf("expected %#v and %#v to be unequal", test.left, test.right) | |||
} | |||
} | |||
} | |||
// TestPeerBehaviourConcurrency constructs a scenario in which | |||
// multiple goroutines are using the same MockReporter instance. | |||
// This test reproduces the conditions in which MockReporter will | |||
// be used within a Reactor `Receive` method tests to ensure thread safety. | |||
func TestMockPeerBehaviourReporterConcurrency(t *testing.T) { | |||
var ( | |||
behaviourScript = []struct { | |||
peerID p2p.NodeID | |||
behaviours []bh.PeerBehaviour | |||
}{ | |||
{"1", []bh.PeerBehaviour{bh.ConsensusVote("1", "")}}, | |||
{"2", []bh.PeerBehaviour{bh.ConsensusVote("2", ""), bh.ConsensusVote("2", ""), bh.ConsensusVote("2", "")}}, | |||
{ | |||
"3", | |||
[]bh.PeerBehaviour{bh.BlockPart("3", ""), | |||
bh.ConsensusVote("3", ""), | |||
bh.BlockPart("3", ""), | |||
bh.ConsensusVote("3", "")}}, | |||
{ | |||
"4", | |||
[]bh.PeerBehaviour{bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", ""), | |||
bh.ConsensusVote("4", "")}}, | |||
{ | |||
"5", | |||
[]bh.PeerBehaviour{bh.BlockPart("5", ""), | |||
bh.ConsensusVote("5", ""), | |||
bh.BlockPart("5", ""), | |||
bh.ConsensusVote("5", "")}}, | |||
} | |||
) | |||
var receiveWg sync.WaitGroup | |||
pr := bh.NewMockReporter() | |||
scriptItems := make(chan scriptItem) | |||
done := make(chan int) | |||
numConsumers := 3 | |||
for i := 0; i < numConsumers; i++ { | |||
receiveWg.Add(1) | |||
go func() { | |||
defer receiveWg.Done() | |||
for { | |||
select { | |||
case pb := <-scriptItems: | |||
if err := pr.Report(pb.behaviour); err != nil { | |||
t.Error(err) | |||
} | |||
case <-done: | |||
return | |||
} | |||
} | |||
}() | |||
} | |||
var sendingWg sync.WaitGroup | |||
sendingWg.Add(1) | |||
go func() { | |||
defer sendingWg.Done() | |||
for _, item := range behaviourScript { | |||
for _, reason := range item.behaviours { | |||
scriptItems <- scriptItem{item.peerID, reason} | |||
} | |||
} | |||
}() | |||
sendingWg.Wait() | |||
for i := 0; i < numConsumers; i++ { | |||
done <- 1 | |||
} | |||
receiveWg.Wait() | |||
for _, items := range behaviourScript { | |||
reported := pr.GetBehaviours(items.peerID) | |||
if !equalBehaviours(reported, items.behaviours) { | |||
t.Errorf("expected peer %s to have behaved \nExpected: %#v \nGot %#v \n", | |||
items.peerID, items.behaviours, reported) | |||
} | |||
} | |||
} |