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  }