github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/backend.go (about) 1 package backend 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 lru "github.com/hashicorp/golang-lru" 9 accessproto "github.com/onflow/flow/protobuf/go/flow/access" 10 "github.com/rs/zerolog" 11 12 "github.com/koko1123/flow-go-1/access" 13 "github.com/koko1123/flow-go-1/engine/common/rpc" 14 "github.com/koko1123/flow-go-1/engine/common/rpc/convert" 15 "github.com/koko1123/flow-go-1/model/flow" 16 "github.com/koko1123/flow-go-1/model/flow/filter" 17 "github.com/koko1123/flow-go-1/module" 18 "github.com/koko1123/flow-go-1/state/protocol" 19 "github.com/koko1123/flow-go-1/storage" 20 ) 21 22 // maxExecutionNodesCnt is the max number of execution nodes that will be contacted to complete an execution api request 23 const maxExecutionNodesCnt = 3 24 25 // minExecutionNodesCnt is the minimum number of execution nodes expected to have sent the execution receipt for a block 26 const minExecutionNodesCnt = 2 27 28 // maxAttemptsForExecutionReceipt is the maximum number of attempts to find execution receipts for a given block ID 29 const maxAttemptsForExecutionReceipt = 3 30 31 // DefaultMaxHeightRange is the default maximum size of range requests. 32 const DefaultMaxHeightRange = 250 33 34 // DefaultSnapshotHistoryLimit the amount of blocks to look back in state 35 // when recursively searching for a valid snapshot 36 const DefaultSnapshotHistoryLimit = 50 37 38 // DefaultLoggedScriptsCacheSize is the default size of the lookup cache used to dedupe logs of scripts sent to ENs 39 // limiting cache size to 16MB and does not affect script execution, only for keeping logs tidy 40 const DefaultLoggedScriptsCacheSize = 1_000_000 41 42 // DefaultConnectionPoolSize is the default size for the connection pool to collection and execution nodes 43 const DefaultConnectionPoolSize = 250 44 45 var preferredENIdentifiers flow.IdentifierList 46 var fixedENIdentifiers flow.IdentifierList 47 48 // Backend implements the Access API. 49 // 50 // It is composed of several sub-backends that implement part of the Access API. 51 // 52 // Script related calls are handled by backendScripts. 53 // Transaction related calls are handled by backendTransactions. 54 // Block Header related calls are handled by backendBlockHeaders. 55 // Block details related calls are handled by backendBlockDetails. 56 // Event related calls are handled by backendEvents. 57 // Account related calls are handled by backendAccounts. 58 // 59 // All remaining calls are handled by the base Backend in this file. 60 type Backend struct { 61 backendScripts 62 backendTransactions 63 backendEvents 64 backendBlockHeaders 65 backendBlockDetails 66 backendAccounts 67 backendExecutionResults 68 backendNetwork 69 70 state protocol.State 71 chainID flow.ChainID 72 collections storage.Collections 73 executionReceipts storage.ExecutionReceipts 74 connFactory ConnectionFactory 75 } 76 77 func New( 78 state protocol.State, 79 collectionRPC accessproto.AccessAPIClient, 80 historicalAccessNodes []accessproto.AccessAPIClient, 81 blocks storage.Blocks, 82 headers storage.Headers, 83 collections storage.Collections, 84 transactions storage.Transactions, 85 executionReceipts storage.ExecutionReceipts, 86 executionResults storage.ExecutionResults, 87 chainID flow.ChainID, 88 transactionMetrics module.TransactionMetrics, 89 connFactory ConnectionFactory, 90 retryEnabled bool, 91 maxHeightRange uint, 92 preferredExecutionNodeIDs []string, 93 fixedExecutionNodeIDs []string, 94 log zerolog.Logger, 95 snapshotHistoryLimit int, 96 ) *Backend { 97 retry := newRetry() 98 if retryEnabled { 99 retry.Activate() 100 } 101 102 loggedScripts, err := lru.New(DefaultLoggedScriptsCacheSize) 103 if err != nil { 104 log.Fatal().Err(err).Msg("failed to initialize script logging cache") 105 } 106 107 b := &Backend{ 108 state: state, 109 // create the sub-backends 110 backendScripts: backendScripts{ 111 headers: headers, 112 executionReceipts: executionReceipts, 113 connFactory: connFactory, 114 state: state, 115 log: log, 116 metrics: transactionMetrics, 117 loggedScripts: loggedScripts, 118 }, 119 backendTransactions: backendTransactions{ 120 staticCollectionRPC: collectionRPC, 121 state: state, 122 chainID: chainID, 123 collections: collections, 124 blocks: blocks, 125 transactions: transactions, 126 executionReceipts: executionReceipts, 127 transactionValidator: configureTransactionValidator(state, chainID), 128 transactionMetrics: transactionMetrics, 129 retry: retry, 130 connFactory: connFactory, 131 previousAccessNodes: historicalAccessNodes, 132 log: log, 133 }, 134 backendEvents: backendEvents{ 135 state: state, 136 headers: headers, 137 executionReceipts: executionReceipts, 138 connFactory: connFactory, 139 log: log, 140 maxHeightRange: maxHeightRange, 141 }, 142 backendBlockHeaders: backendBlockHeaders{ 143 headers: headers, 144 state: state, 145 }, 146 backendBlockDetails: backendBlockDetails{ 147 blocks: blocks, 148 state: state, 149 }, 150 backendAccounts: backendAccounts{ 151 state: state, 152 headers: headers, 153 executionReceipts: executionReceipts, 154 connFactory: connFactory, 155 log: log, 156 }, 157 backendExecutionResults: backendExecutionResults{ 158 executionResults: executionResults, 159 }, 160 backendNetwork: backendNetwork{ 161 state: state, 162 chainID: chainID, 163 snapshotHistoryLimit: snapshotHistoryLimit, 164 }, 165 collections: collections, 166 executionReceipts: executionReceipts, 167 connFactory: connFactory, 168 chainID: chainID, 169 } 170 171 retry.SetBackend(b) 172 173 preferredENIdentifiers, err = identifierList(preferredExecutionNodeIDs) 174 if err != nil { 175 log.Fatal().Err(err).Msg("failed to convert node id string to Flow Identifier for preferred EN map") 176 } 177 178 fixedENIdentifiers, err = identifierList(fixedExecutionNodeIDs) 179 if err != nil { 180 log.Fatal().Err(err).Msg("failed to convert node id string to Flow Identifier for fixed EN map") 181 } 182 183 return b 184 } 185 186 func identifierList(ids []string) (flow.IdentifierList, error) { 187 idList := make(flow.IdentifierList, len(ids)) 188 for i, idStr := range ids { 189 id, err := flow.HexStringToIdentifier(idStr) 190 if err != nil { 191 return nil, fmt.Errorf("failed to convert node id string %s to Flow Identifier: %v", id, err) 192 } 193 idList[i] = id 194 } 195 return idList, nil 196 } 197 198 func configureTransactionValidator(state protocol.State, chainID flow.ChainID) *access.TransactionValidator { 199 return access.NewTransactionValidator( 200 access.NewProtocolStateBlocks(state), 201 chainID.Chain(), 202 access.TransactionValidationOptions{ 203 Expiry: flow.DefaultTransactionExpiry, 204 ExpiryBuffer: flow.DefaultTransactionExpiryBuffer, 205 AllowEmptyReferenceBlockID: false, 206 AllowUnknownReferenceBlockID: false, 207 CheckScriptsParse: false, 208 MaxGasLimit: flow.DefaultMaxTransactionGasLimit, 209 MaxTransactionByteSize: flow.DefaultMaxTransactionByteSize, 210 MaxCollectionByteSize: flow.DefaultMaxCollectionByteSize, 211 }, 212 ) 213 } 214 215 // Ping responds to requests when the server is up. 216 func (b *Backend) Ping(ctx context.Context) error { 217 218 // staticCollectionRPC is only set if a collection node address was provided at startup 219 if b.staticCollectionRPC != nil { 220 _, err := b.staticCollectionRPC.Ping(ctx, &accessproto.PingRequest{}) 221 if err != nil { 222 return fmt.Errorf("could not ping collection node: %w", err) 223 } 224 } 225 226 return nil 227 } 228 229 func (b *Backend) GetCollectionByID(_ context.Context, colID flow.Identifier) (*flow.LightCollection, error) { 230 // retrieve the collection from the collection storage 231 col, err := b.collections.LightByID(colID) 232 if err != nil { 233 // Collections are retrieved asynchronously as we finalize blocks, so 234 // it is possible for a client to request a finalized block from us 235 // containing some collection, then get a not found error when requesting 236 // that collection. These clients should retry. 237 err = rpc.ConvertStorageError(fmt.Errorf("please retry for collection in finalized block: %w", err)) 238 return nil, err 239 } 240 241 return col, nil 242 } 243 244 func (b *Backend) GetNetworkParameters(_ context.Context) access.NetworkParameters { 245 return access.NetworkParameters{ 246 ChainID: b.chainID, 247 } 248 } 249 250 // GetLatestProtocolStateSnapshot returns the latest finalized snapshot 251 func (b *Backend) GetLatestProtocolStateSnapshot(_ context.Context) ([]byte, error) { 252 snapshot := b.state.Final() 253 254 validSnapshot, err := b.getValidSnapshot(snapshot, 0) 255 if err != nil { 256 return nil, err 257 } 258 259 return convert.SnapshotToBytes(validSnapshot) 260 } 261 262 // executionNodesForBlockID returns upto maxExecutionNodesCnt number of randomly chosen execution node identities 263 // which have executed the given block ID. 264 // If no such execution node is found, an InsufficientExecutionReceipts error is returned. 265 func executionNodesForBlockID( 266 ctx context.Context, 267 blockID flow.Identifier, 268 executionReceipts storage.ExecutionReceipts, 269 state protocol.State, 270 log zerolog.Logger) (flow.IdentityList, error) { 271 272 var executorIDs flow.IdentifierList 273 274 // check if the block ID is of the root block. If it is then don't look for execution receipts since they 275 // will not be present for the root block. 276 rootBlock, err := state.Params().Root() 277 if err != nil { 278 return nil, fmt.Errorf("failed to retreive execution IDs for block ID %v: %w", blockID, err) 279 } 280 281 if rootBlock.ID() == blockID { 282 executorIdentities, err := state.Final().Identities(filter.HasRole(flow.RoleExecution)) 283 if err != nil { 284 return nil, fmt.Errorf("failed to retreive execution IDs for block ID %v: %w", blockID, err) 285 } 286 executorIDs = executorIdentities.NodeIDs() 287 } else { 288 // try to find atleast minExecutionNodesCnt execution node ids from the execution receipts for the given blockID 289 for attempt := 0; attempt < maxAttemptsForExecutionReceipt; attempt++ { 290 executorIDs, err = findAllExecutionNodes(blockID, executionReceipts, log) 291 if err != nil { 292 return nil, err 293 } 294 295 if len(executorIDs) >= minExecutionNodesCnt { 296 break 297 } 298 299 // log the attempt 300 log.Debug().Int("attempt", attempt).Int("max_attempt", maxAttemptsForExecutionReceipt). 301 Int("execution_receipts_found", len(executorIDs)). 302 Str("block_id", blockID.String()). 303 Msg("insufficient execution receipts") 304 305 // if one or less execution receipts may have been received then re-query 306 // in the hope that more might have been received by now 307 308 select { 309 case <-ctx.Done(): 310 return nil, ctx.Err() 311 case <-time.After(100 * time.Millisecond << time.Duration(attempt)): 312 //retry after an exponential backoff 313 } 314 } 315 316 receiptCnt := len(executorIDs) 317 // if less than minExecutionNodesCnt execution receipts have been received so far, then return random ENs 318 if receiptCnt < minExecutionNodesCnt { 319 newExecutorIDs, err := state.AtBlockID(blockID).Identities(filter.HasRole(flow.RoleExecution)) 320 if err != nil { 321 return nil, fmt.Errorf("failed to retreive execution IDs for block ID %v: %w", blockID, err) 322 } 323 executorIDs = newExecutorIDs.NodeIDs() 324 } 325 } 326 327 // choose from the preferred or fixed execution nodes 328 subsetENs, err := chooseExecutionNodes(state, executorIDs) 329 if err != nil { 330 return nil, fmt.Errorf("failed to retreive execution IDs for block ID %v: %w", blockID, err) 331 } 332 333 // randomly choose upto maxExecutionNodesCnt identities 334 executionIdentitiesRandom := subsetENs.Sample(maxExecutionNodesCnt) 335 336 if len(executionIdentitiesRandom) == 0 { 337 return nil, fmt.Errorf("no matching execution node found for block ID %v", blockID) 338 } 339 340 return executionIdentitiesRandom, nil 341 } 342 343 // findAllExecutionNodes find all the execution nodes ids from the execution receipts that have been received for the 344 // given blockID 345 func findAllExecutionNodes( 346 blockID flow.Identifier, 347 executionReceipts storage.ExecutionReceipts, 348 log zerolog.Logger) (flow.IdentifierList, error) { 349 350 // lookup the receipt's storage with the block ID 351 allReceipts, err := executionReceipts.ByBlockID(blockID) 352 if err != nil { 353 return nil, fmt.Errorf("failed to retreive execution receipts for block ID %v: %w", blockID, err) 354 } 355 356 executionResultMetaList := make(flow.ExecutionReceiptMetaList, 0, len(allReceipts)) 357 for _, r := range allReceipts { 358 executionResultMetaList = append(executionResultMetaList, r.Meta()) 359 } 360 executionResultGroupedMetaList := executionResultMetaList.GroupByResultID() 361 362 // maximum number of matching receipts found so far for any execution result id 363 maxMatchedReceiptCnt := 0 364 // execution result id key for the highest number of matching receipts in the identicalReceipts map 365 var maxMatchedReceiptResultID flow.Identifier 366 367 // find the largest list of receipts which have the same result ID 368 for resultID, executionReceiptList := range executionResultGroupedMetaList { 369 currentMatchedReceiptCnt := executionReceiptList.Size() 370 if currentMatchedReceiptCnt > maxMatchedReceiptCnt { 371 maxMatchedReceiptCnt = currentMatchedReceiptCnt 372 maxMatchedReceiptResultID = resultID 373 } 374 } 375 376 // if there are more than one execution result for the same block ID, log as error 377 if executionResultGroupedMetaList.NumberGroups() > 1 { 378 identicalReceiptsStr := fmt.Sprintf("%v", flow.GetIDs(allReceipts)) 379 log.Error(). 380 Str("block_id", blockID.String()). 381 Str("execution_receipts", identicalReceiptsStr). 382 Msg("execution receipt mismatch") 383 } 384 385 // pick the largest list of matching receipts 386 matchingReceiptMetaList := executionResultGroupedMetaList.GetGroup(maxMatchedReceiptResultID) 387 388 metaReceiptGroupedByExecutorID := matchingReceiptMetaList.GroupByExecutorID() 389 390 // collect all unique execution node ids from the receipts 391 var executorIDs flow.IdentifierList 392 for executorID := range metaReceiptGroupedByExecutorID { 393 executorIDs = append(executorIDs, executorID) 394 } 395 396 return executorIDs, nil 397 } 398 399 // chooseExecutionNodes finds the subset of execution nodes defined in the identity table by first 400 // choosing the preferred execution nodes which have executed the transaction. If no such preferred 401 // execution nodes are found, then the fixed execution nodes defined in the identity table are returned 402 // If neither preferred nor fixed nodes are defined, then all execution node matching the executor IDs are returned. 403 // e.g. If execution nodes in identity table are {1,2,3,4}, preferred ENs are defined as {2,3,4} 404 // and the executor IDs is {1,2,3}, then {2, 3} is returned as the chosen subset of ENs 405 func chooseExecutionNodes(state protocol.State, executorIDs flow.IdentifierList) (flow.IdentityList, error) { 406 407 allENs, err := state.Final().Identities(filter.HasRole(flow.RoleExecution)) 408 if err != nil { 409 return nil, fmt.Errorf("failed to retreive all execution IDs: %w", err) 410 } 411 412 // first try and choose from the preferred EN IDs 413 var chosenIDs flow.IdentityList 414 if len(preferredENIdentifiers) > 0 { 415 // find the preferred execution node IDs which have executed the transaction 416 chosenIDs = allENs.Filter(filter.And(filter.HasNodeID(preferredENIdentifiers...), 417 filter.HasNodeID(executorIDs...))) 418 if len(chosenIDs) > 0 { 419 return chosenIDs, nil 420 } 421 } 422 423 // if no preferred EN ID is found, then choose from the fixed EN IDs 424 if len(fixedENIdentifiers) > 0 { 425 // choose fixed ENs which have executed the transaction 426 chosenIDs = allENs.Filter(filter.And(filter.HasNodeID(fixedENIdentifiers...), filter.HasNodeID(executorIDs...))) 427 if len(chosenIDs) > 0 { 428 return chosenIDs, nil 429 } 430 // if no such ENs are found then just choose all fixed ENs 431 chosenIDs = allENs.Filter(filter.HasNodeID(fixedENIdentifiers...)) 432 return chosenIDs, nil 433 } 434 435 // If no preferred or fixed ENs have been specified, then return all executor IDs i.e. no preference at all 436 return allENs.Filter(filter.HasNodeID(executorIDs...)), nil 437 }