github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunkVerifier.go (about) 1 package chunks 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/rs/zerolog" 8 9 "github.com/koko1123/flow-go-1/fvm/blueprints" 10 "github.com/koko1123/flow-go-1/model/convert" 11 "github.com/koko1123/flow-go-1/model/verification" 12 13 "github.com/koko1123/flow-go-1/engine/execution/computation/computer" 14 executionState "github.com/koko1123/flow-go-1/engine/execution/state" 15 "github.com/koko1123/flow-go-1/engine/execution/state/delta" 16 "github.com/koko1123/flow-go-1/fvm" 17 "github.com/koko1123/flow-go-1/fvm/derived" 18 "github.com/koko1123/flow-go-1/ledger" 19 "github.com/koko1123/flow-go-1/ledger/partial" 20 chmodels "github.com/koko1123/flow-go-1/model/chunks" 21 "github.com/koko1123/flow-go-1/model/flow" 22 ) 23 24 // ChunkVerifier is a verifier based on the current definitions of the flow network 25 type ChunkVerifier struct { 26 vm fvm.VM 27 vmCtx fvm.Context 28 systemChunkCtx fvm.Context 29 logger zerolog.Logger 30 } 31 32 // NewChunkVerifier creates a chunk verifier containing a flow virtual machine 33 func NewChunkVerifier(vm fvm.VM, vmCtx fvm.Context, logger zerolog.Logger) *ChunkVerifier { 34 return &ChunkVerifier{ 35 vm: vm, 36 vmCtx: vmCtx, 37 systemChunkCtx: computer.SystemChunkContext(vmCtx, vmCtx.Logger), 38 logger: logger.With().Str("component", "chunk_verifier").Logger(), 39 } 40 } 41 42 // Verify verifies a given VerifiableChunk by executing it and checking the 43 // final state commitment. 44 // It returns a Spock Secret as a byte array, verification fault of the chunk, 45 // and an error. 46 func (fcv *ChunkVerifier) Verify( 47 vc *verification.VerifiableChunkData, 48 ) ( 49 []byte, 50 chmodels.ChunkFault, 51 error, 52 ) { 53 54 var ctx fvm.Context 55 var transactions []*fvm.TransactionProcedure 56 if vc.IsSystemChunk { 57 ctx = fvm.NewContextFromParent( 58 fcv.systemChunkCtx, 59 fvm.WithBlockHeader(vc.Header)) 60 61 txBody, err := blueprints.SystemChunkTransaction(fcv.vmCtx.Chain) 62 if err != nil { 63 return nil, nil, fmt.Errorf("could not get system chunk transaction: %w", err) 64 } 65 66 transactions = []*fvm.TransactionProcedure{ 67 fvm.Transaction(txBody, vc.TransactionOffset+uint32(0)), 68 } 69 } else { 70 ctx = fvm.NewContextFromParent( 71 fcv.vmCtx, 72 fvm.WithBlockHeader(vc.Header)) 73 74 transactions = make( 75 []*fvm.TransactionProcedure, 76 0, 77 len(vc.ChunkDataPack.Collection.Transactions)) 78 for i, txBody := range vc.ChunkDataPack.Collection.Transactions { 79 tx := fvm.Transaction(txBody, vc.TransactionOffset+uint32(i)) 80 transactions = append(transactions, tx) 81 } 82 } 83 84 return fcv.verifyTransactionsInContext( 85 ctx, 86 vc.TransactionOffset, 87 vc.Chunk, 88 vc.ChunkDataPack, 89 vc.Result, 90 transactions, 91 vc.EndState, 92 vc.IsSystemChunk) 93 } 94 95 func (fcv *ChunkVerifier) verifyTransactionsInContext( 96 context fvm.Context, 97 transactionOffset uint32, 98 chunk *flow.Chunk, 99 chunkDataPack *flow.ChunkDataPack, 100 result *flow.ExecutionResult, 101 transactions []*fvm.TransactionProcedure, 102 endState flow.StateCommitment, 103 systemChunk bool, 104 ) ( 105 []byte, 106 chmodels.ChunkFault, 107 error, 108 ) { 109 110 // TODO check collection hash to match 111 // TODO check datapack hash to match 112 // TODO check the number of transactions and computation used 113 114 chIndex := chunk.Index 115 execResID := result.ID() 116 117 if chunkDataPack == nil { 118 return nil, nil, fmt.Errorf("missing chunk data pack") 119 } 120 121 events := make(flow.EventsList, 0) 122 serviceEvents := make(flow.EventsList, 0) 123 124 // constructing a partial trie given chunk data package 125 psmt, err := partial.NewLedger(chunkDataPack.Proof, ledger.State(chunkDataPack.StartState), partial.DefaultPathFinderVersion) 126 127 if err != nil { 128 // TODO provide more details based on the error type 129 return nil, chmodels.NewCFInvalidVerifiableChunk("error constructing partial trie: ", err, chIndex, execResID), 130 nil 131 } 132 133 context = fvm.NewContextFromParent( 134 context, 135 fvm.WithDerivedBlockData( 136 derived.NewEmptyDerivedBlockDataWithTransactionOffset( 137 transactionOffset))) 138 139 // chunk view construction 140 // unknown register tracks access to parts of the partial trie which 141 // are not expanded and values are unknown. 142 unknownRegTouch := make(map[flow.RegisterID]*ledger.Key) 143 var problematicTx flow.Identifier 144 getRegister := func(owner, key string) (flow.RegisterValue, error) { 145 // check if register has been provided in the chunk data pack 146 registerID := flow.NewRegisterID(owner, key) 147 148 registerKey := executionState.RegisterIDToKey(registerID) 149 150 query, err := ledger.NewQuerySingleValue(ledger.State(chunkDataPack.StartState), registerKey) 151 152 if err != nil { 153 return nil, fmt.Errorf("cannot create query: %w", err) 154 } 155 156 value, err := psmt.GetSingleValue(query) 157 if err != nil { 158 if errors.Is(err, ledger.ErrMissingKeys{}) { 159 160 unknownRegTouch[registerID] = ®isterKey 161 162 // don't send error just return empty byte slice 163 // we always assume empty value for missing registers (which might cause the transaction to fail) 164 // but after execution we check unknownRegTouch and if any 165 // register is inside it, code won't generate approvals and 166 // it activates a challenge 167 168 return []byte{}, nil 169 } 170 // append to missing keys if error is ErrMissingKeys 171 172 return nil, fmt.Errorf("cannot query register: %w", err) 173 } 174 175 return value, nil 176 } 177 178 chunkView := delta.NewView(getRegister) 179 180 // executes all transactions in this chunk 181 for i, tx := range transactions { 182 txView := chunkView.NewChild() 183 184 err := fcv.vm.Run(context, tx, txView) 185 if err != nil { 186 // this covers unexpected and very rare cases (e.g. system memory issues...), 187 // so we shouldn't be here even if transaction naturally fails (e.g. permission, runtime ... ) 188 return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err) 189 } 190 191 if len(unknownRegTouch) > 0 { 192 problematicTx = tx.ID 193 } 194 195 events = append(events, tx.Events...) 196 serviceEvents = append(serviceEvents, tx.ServiceEvents...) 197 198 // always merge back the tx view (fvm is responsible for changes on tx errors) 199 err = chunkView.MergeView(txView) 200 if err != nil { 201 return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err) 202 } 203 } 204 205 // check read access to unknown registers 206 if len(unknownRegTouch) > 0 { 207 var missingRegs []string 208 for _, key := range unknownRegTouch { 209 missingRegs = append(missingRegs, key.String()) 210 } 211 return nil, chmodels.NewCFMissingRegisterTouch(missingRegs, chIndex, execResID, problematicTx), nil 212 } 213 214 eventsHash, err := flow.EventsMerkleRootHash(events) 215 if err != nil { 216 return nil, nil, fmt.Errorf("cannot calculate events collection hash: %w", err) 217 } 218 if chunk.EventCollection != eventsHash { 219 220 for i, event := range events { 221 222 fcv.logger.Warn().Int("list_index", i). 223 Str("event_id", event.ID().String()). 224 Hex("event_fingerptint", event.Fingerprint()). 225 Str("event_type", string(event.Type)). 226 Str("event_tx_id", event.TransactionID.String()). 227 Uint32("event_tx_index", event.TransactionIndex). 228 Uint32("event_index", event.EventIndex). 229 Bytes("event_payload", event.Payload). 230 Str("block_id", chunk.BlockID.String()). 231 Str("collection_id", chunkDataPack.Collection.ID().String()). 232 Str("result_id", result.ID().String()). 233 Uint64("chunk_index", chunk.Index). 234 Msg("not matching events debug") 235 } 236 237 return nil, chmodels.NewCFInvalidEventsCollection(chunk.EventCollection, eventsHash, chIndex, execResID, events), nil 238 } 239 240 if systemChunk { 241 242 computedServiceEvents := make(flow.ServiceEventList, len(serviceEvents)) 243 244 for i, serviceEvent := range serviceEvents { 245 realServiceEvent, err := convert.ServiceEvent(fcv.vmCtx.Chain.ChainID(), serviceEvent) 246 if err != nil { 247 return nil, nil, fmt.Errorf("cannot convert service event %d: %w", i, err) 248 } 249 computedServiceEvents[i] = *realServiceEvent 250 } 251 252 equal, err := result.ServiceEvents.EqualTo(computedServiceEvents) 253 if err != nil { 254 return nil, nil, fmt.Errorf("error while compariong service events: %w", err) 255 } 256 if !equal { 257 return nil, chmodels.CFInvalidServiceSystemEventsEmitted(result.ServiceEvents, computedServiceEvents, chIndex, execResID), nil 258 } 259 } 260 261 // applying chunk delta (register updates at chunk level) to the partial trie 262 // this returns the expected end state commitment after updates and the list of 263 // register keys that was not provided by the chunk data package (err). 264 regs, values := chunkView.Delta().RegisterUpdates() 265 266 update, err := ledger.NewUpdate( 267 ledger.State(chunkDataPack.StartState), 268 executionState.RegisterIDSToKeys(regs), 269 executionState.RegisterValuesToValues(values), 270 ) 271 if err != nil { 272 return nil, nil, fmt.Errorf("cannot create ledger update: %w", err) 273 } 274 275 expEndStateComm, _, err := psmt.Set(update) 276 277 if err != nil { 278 if errors.Is(err, ledger.ErrMissingKeys{}) { 279 keys := err.(*ledger.ErrMissingKeys).Keys 280 stringKeys := make([]string, len(keys)) 281 for i, key := range keys { 282 stringKeys[i] = key.String() 283 } 284 return nil, chmodels.NewCFMissingRegisterTouch(stringKeys, chIndex, execResID, problematicTx), nil 285 } 286 return nil, chmodels.NewCFMissingRegisterTouch(nil, chIndex, execResID, problematicTx), nil 287 } 288 289 // TODO check if exec node provided register touches that was not used (no read and no update) 290 // check if the end state commitment mentioned in the chunk matches 291 // what the partial trie is providing. 292 if flow.StateCommitment(expEndStateComm) != endState { 293 return nil, chmodels.NewCFNonMatchingFinalState(flow.StateCommitment(expEndStateComm), endState, chIndex, execResID), nil 294 } 295 return chunkView.SpockSecret(), nil, nil 296 }