@ -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) | |||||
} | |||||
} | |||||
} |