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 }