Commit 4db32f78 authored by Andreas Bigger's avatar Andreas Bigger

peer scorer testing

parent c49f85e8
......@@ -99,6 +99,7 @@ type Config struct {
ConnMngr func(conf *Config) (connmgr.ConnManager, error)
}
//go:generate mockery --name ConnectionGater
type ConnectionGater interface {
connmgr.ConnectionGater
......
......@@ -59,6 +59,7 @@ type GossipRuntimeConfig interface {
P2PSequencerAddress() common.Address
}
//go:generate mockery --name GossipMetricer
type GossipMetricer interface {
RecordGossipEvent(evType int32)
RecordPeerScoring(peerID peer.ID, score float64)
......
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
control "github.com/libp2p/go-libp2p/core/control"
mock "github.com/stretchr/testify/mock"
multiaddr "github.com/multiformats/go-multiaddr"
net "net"
network "github.com/libp2p/go-libp2p/core/network"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// ConnectionGater is an autogenerated mock type for the ConnectionGater type
type ConnectionGater struct {
mock.Mock
}
// BlockAddr provides a mock function with given fields: ip
func (_m *ConnectionGater) BlockAddr(ip net.IP) error {
ret := _m.Called(ip)
var r0 error
if rf, ok := ret.Get(0).(func(net.IP) error); ok {
r0 = rf(ip)
} else {
r0 = ret.Error(0)
}
return r0
}
// BlockPeer provides a mock function with given fields: p
func (_m *ConnectionGater) BlockPeer(p peer.ID) error {
ret := _m.Called(p)
var r0 error
if rf, ok := ret.Get(0).(func(peer.ID) error); ok {
r0 = rf(p)
} else {
r0 = ret.Error(0)
}
return r0
}
// BlockSubnet provides a mock function with given fields: ipnet
func (_m *ConnectionGater) BlockSubnet(ipnet *net.IPNet) error {
ret := _m.Called(ipnet)
var r0 error
if rf, ok := ret.Get(0).(func(*net.IPNet) error); ok {
r0 = rf(ipnet)
} else {
r0 = ret.Error(0)
}
return r0
}
// InterceptAccept provides a mock function with given fields: _a0
func (_m *ConnectionGater) InterceptAccept(_a0 network.ConnMultiaddrs) bool {
ret := _m.Called(_a0)
var r0 bool
if rf, ok := ret.Get(0).(func(network.ConnMultiaddrs) bool); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// InterceptAddrDial provides a mock function with given fields: _a0, _a1
func (_m *ConnectionGater) InterceptAddrDial(_a0 peer.ID, _a1 multiaddr.Multiaddr) bool {
ret := _m.Called(_a0, _a1)
var r0 bool
if rf, ok := ret.Get(0).(func(peer.ID, multiaddr.Multiaddr) bool); ok {
r0 = rf(_a0, _a1)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// InterceptPeerDial provides a mock function with given fields: p
func (_m *ConnectionGater) InterceptPeerDial(p peer.ID) bool {
ret := _m.Called(p)
var r0 bool
if rf, ok := ret.Get(0).(func(peer.ID) bool); ok {
r0 = rf(p)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// InterceptSecured provides a mock function with given fields: _a0, _a1, _a2
func (_m *ConnectionGater) InterceptSecured(_a0 network.Direction, _a1 peer.ID, _a2 network.ConnMultiaddrs) bool {
ret := _m.Called(_a0, _a1, _a2)
var r0 bool
if rf, ok := ret.Get(0).(func(network.Direction, peer.ID, network.ConnMultiaddrs) bool); ok {
r0 = rf(_a0, _a1, _a2)
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// InterceptUpgraded provides a mock function with given fields: _a0
func (_m *ConnectionGater) InterceptUpgraded(_a0 network.Conn) (bool, control.DisconnectReason) {
ret := _m.Called(_a0)
var r0 bool
if rf, ok := ret.Get(0).(func(network.Conn) bool); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(bool)
}
var r1 control.DisconnectReason
if rf, ok := ret.Get(1).(func(network.Conn) control.DisconnectReason); ok {
r1 = rf(_a0)
} else {
r1 = ret.Get(1).(control.DisconnectReason)
}
return r0, r1
}
// ListBlockedAddrs provides a mock function with given fields:
func (_m *ConnectionGater) ListBlockedAddrs() []net.IP {
ret := _m.Called()
var r0 []net.IP
if rf, ok := ret.Get(0).(func() []net.IP); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]net.IP)
}
}
return r0
}
// ListBlockedPeers provides a mock function with given fields:
func (_m *ConnectionGater) ListBlockedPeers() []peer.ID {
ret := _m.Called()
var r0 []peer.ID
if rf, ok := ret.Get(0).(func() []peer.ID); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]peer.ID)
}
}
return r0
}
// ListBlockedSubnets provides a mock function with given fields:
func (_m *ConnectionGater) ListBlockedSubnets() []*net.IPNet {
ret := _m.Called()
var r0 []*net.IPNet
if rf, ok := ret.Get(0).(func() []*net.IPNet); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*net.IPNet)
}
}
return r0
}
// UnblockAddr provides a mock function with given fields: ip
func (_m *ConnectionGater) UnblockAddr(ip net.IP) error {
ret := _m.Called(ip)
var r0 error
if rf, ok := ret.Get(0).(func(net.IP) error); ok {
r0 = rf(ip)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnblockPeer provides a mock function with given fields: p
func (_m *ConnectionGater) UnblockPeer(p peer.ID) error {
ret := _m.Called(p)
var r0 error
if rf, ok := ret.Get(0).(func(peer.ID) error); ok {
r0 = rf(p)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnblockSubnet provides a mock function with given fields: ipnet
func (_m *ConnectionGater) UnblockSubnet(ipnet *net.IPNet) error {
ret := _m.Called(ipnet)
var r0 error
if rf, ok := ret.Get(0).(func(*net.IPNet) error); ok {
r0 = rf(ipnet)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewConnectionGater interface {
mock.TestingT
Cleanup(func())
}
// NewConnectionGater creates a new instance of ConnectionGater. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewConnectionGater(t mockConstructorTestingTNewConnectionGater) *ConnectionGater {
mock := &ConnectionGater{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// GossipMetricer is an autogenerated mock type for the GossipMetricer type
type GossipMetricer struct {
mock.Mock
}
// RecordGossipEvent provides a mock function with given fields: evType
func (_m *GossipMetricer) RecordGossipEvent(evType int32) {
_m.Called(evType)
}
// RecordPeerScoring provides a mock function with given fields: peerID, score
func (_m *GossipMetricer) RecordPeerScoring(peerID peer.ID, score float64) {
_m.Called(peerID, score)
}
type mockConstructorTestingTNewGossipMetricer interface {
mock.TestingT
Cleanup(func())
}
// NewGossipMetricer creates a new instance of GossipMetricer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewGossipMetricer(t mockConstructorTestingTNewGossipMetricer) *GossipMetricer {
mock := &GossipMetricer{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
mock "github.com/stretchr/testify/mock"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// Peerstore is an autogenerated mock type for the Peerstore type
type Peerstore struct {
mock.Mock
}
// PeerInfo provides a mock function with given fields: _a0
func (_m *Peerstore) PeerInfo(_a0 peer.ID) peer.AddrInfo {
ret := _m.Called(_a0)
var r0 peer.AddrInfo
if rf, ok := ret.Get(0).(func(peer.ID) peer.AddrInfo); ok {
r0 = rf(_a0)
} else {
r0 = ret.Get(0).(peer.AddrInfo)
}
return r0
}
// Peers provides a mock function with given fields:
func (_m *Peerstore) Peers() peer.IDSlice {
ret := _m.Called()
var r0 peer.IDSlice
if rf, ok := ret.Get(0).(func() peer.IDSlice); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(peer.IDSlice)
}
}
return r0
}
type mockConstructorTestingTNewPeerstore interface {
mock.TestingT
Cleanup(func())
}
// NewPeerstore creates a new instance of Peerstore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewPeerstore(t mockConstructorTestingTNewPeerstore) *Peerstore {
mock := &Peerstore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
......@@ -57,6 +57,9 @@ var LightPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams {
// [PeerScoreParams]: https://pkg.go.dev/github.com/libp2p/go-libp2p-pubsub@v0.8.1#PeerScoreParams
var DisabledPeerScoreParams = func(blockTime uint64) pubsub.PeerScoreParams {
slot := time.Duration(blockTime) * time.Second
if slot == 0 {
slot = 2 * time.Second
}
// We initialize an "epoch" as 6 blocks suggesting 6 blocks,
// each taking ~ 2 seconds, is 12 seconds
epoch := 6 * slot
......
......@@ -15,11 +15,6 @@ type PeerParamsTestSuite struct {
suite.Suite
}
// SetupTest sets up the test suite.
func (testSuite *PeerParamsTestSuite) SetupTest() {
// TODO:
}
// TestPeerParams runs the PeerParamsTestSuite.
func TestPeerParams(t *testing.T) {
suite.Run(t, new(PeerParamsTestSuite))
......@@ -127,4 +122,15 @@ func (testSuite *PeerParamsTestSuite) TestDisabledPeerScoreParams() {
testSuite.Equal(params.DecayInterval, slot)
testSuite.Equal(params.DecayToZero, DecayToZero)
testSuite.Equal(params.RetainScore, oneHundredEpochs)
}
\ No newline at end of file
}
// TestParamsZeroBlockTime validates peer score params use default slot for 0 block time.
func (testSuite *PeerParamsTestSuite) TestParamsZeroBlockTime() {
slot := 2 * time.Second
params, err := GetPeerScoreParams("none", uint64(0))
testSuite.NoError(err)
testSuite.Equal(params.DecayInterval, slot)
params, err = GetPeerScoreParams("light", uint64(0))
testSuite.NoError(err)
testSuite.Equal(params.DecayInterval, slot)
}
......@@ -4,8 +4,9 @@ import (
"github.com/ethereum/go-ethereum/log"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
// "github.com/libp2p/go-libp2p/core/peerstore"
"golang.org/x/exp/slices"
)
const ConnectionFactor = -10
......@@ -14,11 +15,25 @@ const PeerScoreThreshold = -100
type scorer struct {
connGater ConnectionGater
peerStore peerstore.Peerstore
peerStore Peerstore
metricer GossipMetricer
log log.Logger
}
// Peerstore is a subset of the libp2p peerstore.Peerstore interface.
//
//go:generate mockery --name Peerstore --output mocks/
type Peerstore interface {
// PeerInfo returns a peer.PeerInfo struct for given peer.ID.
// This is a small slice of the information Peerstore has on
// that peer, useful to other services.
PeerInfo(peer.ID) peer.AddrInfo
// Peers returns all of the peer IDs stored across all inner stores.
Peers() peer.IDSlice
}
// Scorer is a peer scorer that scores peers based on application-specific metrics.
type Scorer interface {
OnConnect()
......@@ -27,7 +42,7 @@ type Scorer interface {
}
// NewScorer returns a new peer scorer.
func NewScorer(connGater ConnectionGater, peerStore peerstore.Peerstore, metricer GossipMetricer, log log.Logger) Scorer {
func NewScorer(connGater ConnectionGater, peerStore Peerstore, metricer GossipMetricer, log log.Logger) Scorer {
return &scorer{
connGater: connGater,
peerStore: peerStore,
......@@ -52,8 +67,6 @@ func (s *scorer) SnapshotHook() pubsub.ExtendedPeerScoreInspectFn {
// Record peer score in the metricer
s.metricer.RecordPeerScoring(id, snap.Score)
// TODO: encorporate the number of peer connections/disconnections into the score
// TODO: or should we just affect the score in the OnConnect/OnDisconnect methods?
// TODO: if we don't have to do this calculation here, we can push score updates to the metricer
// TODO: which would leave the scoring to the pubsub lib
// peer, err := s.peerStore.Get(id)
......
package p2p_test
import (
"testing"
p2p "github.com/ethereum-optimism/optimism/op-node/p2p"
log "github.com/ethereum/go-ethereum/log"
node "github.com/ethereum-optimism/optimism/op-node/node"
suite "github.com/stretchr/testify/suite"
p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks"
)
// PeerScorerTestSuite tests peer parameterization.
type PeerScorerTestSuite struct {
suite.Suite
mockGater *p2pMocks.ConnectionGater
mockStore *p2pMocks.Peerstore
mockMetricer *p2pMocks.GossipMetricer
logger log.Logger
}
// SetupTest sets up the test suite.
func (testSuite *PeerScorerTestSuite) SetupTest() {
testSuite.mockGater = &p2pMocks.ConnectionGater{}
testSuite.mockStore = &p2pMocks.Peerstore{}
testSuite.mockMetricer = &p2pMocks.GossipMetricer{}
logger := node.DefaultLogConfig()
testSuite.logger = logger.NewLogger()
}
// TestPeerScorer runs the PeerScorerTestSuite.
func TestPeerScorer(t *testing.T) {
suite.Run(t, new(PeerScorerTestSuite))
}
// TestPeerScoreConstants validates the peer score constants.
func (testSuite *PeerScorerTestSuite) TestPeerScoreConstants() {
testSuite.Equal(-10, p2p.ConnectionFactor)
testSuite.Equal(-100, p2p.PeerScoreThreshold)
}
// TestPeerScorerOnConnect ensures we can call the OnConnect method on the peer scorer.
func (testSuite *PeerScorerTestSuite) TestPeerScorerOnConnect() {
scorer := p2p.NewScorer(
testSuite.mockGater,
testSuite.mockStore,
testSuite.mockMetricer,
testSuite.logger,
)
scorer.OnConnect()
}
// TestPeerScorerOnDisconnect ensures we can call the OnDisconnect method on the peer scorer.
func (testSuite *PeerScorerTestSuite) TestPeerScorerOnDisconnect() {
scorer := p2p.NewScorer(
testSuite.mockGater,
testSuite.mockStore,
testSuite.mockMetricer,
testSuite.logger,
)
scorer.OnDisconnect()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment