github.com/ethereum-optimism/optimism@v1.7.2/op-node/p2p/peer_scores_test.go (about) 1 package p2p_test 2 3 import ( 4 "context" 5 "fmt" 6 "math/big" 7 "math/rand" 8 "testing" 9 "time" 10 11 log "github.com/ethereum/go-ethereum/log" 12 ds "github.com/ipfs/go-datastore" 13 "github.com/ipfs/go-datastore/sync" 14 pubsub "github.com/libp2p/go-libp2p-pubsub" 15 host "github.com/libp2p/go-libp2p/core/host" 16 "github.com/libp2p/go-libp2p/core/network" 17 peer "github.com/libp2p/go-libp2p/core/peer" 18 "github.com/libp2p/go-libp2p/core/peerstore" 19 bhost "github.com/libp2p/go-libp2p/p2p/host/blank" 20 21 //nolint:all 22 "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" 23 tswarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing" 24 "github.com/stretchr/testify/mock" 25 "github.com/stretchr/testify/require" 26 "github.com/stretchr/testify/suite" 27 28 p2p "github.com/ethereum-optimism/optimism/op-node/p2p" 29 p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks" 30 "github.com/ethereum-optimism/optimism/op-node/p2p/store" 31 "github.com/ethereum-optimism/optimism/op-node/rollup" 32 "github.com/ethereum-optimism/optimism/op-service/clock" 33 testlog "github.com/ethereum-optimism/optimism/op-service/testlog" 34 ) 35 36 // PeerScoresTestSuite tests peer parameterization. 37 type PeerScoresTestSuite struct { 38 suite.Suite 39 40 mockStore *p2pMocks.Peerstore 41 mockMetricer *p2pMocks.ScoreMetrics 42 logger log.Logger 43 } 44 45 // SetupTest sets up the test suite. 46 func (testSuite *PeerScoresTestSuite) SetupTest() { 47 testSuite.mockStore = &p2pMocks.Peerstore{} 48 testSuite.mockMetricer = &p2pMocks.ScoreMetrics{} 49 testSuite.logger = testlog.Logger(testSuite.T(), log.LevelError) 50 } 51 52 // TestPeerScores runs the PeerScoresTestSuite. 53 func TestPeerScores(t *testing.T) { 54 suite.Run(t, new(PeerScoresTestSuite)) 55 } 56 57 type customPeerstoreNetwork struct { 58 network.Network 59 ps peerstore.Peerstore 60 } 61 62 func (c *customPeerstoreNetwork) Peerstore() peerstore.Peerstore { 63 return c.ps 64 } 65 66 func (c *customPeerstoreNetwork) Close() error { 67 _ = c.ps.Close() 68 return c.Network.Close() 69 } 70 71 // getNetHosts generates a slice of hosts using the [libp2p/go-libp2p] library. 72 func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []host.Host { 73 var out []host.Host 74 log := testlog.Logger(testSuite.T(), log.LevelError) 75 for i := 0; i < n; i++ { 76 swarm := tswarm.GenSwarm(testSuite.T()) 77 eps, err := store.NewExtendedPeerstore(ctx, log, clock.SystemClock, swarm.Peerstore(), sync.MutexWrap(ds.NewMapDatastore()), 1*time.Hour) 78 netw := &customPeerstoreNetwork{swarm, eps} 79 require.NoError(testSuite.T(), err) 80 h := bhost.NewBlankHost(netw) 81 testSuite.T().Cleanup(func() { h.Close() }) 82 out = append(out, h) 83 } 84 return out 85 } 86 87 type discriminatingAppScorer struct { 88 badPeer peer.ID 89 p2p.NoopApplicationScorer 90 } 91 92 func (d *discriminatingAppScorer) ApplicationScore(id peer.ID) float64 { 93 if id == d.badPeer { 94 return -1000 95 } 96 return 0 97 } 98 99 func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []host.Host) []*pubsub.PubSub { 100 var psubs []*pubsub.PubSub 101 102 logger := testlog.Logger(testSuite.T(), log.LevelCrit) 103 104 // For each host, create a default gossipsub router. 105 for _, h := range hosts { 106 rt := pubsub.DefaultGossipSubRouter(h) 107 opts := []pubsub.Option{} 108 109 dataStore := sync.MutexWrap(ds.NewMapDatastore()) 110 peerStore, err := pstoreds.NewPeerstore(context.Background(), dataStore, pstoreds.DefaultOpts()) 111 require.NoError(testSuite.T(), err) 112 extPeerStore, err := store.NewExtendedPeerstore(context.Background(), logger, clock.SystemClock, peerStore, dataStore, 1*time.Hour) 113 require.NoError(testSuite.T(), err) 114 115 scorer := p2p.NewScorer( 116 &rollup.Config{L2ChainID: big.NewInt(123)}, 117 extPeerStore, testSuite.mockMetricer, &discriminatingAppScorer{badPeer: hosts[0].ID()}, logger) 118 opts = append(opts, p2p.ConfigurePeerScoring(&p2p.Config{ 119 ScoringParams: &p2p.ScoringParams{ 120 PeerScoring: pubsub.PeerScoreParams{ 121 AppSpecificWeight: 1, 122 DecayInterval: time.Second, 123 DecayToZero: 0.01, 124 }, 125 }, 126 }, scorer, logger)...) 127 ps, err := pubsub.NewGossipSubWithRouter(ctx, h, rt, opts...) 128 if err != nil { 129 panic(err) 130 } 131 psubs = append(psubs, ps) 132 } 133 134 return psubs 135 } 136 137 func connectHosts(t *testing.T, hosts []host.Host, d int) { 138 for i, a := range hosts { 139 for j := 0; j < d; j++ { 140 n := rand.Intn(len(hosts)) 141 if n == i { 142 j-- 143 continue 144 } 145 146 b := hosts[n] 147 148 pinfo := a.Peerstore().PeerInfo(a.ID()) 149 err := b.Connect(context.Background(), pinfo) 150 if err != nil { 151 t.Fatal(err) 152 } 153 } 154 } 155 } 156 157 // TestNegativeScores tests blocking peers with negative scores. 158 // 159 // This follows the testing done in libp2p's gossipsub_test.go [TestGossipsubNegativeScore] function. 160 func (testSuite *PeerScoresTestSuite) TestNegativeScores() { 161 ctx, cancel := context.WithCancel(context.Background()) 162 defer cancel() 163 164 testSuite.mockMetricer.On("SetPeerScores", mock.Anything, mock.Anything).Return(nil) 165 166 // Construct 20 hosts using the [getNetHosts] function. 167 hosts := getNetHosts(testSuite, ctx, 20) 168 testSuite.Equal(20, len(hosts)) 169 170 // Construct 20 gossipsub routers using the [newGossipSubs] function. 171 pubsubs := newGossipSubs(testSuite, ctx, hosts) 172 testSuite.Equal(20, len(pubsubs)) 173 174 // Connect the hosts in a dense network 175 connectHosts(testSuite.T(), hosts, 10) 176 177 // Create subscriptions 178 var subs []*pubsub.Subscription 179 var topics []*pubsub.Topic 180 for _, ps := range pubsubs { 181 topic, err := ps.Join("test") 182 testSuite.NoError(err) 183 sub, err := topic.Subscribe() 184 testSuite.NoError(err) 185 subs = append(subs, sub) 186 topics = append(topics, topic) 187 } 188 189 // Wait and then publish messages 190 time.Sleep(3 * time.Second) 191 for i := 0; i < 20; i++ { 192 msg := []byte(fmt.Sprintf("message %d", i)) 193 topic := topics[i] 194 err := topic.Publish(ctx, msg) 195 testSuite.NoError(err) 196 time.Sleep(20 * time.Millisecond) 197 } 198 199 // Allow gossip to propagate 200 time.Sleep(2 * time.Second) 201 202 // Collects all messages from a subscription 203 collectAll := func(sub *pubsub.Subscription) []*pubsub.Message { 204 var res []*pubsub.Message 205 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) 206 defer cancel() 207 for { 208 msg, err := sub.Next(ctx) 209 if err != nil { 210 break 211 } 212 res = append(res, msg) 213 } 214 return res 215 } 216 217 // Collect messages for the first host subscription 218 // This host should only receive 1 message from itself 219 count := len(collectAll(subs[0])) 220 testSuite.Equal(1, count) 221 222 // Validate that all messages were received from the first peer 223 for _, sub := range subs[1:] { 224 all := collectAll(sub) 225 for _, m := range all { 226 testSuite.NotEqual(hosts[0].ID(), m.ReceivedFrom) 227 } 228 } 229 }