github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rpc/backend/backend_accounts.go (about)

     1  package backend
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/rs/zerolog"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/status"
    12  
    13  	execproto "github.com/onflow/flow/protobuf/go/flow/execution"
    14  
    15  	"github.com/onflow/flow-go/engine/access/rpc/connection"
    16  	"github.com/onflow/flow-go/engine/common/rpc"
    17  	"github.com/onflow/flow-go/engine/common/rpc/convert"
    18  	fvmerrors "github.com/onflow/flow-go/fvm/errors"
    19  	"github.com/onflow/flow-go/model/flow"
    20  	"github.com/onflow/flow-go/module/execution"
    21  	"github.com/onflow/flow-go/module/irrecoverable"
    22  	"github.com/onflow/flow-go/state/protocol"
    23  	"github.com/onflow/flow-go/storage"
    24  )
    25  
    26  type backendAccounts struct {
    27  	log               zerolog.Logger
    28  	state             protocol.State
    29  	headers           storage.Headers
    30  	executionReceipts storage.ExecutionReceipts
    31  	connFactory       connection.ConnectionFactory
    32  	nodeCommunicator  Communicator
    33  	scriptExecutor    execution.ScriptExecutor
    34  	scriptExecMode    IndexQueryMode
    35  }
    36  
    37  // GetAccount returns the account details at the latest sealed block.
    38  // Alias for GetAccountAtLatestBlock
    39  func (b *backendAccounts) GetAccount(ctx context.Context, address flow.Address) (*flow.Account, error) {
    40  	return b.GetAccountAtLatestBlock(ctx, address)
    41  }
    42  
    43  // GetAccountAtLatestBlock returns the account details at the latest sealed block.
    44  func (b *backendAccounts) GetAccountAtLatestBlock(ctx context.Context, address flow.Address) (*flow.Account, error) {
    45  	sealed, err := b.state.Sealed().Head()
    46  	if err != nil {
    47  		err := irrecoverable.NewExceptionf("failed to lookup sealed header: %w", err)
    48  		irrecoverable.Throw(ctx, err)
    49  		return nil, err
    50  	}
    51  
    52  	sealedBlockID := sealed.ID()
    53  
    54  	account, err := b.getAccountAtBlock(ctx, address, sealedBlockID, sealed.Height)
    55  	if err != nil {
    56  		b.log.Debug().Err(err).Msgf("failed to get account at blockID: %v", sealedBlockID)
    57  		return nil, err
    58  	}
    59  
    60  	return account, nil
    61  }
    62  
    63  // GetAccountAtBlockHeight returns the account details at the given block height
    64  func (b *backendAccounts) GetAccountAtBlockHeight(
    65  	ctx context.Context,
    66  	address flow.Address,
    67  	height uint64,
    68  ) (*flow.Account, error) {
    69  	blockID, err := b.headers.BlockIDByHeight(height)
    70  	if err != nil {
    71  		return nil, rpc.ConvertStorageError(err)
    72  	}
    73  
    74  	account, err := b.getAccountAtBlock(ctx, address, blockID, height)
    75  	if err != nil {
    76  		b.log.Debug().Err(err).Msgf("failed to get account at height: %d", height)
    77  		return nil, err
    78  	}
    79  
    80  	return account, nil
    81  }
    82  
    83  // getAccountAtBlock returns the account details at the given block
    84  //
    85  // The data may be sourced from the local storage or from an execution node depending on the nodes's
    86  // configuration and the availability of the data.
    87  func (b *backendAccounts) getAccountAtBlock(
    88  	ctx context.Context,
    89  	address flow.Address,
    90  	blockID flow.Identifier,
    91  	height uint64,
    92  ) (*flow.Account, error) {
    93  	switch b.scriptExecMode {
    94  	case IndexQueryModeExecutionNodesOnly:
    95  		return b.getAccountFromAnyExeNode(ctx, address, blockID)
    96  
    97  	case IndexQueryModeLocalOnly:
    98  		return b.getAccountFromLocalStorage(ctx, address, height)
    99  
   100  	case IndexQueryModeFailover:
   101  		localResult, localErr := b.getAccountFromLocalStorage(ctx, address, height)
   102  		if localErr == nil {
   103  			return localResult, nil
   104  		}
   105  		execResult, execErr := b.getAccountFromAnyExeNode(ctx, address, blockID)
   106  
   107  		b.compareAccountResults(execResult, execErr, localResult, localErr, blockID, address)
   108  
   109  		return execResult, execErr
   110  
   111  	case IndexQueryModeCompare:
   112  		execResult, execErr := b.getAccountFromAnyExeNode(ctx, address, blockID)
   113  		// Only compare actual get account errors from the EN, not system errors
   114  		if execErr != nil && !isInvalidArgumentError(execErr) {
   115  			return nil, execErr
   116  		}
   117  		localResult, localErr := b.getAccountFromLocalStorage(ctx, address, height)
   118  
   119  		b.compareAccountResults(execResult, execErr, localResult, localErr, blockID, address)
   120  
   121  		// always return EN results
   122  		return execResult, execErr
   123  
   124  	default:
   125  		return nil, status.Errorf(codes.Internal, "unknown execution mode: %v", b.scriptExecMode)
   126  	}
   127  }
   128  
   129  // getAccountFromLocalStorage retrieves the given account from the local storage.
   130  func (b *backendAccounts) getAccountFromLocalStorage(
   131  	ctx context.Context,
   132  	address flow.Address,
   133  	height uint64,
   134  ) (*flow.Account, error) {
   135  	// make sure data is available for the requested block
   136  	account, err := b.scriptExecutor.GetAccountAtBlockHeight(ctx, address, height)
   137  	if err != nil {
   138  		return nil, convertAccountError(err, address, height)
   139  	}
   140  	return account, nil
   141  }
   142  
   143  // getAccountFromAnyExeNode retrieves the given account from any EN in `execNodes`.
   144  // We attempt querying each EN in sequence. If any EN returns a valid response, then errors from
   145  // other ENs are logged and swallowed. If all ENs fail to return a valid response, then an
   146  // error aggregating all failures is returned.
   147  func (b *backendAccounts) getAccountFromAnyExeNode(
   148  	ctx context.Context,
   149  	address flow.Address,
   150  	blockID flow.Identifier,
   151  ) (*flow.Account, error) {
   152  	req := &execproto.GetAccountAtBlockIDRequest{
   153  		Address: address.Bytes(),
   154  		BlockId: blockID[:],
   155  	}
   156  
   157  	execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log)
   158  	if err != nil {
   159  		return nil, rpc.ConvertError(err, "failed to find execution node to query", codes.Internal)
   160  	}
   161  
   162  	var resp *execproto.GetAccountAtBlockIDResponse
   163  	errToReturn := b.nodeCommunicator.CallAvailableNode(
   164  		execNodes,
   165  		func(node *flow.IdentitySkeleton) error {
   166  			var err error
   167  			start := time.Now()
   168  
   169  			resp, err = b.tryGetAccount(ctx, node, req)
   170  			duration := time.Since(start)
   171  
   172  			lg := b.log.With().
   173  				Str("execution_node", node.String()).
   174  				Hex("block_id", req.GetBlockId()).
   175  				Hex("address", req.GetAddress()).
   176  				Int64("rtt_ms", duration.Milliseconds()).
   177  				Logger()
   178  
   179  			if err != nil {
   180  				lg.Err(err).Msg("failed to execute GetAccount")
   181  				return err
   182  			}
   183  
   184  			// return if any execution node replied successfully
   185  			lg.Debug().Msg("Successfully got account info")
   186  			return nil
   187  		},
   188  		nil,
   189  	)
   190  
   191  	if errToReturn != nil {
   192  		return nil, rpc.ConvertError(errToReturn, "failed to get account from the execution node", codes.Internal)
   193  	}
   194  
   195  	account, err := convert.MessageToAccount(resp.GetAccount())
   196  	if err != nil {
   197  		return nil, status.Errorf(codes.Internal, "failed to convert account message: %v", err)
   198  	}
   199  
   200  	return account, nil
   201  }
   202  
   203  // tryGetAccount attempts to get the account from the given execution node.
   204  func (b *backendAccounts) tryGetAccount(
   205  	ctx context.Context,
   206  	execNode *flow.IdentitySkeleton,
   207  	req *execproto.GetAccountAtBlockIDRequest,
   208  ) (*execproto.GetAccountAtBlockIDResponse, error) {
   209  	execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	defer closer.Close()
   214  
   215  	return execRPCClient.GetAccountAtBlockID(ctx, req)
   216  }
   217  
   218  // compareAccountResults compares the result and error returned from local and remote getAccount calls
   219  // and logs the results if they are different
   220  func (b *backendAccounts) compareAccountResults(
   221  	execNodeResult *flow.Account,
   222  	execErr error,
   223  	localResult *flow.Account,
   224  	localErr error,
   225  	blockID flow.Identifier,
   226  	address flow.Address,
   227  ) {
   228  	if b.log.GetLevel() > zerolog.DebugLevel {
   229  		return
   230  	}
   231  
   232  	lgCtx := b.log.With().
   233  		Hex("block_id", blockID[:]).
   234  		Str("address", address.String())
   235  
   236  	// errors are different
   237  	if execErr != localErr {
   238  		lgCtx = lgCtx.
   239  			AnErr("execution_node_error", execErr).
   240  			AnErr("local_error", localErr)
   241  
   242  		lg := lgCtx.Logger()
   243  		lg.Debug().Msg("errors from getting account on local and EN do not match")
   244  		return
   245  	}
   246  
   247  	// both errors are nil, compare the accounts
   248  	if execErr == nil {
   249  		lgCtx, ok := compareAccountsLogger(execNodeResult, localResult, lgCtx)
   250  		if !ok {
   251  			lg := lgCtx.Logger()
   252  			lg.Debug().Msg("accounts from local and EN do not match")
   253  		}
   254  	}
   255  }
   256  
   257  // compareAccountsLogger compares accounts produced by the execution node and local storage and
   258  // return a logger configured to log the differences
   259  func compareAccountsLogger(exec, local *flow.Account, lgCtx zerolog.Context) (zerolog.Context, bool) {
   260  	different := false
   261  
   262  	if exec.Address != local.Address {
   263  		lgCtx = lgCtx.
   264  			Str("exec_node_address", exec.Address.String()).
   265  			Str("local_address", local.Address.String())
   266  		different = true
   267  	}
   268  
   269  	if exec.Balance != local.Balance {
   270  		lgCtx = lgCtx.
   271  			Uint64("exec_node_balance", exec.Balance).
   272  			Uint64("local_balance", local.Balance)
   273  		different = true
   274  	}
   275  
   276  	contractListMatches := true
   277  	if len(exec.Contracts) != len(local.Contracts) {
   278  		lgCtx = lgCtx.
   279  			Int("exec_node_contract_count", len(exec.Contracts)).
   280  			Int("local_contract_count", len(local.Contracts))
   281  		contractListMatches = false
   282  		different = true
   283  	}
   284  
   285  	missingContracts := zerolog.Arr()
   286  	mismatchContracts := zerolog.Arr()
   287  
   288  	for name, execContract := range exec.Contracts {
   289  		localContract, ok := local.Contracts[name]
   290  
   291  		if !ok {
   292  			missingContracts.Str(name)
   293  			contractListMatches = false
   294  			different = true
   295  		}
   296  
   297  		if !bytes.Equal(execContract, localContract) {
   298  			mismatchContracts.Str(name)
   299  			different = true
   300  		}
   301  	}
   302  
   303  	lgCtx = lgCtx.
   304  		Array("missing_contracts", missingContracts).
   305  		Array("mismatch_contracts", mismatchContracts)
   306  
   307  	// only check if there were any missing
   308  	if !contractListMatches {
   309  		extraContracts := zerolog.Arr()
   310  		for name := range local.Contracts {
   311  			if _, ok := exec.Contracts[name]; !ok {
   312  				extraContracts.Str(name)
   313  				different = true
   314  			}
   315  		}
   316  		lgCtx = lgCtx.Array("extra_contracts", extraContracts)
   317  	}
   318  
   319  	if len(exec.Keys) != len(local.Keys) {
   320  		lgCtx = lgCtx.
   321  			Int("exec_node_key_count", len(exec.Keys)).
   322  			Int("local_key_count", len(local.Keys))
   323  		different = true
   324  	}
   325  
   326  	mismatchKeys := zerolog.Arr()
   327  
   328  	for i, execKey := range exec.Keys {
   329  		localKey := local.Keys[i]
   330  
   331  		if !execKey.PublicKey.Equals(localKey.PublicKey) {
   332  			mismatchKeys.Int(execKey.Index)
   333  			different = true
   334  		}
   335  	}
   336  
   337  	lgCtx = lgCtx.Array("mismatch_keys", mismatchKeys)
   338  
   339  	return lgCtx, !different
   340  }
   341  
   342  // convertAccountError converts the script execution error to a gRPC error
   343  func convertAccountError(err error, address flow.Address, height uint64) error {
   344  	if err == nil {
   345  		return nil
   346  	}
   347  
   348  	if errors.Is(err, storage.ErrNotFound) {
   349  		return status.Errorf(codes.NotFound, "account with address %s not found: %v", address, err)
   350  	}
   351  
   352  	if fvmerrors.IsAccountNotFoundError(err) {
   353  		return status.Errorf(codes.NotFound, "account not found")
   354  	}
   355  
   356  	return rpc.ConvertIndexError(err, height, "failed to get account")
   357  }