code.vegaprotocol.io/vega@v0.79.0/core/evtforward/forwarder_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 evtforward_test
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"code.vegaprotocol.io/vega/core/evtforward"
    26  	"code.vegaprotocol.io/vega/core/evtforward/mocks"
    27  	"code.vegaprotocol.io/vega/core/integration/stubs"
    28  	snp "code.vegaprotocol.io/vega/core/snapshot"
    29  	"code.vegaprotocol.io/vega/core/stats"
    30  	"code.vegaprotocol.io/vega/core/types"
    31  	"code.vegaprotocol.io/vega/libs/crypto"
    32  	"code.vegaprotocol.io/vega/libs/proto"
    33  	vgtest "code.vegaprotocol.io/vega/libs/test"
    34  	"code.vegaprotocol.io/vega/logging"
    35  	"code.vegaprotocol.io/vega/paths"
    36  	prototypes "code.vegaprotocol.io/vega/protos/vega"
    37  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    38  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    39  
    40  	"github.com/golang/mock/gomock"
    41  	"github.com/stretchr/testify/assert"
    42  	"github.com/stretchr/testify/require"
    43  )
    44  
    45  var (
    46  	testSelfVegaPubKey = "self-pubkey"
    47  	testAllPubKeys     = []string{
    48  		testSelfVegaPubKey,
    49  		"another-pubkey1",
    50  		"another-pubkey2",
    51  	}
    52  	okEventEmitter = "somechaineventpubkey"
    53  	allowlist      = []string{okEventEmitter}
    54  	initTime       = time.Unix(10, 0)
    55  )
    56  
    57  type testEvtFwd struct {
    58  	*evtforward.Forwarder
    59  	ctrl *gomock.Controller
    60  	time *mocks.MockTimeService
    61  	top  *mocks.MockValidatorTopology
    62  	cmd  *mocks.MockCommander
    63  	cb   func(context.Context, time.Time)
    64  }
    65  
    66  func getTestEvtFwd(t *testing.T) *testEvtFwd {
    67  	t.Helper()
    68  	ctrl := gomock.NewController(t)
    69  	tim := mocks.NewMockTimeService(ctrl)
    70  	top := mocks.NewMockValidatorTopology(ctrl)
    71  	cmd := mocks.NewMockCommander(ctrl)
    72  
    73  	top.EXPECT().AllNodeIDs().Times(1).Return(testAllPubKeys)
    74  	top.EXPECT().SelfNodeID().AnyTimes().Return(testSelfVegaPubKey)
    75  
    76  	cfg := evtforward.NewDefaultConfig()
    77  	// add the pubkeys
    78  	cfg.BlockchainQueueAllowlist = allowlist
    79  	evtfwd := evtforward.New(logging.NewTestLogger(), cfg, cmd, tim, top)
    80  
    81  	return &testEvtFwd{
    82  		Forwarder: evtfwd,
    83  		ctrl:      ctrl,
    84  		time:      tim,
    85  		top:       top,
    86  		cmd:       cmd,
    87  		cb:        evtfwd.OnTick,
    88  	}
    89  }
    90  
    91  func TestEvtForwarder(t *testing.T) {
    92  	t.Run("test forward success node is forwarder", testForwardSuccessNodeIsForwarder)
    93  	t.Run("test forward failure duplicate event", testForwardFailureDuplicateEvent)
    94  	t.Run("test ensure validators lists are updated", testUpdateValidatorList)
    95  	t.Run("test ack success", testAckSuccess)
    96  	t.Run("test ack failure already acked", testAckFailureAlreadyAcked)
    97  	t.Run("error event emitter not allowlisted", testEventEmitterNotAllowlisted)
    98  }
    99  
   100  func testEventEmitterNotAllowlisted(t *testing.T) {
   101  	evtfwd := getTestEvtFwd(t)
   102  	evt := getTestChainEvent("some")
   103  	evtfwd.top.EXPECT().AllNodeIDs().Times(1).Return(testAllPubKeys)
   104  	// set the time so the hash match our current node
   105  	evtfwd.cb(context.Background(), time.Unix(11, 0))
   106  	err := evtfwd.Forward(context.Background(), evt, "not allowlisted")
   107  	assert.EqualError(t, err, evtforward.ErrPubKeyNotAllowlisted.Error())
   108  }
   109  
   110  func testForwardSuccessNodeIsForwarder(t *testing.T) {
   111  	evtfwd := getTestEvtFwd(t)
   112  	evt := getTestChainEvent("some")
   113  	evtfwd.cmd.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   114  	evtfwd.top.EXPECT().AllNodeIDs().Times(1).Return(testAllPubKeys)
   115  	evtfwd.time.EXPECT().GetTimeNow().AnyTimes()
   116  	// set the time so the hash match our current node
   117  	evtfwd.cb(context.Background(), time.Unix(3, 0))
   118  	err := evtfwd.Forward(context.Background(), evt, okEventEmitter)
   119  	assert.NoError(t, err)
   120  }
   121  
   122  func testForwardFailureDuplicateEvent(t *testing.T) {
   123  	evtfwd := getTestEvtFwd(t)
   124  	evt := getTestChainEvent("some")
   125  	evtfwd.cmd.EXPECT().Command(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
   126  	evtfwd.top.EXPECT().AllNodeIDs().Times(1).Return(testAllPubKeys)
   127  	evtfwd.time.EXPECT().GetTimeNow().AnyTimes()
   128  	// set the time so the hash match our current node
   129  	evtfwd.cb(context.Background(), time.Unix(12, 0))
   130  	err := evtfwd.Forward(context.Background(), evt, okEventEmitter)
   131  	assert.NoError(t, err)
   132  	// now the event should exist, let's try toforward againt
   133  	err = evtfwd.Forward(context.Background(), evt, okEventEmitter)
   134  	assert.EqualError(t, err, evtforward.ErrEvtAlreadyExist.Error())
   135  }
   136  
   137  func testUpdateValidatorList(t *testing.T) {
   138  	evtfwd := getTestEvtFwd(t)
   139  	// no event, just call callback to ensure the validator list is updated
   140  	evtfwd.top.EXPECT().AllNodeIDs().Times(1).Return(testAllPubKeys)
   141  	evtfwd.cb(context.Background(), initTime.Add(time.Second))
   142  }
   143  
   144  func testAckSuccess(t *testing.T) {
   145  	evtfwd := getTestEvtFwd(t)
   146  	evt := getTestChainEvent("some")
   147  	state1, _, err := evtfwd.GetState("all")
   148  	require.Nil(t, err)
   149  
   150  	evtfwd.time.EXPECT().GetTimeNow().Times(1)
   151  	ok := evtfwd.Ack(evt)
   152  	assert.True(t, ok)
   153  	state2, _, err := evtfwd.GetState("all")
   154  	require.Nil(t, err)
   155  	require.False(t, bytes.Equal(state1, state2))
   156  
   157  	// try to ack again the same event
   158  	ok = evtfwd.Ack(evt)
   159  	assert.False(t, ok)
   160  	state3, _, err := evtfwd.GetState("all")
   161  	require.Nil(t, err)
   162  	require.True(t, bytes.Equal(state3, state2))
   163  
   164  	// restore the state
   165  	var pl snapshot.Payload
   166  	proto.Unmarshal(state3, &pl)
   167  	payload := types.PayloadFromProto(&pl)
   168  	_, err = evtfwd.LoadState(context.Background(), payload)
   169  	require.Nil(t, err)
   170  
   171  	// the event exists after the reload so expect to fail
   172  	ok = evtfwd.Ack(evt)
   173  	assert.False(t, ok)
   174  
   175  	// expect the state after the reload to equal what it was before
   176  	state4, _, err := evtfwd.GetState("all")
   177  	require.Nil(t, err)
   178  	require.True(t, bytes.Equal(state4, state3))
   179  
   180  	// ack a new event for the hash/state to change
   181  	evt2 := getTestChainEvent("somenew")
   182  	evtfwd.time.EXPECT().GetTimeNow().Times(1)
   183  	ok = evtfwd.Ack(evt2)
   184  	assert.True(t, ok)
   185  	state5, _, err := evtfwd.GetState("all")
   186  	require.Nil(t, err)
   187  	require.False(t, bytes.Equal(state5, state4))
   188  }
   189  
   190  func testAckFailureAlreadyAcked(t *testing.T) {
   191  	evtfwd := getTestEvtFwd(t)
   192  	evt := getTestChainEvent("some")
   193  	evtfwd.time.EXPECT().GetTimeNow().Times(1)
   194  	ok := evtfwd.Ack(evt)
   195  	assert.True(t, ok)
   196  	// try to ack again
   197  	ko := evtfwd.Ack(evt)
   198  	assert.False(t, ko)
   199  }
   200  
   201  func getTestChainEvent(txid string) *commandspb.ChainEvent {
   202  	return &commandspb.ChainEvent{
   203  		TxId: txid,
   204  		Event: &commandspb.ChainEvent_Erc20{
   205  			Erc20: &prototypes.ERC20Event{
   206  				Index: 1,
   207  				Block: 100,
   208  				Action: &prototypes.ERC20Event_AssetList{
   209  					AssetList: &prototypes.ERC20AssetList{
   210  						VegaAssetId: "asset-id-1",
   211  					},
   212  				},
   213  			},
   214  		},
   215  	}
   216  }
   217  
   218  func TestSnapshotRoundTripViaEngine(t *testing.T) {
   219  	eventForwarder1 := getTestEvtFwd(t)
   220  
   221  	for i := 0; i < 100; i++ {
   222  		eventForwarder1.time.EXPECT().GetTimeNow().Times(1)
   223  		eventForwarder1.Ack(getTestChainEvent(crypto.RandomHash()))
   224  	}
   225  
   226  	ctx := vgtest.VegaContext("chainid", 100)
   227  	vegaPath := paths.New(t.TempDir())
   228  	now := time.Now()
   229  	log := logging.NewTestLogger()
   230  	timeService := stubs.NewTimeStub()
   231  	timeService.SetTime(now)
   232  	statsData := stats.New(log, stats.NewDefaultConfig())
   233  	config := snp.DefaultConfig()
   234  
   235  	snapshotEngine1, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain)
   236  	require.NoError(t, err)
   237  	snapshotEngine1CloseFn := vgtest.OnlyOnce(snapshotEngine1.Close)
   238  	defer snapshotEngine1CloseFn()
   239  
   240  	snapshotEngine1.AddProviders(eventForwarder1)
   241  
   242  	require.NoError(t, snapshotEngine1.Start(ctx))
   243  
   244  	hash1, err := snapshotEngine1.SnapshotNow(ctx)
   245  	require.NoError(t, err)
   246  
   247  	for i := 0; i < 10; i++ {
   248  		eventForwarder1.time.EXPECT().GetTimeNow().Times(1)
   249  		eventForwarder1.Ack(getTestChainEvent(fmt.Sprintf("txHash%d", i)))
   250  	}
   251  
   252  	state1 := map[string][]byte{}
   253  	for _, key := range eventForwarder1.Keys() {
   254  		state, additionalProvider, err := eventForwarder1.GetState(key)
   255  		require.NoError(t, err)
   256  		assert.Empty(t, additionalProvider)
   257  		state1[key] = state
   258  	}
   259  
   260  	snapshotEngine1CloseFn()
   261  
   262  	eventForwarder2 := getTestEvtFwd(t)
   263  	snapshotEngine2, err := snp.NewEngine(vegaPath, config, log, timeService, statsData.Blockchain)
   264  	require.NoError(t, err)
   265  	defer snapshotEngine2.Close()
   266  
   267  	snapshotEngine2.AddProviders(eventForwarder2)
   268  
   269  	// This triggers the state restoration from the local snapshot.
   270  	require.NoError(t, snapshotEngine2.Start(ctx))
   271  
   272  	// Comparing the hash after restoration, to ensure it produces the same result.
   273  	hash2, _, _ := snapshotEngine2.Info()
   274  	require.Equal(t, hash1, hash2)
   275  
   276  	for i := 0; i < 10; i++ {
   277  		eventForwarder2.time.EXPECT().GetTimeNow().Times(1)
   278  		eventForwarder2.Ack(getTestChainEvent(fmt.Sprintf("txHash%d", i)))
   279  	}
   280  
   281  	state2 := map[string][]byte{}
   282  	for _, key := range eventForwarder2.Keys() {
   283  		state, additionalProvider, err := eventForwarder2.GetState(key)
   284  		require.NoError(t, err)
   285  		assert.Empty(t, additionalProvider)
   286  		state2[key] = state
   287  	}
   288  
   289  	for key := range state1 {
   290  		assert.Equalf(t, state1[key], state2[key], "Key %q does not have the same data", key)
   291  	}
   292  }