277 lines
7.8 KiB
Go
277 lines
7.8 KiB
Go
package finalizers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/certusone/wormhole/node/pkg/watchers/evm/connectors"
|
|
"github.com/certusone/wormhole/node/pkg/watchers/interfaces"
|
|
|
|
ethBind "github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
ethCommon "github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type (
|
|
mockCtcCaller struct {
|
|
mutex sync.Mutex
|
|
totalElements []*big.Int
|
|
lastBlockNumbers []*big.Int
|
|
totalElementsErr error
|
|
lastBlockNumbersErr error
|
|
}
|
|
)
|
|
|
|
// SetTotalElements takes an array of big int pointers that represent the L2 block numbers to be returned by GetTotalElements()
|
|
func (m *mockCtcCaller) SetTotalElements(totalElements []*big.Int) {
|
|
m.mutex.Lock()
|
|
m.totalElements = totalElements
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
// SetLastBlockNumber takes an array of big int pointers that represent the L1 block numbers to be returned by GetLastBlockNumber()
|
|
func (m *mockCtcCaller) SetLastBlockNumbers(lastBlockNumbers []*big.Int) {
|
|
m.mutex.Lock()
|
|
m.lastBlockNumbers = lastBlockNumbers
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
// SetTotalElementsError takes an error (or nil) which will be returned on the next call to GetTotalElements. The error will persist until cleared.
|
|
func (m *mockCtcCaller) SetTotalElementsError(err error) {
|
|
m.mutex.Lock()
|
|
m.totalElementsErr = err
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
// SetLastBlockNumber takes an error (or nil) which will be returned on the next call to GetLastBlockNumber. The error will persist until cleared.
|
|
func (m *mockCtcCaller) SetLastBlockNumberError(err error) {
|
|
m.mutex.Lock()
|
|
m.lastBlockNumbersErr = err
|
|
m.mutex.Unlock()
|
|
}
|
|
|
|
func (m *mockCtcCaller) GetTotalElements(opts *ethBind.CallOpts) (result *big.Int, err error) {
|
|
m.totalElements, result, err = m.getResult(m.totalElements, m.totalElementsErr)
|
|
return
|
|
}
|
|
|
|
func (m *mockCtcCaller) GetLastBlockNumber(opts *ethBind.CallOpts) (result *big.Int, err error) {
|
|
m.lastBlockNumbers, result, err = m.getResult(m.lastBlockNumbers, m.lastBlockNumbersErr)
|
|
return
|
|
}
|
|
|
|
func (m *mockCtcCaller) getResult(resultsIn []*big.Int, errIn error) (resultsOut []*big.Int, result *big.Int, err error) {
|
|
for {
|
|
m.mutex.Lock()
|
|
// If they set the error, return that immediately.
|
|
if errIn != nil {
|
|
err = errIn
|
|
break
|
|
}
|
|
|
|
// If there are pending results, return the first one.
|
|
if len(resultsIn) != 0 {
|
|
result = resultsIn[0]
|
|
resultsOut = resultsIn[1:]
|
|
break
|
|
}
|
|
|
|
// If we don't have any results, sleep and try again.
|
|
m.mutex.Unlock()
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
m.mutex.Unlock()
|
|
return
|
|
}
|
|
|
|
func NewOptimismFinalizerForTest(
|
|
ctx context.Context,
|
|
logger *zap.Logger,
|
|
l1Finalizer interfaces.L1Finalizer,
|
|
ctcCaller ctcCallerIntf,
|
|
) *OptimismFinalizer {
|
|
finalizer := &OptimismFinalizer{
|
|
logger: logger,
|
|
l1Finalizer: l1Finalizer,
|
|
latestFinalizedL2Block: big.NewInt(0),
|
|
finalizerMapping: make([]RollupInfo, 0),
|
|
ctcCaller: ctcCaller,
|
|
}
|
|
|
|
return finalizer
|
|
}
|
|
|
|
func TestOptimismErrorReturnedIfBlockIsNil(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 125}
|
|
ctcCaller := &mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
_, err := finalizer.IsBlockFinalized(ctx, nil)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestOptimismNotFinalizedIfNoFinalizedL1BlockYet(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
finalized, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, finalized)
|
|
}
|
|
|
|
func TestOptimismNotFinalizedWhenFinalizedL1IsLessThanTargetL1(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 7954401}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
ctcCaller.SetLastBlockNumbers([]*big.Int{big.NewInt(7954402)})
|
|
ctcCaller.SetTotalElements([]*big.Int{big.NewInt(127)})
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(127),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
finalized, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, false, finalized)
|
|
}
|
|
|
|
func TestOptimismNotFinalizedWhenFinalizedL1IsEqualsTargetL1(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 7954402}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
ctcCaller.SetLastBlockNumbers([]*big.Int{big.NewInt(7954402)})
|
|
ctcCaller.SetTotalElements([]*big.Int{big.NewInt(125)})
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
finalized, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, true, finalized)
|
|
}
|
|
|
|
func TestOptimismIsFinalizedWhenFinalizedL1IsGreaterThanTargetL1(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 7954403}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
ctcCaller.SetLastBlockNumbers([]*big.Int{big.NewInt(7954402)})
|
|
ctcCaller.SetTotalElements([]*big.Int{big.NewInt(125)})
|
|
|
|
finalized, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, true, finalized)
|
|
}
|
|
|
|
func TestOptimismL2BlockNumberMustNotGoBackwards(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 7954400}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
ctcCaller.SetLastBlockNumbers([]*big.Int{big.NewInt(7954402), big.NewInt(7954403)})
|
|
ctcCaller.SetTotalElements([]*big.Int{big.NewInt(124), big.NewInt(123)})
|
|
|
|
isFinalized, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, isFinalized)
|
|
|
|
_, err = finalizer.IsBlockFinalized(ctx, block)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestOptimismGetTotalElementsRpcError(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 125}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
ctcCaller.SetTotalElementsError(fmt.Errorf("RPC failed"))
|
|
ctcCaller.SetLastBlockNumbers([]*big.Int{big.NewInt(7954402)})
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
_, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestOptimismGetLastBlockNumberRpcError(t *testing.T) {
|
|
ctx := context.Background()
|
|
logger := zap.NewNop()
|
|
l1Finalizer := mockL1Finalizer{LatestFinalizedBlockNumber: 125}
|
|
ctcCaller := mockCtcCaller{}
|
|
|
|
finalizer := NewOptimismFinalizerForTest(ctx, logger, &l1Finalizer, &ctcCaller)
|
|
require.NotNil(t, finalizer)
|
|
|
|
ctcCaller.SetLastBlockNumberError(fmt.Errorf("RPC failed"))
|
|
ctcCaller.SetTotalElements([]*big.Int{big.NewInt(125)})
|
|
|
|
block := &connectors.NewBlock{
|
|
Number: big.NewInt(125),
|
|
Hash: ethCommon.Hash{},
|
|
}
|
|
|
|
_, err := finalizer.IsBlockFinalized(ctx, block)
|
|
require.Error(t, err)
|
|
}
|