code.vegaprotocol.io/vega@v0.79.0/core/datasource/external/ethverifier/verifier_snapshot_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package ethverifier_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"testing"
    22  	"time"
    23  
    24  	errors "code.vegaprotocol.io/vega/core/datasource/errors"
    25  	"code.vegaprotocol.io/vega/core/datasource/external/ethcall"
    26  	"code.vegaprotocol.io/vega/core/types"
    27  	"code.vegaprotocol.io/vega/core/validators"
    28  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    29  	"code.vegaprotocol.io/vega/libs/proto"
    30  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  )
    36  
    37  var (
    38  	contractCallKey = (&types.PayloadEthContractCallEvent{}).Key()
    39  	lastEthBlockKey = (&types.PayloadEthOracleLastBlock{}).Key()
    40  	miscKey         = (&types.PayloadEthVerifierMisc{}).Key()
    41  )
    42  
    43  func TestEthereumOracleVerifierSnapshotEmpty(t *testing.T) {
    44  	eov := getTestEthereumOracleVerifier(t)
    45  	defer eov.ctrl.Finish()
    46  
    47  	assert.Equal(t, 3, len(eov.Keys()))
    48  
    49  	state, _, err := eov.GetState(contractCallKey)
    50  	require.Nil(t, err)
    51  	require.NotNil(t, state)
    52  
    53  	snap := &snapshot.Payload{}
    54  	err = proto.Unmarshal(state, snap)
    55  	require.Nil(t, err)
    56  
    57  	slbstate, _, err := eov.GetState(lastEthBlockKey)
    58  	require.Nil(t, err)
    59  
    60  	slbsnap := &snapshot.Payload{}
    61  	err = proto.Unmarshal(slbstate, slbsnap)
    62  	require.Nil(t, err)
    63  
    64  	// Restore
    65  	restoredVerifier := getTestEthereumOracleVerifier(t)
    66  	defer restoredVerifier.ctrl.Finish()
    67  
    68  	_, err = restoredVerifier.LoadState(context.Background(), types.PayloadFromProto(snap))
    69  	require.Nil(t, err)
    70  	_, err = restoredVerifier.LoadState(context.Background(), types.PayloadFromProto(slbsnap))
    71  	require.Nil(t, err)
    72  
    73  	restoredVerifier.ethCallEngine.EXPECT().Start()
    74  
    75  	// As the verifier has no state, the call engine should not have its last block set.
    76  	restoredVerifier.OnStateLoaded(context.Background())
    77  }
    78  
    79  func TestEthereumOracleVerifierWithPendingQueryResults(t *testing.T) {
    80  	eov := getTestEthereumOracleVerifier(t)
    81  	defer eov.ctrl.Finish()
    82  	assert.NotNil(t, eov)
    83  
    84  	s1, _, err := eov.GetState(contractCallKey)
    85  	require.Nil(t, err)
    86  	require.NotNil(t, s1)
    87  
    88  	slb1, _, err := eov.GetState(lastEthBlockKey)
    89  	require.Nil(t, err)
    90  	require.NotNil(t, slb1)
    91  
    92  	misc1, _, err := eov.GetState(miscKey)
    93  	require.Nil(t, err)
    94  	require.NotNil(t, misc1)
    95  
    96  	callEvent := ethcall.ContractCallEvent{
    97  		BlockHeight: 5,
    98  		BlockTime:   100,
    99  		SpecId:      "testspec",
   100  		Result:      []byte("testbytes"),
   101  	}
   102  
   103  	err, checkResult := sendEthereumEvent(t, eov, callEvent, true)
   104  	assert.NoError(t, err)
   105  	assert.NoError(t, checkResult)
   106  
   107  	eov.oracleBroadcaster.EXPECT().BroadcastData(gomock.Any(), gomock.Any())
   108  	eov.onTick(context.Background(), time.Now())
   109  	assert.NoError(t, err)
   110  	assert.NoError(t, checkResult)
   111  
   112  	callEvent = ethcall.ContractCallEvent{
   113  		BlockHeight: 6,
   114  		BlockTime:   101,
   115  		SpecId:      "testspec",
   116  		Result:      []byte("testbytes"),
   117  	}
   118  
   119  	err, checkResult = sendEthereumEvent(t, eov, callEvent, false)
   120  	assert.NoError(t, err)
   121  	assert.NoError(t, checkResult)
   122  
   123  	s2, _, err := eov.GetState(contractCallKey)
   124  	require.Nil(t, err)
   125  	require.False(t, bytes.Equal(s1, s2))
   126  
   127  	state, _, err := eov.GetState(contractCallKey)
   128  	require.Nil(t, err)
   129  
   130  	snap := &snapshot.Payload{}
   131  	err = proto.Unmarshal(state, snap)
   132  	require.Nil(t, err)
   133  
   134  	slb2, _, err := eov.GetState(lastEthBlockKey)
   135  	require.Nil(t, err)
   136  	require.False(t, bytes.Equal(slb1, slb2))
   137  
   138  	slbstate, _, err := eov.GetState(lastEthBlockKey)
   139  	require.Nil(t, err)
   140  
   141  	slbsnap := &snapshot.Payload{}
   142  	err = proto.Unmarshal(slbstate, slbsnap)
   143  	require.Nil(t, err)
   144  
   145  	misc2, _, err := eov.GetState(miscKey)
   146  	require.Nil(t, err)
   147  	assert.NotEqual(t, misc1, misc2)
   148  
   149  	miscState := &snapshot.Payload{}
   150  	err = proto.Unmarshal(misc2, miscState)
   151  	require.Nil(t, err)
   152  
   153  	// Restore
   154  	restoredVerifier := getTestEthereumOracleVerifier(t)
   155  	defer restoredVerifier.ctrl.Finish()
   156  
   157  	restoredVerifier.ts.EXPECT().GetTimeNow().AnyTimes()
   158  	restoredVerifier.witness.EXPECT().RestoreResource(gomock.Any(), gomock.Any()).Times(1)
   159  
   160  	_, err = restoredVerifier.LoadState(context.Background(), types.PayloadFromProto(snap))
   161  	require.Nil(t, err)
   162  	_, err = restoredVerifier.LoadState(context.Background(), types.PayloadFromProto(slbsnap))
   163  	require.Nil(t, err)
   164  	_, err = restoredVerifier.LoadState(context.Background(), types.PayloadFromProto(miscState))
   165  	require.Nil(t, err)
   166  
   167  	// After the state of the verifier is loaded it should start the call engine at the restored height
   168  	restoredVerifier.ethCallEngine.EXPECT().StartAtHeight(uint64(5), uint64(100))
   169  	restoredVerifier.OnStateLoaded(context.Background())
   170  
   171  	// Check its there by adding it again and checking for duplication error
   172  	require.ErrorIs(t, errors.ErrDuplicatedEthereumCallEvent, restoredVerifier.ProcessEthereumContractCallResult(callEvent))
   173  }
   174  
   175  func TestEthereumVerifierPatchBlock(t *testing.T) {
   176  	eov := getTestEthereumOracleVerifier(t)
   177  	defer eov.ctrl.Finish()
   178  	assert.NotNil(t, eov)
   179  
   180  	patchBlock := uint64(5)
   181  
   182  	callEvent := ethcall.ContractCallEvent{
   183  		BlockHeight: patchBlock,
   184  		BlockTime:   100,
   185  		SpecId:      "testspec",
   186  		Result:      []byte("testbytes"),
   187  	}
   188  
   189  	err, checkResult := sendEthereumEvent(t, eov, callEvent, true)
   190  	assert.NoError(t, err)
   191  	assert.NoError(t, checkResult)
   192  
   193  	eov.oracleBroadcaster.EXPECT().BroadcastData(gomock.Any(), gomock.Any())
   194  	eov.onTick(context.Background(), time.Now())
   195  
   196  	// now we want to restore as if we are doing an upgrade
   197  	ctx := vgcontext.WithSnapshotInfo(context.Background(), "v0.74.7", true)
   198  
   199  	lb, _, err := eov.GetState(lastEthBlockKey)
   200  	require.Nil(t, err)
   201  	require.NotNil(t, lb)
   202  
   203  	state := &snapshot.Payload{}
   204  	err = proto.Unmarshal(lb, state)
   205  	require.Nil(t, err)
   206  
   207  	restoredVerifier := getTestEthereumOracleVerifier(t)
   208  	defer restoredVerifier.ctrl.Finish()
   209  
   210  	restoredVerifier.ts.EXPECT().GetTimeNow().AnyTimes()
   211  	restoredVerifier.ethCallEngine.EXPECT().StartAtHeight(gomock.Any(), gomock.Any()).Times(1)
   212  	_, err = restoredVerifier.LoadState(ctx, types.PayloadFromProto(state))
   213  	require.NoError(t, err)
   214  	restoredVerifier.OnStateLoaded(ctx)
   215  
   216  	// now send in an event with an block height before
   217  	oldEvent := ethcall.ContractCallEvent{
   218  		BlockHeight: patchBlock - 1,
   219  		BlockTime:   50,
   220  		SpecId:      "testspec",
   221  		Result:      []byte("testbytes"),
   222  	}
   223  
   224  	err = restoredVerifier.ProcessEthereumContractCallResult(oldEvent)
   225  	assert.ErrorIs(t, err, errors.ErrEthereumCallEventTooOld)
   226  
   227  	// send in a new later event so that last block updates
   228  	callEvent = ethcall.ContractCallEvent{
   229  		BlockHeight: patchBlock + 5,
   230  		BlockTime:   100,
   231  		SpecId:      "testspec",
   232  		Result:      []byte("testbytes"),
   233  	}
   234  	err, checkResult = sendEthereumEvent(t, eov, callEvent, false)
   235  	assert.NoError(t, err)
   236  	assert.NoError(t, checkResult)
   237  
   238  	// restore from the snapshot not at upgrade height
   239  	ctx = context.Background()
   240  	lb, _, err = restoredVerifier.GetState(lastEthBlockKey)
   241  	require.Nil(t, err)
   242  	require.NotNil(t, lb)
   243  
   244  	state = &snapshot.Payload{}
   245  	err = proto.Unmarshal(lb, state)
   246  	require.Nil(t, err)
   247  
   248  	m, _, err := restoredVerifier.GetState(miscKey)
   249  	require.Nil(t, err)
   250  	require.NotNil(t, m)
   251  
   252  	miscState := &snapshot.Payload{}
   253  	err = proto.Unmarshal(m, miscState)
   254  	require.Nil(t, err)
   255  
   256  	restoredVerifier = getTestEthereumOracleVerifier(t)
   257  	defer restoredVerifier.ctrl.Finish()
   258  
   259  	restoredVerifier.ts.EXPECT().GetTimeNow().AnyTimes()
   260  	restoredVerifier.ethCallEngine.EXPECT().StartAtHeight(gomock.Any(), gomock.Any()).Times(1)
   261  	restoredVerifier.LoadState(ctx, types.PayloadFromProto(state))
   262  	restoredVerifier.LoadState(ctx, types.PayloadFromProto(miscState))
   263  	restoredVerifier.OnStateLoaded(ctx)
   264  
   265  	// check that the patch block hasn't updated, old event is still old
   266  	err = restoredVerifier.ProcessEthereumContractCallResult(oldEvent)
   267  	assert.ErrorIs(t, err, errors.ErrEthereumCallEventTooOld)
   268  
   269  	// event at block after patch-block but before last block is allowed
   270  	callEvent = ethcall.ContractCallEvent{
   271  		BlockHeight: patchBlock + 2,
   272  		BlockTime:   100,
   273  		SpecId:      "testspec",
   274  		Result:      []byte("testbytes"),
   275  	}
   276  	err, checkResult = sendEthereumEvent(t, eov, callEvent, false)
   277  	assert.NoError(t, err)
   278  	assert.NoError(t, checkResult)
   279  }
   280  
   281  func TestEthereumVerifierRejectTooOld(t *testing.T) {
   282  	eov := getTestEthereumOracleVerifier(t)
   283  	defer eov.ctrl.Finish()
   284  	assert.NotNil(t, eov)
   285  
   286  	now := time.Now()
   287  
   288  	patchBlock := uint64(5)
   289  	callEvent := ethcall.ContractCallEvent{
   290  		BlockHeight: patchBlock,
   291  		BlockTime:   uint64(now.Unix()),
   292  		SpecId:      "testspec",
   293  		Result:      []byte("testbytes"),
   294  	}
   295  
   296  	err, checkResult := sendEthereumEvent(t, eov, callEvent, false)
   297  	assert.NoError(t, err)
   298  	assert.NoError(t, checkResult)
   299  
   300  	// send it in again and check its rejected as a dupe
   301  	eov.ts.EXPECT().GetTimeNow().Times(1).Return(now)
   302  	err = eov.ProcessEthereumContractCallResult(callEvent)
   303  	assert.ErrorIs(t, err, errors.ErrDuplicatedEthereumCallEvent)
   304  
   305  	// let time pass more than a week
   306  	now = now.Add(24 * 7 * time.Hour)
   307  	eov.onTick(context.Background(), now)
   308  
   309  	// now send in the event again
   310  	eov.ts.EXPECT().GetTimeNow().Times(1).Return(now)
   311  	err = eov.ProcessEthereumContractCallResult(callEvent)
   312  	assert.ErrorIs(t, err, errors.ErrEthereumCallEventTooOld)
   313  }
   314  
   315  func sendEthereumEvent(t *testing.T, eov *verifierTest, callEvent ethcall.ContractCallEvent, finalize bool) (error, error) {
   316  	t.Helper()
   317  	result := okResult()
   318  	eov.ethCallEngine.EXPECT().GetEthTime(gomock.Any(), callEvent.BlockHeight).Return(callEvent.BlockTime, nil)
   319  	eov.ethCallEngine.EXPECT().CallSpec(gomock.Any(), "testspec", callEvent.BlockHeight).Return(result, nil)
   320  	eov.ethCallEngine.EXPECT().GetInitialTriggerTime("testspec").Return(uint64(90), nil)
   321  	eov.ethCallEngine.EXPECT().GetRequiredConfirmations("testspec").Return(uint64(5), nil).Times(2)
   322  	eov.ethCallEngine.EXPECT().MakeResult("testspec", []byte("testbytes")).Return(result, nil).AnyTimes()
   323  
   324  	eov.ts.EXPECT().GetTimeNow().Times(2)
   325  	eov.ethConfirmations.EXPECT().CheckRequiredConfirmations(callEvent.BlockHeight, uint64(5)).Return(nil)
   326  
   327  	var onQueryResultVerified func(interface{}, bool)
   328  	var checkResult error
   329  	var resourceToCheck interface{}
   330  	eov.witness.EXPECT().StartCheckWithDelay(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   331  		Times(1).
   332  		DoAndReturn(func(toCheck validators.Resource, fn func(interface{}, bool), _ time.Time, _ int64) error {
   333  			resourceToCheck = toCheck
   334  			onQueryResultVerified = fn
   335  			checkResult = toCheck.Check(context.Background())
   336  			return nil
   337  		})
   338  
   339  	err := eov.ProcessEthereumContractCallResult(callEvent)
   340  
   341  	if finalize {
   342  		onQueryResultVerified(resourceToCheck, true)
   343  	}
   344  
   345  	return err, checkResult
   346  }