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 }