package behavior_test import ( "sync" "testing" bh "github.com/tendermint/tendermint/blockchain/v2/internal/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) } } }