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 }