controller_test.go 7.45 KB
Newer Older
1 2 3 4 5 6
package syncnode

import (
	"context"
	"testing"

7 8 9 10 11 12 13
	"github.com/stretchr/testify/require"

	"github.com/ethereum/go-ethereum"
	gethevent "github.com/ethereum/go-ethereum/event"
	"github.com/ethereum/go-ethereum/log"

	"github.com/ethereum-optimism/optimism/op-node/rollup/event"
14
	"github.com/ethereum-optimism/optimism/op-service/eth"
15
	"github.com/ethereum-optimism/optimism/op-service/testlog"
16
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
17
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/superevents"
18 19 20 21
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
)

type mockSyncControl struct {
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
	anchorPointFn       func(ctx context.Context) (types.DerivedBlockRefPair, error)
	provideL1Fn         func(ctx context.Context, ref eth.BlockRef) error
	resetFn             func(ctx context.Context, unsafe, safe, finalized eth.BlockID) error
	updateCrossSafeFn   func(ctx context.Context, derived, derivedFrom eth.BlockID) error
	updateCrossUnsafeFn func(ctx context.Context, derived eth.BlockID) error
	updateFinalizedFn   func(ctx context.Context, id eth.BlockID) error
	pullEventFn         func(ctx context.Context) (*types.ManagedEvent, error)

	subscribeEvents gethevent.FeedOf[*types.ManagedEvent]
}

func (m *mockSyncControl) AnchorPoint(ctx context.Context) (types.DerivedBlockRefPair, error) {
	if m.anchorPointFn != nil {
		return m.anchorPointFn(ctx)
	}
	return types.DerivedBlockRefPair{}, nil
}

func (m *mockSyncControl) ProvideL1(ctx context.Context, ref eth.BlockRef) error {
	if m.provideL1Fn != nil {
		return m.provideL1Fn(ctx, ref)
	}
	return nil
}

func (m *mockSyncControl) Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error {
	if m.resetFn != nil {
		return m.resetFn(ctx, unsafe, safe, finalized)
	}
	return nil
}

func (m *mockSyncControl) PullEvent(ctx context.Context) (*types.ManagedEvent, error) {
	if m.pullEventFn != nil {
		return m.pullEventFn(ctx)
	}
	return nil, nil
}

func (m *mockSyncControl) SubscribeEvents(ctx context.Context, ch chan *types.ManagedEvent) (ethereum.Subscription, error) {
	return m.subscribeEvents.Subscribe(ch), nil
}

func (m *mockSyncControl) UpdateCrossSafe(ctx context.Context, derived eth.BlockID, derivedFrom eth.BlockID) error {
	if m.updateCrossSafeFn != nil {
		return m.updateCrossSafeFn(ctx, derived, derivedFrom)
	}
	return nil
}

func (m *mockSyncControl) UpdateCrossUnsafe(ctx context.Context, derived eth.BlockID) error {
	if m.updateCrossUnsafeFn != nil {
		return m.updateCrossUnsafeFn(ctx, derived)
	}
	return nil
}

func (m *mockSyncControl) UpdateFinalized(ctx context.Context, id eth.BlockID) error {
	if m.updateFinalizedFn != nil {
		return m.updateFinalizedFn(ctx, id)
	}
	return nil
}

var _ SyncControl = (*mockSyncControl)(nil)

88 89 90
type mockBackend struct {
	safeDerivedAtFn func(ctx context.Context, chainID eth.ChainID, derivedFrom eth.BlockID) (eth.BlockID, error)
}
91

92
func (m *mockBackend) LocalSafe(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) {
93 94 95
	return types.DerivedIDPair{}, nil
}

96
func (m *mockBackend) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) {
97 98 99
	return eth.BlockID{}, nil
}

100
func (m *mockBackend) SafeDerivedAt(ctx context.Context, chainID eth.ChainID, derivedFrom eth.BlockID) (derived eth.BlockID, err error) {
101 102 103
	if m.safeDerivedAtFn != nil {
		return m.safeDerivedAtFn(ctx, chainID, derivedFrom)
	}
104 105 106
	return eth.BlockID{}, nil
}

107
func (m *mockBackend) Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) {
108 109 110 111 112
	return eth.BlockID{}, nil
}

func (m *mockBackend) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) {
	return eth.L1BlockRef{}, nil
113
}
114 115 116

var _ backend = (*mockBackend)(nil)

