Commit 7a678883 authored by Francis Li's avatar Francis Li Committed by GitHub

[op-conductor] sequencer health monitor (#8714)

* Implement sequencer health monitor

* Remove unnecessary state variables
parent c205f685
package health
import (
"context"
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/dial"
)
// HealthMonitor defines the interface for monitoring the health of the sequencer.
type HealthMonitor interface {
// Subscribe returns a channel that will be notified for every health check.
Subscribe() <-chan bool
// Start starts the health check.
Start() error
// Stop stops the health check.
Stop() error
}
// NewSequencerHealthMonitor creates a new sequencer health monitor.
// interval is the interval between health checks measured in seconds.
// safeInterval is the interval between safe head progress measured in seconds.
// minPeerCount is the minimum number of peers required for the sequencer to be healthy.
func NewSequencerHealthMonitor(log log.Logger, interval, safeInterval, minPeerCount uint64, rollupCfg *rollup.Config, node dial.RollupClientInterface, p2p p2p.API) HealthMonitor {
return &SequencerHealthMonitor{
log: log,
done: make(chan struct{}),
interval: interval,
healthUpdateCh: make(chan bool),
rollupCfg: rollupCfg,
safeInterval: safeInterval,
minPeerCount: minPeerCount,
node: node,
p2p: p2p,
}
}
// SequencerHealthMonitor monitors sequencer health.
type SequencerHealthMonitor struct {
log log.Logger
done chan struct{}
wg sync.WaitGroup
rollupCfg *rollup.Config
safeInterval uint64
minPeerCount uint64
interval uint64
healthUpdateCh chan bool
node dial.RollupClientInterface
p2p p2p.API
}
var _ HealthMonitor = (*SequencerHealthMonitor)(nil)
// Start implements HealthMonitor.
func (hm *SequencerHealthMonitor) Start() error {
hm.log.Info("starting health monitor")
hm.wg.Add(1)
go hm.loop()
hm.log.Info("health monitor started")
return nil
}
// Stop implements HealthMonitor.
func (hm *SequencerHealthMonitor) Stop() error {
hm.log.Info("stopping health monitor")
close(hm.done)
hm.wg.Wait()
hm.log.Info("health monitor stopped")
return nil
}
// Subscribe implements HealthMonitor.
func (hm *SequencerHealthMonitor) Subscribe() <-chan bool {
return hm.healthUpdateCh
}
func (hm *SequencerHealthMonitor) loop() {
defer hm.wg.Done()
duration := time.Duration(hm.interval) * time.Second
ticker := time.NewTicker(duration)
defer ticker.Stop()
for {
select {
case <-hm.done:
return
case <-ticker.C:
hm.healthUpdateCh <- hm.healthCheck()
}
}
}
// healthCheck checks the health of the sequencer by 3 criteria:
// 1. unsafe head is progressing per block time
// 2. safe head is progressing every configured batch submission interval
// 3. peer count is above the configured minimum
func (hm *SequencerHealthMonitor) healthCheck() bool {
ctx := context.Background()
status, err := hm.node.SyncStatus(ctx)
if err != nil {
hm.log.Error("health monitor failed to get sync status", "err", err)
return false
}
now := uint64(time.Now().Unix())
// allow at most one block drift for unsafe head
if now-status.UnsafeL2.Time > hm.interval+hm.rollupCfg.BlockTime {
hm.log.Error("unsafe head is not progressing", "lastSeenUnsafeBlock", status.UnsafeL2)
return false
}
if now-status.SafeL2.Time > hm.safeInterval {
hm.log.Error("safe head is not progressing", "safe_head_time", status.SafeL2.Time, "now", now)
return false
}
stats, err := hm.p2p.PeerStats(ctx)
if err != nil {
hm.log.Error("health monitor failed to get peer stats", "err", err)
return false
}
if uint64(stats.Connected) < hm.minPeerCount {
hm.log.Error("peer count is below minimum", "connected", stats.Connected, "minPeerCount", hm.minPeerCount)
return false
}
return true
}
package health
import (
"context"
"testing"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/suite"
"github.com/ethereum-optimism/optimism/op-node/p2p"
p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testutils"
)
const (
unhealthyPeerCount = 0
minPeerCount = 1
healthyPeerCount = 2
blockTime = 2
)
type HealthMonitorTestSuite struct {
suite.Suite
log log.Logger
rc *testutils.MockRollupClient
pc *p2pMocks.API
interval uint64
safeInterval uint64
minPeerCount uint64
rollupCfg *rollup.Config
monitor HealthMonitor
}
func (s *HealthMonitorTestSuite) SetupSuite() {
s.log = testlog.Logger(s.T(), log.LvlInfo)
s.rc = &testutils.MockRollupClient{}
s.pc = &p2pMocks.API{}
s.interval = 1
s.safeInterval = 5
s.minPeerCount = minPeerCount
s.rollupCfg = &rollup.Config{
BlockTime: blockTime,
}
}
func (s *HealthMonitorTestSuite) SetupTest() {
s.monitor = NewSequencerHealthMonitor(s.log, s.interval, s.safeInterval, s.minPeerCount, s.rollupCfg, s.rc, s.pc)
err := s.monitor.Start()
s.NoError(err)
}
func (s *HealthMonitorTestSuite) TearDownTest() {
err := s.monitor.Stop()
s.NoError(err)
}
func (s *HealthMonitorTestSuite) TestUnhealthyLowPeerCount() {
now := uint64(time.Now().Unix())
ss1 := &eth.SyncStatus{
UnsafeL2: eth.L2BlockRef{
Time: now - 1,
},
SafeL2: eth.L2BlockRef{
Time: now - 2,
},
}
s.rc.ExpectSyncStatus(ss1, nil)
ps1 := &p2p.PeerStats{
Connected: unhealthyPeerCount,
}
s.pc.EXPECT().PeerStats(context.Background()).Return(ps1, nil).Times(1)
healthUpdateCh := s.monitor.Subscribe()
healthy := <-healthUpdateCh
s.False(healthy)
}
func (s *HealthMonitorTestSuite) TestUnhealthyUnsafeHeadNotProgressing() {
ps1 := &p2p.PeerStats{
Connected: healthyPeerCount,
}
s.pc.EXPECT().PeerStats(context.Background()).Return(ps1, nil).Times(3)
now := uint64(time.Now().Unix())
ss1 := &eth.SyncStatus{
UnsafeL2: eth.L2BlockRef{
Time: now - 1,
},
SafeL2: eth.L2BlockRef{
Time: now - 2,
},
}
s.rc.ExpectSyncStatus(ss1, nil)
s.rc.ExpectSyncStatus(ss1, nil)
s.rc.ExpectSyncStatus(ss1, nil)
healthUpdateCh := s.monitor.Subscribe()
for i := 0; i < 3; i++ {
healthy := <-healthUpdateCh
if i < 2 {
s.True(healthy)
} else {
s.False(healthy)
}
}
}
func (s *HealthMonitorTestSuite) TestUnhealthySafeHeadNotProgressing() {
ps1 := &p2p.PeerStats{
Connected: healthyPeerCount,
}
s.pc.EXPECT().PeerStats(context.Background()).Return(ps1, nil).Times(6)
now := uint64(time.Now().Unix())
syncStatusGenerator := func(unsafeTime uint64) *eth.SyncStatus {
return &eth.SyncStatus{
UnsafeL2: eth.L2BlockRef{
Time: unsafeTime,
},
SafeL2: eth.L2BlockRef{
Time: now,
},
}
}
s.rc.ExpectSyncStatus(syncStatusGenerator(now), nil)
s.rc.ExpectSyncStatus(syncStatusGenerator(now), nil)
s.rc.ExpectSyncStatus(syncStatusGenerator(now+2), nil)
s.rc.ExpectSyncStatus(syncStatusGenerator(now+2), nil)
s.rc.ExpectSyncStatus(syncStatusGenerator(now+4), nil)
s.rc.ExpectSyncStatus(syncStatusGenerator(now+4), nil)
healthUpdateCh := s.monitor.Subscribe()
for i := 0; i < 6; i++ {
healthy := <-healthUpdateCh
if i < 5 {
s.True(healthy)
} else {
s.False(healthy)
}
}
}
func TestHealthMonitor(t *testing.T) {
suite.Run(t, new(HealthMonitorTestSuite))
}
// Code generated by mockery v2.28.1. DO NOT EDIT.
package mocks
import (
context "context"
enode "github.com/ethereum/go-ethereum/p2p/enode"
mock "github.com/stretchr/testify/mock"
net "net"
p2p "github.com/ethereum-optimism/optimism/op-node/p2p"
peer "github.com/libp2p/go-libp2p/core/peer"
)
// API is an autogenerated mock type for the API type
type API struct {
mock.Mock
}
type API_Expecter struct {
mock *mock.Mock
}
func (_m *API) EXPECT() *API_Expecter {
return &API_Expecter{mock: &_m.Mock}
}
// BlockAddr provides a mock function with given fields: ctx, ip
func (_m *API) BlockAddr(ctx context.Context, ip net.IP) error {
ret := _m.Called(ctx, ip)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, net.IP) error); ok {
r0 = rf(ctx, ip)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_BlockAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockAddr'
type API_BlockAddr_Call struct {
*mock.Call
}
// BlockAddr is a helper method to define mock.On call
// - ctx context.Context
// - ip net.IP
func (_e *API_Expecter) BlockAddr(ctx interface{}, ip interface{}) *API_BlockAddr_Call {
return &API_BlockAddr_Call{Call: _e.mock.On("BlockAddr", ctx, ip)}
}
func (_c *API_BlockAddr_Call) Run(run func(ctx context.Context, ip net.IP)) *API_BlockAddr_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(net.IP))
})
return _c
}
func (_c *API_BlockAddr_Call) Return(_a0 error) *API_BlockAddr_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_BlockAddr_Call) RunAndReturn(run func(context.Context, net.IP) error) *API_BlockAddr_Call {
_c.Call.Return(run)
return _c
}
// BlockPeer provides a mock function with given fields: ctx, p
func (_m *API) BlockPeer(ctx context.Context, p peer.ID) error {
ret := _m.Called(ctx, p)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, peer.ID) error); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_BlockPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockPeer'
type API_BlockPeer_Call struct {
*mock.Call
}
// BlockPeer is a helper method to define mock.On call
// - ctx context.Context
// - p peer.ID
func (_e *API_Expecter) BlockPeer(ctx interface{}, p interface{}) *API_BlockPeer_Call {
return &API_BlockPeer_Call{Call: _e.mock.On("BlockPeer", ctx, p)}
}
func (_c *API_BlockPeer_Call) Run(run func(ctx context.Context, p peer.ID)) *API_BlockPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(peer.ID))
})
return _c
}
func (_c *API_BlockPeer_Call) Return(_a0 error) *API_BlockPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_BlockPeer_Call) RunAndReturn(run func(context.Context, peer.ID) error) *API_BlockPeer_Call {
_c.Call.Return(run)
return _c
}
// BlockSubnet provides a mock function with given fields: ctx, ipnet
func (_m *API) BlockSubnet(ctx context.Context, ipnet *net.IPNet) error {
ret := _m.Called(ctx, ipnet)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *net.IPNet) error); ok {
r0 = rf(ctx, ipnet)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_BlockSubnet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockSubnet'
type API_BlockSubnet_Call struct {
*mock.Call
}
// BlockSubnet is a helper method to define mock.On call
// - ctx context.Context
// - ipnet *net.IPNet
func (_e *API_Expecter) BlockSubnet(ctx interface{}, ipnet interface{}) *API_BlockSubnet_Call {
return &API_BlockSubnet_Call{Call: _e.mock.On("BlockSubnet", ctx, ipnet)}
}
func (_c *API_BlockSubnet_Call) Run(run func(ctx context.Context, ipnet *net.IPNet)) *API_BlockSubnet_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*net.IPNet))
})
return _c
}
func (_c *API_BlockSubnet_Call) Return(_a0 error) *API_BlockSubnet_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_BlockSubnet_Call) RunAndReturn(run func(context.Context, *net.IPNet) error) *API_BlockSubnet_Call {
_c.Call.Return(run)
return _c
}
// ConnectPeer provides a mock function with given fields: ctx, addr
func (_m *API) ConnectPeer(ctx context.Context, addr string) error {
ret := _m.Called(ctx, addr)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, addr)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_ConnectPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConnectPeer'
type API_ConnectPeer_Call struct {
*mock.Call
}
// ConnectPeer is a helper method to define mock.On call
// - ctx context.Context
// - addr string
func (_e *API_Expecter) ConnectPeer(ctx interface{}, addr interface{}) *API_ConnectPeer_Call {
return &API_ConnectPeer_Call{Call: _e.mock.On("ConnectPeer", ctx, addr)}
}
func (_c *API_ConnectPeer_Call) Run(run func(ctx context.Context, addr string)) *API_ConnectPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *API_ConnectPeer_Call) Return(_a0 error) *API_ConnectPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_ConnectPeer_Call) RunAndReturn(run func(context.Context, string) error) *API_ConnectPeer_Call {
_c.Call.Return(run)
return _c
}
// DisconnectPeer provides a mock function with given fields: ctx, id
func (_m *API) DisconnectPeer(ctx context.Context, id peer.ID) error {
ret := _m.Called(ctx, id)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, peer.ID) error); ok {
r0 = rf(ctx, id)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_DisconnectPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectPeer'
type API_DisconnectPeer_Call struct {
*mock.Call
}
// DisconnectPeer is a helper method to define mock.On call
// - ctx context.Context
// - id peer.ID
func (_e *API_Expecter) DisconnectPeer(ctx interface{}, id interface{}) *API_DisconnectPeer_Call {
return &API_DisconnectPeer_Call{Call: _e.mock.On("DisconnectPeer", ctx, id)}
}
func (_c *API_DisconnectPeer_Call) Run(run func(ctx context.Context, id peer.ID)) *API_DisconnectPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(peer.ID))
})
return _c
}
func (_c *API_DisconnectPeer_Call) Return(_a0 error) *API_DisconnectPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_DisconnectPeer_Call) RunAndReturn(run func(context.Context, peer.ID) error) *API_DisconnectPeer_Call {
_c.Call.Return(run)
return _c
}
// DiscoveryTable provides a mock function with given fields: ctx
func (_m *API) DiscoveryTable(ctx context.Context) ([]*enode.Node, error) {
ret := _m.Called(ctx)
var r0 []*enode.Node
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]*enode.Node, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []*enode.Node); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*enode.Node)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_DiscoveryTable_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DiscoveryTable'
type API_DiscoveryTable_Call struct {
*mock.Call
}
// DiscoveryTable is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) DiscoveryTable(ctx interface{}) *API_DiscoveryTable_Call {
return &API_DiscoveryTable_Call{Call: _e.mock.On("DiscoveryTable", ctx)}
}
func (_c *API_DiscoveryTable_Call) Run(run func(ctx context.Context)) *API_DiscoveryTable_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_DiscoveryTable_Call) Return(_a0 []*enode.Node, _a1 error) *API_DiscoveryTable_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_DiscoveryTable_Call) RunAndReturn(run func(context.Context) ([]*enode.Node, error)) *API_DiscoveryTable_Call {
_c.Call.Return(run)
return _c
}
// ListBlockedAddrs provides a mock function with given fields: ctx
func (_m *API) ListBlockedAddrs(ctx context.Context) ([]net.IP, error) {
ret := _m.Called(ctx)
var r0 []net.IP
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]net.IP, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []net.IP); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]net.IP)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_ListBlockedAddrs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBlockedAddrs'
type API_ListBlockedAddrs_Call struct {
*mock.Call
}
// ListBlockedAddrs is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) ListBlockedAddrs(ctx interface{}) *API_ListBlockedAddrs_Call {
return &API_ListBlockedAddrs_Call{Call: _e.mock.On("ListBlockedAddrs", ctx)}
}
func (_c *API_ListBlockedAddrs_Call) Run(run func(ctx context.Context)) *API_ListBlockedAddrs_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_ListBlockedAddrs_Call) Return(_a0 []net.IP, _a1 error) *API_ListBlockedAddrs_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_ListBlockedAddrs_Call) RunAndReturn(run func(context.Context) ([]net.IP, error)) *API_ListBlockedAddrs_Call {
_c.Call.Return(run)
return _c
}
// ListBlockedPeers provides a mock function with given fields: ctx
func (_m *API) ListBlockedPeers(ctx context.Context) ([]peer.ID, error) {
ret := _m.Called(ctx)
var r0 []peer.ID
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]peer.ID, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []peer.ID); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]peer.ID)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_ListBlockedPeers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBlockedPeers'
type API_ListBlockedPeers_Call struct {
*mock.Call
}
// ListBlockedPeers is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) ListBlockedPeers(ctx interface{}) *API_ListBlockedPeers_Call {
return &API_ListBlockedPeers_Call{Call: _e.mock.On("ListBlockedPeers", ctx)}
}
func (_c *API_ListBlockedPeers_Call) Run(run func(ctx context.Context)) *API_ListBlockedPeers_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_ListBlockedPeers_Call) Return(_a0 []peer.ID, _a1 error) *API_ListBlockedPeers_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_ListBlockedPeers_Call) RunAndReturn(run func(context.Context) ([]peer.ID, error)) *API_ListBlockedPeers_Call {
_c.Call.Return(run)
return _c
}
// ListBlockedSubnets provides a mock function with given fields: ctx
func (_m *API) ListBlockedSubnets(ctx context.Context) ([]*net.IPNet, error) {
ret := _m.Called(ctx)
var r0 []*net.IPNet
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) ([]*net.IPNet, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) []*net.IPNet); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*net.IPNet)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_ListBlockedSubnets_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListBlockedSubnets'
type API_ListBlockedSubnets_Call struct {
*mock.Call
}
// ListBlockedSubnets is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) ListBlockedSubnets(ctx interface{}) *API_ListBlockedSubnets_Call {
return &API_ListBlockedSubnets_Call{Call: _e.mock.On("ListBlockedSubnets", ctx)}
}
func (_c *API_ListBlockedSubnets_Call) Run(run func(ctx context.Context)) *API_ListBlockedSubnets_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_ListBlockedSubnets_Call) Return(_a0 []*net.IPNet, _a1 error) *API_ListBlockedSubnets_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_ListBlockedSubnets_Call) RunAndReturn(run func(context.Context) ([]*net.IPNet, error)) *API_ListBlockedSubnets_Call {
_c.Call.Return(run)
return _c
}
// PeerStats provides a mock function with given fields: ctx
func (_m *API) PeerStats(ctx context.Context) (*p2p.PeerStats, error) {
ret := _m.Called(ctx)
var r0 *p2p.PeerStats
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (*p2p.PeerStats, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *p2p.PeerStats); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*p2p.PeerStats)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_PeerStats_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PeerStats'
type API_PeerStats_Call struct {
*mock.Call
}
// PeerStats is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) PeerStats(ctx interface{}) *API_PeerStats_Call {
return &API_PeerStats_Call{Call: _e.mock.On("PeerStats", ctx)}
}
func (_c *API_PeerStats_Call) Run(run func(ctx context.Context)) *API_PeerStats_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_PeerStats_Call) Return(_a0 *p2p.PeerStats, _a1 error) *API_PeerStats_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_PeerStats_Call) RunAndReturn(run func(context.Context) (*p2p.PeerStats, error)) *API_PeerStats_Call {
_c.Call.Return(run)
return _c
}
// Peers provides a mock function with given fields: ctx, connected
func (_m *API) Peers(ctx context.Context, connected bool) (*p2p.PeerDump, error) {
ret := _m.Called(ctx, connected)
var r0 *p2p.PeerDump
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, bool) (*p2p.PeerDump, error)); ok {
return rf(ctx, connected)
}
if rf, ok := ret.Get(0).(func(context.Context, bool) *p2p.PeerDump); ok {
r0 = rf(ctx, connected)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*p2p.PeerDump)
}
}
if rf, ok := ret.Get(1).(func(context.Context, bool) error); ok {
r1 = rf(ctx, connected)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_Peers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Peers'
type API_Peers_Call struct {
*mock.Call
}
// Peers is a helper method to define mock.On call
// - ctx context.Context
// - connected bool
func (_e *API_Expecter) Peers(ctx interface{}, connected interface{}) *API_Peers_Call {
return &API_Peers_Call{Call: _e.mock.On("Peers", ctx, connected)}
}
func (_c *API_Peers_Call) Run(run func(ctx context.Context, connected bool)) *API_Peers_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(bool))
})
return _c
}
func (_c *API_Peers_Call) Return(_a0 *p2p.PeerDump, _a1 error) *API_Peers_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_Peers_Call) RunAndReturn(run func(context.Context, bool) (*p2p.PeerDump, error)) *API_Peers_Call {
_c.Call.Return(run)
return _c
}
// ProtectPeer provides a mock function with given fields: ctx, p
func (_m *API) ProtectPeer(ctx context.Context, p peer.ID) error {
ret := _m.Called(ctx, p)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, peer.ID) error); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_ProtectPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ProtectPeer'
type API_ProtectPeer_Call struct {
*mock.Call
}
// ProtectPeer is a helper method to define mock.On call
// - ctx context.Context
// - p peer.ID
func (_e *API_Expecter) ProtectPeer(ctx interface{}, p interface{}) *API_ProtectPeer_Call {
return &API_ProtectPeer_Call{Call: _e.mock.On("ProtectPeer", ctx, p)}
}
func (_c *API_ProtectPeer_Call) Run(run func(ctx context.Context, p peer.ID)) *API_ProtectPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(peer.ID))
})
return _c
}
func (_c *API_ProtectPeer_Call) Return(_a0 error) *API_ProtectPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_ProtectPeer_Call) RunAndReturn(run func(context.Context, peer.ID) error) *API_ProtectPeer_Call {
_c.Call.Return(run)
return _c
}
// Self provides a mock function with given fields: ctx
func (_m *API) Self(ctx context.Context) (*p2p.PeerInfo, error) {
ret := _m.Called(ctx)
var r0 *p2p.PeerInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (*p2p.PeerInfo, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *p2p.PeerInfo); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*p2p.PeerInfo)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// API_Self_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Self'
type API_Self_Call struct {
*mock.Call
}
// Self is a helper method to define mock.On call
// - ctx context.Context
func (_e *API_Expecter) Self(ctx interface{}) *API_Self_Call {
return &API_Self_Call{Call: _e.mock.On("Self", ctx)}
}
func (_c *API_Self_Call) Run(run func(ctx context.Context)) *API_Self_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *API_Self_Call) Return(_a0 *p2p.PeerInfo, _a1 error) *API_Self_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *API_Self_Call) RunAndReturn(run func(context.Context) (*p2p.PeerInfo, error)) *API_Self_Call {
_c.Call.Return(run)
return _c
}
// UnblockAddr provides a mock function with given fields: ctx, ip
func (_m *API) UnblockAddr(ctx context.Context, ip net.IP) error {
ret := _m.Called(ctx, ip)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, net.IP) error); ok {
r0 = rf(ctx, ip)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_UnblockAddr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnblockAddr'
type API_UnblockAddr_Call struct {
*mock.Call
}
// UnblockAddr is a helper method to define mock.On call
// - ctx context.Context
// - ip net.IP
func (_e *API_Expecter) UnblockAddr(ctx interface{}, ip interface{}) *API_UnblockAddr_Call {
return &API_UnblockAddr_Call{Call: _e.mock.On("UnblockAddr", ctx, ip)}
}
func (_c *API_UnblockAddr_Call) Run(run func(ctx context.Context, ip net.IP)) *API_UnblockAddr_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(net.IP))
})
return _c
}
func (_c *API_UnblockAddr_Call) Return(_a0 error) *API_UnblockAddr_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_UnblockAddr_Call) RunAndReturn(run func(context.Context, net.IP) error) *API_UnblockAddr_Call {
_c.Call.Return(run)
return _c
}
// UnblockPeer provides a mock function with given fields: ctx, p
func (_m *API) UnblockPeer(ctx context.Context, p peer.ID) error {
ret := _m.Called(ctx, p)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, peer.ID) error); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_UnblockPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnblockPeer'
type API_UnblockPeer_Call struct {
*mock.Call
}
// UnblockPeer is a helper method to define mock.On call
// - ctx context.Context
// - p peer.ID
func (_e *API_Expecter) UnblockPeer(ctx interface{}, p interface{}) *API_UnblockPeer_Call {
return &API_UnblockPeer_Call{Call: _e.mock.On("UnblockPeer", ctx, p)}
}
func (_c *API_UnblockPeer_Call) Run(run func(ctx context.Context, p peer.ID)) *API_UnblockPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(peer.ID))
})
return _c
}
func (_c *API_UnblockPeer_Call) Return(_a0 error) *API_UnblockPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_UnblockPeer_Call) RunAndReturn(run func(context.Context, peer.ID) error) *API_UnblockPeer_Call {
_c.Call.Return(run)
return _c
}
// UnblockSubnet provides a mock function with given fields: ctx, ipnet
func (_m *API) UnblockSubnet(ctx context.Context, ipnet *net.IPNet) error {
ret := _m.Called(ctx, ipnet)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *net.IPNet) error); ok {
r0 = rf(ctx, ipnet)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_UnblockSubnet_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnblockSubnet'
type API_UnblockSubnet_Call struct {
*mock.Call
}
// UnblockSubnet is a helper method to define mock.On call
// - ctx context.Context
// - ipnet *net.IPNet
func (_e *API_Expecter) UnblockSubnet(ctx interface{}, ipnet interface{}) *API_UnblockSubnet_Call {
return &API_UnblockSubnet_Call{Call: _e.mock.On("UnblockSubnet", ctx, ipnet)}
}
func (_c *API_UnblockSubnet_Call) Run(run func(ctx context.Context, ipnet *net.IPNet)) *API_UnblockSubnet_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(*net.IPNet))
})
return _c
}
func (_c *API_UnblockSubnet_Call) Return(_a0 error) *API_UnblockSubnet_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_UnblockSubnet_Call) RunAndReturn(run func(context.Context, *net.IPNet) error) *API_UnblockSubnet_Call {
_c.Call.Return(run)
return _c
}
// UnprotectPeer provides a mock function with given fields: ctx, p
func (_m *API) UnprotectPeer(ctx context.Context, p peer.ID) error {
ret := _m.Called(ctx, p)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, peer.ID) error); ok {
r0 = rf(ctx, p)
} else {
r0 = ret.Error(0)
}
return r0
}
// API_UnprotectPeer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnprotectPeer'
type API_UnprotectPeer_Call struct {
*mock.Call
}
// UnprotectPeer is a helper method to define mock.On call
// - ctx context.Context
// - p peer.ID
func (_e *API_Expecter) UnprotectPeer(ctx interface{}, p interface{}) *API_UnprotectPeer_Call {
return &API_UnprotectPeer_Call{Call: _e.mock.On("UnprotectPeer", ctx, p)}
}
func (_c *API_UnprotectPeer_Call) Run(run func(ctx context.Context, p peer.ID)) *API_UnprotectPeer_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(peer.ID))
})
return _c
}
func (_c *API_UnprotectPeer_Call) Return(_a0 error) *API_UnprotectPeer_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *API_UnprotectPeer_Call) RunAndReturn(run func(context.Context, peer.ID) error) *API_UnprotectPeer_Call {
_c.Call.Return(run)
return _c
}
type mockConstructorTestingTNewAPI interface {
mock.TestingT
Cleanup(func())
}
// NewAPI creates a new instance of API. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewAPI(t mockConstructorTestingTNewAPI) *API {
mock := &API{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
package p2p package p2p_test
import ( import (
"context" "context"
...@@ -8,15 +8,6 @@ import ( ...@@ -8,15 +8,6 @@ import (
"testing" "testing"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/rollup"
//nolint:all
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks"
"github.com/ethereum-optimism/optimism/op-node/p2p/store"
"github.com/ethereum-optimism/optimism/op-service/clock"
testlog "github.com/ethereum-optimism/optimism/op-service/testlog"
log "github.com/ethereum/go-ethereum/log" log "github.com/ethereum/go-ethereum/log"
ds "github.com/ipfs/go-datastore" ds "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/sync" "github.com/ipfs/go-datastore/sync"
...@@ -26,11 +17,20 @@ import ( ...@@ -26,11 +17,20 @@ import (
peer "github.com/libp2p/go-libp2p/core/peer" peer "github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore" "github.com/libp2p/go-libp2p/core/peerstore"
bhost "github.com/libp2p/go-libp2p/p2p/host/blank" bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
tswarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
//nolint:all
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
tswarm "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
p2p "github.com/ethereum-optimism/optimism/op-node/p2p"
p2pMocks "github.com/ethereum-optimism/optimism/op-node/p2p/mocks"
"github.com/ethereum-optimism/optimism/op-node/p2p/store"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/clock"
testlog "github.com/ethereum-optimism/optimism/op-service/testlog"
) )
// PeerScoresTestSuite tests peer parameterization. // PeerScoresTestSuite tests peer parameterization.
...@@ -86,7 +86,7 @@ func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []h ...@@ -86,7 +86,7 @@ func getNetHosts(testSuite *PeerScoresTestSuite, ctx context.Context, n int) []h
type discriminatingAppScorer struct { type discriminatingAppScorer struct {
badPeer peer.ID badPeer peer.ID
NoopApplicationScorer p2p.NoopApplicationScorer
} }
func (d *discriminatingAppScorer) ApplicationScore(id peer.ID) float64 { func (d *discriminatingAppScorer) ApplicationScore(id peer.ID) float64 {
...@@ -112,11 +112,11 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts [] ...@@ -112,11 +112,11 @@ func newGossipSubs(testSuite *PeerScoresTestSuite, ctx context.Context, hosts []
extPeerStore, err := store.NewExtendedPeerstore(context.Background(), logger, clock.SystemClock, peerStore, dataStore, 1*time.Hour) extPeerStore, err := store.NewExtendedPeerstore(context.Background(), logger, clock.SystemClock, peerStore, dataStore, 1*time.Hour)
require.NoError(testSuite.T(), err) require.NoError(testSuite.T(), err)
scorer := NewScorer( scorer := p2p.NewScorer(
&rollup.Config{L2ChainID: big.NewInt(123)}, &rollup.Config{L2ChainID: big.NewInt(123)},
extPeerStore, testSuite.mockMetricer, &discriminatingAppScorer{badPeer: hosts[0].ID()}, logger) extPeerStore, testSuite.mockMetricer, &discriminatingAppScorer{badPeer: hosts[0].ID()}, logger)
opts = append(opts, ConfigurePeerScoring(&Config{ opts = append(opts, p2p.ConfigurePeerScoring(&p2p.Config{
ScoringParams: &ScoringParams{ ScoringParams: &p2p.ScoringParams{
PeerScoring: pubsub.PeerScoreParams{ PeerScoring: pubsub.PeerScoreParams{
AppSpecificWeight: 1, AppSpecificWeight: 1,
DecayInterval: time.Second, DecayInterval: time.Second,
......
...@@ -5,11 +5,11 @@ import ( ...@@ -5,11 +5,11 @@ import (
"net" "net"
"time" "time"
"github.com/ethereum-optimism/optimism/op-node/p2p/store" "github.com/ethereum/go-ethereum/p2p/enode"
"github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peer"
"github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum-optimism/optimism/op-node/p2p/store"
) )
type PeerInfo struct { type PeerInfo struct {
...@@ -20,8 +20,8 @@ type PeerInfo struct { ...@@ -20,8 +20,8 @@ type PeerInfo struct {
ENR string `json:"ENR"` // might not always be known, e.g. if the peer connected us instead of us discovering them ENR string `json:"ENR"` // might not always be known, e.g. if the peer connected us instead of us discovering them
Addresses []string `json:"addresses"` // multi-addresses. may be mix of LAN / docker / external IPs. All of them are communicated. Addresses []string `json:"addresses"` // multi-addresses. may be mix of LAN / docker / external IPs. All of them are communicated.
Protocols []string `json:"protocols"` // negotiated protocols list Protocols []string `json:"protocols"` // negotiated protocols list
//GossipScore float64 // GossipScore float64
//PeerScore float64 // PeerScore float64
Connectedness network.Connectedness `json:"connectedness"` // "NotConnected", "Connected", "CanConnect" (gracefully disconnected), or "CannotConnect" (tried but failed) Connectedness network.Connectedness `json:"connectedness"` // "NotConnected", "Connected", "CanConnect" (gracefully disconnected), or "CannotConnect" (tried but failed)
Direction network.Direction `json:"direction"` // "Unknown", "Inbound" (if the peer contacted us), "Outbound" (if we connected to them) Direction network.Direction `json:"direction"` // "Unknown", "Inbound" (if the peer contacted us), "Outbound" (if we connected to them)
Protected bool `json:"protected"` // Protected peers do not get Protected bool `json:"protected"` // Protected peers do not get
...@@ -41,6 +41,7 @@ type PeerDump struct { ...@@ -41,6 +41,7 @@ type PeerDump struct {
BannedSubnets []*net.IPNet `json:"bannedSubnets"` BannedSubnets []*net.IPNet `json:"bannedSubnets"`
} }
//go:generate mockery --name API --output mocks/ --with-expecter=true
type API interface { type API interface {
Self(ctx context.Context) (*PeerInfo, error) Self(ctx context.Context) (*PeerInfo, error)
Peers(ctx context.Context, connected bool) (*PeerDump, error) Peers(ctx context.Context, connected bool) (*PeerDump, error)
......
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