github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunkVerifier_test.go (about) 1 package chunks_test 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 "time" 8 9 "github.com/onflow/cadence/runtime" 10 "github.com/rs/zerolog" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 "github.com/stretchr/testify/suite" 14 15 executionState "github.com/koko1123/flow-go-1/engine/execution/state" 16 "github.com/koko1123/flow-go-1/fvm" 17 fvmErrors "github.com/koko1123/flow-go-1/fvm/errors" 18 "github.com/koko1123/flow-go-1/fvm/state" 19 "github.com/koko1123/flow-go-1/ledger" 20 completeLedger "github.com/koko1123/flow-go-1/ledger/complete" 21 "github.com/koko1123/flow-go-1/ledger/complete/wal/fixtures" 22 chunksmodels "github.com/koko1123/flow-go-1/model/chunks" 23 "github.com/koko1123/flow-go-1/model/convert" 24 convertfixtures "github.com/koko1123/flow-go-1/model/convert/fixtures" 25 "github.com/koko1123/flow-go-1/model/flow" 26 "github.com/koko1123/flow-go-1/model/verification" 27 "github.com/koko1123/flow-go-1/module/chunks" 28 "github.com/koko1123/flow-go-1/module/metrics" 29 "github.com/koko1123/flow-go-1/utils/unittest" 30 ) 31 32 var eventsList = flow.EventsList{ 33 { 34 Type: "event.someType", 35 TransactionID: flow.Identifier{2, 3, 2, 3}, 36 TransactionIndex: 1, 37 EventIndex: 2, 38 Payload: []byte{7, 3, 1, 2}, 39 }, 40 { 41 Type: "event.otherType", 42 TransactionID: flow.Identifier{3, 3, 3}, 43 TransactionIndex: 4, 44 EventIndex: 4, 45 Payload: []byte{7, 3, 1, 2}, 46 }, 47 } 48 49 // the chain we use for this test suite 50 var testChain = flow.Emulator 51 var epochSetupEvent, _ = convertfixtures.EpochSetupFixtureByChainID(testChain) 52 var epochCommitEvent, _ = convertfixtures.EpochCommitFixtureByChainID(testChain) 53 54 var epochSetupServiceEvent, _ = convert.ServiceEvent(testChain, epochSetupEvent) 55 56 var serviceEventsList = []flow.ServiceEvent{ 57 *epochSetupServiceEvent, 58 } 59 60 type ChunkVerifierTestSuite struct { 61 suite.Suite 62 verifier *chunks.ChunkVerifier 63 systemOkVerifier *chunks.ChunkVerifier 64 systemBadVerifier *chunks.ChunkVerifier 65 } 66 67 // Make sure variables are set properly 68 // SetupTest is executed prior to each individual test in this test suite 69 func (s *ChunkVerifierTestSuite) SetupSuite() { 70 // seed the RNG 71 rand.Seed(time.Now().UnixNano()) 72 73 vm := new(vmMock) 74 systemOkVm := new(vmSystemOkMock) 75 systemBadVm := new(vmSystemBadMock) 76 vmCtx := fvm.NewContext(fvm.WithChain(testChain.Chain())) 77 78 // system chunk runs predefined system transaction, hence we can't distinguish 79 // based on its content and we need separate VMs 80 s.verifier = chunks.NewChunkVerifier(vm, vmCtx, zerolog.Nop()) 81 s.systemOkVerifier = chunks.NewChunkVerifier(systemOkVm, vmCtx, zerolog.Nop()) 82 s.systemBadVerifier = chunks.NewChunkVerifier(systemBadVm, vmCtx, zerolog.Nop()) 83 } 84 85 // TestChunkVerifier invokes all the tests in this test suite 86 func TestChunkVerifier(t *testing.T) { 87 suite.Run(t, new(ChunkVerifierTestSuite)) 88 } 89 90 // TestHappyPath tests verification of the baseline verifiable chunk 91 func (s *ChunkVerifierTestSuite) TestHappyPath() { 92 vch := GetBaselineVerifiableChunk(s.T(), "", false) 93 assert.NotNil(s.T(), vch) 94 spockSecret, chFaults, err := s.verifier.Verify(vch) 95 assert.Nil(s.T(), err) 96 assert.Nil(s.T(), chFaults) 97 assert.NotNil(s.T(), spockSecret) 98 } 99 100 // TestMissingRegisterTouchForUpdate tests verification given a chunkdatapack missing a register touch (update) 101 func (s *ChunkVerifierTestSuite) TestMissingRegisterTouchForUpdate() { 102 unittest.SkipUnless(s.T(), unittest.TEST_DEPRECATED, "Check new partial ledger for missing keys") 103 104 vch := GetBaselineVerifiableChunk(s.T(), "", false) 105 assert.NotNil(s.T(), vch) 106 // remove the second register touch 107 // vch.ChunkDataPack.RegisterTouches = vch.ChunkDataPack.RegisterTouches[:1] 108 spockSecret, chFaults, err := s.verifier.Verify(vch) 109 assert.Nil(s.T(), err) 110 assert.NotNil(s.T(), chFaults) 111 assert.Nil(s.T(), spockSecret) 112 _, ok := chFaults.(*chunksmodels.CFMissingRegisterTouch) 113 assert.True(s.T(), ok) 114 } 115 116 // TestMissingRegisterTouchForRead tests verification given a chunkdatapack missing a register touch (read) 117 func (s *ChunkVerifierTestSuite) TestMissingRegisterTouchForRead() { 118 unittest.SkipUnless(s.T(), unittest.TEST_DEPRECATED, "Check new partial ledger for missing keys") 119 120 vch := GetBaselineVerifiableChunk(s.T(), "", false) 121 assert.NotNil(s.T(), vch) 122 // remove the second register touch 123 // vch.ChunkDataPack.RegisterTouches = vch.ChunkDataPack.RegisterTouches[1:] 124 spockSecret, chFaults, err := s.verifier.Verify(vch) 125 assert.Nil(s.T(), err) 126 assert.NotNil(s.T(), chFaults) 127 assert.Nil(s.T(), spockSecret) 128 _, ok := chFaults.(*chunksmodels.CFMissingRegisterTouch) 129 assert.True(s.T(), ok) 130 } 131 132 // TestWrongEndState tests verification covering the case 133 // the state commitment computed after updating the partial trie 134 // doesn't match the one provided by the chunks 135 func (s *ChunkVerifierTestSuite) TestWrongEndState() { 136 vch := GetBaselineVerifiableChunk(s.T(), "wrongEndState", false) 137 assert.NotNil(s.T(), vch) 138 spockSecret, chFaults, err := s.verifier.Verify(vch) 139 assert.Nil(s.T(), err) 140 assert.NotNil(s.T(), chFaults) 141 assert.Nil(s.T(), spockSecret) 142 _, ok := chFaults.(*chunksmodels.CFNonMatchingFinalState) 143 assert.True(s.T(), ok) 144 } 145 146 // TestFailedTx tests verification behavior in case 147 // of failed transaction. if a transaction fails, it should 148 // still change the state commitment. 149 func (s *ChunkVerifierTestSuite) TestFailedTx() { 150 vch := GetBaselineVerifiableChunk(s.T(), "failedTx", false) 151 assert.NotNil(s.T(), vch) 152 spockSecret, chFaults, err := s.verifier.Verify(vch) 153 assert.Nil(s.T(), err) 154 assert.Nil(s.T(), chFaults) 155 assert.NotNil(s.T(), spockSecret) 156 } 157 158 // TestEventsMismatch tests verification behavior in case 159 // of emitted events not matching chunks 160 func (s *ChunkVerifierTestSuite) TestEventsMismatch() { 161 vch := GetBaselineVerifiableChunk(s.T(), "eventsMismatch", false) 162 assert.NotNil(s.T(), vch) 163 _, chFault, err := s.verifier.Verify(vch) 164 assert.Nil(s.T(), err) 165 assert.NotNil(s.T(), chFault) 166 assert.IsType(s.T(), &chunksmodels.CFInvalidEventsCollection{}, chFault) 167 } 168 169 // TestServiceEventsMismatch tests verification behavior in case 170 // of emitted service events not matching chunks' 171 func (s *ChunkVerifierTestSuite) TestServiceEventsMismatch() { 172 vch := GetBaselineVerifiableChunk(s.T(), "doesn't matter", true) 173 assert.NotNil(s.T(), vch) 174 _, chFault, err := s.systemBadVerifier.Verify(vch) 175 assert.Nil(s.T(), err) 176 assert.NotNil(s.T(), chFault) 177 assert.IsType(s.T(), &chunksmodels.CFInvalidServiceEventsEmitted{}, chFault) 178 } 179 180 // TestServiceEventsAreChecked ensures that service events are in fact checked 181 func (s *ChunkVerifierTestSuite) TestServiceEventsAreChecked() { 182 vch := GetBaselineVerifiableChunk(s.T(), "doesn't matter", true) 183 assert.NotNil(s.T(), vch) 184 _, chFault, err := s.systemOkVerifier.Verify(vch) 185 assert.Nil(s.T(), err) 186 assert.Nil(s.T(), chFault) 187 } 188 189 // TestEmptyCollection tests verification behaviour if a 190 // collection doesn't have any transaction. 191 func (s *ChunkVerifierTestSuite) TestEmptyCollection() { 192 vch := GetBaselineVerifiableChunk(s.T(), "", false) 193 assert.NotNil(s.T(), vch) 194 col := unittest.CollectionFixture(0) 195 vch.ChunkDataPack.Collection = &col 196 vch.EndState = vch.ChunkDataPack.StartState 197 emptyListHash, err := flow.EventsMerkleRootHash(flow.EventsList{}) 198 assert.NoError(s.T(), err) 199 vch.Chunk.EventCollection = emptyListHash // empty collection emits no events 200 spockSecret, chFaults, err := s.verifier.Verify(vch) 201 assert.Nil(s.T(), err) 202 assert.Nil(s.T(), chFaults) 203 assert.NotNil(s.T(), spockSecret) 204 } 205 206 // GetBaselineVerifiableChunk returns a verifiable chunk and sets the script 207 // of a transaction in the middle of the collection to some value to signal the 208 // mocked vm on what to return as tx exec outcome. 209 func GetBaselineVerifiableChunk(t *testing.T, script string, system bool) *verification.VerifiableChunkData { 210 211 // Collection setup 212 213 collectionSize := 5 214 magicTxIndex := 3 215 coll := unittest.CollectionFixture(collectionSize) 216 coll.Transactions[magicTxIndex] = &flow.TransactionBody{Script: []byte(script)} 217 218 guarantee := coll.Guarantee() 219 220 // Block setup 221 payload := flow.Payload{ 222 Guarantees: []*flow.CollectionGuarantee{&guarantee}, 223 } 224 header := unittest.BlockHeaderFixture() 225 header.PayloadHash = payload.Hash() 226 block := flow.Block{ 227 Header: header, 228 Payload: &payload, 229 } 230 blockID := block.ID() 231 232 // registerTouch and State setup 233 id1 := flow.NewRegisterID("00", "") 234 value1 := []byte{'a'} 235 236 id2Bytes := make([]byte, 32) 237 id2Bytes[0] = byte(5) 238 id2 := flow.NewRegisterID("05", "") 239 value2 := []byte{'b'} 240 UpdatedValue2 := []byte{'B'} 241 242 ids := make([]flow.RegisterID, 0) 243 values := make([]flow.RegisterValue, 0) 244 ids = append(ids, id1, id2) 245 values = append(values, value1, value2) 246 247 var verifiableChunkData verification.VerifiableChunkData 248 249 metricsCollector := &metrics.NoopCollector{} 250 251 f, _ := completeLedger.NewLedger(&fixtures.NoopWAL{}, 1000, metricsCollector, zerolog.Nop(), completeLedger.DefaultPathFinderVersion) 252 253 compactor := fixtures.NewNoopCompactor(f) 254 <-compactor.Ready() 255 256 defer func() { 257 <-f.Done() 258 <-compactor.Done() 259 }() 260 261 keys := executionState.RegisterIDSToKeys(ids) 262 update, err := ledger.NewUpdate( 263 f.InitialState(), 264 keys, 265 executionState.RegisterValuesToValues(values), 266 ) 267 268 require.NoError(t, err) 269 270 startState, _, err := f.Set(update) 271 require.NoError(t, err) 272 273 query, err := ledger.NewQuery(startState, keys) 274 require.NoError(t, err) 275 276 proof, err := f.Prove(query) 277 require.NoError(t, err) 278 279 ids = []flow.RegisterID{id2} 280 values = [][]byte{UpdatedValue2} 281 282 keys = executionState.RegisterIDSToKeys(ids) 283 update, err = ledger.NewUpdate( 284 startState, 285 keys, 286 executionState.RegisterValuesToValues(values), 287 ) 288 require.NoError(t, err) 289 290 endState, _, err := f.Set(update) 291 require.NoError(t, err) 292 293 // events 294 chunkEvents := make(flow.EventsList, 0) 295 296 erServiceEvents := make([]flow.ServiceEvent, 0) 297 298 if system { 299 chunkEvents = flow.EventsList{} 300 erServiceEvents = serviceEventsList 301 } else { 302 for i := 0; i < collectionSize; i++ { 303 if i == magicTxIndex { 304 switch script { 305 case "failedTx": 306 continue 307 } 308 } 309 chunkEvents = append(chunkEvents, eventsList...) 310 } 311 } 312 313 EventsMerkleRootHash, err := flow.EventsMerkleRootHash(chunkEvents) 314 require.NoError(t, err) 315 316 // Chunk setup 317 chunk := flow.Chunk{ 318 ChunkBody: flow.ChunkBody{ 319 CollectionIndex: 0, 320 StartState: flow.StateCommitment(startState), 321 BlockID: blockID, 322 EventCollection: EventsMerkleRootHash, 323 }, 324 Index: 0, 325 } 326 327 chunkDataPack := flow.ChunkDataPack{ 328 ChunkID: chunk.ID(), 329 StartState: flow.StateCommitment(startState), 330 Proof: proof, 331 Collection: &coll, 332 } 333 334 // ExecutionResult setup 335 result := flow.ExecutionResult{ 336 BlockID: blockID, 337 Chunks: flow.ChunkList{&chunk}, 338 ServiceEvents: erServiceEvents, 339 } 340 341 verifiableChunkData = verification.VerifiableChunkData{ 342 IsSystemChunk: system, 343 Chunk: &chunk, 344 Header: header, 345 Result: &result, 346 ChunkDataPack: &chunkDataPack, 347 EndState: flow.StateCommitment(endState), 348 } 349 350 return &verifiableChunkData 351 } 352 353 type vmMock struct{} 354 355 func (vm *vmMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error { 356 tx, ok := proc.(*fvm.TransactionProcedure) 357 if !ok { 358 return fmt.Errorf("invokable is not a transaction") 359 } 360 361 switch string(tx.Transaction.Script) { 362 case "wrongEndState": 363 // add updates to the ledger 364 _ = led.Set("00", "", []byte{'F'}) 365 tx.Logs = []string{"log1", "log2"} 366 tx.Events = eventsList 367 case "failedTx": 368 // add updates to the ledger 369 _ = led.Set("05", "", []byte{'B'}) 370 tx.Err = fvmErrors.NewCadenceRuntimeError(runtime.Error{}) // inside the runtime (e.g. div by zero, access account) 371 case "eventsMismatch": 372 tx.Events = append(eventsList, flow.Event{ 373 Type: "event.Extra", 374 TransactionID: flow.Identifier{2, 3}, 375 TransactionIndex: 0, 376 EventIndex: 0, 377 Payload: []byte{88}, 378 }) 379 default: 380 _, _ = led.Get("00", "") 381 _, _ = led.Get("05", "") 382 _ = led.Set("05", "", []byte{'B'}) 383 tx.Logs = []string{"log1", "log2"} 384 tx.Events = eventsList 385 } 386 387 return nil 388 } 389 390 func (vmMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) { 391 panic("not expected") 392 } 393 394 type vmSystemOkMock struct{} 395 396 func (vm *vmSystemOkMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error { 397 tx, ok := proc.(*fvm.TransactionProcedure) 398 if !ok { 399 return fmt.Errorf("invokable is not a transaction") 400 } 401 402 tx.ServiceEvents = []flow.Event{epochSetupEvent} 403 404 // add "default" interaction expected in tests 405 _, _ = led.Get("00", "") 406 _, _ = led.Get("05", "") 407 _ = led.Set("05", "", []byte{'B'}) 408 tx.Logs = []string{"log1", "log2"} 409 410 return nil 411 } 412 413 func (vmSystemOkMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) { 414 panic("not expected") 415 } 416 417 type vmSystemBadMock struct{} 418 419 func (vm *vmSystemBadMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error { 420 tx, ok := proc.(*fvm.TransactionProcedure) 421 if !ok { 422 return fmt.Errorf("invokable is not a transaction") 423 } 424 // EpochSetup event is expected, but we emit EpochCommit here resulting in a chunk fault 425 tx.ServiceEvents = []flow.Event{epochCommitEvent} 426 427 return nil 428 } 429 430 func (vmSystemBadMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) { 431 panic("not expected") 432 }