117 118
func sampleDepSet(t *testing.T) depset.DependencySet {
	depSet, err := depset.NewStaticConfigDependencySet(
119 120
		map[eth.ChainID]*depset.StaticConfigDependency{
			eth.ChainIDFromUInt64(900): {
121 122 123 124
				ChainIndex:     900,
				ActivationTime: 42,
				HistoryMinTime: 100,
			},
125
			eth.ChainIDFromUInt64(901): {
126 127 128 129 130 131 132 133 134
				ChainIndex:     901,
				ActivationTime: 30,
				HistoryMinTime: 20,
			},
		})
	require.NoError(t, err)
	return depSet
}

135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
type eventMonitor struct {
	anchorCalled        int
	localDerived        int
	receivedLocalUnsafe int
}

func (m *eventMonitor) OnEvent(ev event.Event) bool {
	switch ev.(type) {
	case superevents.AnchorEvent:
		m.anchorCalled += 1
	case superevents.LocalDerivedEvent:
		m.localDerived += 1
	case superevents.LocalUnsafeReceivedEvent:
		m.receivedLocalUnsafe += 1
	default:
		return false
	}
	return true
}

155 156 157
// TestInitFromAnchorPoint tests that the SyncNodesController uses the Anchor Point to initialize databases
func TestInitFromAnchorPoint(t *testing.T) {
	logger := testlog.Logger(t, log.LvlInfo)
158
	depSet := sampleDepSet(t)
159 160 161 162 163 164 165 166
	ex := event.NewGlobalSynchronous(context.Background())
	eventSys := event.NewSystem(logger, ex)

	mon := &eventMonitor{}
	eventSys.Register("monitor", mon, event.DefaultRegisterOpts())

	controller := NewSyncNodesController(logger, depSet, eventSys, &mockBackend{})
	eventSys.Register("controller", controller, event.DefaultRegisterOpts())
167 168 169 170

	require.Zero(t, controller.controllers.Len(), "controllers should be empty to start")

	// Attach a controller for chain 900
171
	// make the controller return an anchor point
172
	ctrl := mockSyncControl{}
173 174 175 176 177 178
	ctrl.anchorPointFn = func(ctx context.Context) (types.DerivedBlockRefPair, error) {
		return types.DerivedBlockRefPair{
			Derived:     eth.BlockRef{Number: 1},
			DerivedFrom: eth.BlockRef{Number: 0},
		}, nil
	}
179

180
	// after the first attach, both databases are called for update
181
	_, err := controller.AttachNodeController(eth.ChainIDFromUInt64(900), &ctrl, false)
182
	require.NoError(t, err)
183 184
	require.NoError(t, ex.Drain())
	require.Equal(t, 1, mon.anchorCalled, "an anchor point should be received")
185

186
	// on second attach we send the anchor again; it's up to the DB to use it or not.
187
	ctrl2 := mockSyncControl{}
188
	_, err = controller.AttachNodeController(eth.ChainIDFromUInt64(901), &ctrl2, false)
189
	require.NoError(t, err)
190 191
	require.NoError(t, ex.Drain())
	require.Equal(t, 2, mon.anchorCalled, "anchor point again")
192 193
}

194 195 196
// TestAttachNodeController tests the AttachNodeController function of the SyncNodesController.
// Only controllers for chains in the dependency set can be attached.
func TestAttachNodeController(t *testing.T) {
197 198
	logger := log.New()
	depSet := sampleDepSet(t)
199 200 201 202
	ex := event.NewGlobalSynchronous(context.Background())
	eventSys := event.NewSystem(logger, ex)
	controller := NewSyncNodesController(logger, depSet, eventSys, &mockBackend{})
	eventSys.Register("controller", controller, event.DefaultRegisterOpts())
203 204 205 206
	require.Zero(t, controller.controllers.Len(), "controllers should be empty to start")

	// Attach a controller for chain 900
	ctrl := mockSyncControl{}
207
	_, err := controller.AttachNodeController(eth.ChainIDFromUInt64(900), &ctrl, false)
208 209
	require.NoError(t, err)

210 211 212
	require.Equal(t, 1, controller.controllers.Len(), "controllers should have 1 entry")

	// Attach a controller for chain 901
213
	ctrl2 := mockSyncControl{}
214
	_, err = controller.AttachNodeController(eth.ChainIDFromUInt64(901), &ctrl2, false)
215 216
	require.NoError(t, err)

217
	require.Equal(t, 2, controller.controllers.Len(), "controllers should have 2 entries")
218

219 220
	// Attach a controller for chain 902 (which is not in the dependency set)
	ctrl3 := mockSyncControl{}
221
	_, err = controller.AttachNodeController(eth.ChainIDFromUInt64(902), &ctrl3, false)
222 223
	require.Error(t, err)
	require.Equal(t, 2, controller.controllers.Len(), "controllers should still have 2 entries")
224
}