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  }