From 6938f4046a9468ca25c1335994069ac52748d798 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 8 Jun 2023 12:46:41 +0300 Subject: [PATCH 01/30] Created new RestServerApi, refactored rest router and server, added BuildFromGrpc handlers for rest forwarder --- engine/access/rest/accounts.go | 21 +- engine/access/rest/blocks.go | 243 +++++++----- engine/access/rest/collections.go | 30 +- engine/access/rest/events.go | 41 +- engine/access/rest/execution_result.go | 43 +- engine/access/rest/handler.go | 12 +- engine/access/rest/models/collection.go | 50 +++ engine/access/rest/models/event.go | 27 ++ engine/access/rest/models/network.go | 5 + .../access/rest/models/node_version_info.go | 10 + engine/access/rest/network.go | 9 +- engine/access/rest/node_version_info.go | 12 +- engine/access/rest/rest_server_api.go | 373 ++++++++++++++++++ engine/access/rest/router.go | 5 +- engine/access/rest/scripts.go | 24 +- engine/access/rest/server.go | 6 +- engine/access/rest/transactions.go | 43 +- engine/common/rpc/convert/convert.go | 13 + integration/localnet/builder/bootstrap.go | 2 + 19 files changed, 657 insertions(+), 312 deletions(-) create mode 100644 engine/access/rest/rest_server_api.go diff --git a/engine/access/rest/accounts.go b/engine/access/rest/accounts.go index 36371bf6c57..a7e194de9e3 100644 --- a/engine/access/rest/accounts.go +++ b/engine/access/rest/accounts.go @@ -1,33 +1,16 @@ package rest import ( - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetAccount handler retrieves account by address and returns the response -func GetAccount(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetAccount(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetAccountRequest() if err != nil { return nil, NewBadRequestError(err) } - // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it - if req.Height == request.FinalHeight || req.Height == request.SealedHeight { - header, _, err := backend.GetLatestBlockHeader(r.Context(), req.Height == request.SealedHeight) - if err != nil { - return nil, err - } - req.Height = header.Height - } - - account, err := backend.GetAccountAtBlockHeight(r.Context(), req.Address, req.Height) - if err != nil { - return nil, err - } - - var response models.Account - err = response.Build(account, link, r.ExpandFields) - return response, err + return srv.GetAccount(req, r.Context(), r.ExpandFields, link) } diff --git a/engine/access/rest/blocks.go b/engine/access/rest/blocks.go index e729f67a9bd..1f24f44d717 100644 --- a/engine/access/rest/blocks.go +++ b/engine/access/rest/blocks.go @@ -3,6 +3,7 @@ package rest import ( "context" "fmt" + "net/http" "google.golang.org/grpc/codes" @@ -11,123 +12,110 @@ import ( "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" ) // GetBlocksByIDs gets blocks by provided ID or list of IDs. -func GetBlocksByIDs(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByIDs(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockByIDsRequest() if err != nil { return nil, NewBadRequestError(err) } - blocks := make([]*models.Block, len(req.IDs)) - for i, id := range req.IDs { - block, err := getBlock(forID(&id), r, backend, link) - if err != nil { - return nil, err - } - blocks[i] = block - } + return srv.GetBlocksByIDs(req, r.Context(), r.ExpandFields, link) +} - return blocks, nil +// GetBlocksByHeight gets blocks by height. +func GetBlocksByHeight(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { + return srv.GetBlocksByHeight(r, link) } -func GetBlocksByHeight(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { - req, err := r.GetBlockRequest() +// GetBlockPayloadByID gets block payload by ID +func GetBlockPayloadByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { + req, err := r.GetBlockPayloadRequest() if err != nil { return nil, NewBadRequestError(err) } - if req.FinalHeight || req.SealedHeight { - block, err := getBlock(forFinalized(req.Heights[0]), r, backend, link) - if err != nil { - return nil, err - } + return srv.GetBlockPayloadByID(req, r.Context(), link) +} - return []*models.Block{block}, nil +func getBlock(option blockRequestOption, context context.Context, expandFields map[string]bool, backend access.API, link models.LinkGenerator) (*models.Block, error) { + // lookup block + blkProvider := NewBlockRequestProvider(backend, option) + blk, blockStatus, err := blkProvider.getBlock(context) + if err != nil { + return nil, err } - // if the query is /blocks/height=1000,1008,1049... - if req.HasHeights() { - blocks := make([]*models.Block, len(req.Heights)) - for i, h := range req.Heights { - block, err := getBlock(forHeight(h), r, backend, link) - if err != nil { - return nil, err + // lookup execution result + // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) + var block models.Block + executionResult, err := backend.GetExecutionResultForBlockID(context, blk.ID()) + if err != nil { + // handle case where execution result is not yet available + if se, ok := status.FromError(err); ok { + if se.Code() == codes.NotFound { + err := block.Build(blk, nil, link, blockStatus, expandFields) + if err != nil { + return nil, err + } + return &block, nil } - blocks[i] = block } - - return blocks, nil + return nil, err } - // support providing end height as "sealed" or "final" - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - latest, _, err := backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - req.EndHeight = latest.Header.Height // overwrite special value height with fetched - - if req.StartHeight > req.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) - } + err = block.Build(blk, executionResult, link, blockStatus, expandFields) + if err != nil { + return nil, err } + return &block, nil +} - blocks := make([]*models.Block, 0) - // start and end height inclusive - for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := getBlock(forHeight(i), r, backend, link) - if err != nil { - return nil, err - } - blocks = append(blocks, block) +func getForwarderBlock(option blockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { + // lookup block + blkProvider := NewBlockForwarderProvider(upstream, option) + blk, blockStatus, err := blkProvider.getBlock(context) + if err != nil { + return nil, err } - return blocks, nil -} + // lookup execution result + // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) + var block models.Block + getExecutionResultForBlockIDRequest := &accessproto.GetExecutionResultForBlockIDRequest{ + BlockId: blk.Id, + } -// GetBlockPayloadByID gets block payload by ID -func GetBlockPayloadByID(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { - req, err := r.GetBlockPayloadRequest() + executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockIDRequest) if err != nil { - return nil, NewBadRequestError(err) + return nil, err } - blkProvider := NewBlockProvider(backend, forID(&req.ID)) - blk, _, statusErr := blkProvider.getBlock(r.Context()) - if statusErr != nil { - return nil, statusErr + flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) + if err != nil { + return nil, err } - var payload models.BlockPayload - err = payload.Build(blk.Payload) + flowBlock, err := convert.MessageToBlock(blk) if err != nil { return nil, err } - return payload, nil -} - -func getBlock(option blockProviderOption, req *request.Request, backend access.API, link models.LinkGenerator) (*models.Block, error) { - // lookup block - blkProvider := NewBlockProvider(backend, option) - blk, blockStatus, err := blkProvider.getBlock(req.Context()) + flowBlockStatus, err := convert.MessagesToBlockStatus(blockStatus) if err != nil { return nil, err } - // lookup execution result - // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) - var block models.Block - executionResult, err := backend.GetExecutionResultForBlockID(req.Context(), blk.ID()) if err != nil { // handle case where execution result is not yet available if se, ok := status.FromError(err); ok { if se.Code() == codes.NotFound { - err := block.Build(blk, nil, link, blockStatus, req.ExpandFields) + err := block.Build(flowBlock, nil, link, flowBlockStatus, expandFields) if err != nil { return nil, err } @@ -137,60 +125,64 @@ func getBlock(option blockProviderOption, req *request.Request, backend access.A return nil, err } - err = block.Build(blk, executionResult, link, blockStatus, req.ExpandFields) + err = block.Build(flowBlock, flowExecResult, link, flowBlockStatus, expandFields) if err != nil { return nil, err } return &block, nil } -// blockProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to -// look up a block or a block header either by ID or by height -type blockProvider struct { - id *flow.Identifier - height uint64 - latest bool - sealed bool - backend access.API +type blockRequest struct { + id *flow.Identifier + height uint64 + latest bool + sealed bool } -type blockProviderOption func(blkProvider *blockProvider) +type blockRequestOption func(blkRequest *blockRequest) -func forID(id *flow.Identifier) blockProviderOption { - return func(blkProvider *blockProvider) { - blkProvider.id = id +func forID(id *flow.Identifier) blockRequestOption { + return func(blockRequest *blockRequest) { + blockRequest.id = id } } -func forHeight(height uint64) blockProviderOption { - return func(blkProvider *blockProvider) { - blkProvider.height = height +func forHeight(height uint64) blockRequestOption { + return func(blockRequest *blockRequest) { + blockRequest.height = height } } -func forFinalized(queryParam uint64) blockProviderOption { - return func(blkProvider *blockProvider) { +func forFinalized(queryParam uint64) blockRequestOption { + return func(blockRequest *blockRequest) { switch queryParam { case request.SealedHeight: - blkProvider.sealed = true + blockRequest.sealed = true fallthrough case request.FinalHeight: - blkProvider.latest = true + blockRequest.latest = true } } } -func NewBlockProvider(backend access.API, options ...blockProviderOption) *blockProvider { - blkProvider := &blockProvider{ +// blockProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to +// look up a block or a block header either by ID or by height +type blockRequestProvider struct { + blockRequest + backend access.API +} + +func NewBlockRequestProvider(backend access.API, options ...blockRequestOption) *blockRequestProvider { + blockRequestProvider := &blockRequestProvider{ backend: backend, } for _, o := range options { - o(blkProvider) + o(&blockRequestProvider.blockRequest) } - return blkProvider + return blockRequestProvider } -func (blkProvider *blockProvider) getBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { +func (blkProvider *blockRequestProvider) getBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { if blkProvider.id != nil { blk, _, err := blkProvider.backend.GetBlockByID(ctx, *blkProvider.id) if err != nil { // unfortunately backend returns internal error status if not found @@ -218,3 +210,60 @@ func (blkProvider *blockProvider) getBlock(ctx context.Context) (*flow.Block, fl } return blk, status, nil } + +// blockProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to +// look up a block or a block header either by ID or by height +type blockForwarderProvider struct { + blockRequest + upstream accessproto.AccessAPIClient +} + +func NewBlockForwarderProvider(upstream accessproto.AccessAPIClient, options ...blockRequestOption) *blockForwarderProvider { + blockForwarderProvider := &blockForwarderProvider{ + upstream: upstream, + } + + for _, o := range options { + o(&blockForwarderProvider.blockRequest) + } + return blockForwarderProvider +} + +func (blkProvider *blockForwarderProvider) getBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { + if blkProvider.id != nil { + getBlockByIdRequest := &accessproto.GetBlockByIDRequest{ + Id: []byte(blkProvider.id.String()), + } + blockResponse, err := blkProvider.upstream.GetBlockByID(ctx, getBlockByIdRequest) + if err != nil { // unfortunately grpc returns internal error status if not found + return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewNotFoundError( + fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err, + ) + } + return blockResponse.Block, entities.BlockStatus_BLOCK_UNKNOWN, nil + } + + if blkProvider.latest { + getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ + IsSealed: blkProvider.sealed, + } + blockResponse, err := blkProvider.upstream.GetLatestBlock(ctx, getLatestBlockRequest) + if err != nil { + // cannot be a 'not found' error since final and sealed block should always be found + return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewRestError(http.StatusInternalServerError, "block lookup failed", err) + } + return blockResponse.Block, blockResponse.BlockStatus, nil + } + + getBlockByHeight := &accessproto.GetBlockByHeightRequest{ + Height: blkProvider.height, + FullBlockResponse: true, + } + blockResponse, err := blkProvider.upstream.GetBlockByHeight(ctx, getBlockByHeight) + if err != nil { // unfortunately grpc returns internal error status if not found + return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewNotFoundError( + fmt.Sprintf("error looking up block at height %d", blkProvider.height), err, + ) + } + return blockResponse.Block, blockResponse.BlockStatus, nil +} diff --git a/engine/access/rest/collections.go b/engine/access/rest/collections.go index 807be2c0c41..be1b751b348 100644 --- a/engine/access/rest/collections.go +++ b/engine/access/rest/collections.go @@ -1,42 +1,16 @@ package rest import ( - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/model/flow" ) // GetCollectionByID retrieves a collection by ID and builds a response -func GetCollectionByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetCollectionByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetCollectionRequest() if err != nil { return nil, NewBadRequestError(err) } - collection, err := backend.GetCollectionByID(r.Context(), req.ID) - if err != nil { - return nil, err - } - - // if we expand transactions in the query retrieve each transaction data - transactions := make([]*flow.TransactionBody, 0) - if req.ExpandsTransactions { - for _, tid := range collection.Transactions { - tx, err := backend.GetTransaction(r.Context(), tid) - if err != nil { - return nil, err - } - - transactions = append(transactions, tx) - } - } - - var response models.Collection - err = response.Build(collection, transactions, link, r.ExpandFields) - if err != nil { - return nil, err - } - - return response, nil + return srv.GetCollectionByID(req, r.Context(), r.ExpandFields, link, r.Chain) } diff --git a/engine/access/rest/events.go b/engine/access/rest/events.go index 2a79939bc21..e1e3eb6c7ec 100644 --- a/engine/access/rest/events.go +++ b/engine/access/rest/events.go @@ -1,56 +1,19 @@ package rest import ( - "fmt" - "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" - - "github.com/onflow/flow-go/access" ) const blockQueryParam = "block_ids" const eventTypeQuery = "type" // GetEvents for the provided block range or list of block IDs filtered by type. -func GetEvents(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { +func GetEvents(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetEventsRequest() if err != nil { return nil, NewBadRequestError(err) } - // if the request has block IDs provided then return events for block IDs - var blocksEvents models.BlocksEvents - if len(req.BlockIDs) > 0 { - events, err := backend.GetEventsForBlockIDs(r.Context(), req.Type, req.BlockIDs) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil - } - - // if end height is provided with special values then load the height - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - latest, _, err := backend.GetLatestBlockHeader(r.Context(), req.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - req.EndHeight = latest.Height - // special check after we resolve special height value - if req.StartHeight > req.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) - } - } - - // if request provided block height range then return events for that range - events, err := backend.GetEventsForHeightRange(r.Context(), req.Type, req.StartHeight, req.EndHeight) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil + return srv.GetEvents(req, r.Context()) } diff --git a/engine/access/rest/execution_result.go b/engine/access/rest/execution_result.go index b0583d43b0d..1b9af6c586d 100644 --- a/engine/access/rest/execution_result.go +++ b/engine/access/rest/execution_result.go @@ -1,61 +1,26 @@ package rest import ( - "fmt" - - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func GetExecutionResultsByBlockIDs(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultsByBlockIDs(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultByBlockIDsRequest() if err != nil { return nil, NewBadRequestError(err) } - // for each block ID we retrieve execution result - results := make([]models.ExecutionResult, len(req.BlockIDs)) - for i, id := range req.BlockIDs { - res, err := backend.GetExecutionResultForBlockID(r.Context(), id) - if err != nil { - return nil, err - } - - var response models.ExecutionResult - err = response.Build(res, link) - if err != nil { - return nil, err - } - results[i] = response - } - - return results, nil + return srv.GetExecutionResultsByBlockIDs(req, r.Context(), link) } // GetExecutionResultByID gets execution result by the ID. -func GetExecutionResultByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultRequest() if err != nil { return nil, NewBadRequestError(err) } - res, err := backend.GetExecutionResultByID(r.Context(), req.ID) - if err != nil { - return nil, err - } - - if res == nil { - err := fmt.Errorf("execution result with ID: %s not found", req.ID.String()) - return nil, NewNotFoundError(err.Error(), err) - } - - var response models.ExecutionResult - err = response.Build(res, link) - if err != nil { - return nil, err - } - - return response, nil + return srv.GetExecutionResultByID(req, r.Context(), link) } diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index 028176fc9e0..74e5663172f 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -15,8 +15,6 @@ import ( "github.com/rs/zerolog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - - "github.com/onflow/flow-go/access" ) const MaxRequestSize = 2 << 20 // 2MB @@ -25,7 +23,7 @@ const MaxRequestSize = 2 << 20 // 2MB // it fetches necessary resources and returns an error or response model. type ApiHandlerFunc func( r *request.Request, - backend access.API, + srv RestServerApi, generator models.LinkGenerator, ) (interface{}, error) @@ -34,7 +32,7 @@ type ApiHandlerFunc func( // wraps functionality for handling error and responses outside of endpoint handling. type Handler struct { logger zerolog.Logger - backend access.API + restServerAPI RestServerApi linkGenerator models.LinkGenerator apiHandlerFunc ApiHandlerFunc chain flow.Chain @@ -42,14 +40,14 @@ type Handler struct { func NewHandler( logger zerolog.Logger, - backend access.API, + restServerAPI RestServerApi, handlerFunc ApiHandlerFunc, generator models.LinkGenerator, chain flow.Chain, ) *Handler { return &Handler{ logger: logger, - backend: backend, + restServerAPI: restServerAPI, apiHandlerFunc: handlerFunc, linkGenerator: generator, chain: chain, @@ -74,7 +72,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { decoratedRequest := request.Decorate(r, h.chain) // execute handler function and check for error - response, err := h.apiHandlerFunc(decoratedRequest, h.backend, h.linkGenerator) + response, err := h.apiHandlerFunc(decoratedRequest, h.restServerAPI, h.linkGenerator) if err != nil { h.errorHandler(w, err, errLog) return diff --git a/engine/access/rest/models/collection.go b/engine/access/rest/models/collection.go index c5076fdc7db..b42982a4061 100644 --- a/engine/access/rest/models/collection.go +++ b/engine/access/rest/models/collection.go @@ -1,10 +1,13 @@ package models import ( + "encoding/hex" "fmt" "github.com/onflow/flow-go/engine/access/rest/util" + "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" ) const ExpandsTransactions = "transactions" @@ -42,6 +45,53 @@ func (c *Collection) Build( return nil } +func (c *Collection) BuildFromGrpc( + collection *entities.Collection, + txs []*entities.Transaction, + link LinkGenerator, + expand map[string]bool, + chain flow.Chain) error { + + self, err := SelfLink(convert.MessageToIdentifier(collection.Id), link.CollectionLink) + if err != nil { + return err + } + + transactionsBody := make([]*flow.TransactionBody, 0) + for _, tx := range txs { + flowTransaction, err := convert.MessageToTransaction(tx, chain) + if err != nil { + return err + } + transactionsBody = append(transactionsBody, &flowTransaction) + } + + var expandable CollectionExpandable + var transactions Transactions + if expand[ExpandsTransactions] { + var txIds []flow.Identifier + for _, id := range collection.TransactionIds { + txIds = append(txIds, convert.MessageToIdentifier(id)) + } + transactions.Build(transactionsBody, link) + } else { + expandable.Transactions = make([]string, len(collection.TransactionIds)) + for i, id := range collection.TransactionIds { + expandable.Transactions[i], err = link.TransactionLink(convert.MessageToIdentifier(id)) + if err != nil { + return err + } + } + } + + c.Id = hex.EncodeToString(collection.Id) + c.Transactions = transactions + c.Links = self + c.Expandable = &expandable + + return nil +} + func (c *CollectionGuarantee) Build(guarantee *flow.CollectionGuarantee) { c.CollectionId = guarantee.CollectionID.String() c.SignerIndices = fmt.Sprintf("%x", guarantee.SignerIndices) diff --git a/engine/access/rest/models/event.go b/engine/access/rest/models/event.go index 929dbb3f42c..8cfb3457dc0 100644 --- a/engine/access/rest/models/event.go +++ b/engine/access/rest/models/event.go @@ -1,8 +1,12 @@ package models import ( + "encoding/hex" + "github.com/onflow/flow-go/engine/access/rest/util" + "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) func (e *Event) Build(event flow.Event) { @@ -48,3 +52,26 @@ func (b *BlocksEvents) Build(blocksEvents []flow.BlockEvents) { *b = evs } + +func (b *BlocksEvents) BuildFromGrpc(blocksEvents []*accessproto.EventsResponse_Result) { + evs := make([]BlockEvents, 0) + for _, ev := range blocksEvents { + var blockEvent BlockEvents + blockEvent.BuildFromGrpc(ev) + evs = append(evs, blockEvent) + } + + *b = evs +} + +func (b *BlockEvents) BuildFromGrpc(blockEvents *accessproto.EventsResponse_Result) { + b.BlockHeight = util.FromUint64(blockEvents.BlockHeight) + b.BlockId = hex.EncodeToString(blockEvents.BlockId) + b.BlockTimestamp = blockEvents.BlockTimestamp.AsTime() + + var events Events + flowEvents := convert.MessagesToEvents(blockEvents.Events) + events.Build(flowEvents) + b.Events = events + +} diff --git a/engine/access/rest/models/network.go b/engine/access/rest/models/network.go index 927b5a23362..584e2a73e89 100644 --- a/engine/access/rest/models/network.go +++ b/engine/access/rest/models/network.go @@ -2,8 +2,13 @@ package models import ( "github.com/onflow/flow-go/access" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) func (t *NetworkParameters) Build(params *access.NetworkParameters) { t.ChainId = params.ChainID.String() } + +func (t *NetworkParameters) BuildFromGrpc(response *accessproto.GetNetworkParametersResponse) { + t.ChainId = response.ChainId +} diff --git a/engine/access/rest/models/node_version_info.go b/engine/access/rest/models/node_version_info.go index 6a85e9f8d42..f51878f158d 100644 --- a/engine/access/rest/models/node_version_info.go +++ b/engine/access/rest/models/node_version_info.go @@ -1,8 +1,11 @@ package models import ( + "encoding/hex" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/util" + "github.com/onflow/flow/protobuf/go/flow/entities" ) func (t *NodeVersionInfo) Build(params *access.NodeVersionInfo) { @@ -11,3 +14,10 @@ func (t *NodeVersionInfo) Build(params *access.NodeVersionInfo) { t.SporkId = params.SporkId.String() t.ProtocolVersion = util.FromUint64(params.ProtocolVersion) } + +func (t *NodeVersionInfo) BuildFromGrpc(params *entities.NodeVersionInfo) { + t.Semver = params.Semver + t.Commit = params.Commit + t.SporkId = hex.EncodeToString(params.SporkId) + t.ProtocolVersion = util.FromUint64(params.ProtocolVersion) +} diff --git a/engine/access/rest/network.go b/engine/access/rest/network.go index 6100bc765d5..1fe904e29dd 100644 --- a/engine/access/rest/network.go +++ b/engine/access/rest/network.go @@ -1,16 +1,11 @@ package rest import ( - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNetworkParameters returns network-wide parameters of the blockchain -func GetNetworkParameters(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { - params := backend.GetNetworkParameters(r.Context()) - - var response models.NetworkParameters - response.Build(¶ms) - return response, nil +func GetNetworkParameters(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { + return srv.GetNetworkParameters(r) } diff --git a/engine/access/rest/node_version_info.go b/engine/access/rest/node_version_info.go index 899d159cf4f..a1a04977835 100644 --- a/engine/access/rest/node_version_info.go +++ b/engine/access/rest/node_version_info.go @@ -1,19 +1,11 @@ package rest import ( - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNodeVersionInfo returns node version information -func GetNodeVersionInfo(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { - params, err := backend.GetNodeVersionInfo(r.Context()) - if err != nil { - return nil, err - } - - var response models.NodeVersionInfo - response.Build(params) - return response, nil +func GetNodeVersionInfo(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { + return srv.GetNodeVersionInfo(r) } diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go new file mode 100644 index 00000000000..ebc2e54f831 --- /dev/null +++ b/engine/access/rest/rest_server_api.go @@ -0,0 +1,373 @@ +package rest + +import ( + "context" + "fmt" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/model/flow" +) + +type RestServerApi interface { + // GetTransactionByID gets a transaction by requested ID. + GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) + // CreateTransaction creates a new transaction from provided payload. + CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) + // GetTransactionResultByID retrieves transaction result by the transaction ID. + GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) + // GetBlocksByIDs gets blocks by provided ID or list of IDs. + GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) + // GetBlocksByHeight gets blocks by provided height. + GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) + // GetBlockPayloadByID gets block payload by ID + GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) + // GetExecutionResultByID gets execution result by the ID. + GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) + // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. + GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) + // GetCollectionByID retrieves a collection by ID and builds a response + GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) + // ExecuteScript handler sends the script from the request to be executed. + ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) + // GetAccount handler retrieves account by address and returns the response. + GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) + // GetEvents for the provided block range or list of block IDs filtered by type. + GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) + // GetNetworkParameters returns network-wide parameters of the blockchain + GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) + // GetNodeVersionInfo returns node version information + GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) +} + +type RequestHandler struct { + RestServerApi + log zerolog.Logger + backend access.API +} + +// NewRequestHandler returns new RequestHandler. +func NewRequestHandler(log zerolog.Logger, backend access.API) RestServerApi { + return &RequestHandler{ + log: log, + backend: backend, + } +} + +// GetTransactionByID gets a transaction by requested ID. +func (h *RequestHandler) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, _ flow.Chain) (models.Transaction, error) { + var response models.Transaction + + tx, err := h.backend.GetTransaction(context, r.ID) + if err != nil { + return response, err + } + + var txr *access.TransactionResult + // only lookup result if transaction result is to be expanded + if r.ExpandsResult { + txr, err = h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) + if err != nil { + return response, err + } + } + + response.Build(tx, txr, link) + return response, nil +} + +// CreateTransaction creates a new transaction from provided payload. +func (h *RequestHandler) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + var response models.Transaction + + err := h.backend.SendTransaction(context, &r.Transaction) + if err != nil { + return response, err + } + + response.Build(&r.Transaction, nil, link) + return response, nil +} + +// GetTransactionResultByID retrieves transaction result by the transaction ID. +func (h *RequestHandler) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + var response models.TransactionResult + + txr, err := h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) + if err != nil { + return response, err + } + + response.Build(txr, r.ID, link) + return response, nil +} + +// GetBlocksByIDs gets blocks by provided ID or list of IDs. +func (h *RequestHandler) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + blocks := make([]*models.Block, len(r.IDs)) + + for i, id := range r.IDs { + block, err := getBlock(forID(&id), context, expandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil +} + +// GetBlocksByHeight gets blocks by provided height. +func (h *RequestHandler) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + req, err := r.GetBlockRequest() + if err != nil { + return nil, NewBadRequestError(err) + } + + if req.FinalHeight || req.SealedHeight { + block, err := getBlock(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + + return []*models.Block{block}, nil + } + + // if the query is /blocks/height=1000,1008,1049... + if req.HasHeights() { + blocks := make([]*models.Block, len(req.Heights)) + for i, height := range req.Heights { + block, err := getBlock(forHeight(height), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil + } + + // support providing end height as "sealed" or "final" + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + latest, _, err := h.backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + req.EndHeight = latest.Header.Height // overwrite special value height with fetched + + if req.StartHeight > req.EndHeight { + return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) + } + } + + blocks := make([]*models.Block, 0) + // start and end height inclusive + for i := req.StartHeight; i <= req.EndHeight; i++ { + block, err := getBlock(forHeight(i), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +// GetBlockPayloadByID gets block payload by ID +func (h *RequestHandler) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { + var payload models.BlockPayload + + blkProvider := NewBlockRequestProvider(h.backend, forID(&r.ID)) + blk, _, statusErr := blkProvider.getBlock(context) + if statusErr != nil { + return payload, statusErr + } + + err := payload.Build(blk.Payload) + if err != nil { + return payload, err + } + + return payload, nil +} + +// GetExecutionResultByID gets execution result by the ID. +func (h *RequestHandler) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + var response models.ExecutionResult + + res, err := h.backend.GetExecutionResultByID(context, r.ID) + if err != nil { + return response, err + } + + if res == nil { + err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) + return response, NewNotFoundError(err.Error(), err) + } + + err = response.Build(res, link) + if err != nil { + return response, err + } + + return response, nil +} + +// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. +func (h *RequestHandler) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + // for each block ID we retrieve execution result + results := make([]models.ExecutionResult, len(r.BlockIDs)) + for i, id := range r.BlockIDs { + res, err := h.backend.GetExecutionResultForBlockID(context, id) + if err != nil { + return nil, err + } + + var response models.ExecutionResult + err = response.Build(res, link) + if err != nil { + return nil, err + } + results[i] = response + } + + return results, nil +} + +// GetCollectionByID retrieves a collection by ID and builds a response +func (h *RequestHandler) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, _ flow.Chain) (models.Collection, error) { + var response models.Collection + + collection, err := h.backend.GetCollectionByID(context, r.ID) + if err != nil { + return response, err + } + + // if we expand transactions in the query retrieve each transaction data + transactions := make([]*flow.TransactionBody, 0) + if r.ExpandsTransactions { + for _, tid := range collection.Transactions { + tx, err := h.backend.GetTransaction(context, tid) + if err != nil { + return response, err + } + + transactions = append(transactions, tx) + } + } + + err = response.Build(collection, transactions, link, expandFields) + if err != nil { + return response, err + } + + return response, nil +} + +// ExecuteScript handler sends the script from the request to be executed. +func (h *RequestHandler) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { + if r.BlockID != flow.ZeroID { + return h.backend.ExecuteScriptAtBlockID(context, r.BlockID, r.Script.Source, r.Script.Args) + } + + // default to sealed height + if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { + return h.backend.ExecuteScriptAtLatestBlock(context, r.Script.Source, r.Script.Args) + } + + if r.BlockHeight == request.FinalHeight { + finalBlock, _, err := h.backend.GetLatestBlockHeader(context, false) + if err != nil { + return nil, err + } + r.BlockHeight = finalBlock.Height + } + + return h.backend.ExecuteScriptAtBlockHeight(context, r.BlockHeight, r.Script.Source, r.Script.Args) +} + +// GetAccount handler retrieves account by address and returns the response. +func (h *RequestHandler) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + var response models.Account + + // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it + if r.Height == request.FinalHeight || r.Height == request.SealedHeight { + header, _, err := h.backend.GetLatestBlockHeader(context, r.Height == request.SealedHeight) + if err != nil { + return response, err + } + r.Height = header.Height + } + + account, err := h.backend.GetAccountAtBlockHeight(context, r.Address, r.Height) + if err != nil { + return response, err + } + + err = response.Build(account, link, expandFields) + return response, err +} + +// GetEvents for the provided block range or list of block IDs filtered by type. +func (h *RequestHandler) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { + // if the request has block IDs provided then return events for block IDs + var blocksEvents models.BlocksEvents + if len(r.BlockIDs) > 0 { + events, err := h.backend.GetEventsForBlockIDs(context, r.Type, r.BlockIDs) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil + } + + // if end height is provided with special values then load the height + if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { + latest, _, err := h.backend.GetLatestBlockHeader(context, r.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + r.EndHeight = latest.Height + // special check after we resolve special height value + if r.StartHeight > r.EndHeight { + return nil, NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) + } + } + + // if request provided block height range then return events for that range + events, err := h.backend.GetEventsForHeightRange(context, r.Type, r.StartHeight, r.EndHeight) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil +} + +// GetNetworkParameters returns network-wide parameters of the blockchain +func (h *RequestHandler) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { + params := h.backend.GetNetworkParameters(r.Context()) + + var response models.NetworkParameters + response.Build(¶ms) + return response, nil +} + +// GetNodeVersionInfo returns node version information +func (h *RequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { + var response models.NodeVersionInfo + + params, err := h.backend.GetNodeVersionInfo(r.Context()) + if err != nil { + return response, err + } + + response.Build(params) + return response, nil +} diff --git a/engine/access/rest/router.go b/engine/access/rest/router.go index da39912eff9..9bb04239b66 100644 --- a/engine/access/rest/router.go +++ b/engine/access/rest/router.go @@ -6,14 +6,13 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/middleware" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) -func newRouter(backend access.API, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { +func newRouter(serverAPI RestServerApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { router := mux.NewRouter().StrictSlash(true) v1SubRouter := router.PathPrefix("/v1").Subrouter() @@ -26,7 +25,7 @@ func newRouter(backend access.API, logger zerolog.Logger, chain flow.Chain, rest linkGenerator := models.NewLinkGeneratorImpl(v1SubRouter) for _, r := range Routes { - h := NewHandler(logger, backend, r.Handler, linkGenerator, chain) + h := NewHandler(logger, serverAPI, r.Handler, linkGenerator, chain) v1SubRouter. Methods(r.Method). Path(r.Pattern). diff --git a/engine/access/rest/scripts.go b/engine/access/rest/scripts.go index 8bd86bae54f..827bd25b13a 100644 --- a/engine/access/rest/scripts.go +++ b/engine/access/rest/scripts.go @@ -3,34 +3,14 @@ package rest import ( "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/model/flow" - - "github.com/onflow/flow-go/access" ) // ExecuteScript handler sends the script from the request to be executed. -func ExecuteScript(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { +func ExecuteScript(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetScriptRequest() if err != nil { return nil, NewBadRequestError(err) } - if req.BlockID != flow.ZeroID { - return backend.ExecuteScriptAtBlockID(r.Context(), req.BlockID, req.Script.Source, req.Script.Args) - } - - // default to sealed height - if req.BlockHeight == request.SealedHeight || req.BlockHeight == request.EmptyHeight { - return backend.ExecuteScriptAtLatestBlock(r.Context(), req.Script.Source, req.Script.Args) - } - - if req.BlockHeight == request.FinalHeight { - finalBlock, _, err := backend.GetLatestBlockHeader(r.Context(), false) - if err != nil { - return nil, err - } - req.BlockHeight = finalBlock.Height - } - - return backend.ExecuteScriptAtBlockHeight(r.Context(), req.BlockHeight, req.Script.Source, req.Script.Args) + return srv.ExecuteScript(req, r.Context(), link) } diff --git a/engine/access/rest/server.go b/engine/access/rest/server.go index a1aa83710d8..f196fb1a4a9 100644 --- a/engine/access/rest/server.go +++ b/engine/access/rest/server.go @@ -7,15 +7,13 @@ import ( "github.com/rs/cors" "github.com/rs/zerolog" - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) // NewServer returns an HTTP server initialized with the REST API handler -func NewServer(backend access.API, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { - - router, err := newRouter(backend, logger, chain, restCollector) +func NewServer(serverAPI RestServerApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { + router, err := newRouter(serverAPI, logger, chain, restCollector) if err != nil { return nil, err } diff --git a/engine/access/rest/transactions.go b/engine/access/rest/transactions.go index f8dfc83dedb..1acdccfa1e8 100644 --- a/engine/access/rest/transactions.go +++ b/engine/access/rest/transactions.go @@ -1,67 +1,36 @@ package rest import ( - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetTransactionByID gets a transaction by requested ID. -func GetTransactionByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetTransactionByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionRequest() if err != nil { return nil, NewBadRequestError(err) } - tx, err := backend.GetTransaction(r.Context(), req.ID) - if err != nil { - return nil, err - } - - var txr *access.TransactionResult - // only lookup result if transaction result is to be expanded - if req.ExpandsResult { - txr, err = backend.GetTransactionResult(r.Context(), req.ID, req.BlockID, req.CollectionID) - if err != nil { - return nil, err - } - } - - var response models.Transaction - response.Build(tx, txr, link) - return response, nil + return srv.GetTransactionByID(req, r.Context(), link, r.Chain) } // GetTransactionResultByID retrieves transaction result by the transaction ID. -func GetTransactionResultByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func GetTransactionResultByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionResultRequest() if err != nil { return nil, NewBadRequestError(err) } - txr, err := backend.GetTransactionResult(r.Context(), req.ID, req.BlockID, req.CollectionID) - if err != nil { - return nil, err - } - - var response models.TransactionResult - response.Build(txr, req.ID, link) - return response, nil + return srv.GetTransactionResultByID(req, r.Context(), link) } // CreateTransaction creates a new transaction from provided payload. -func CreateTransaction(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { +func CreateTransaction(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.CreateTransactionRequest() if err != nil { return nil, NewBadRequestError(err) } - err = backend.SendTransaction(r.Context(), &req.Transaction) - if err != nil { - return nil, err - } - - var response models.Transaction - response.Build(&req.Transaction, nil, link) - return response, nil + return srv.CreateTransaction(req, r.Context(), link) } diff --git a/engine/common/rpc/convert/convert.go b/engine/common/rpc/convert/convert.go index 150e760d8de..eb35098f54c 100644 --- a/engine/common/rpc/convert/convert.go +++ b/engine/common/rpc/convert/convert.go @@ -374,6 +374,19 @@ func MessageToBlock(m *entities.Block) (*flow.Block, error) { }, nil } +func MessagesToBlockStatus(s entities.BlockStatus) (flow.BlockStatus, error) { + switch s { + case entities.BlockStatus_BLOCK_UNKNOWN: + return flow.BlockStatusUnknown, nil + case entities.BlockStatus_BLOCK_FINALIZED: + return flow.BlockStatusFinalized, nil + case entities.BlockStatus_BLOCK_SEALED: + return flow.BlockStatusSealed, nil + } + + return flow.BlockStatusUnknown, fmt.Errorf("failed to convert block status") +} + func MessagesToExecutionResultMetaList(m []*entities.ExecutionReceiptMeta) flow.ExecutionReceiptMetaList { execMetaList := make([]*flow.ExecutionReceiptMeta, len(m)) for i, message := range m { diff --git a/integration/localnet/builder/bootstrap.go b/integration/localnet/builder/bootstrap.go index 201aaaade58..ed6cb479acf 100644 --- a/integration/localnet/builder/bootstrap.go +++ b/integration/localnet/builder/bootstrap.go @@ -454,12 +454,14 @@ func prepareObserverService(i int, observerName string, agPublicKey string) Serv fmt.Sprintf("--rpc-addr=%s:%s", observerName, testnet.GRPCPort), fmt.Sprintf("--secure-rpc-addr=%s:%s", observerName, testnet.GRPCSecurePort), fmt.Sprintf("--http-addr=%s:%s", observerName, testnet.GRPCWebPort), + fmt.Sprintf("--rest-addr=%s:%s", observerName, testnet.RESTPort), ) service.AddExposedPorts( testnet.GRPCPort, testnet.GRPCSecurePort, testnet.GRPCWebPort, + testnet.RESTPort, ) // observer services rely on the access gateway From fcbf3974124f2f83f3d01d11339314382c0ef638 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 8 Jun 2023 12:52:34 +0300 Subject: [PATCH 02/30] Separated common forwarder logic to forwarder package, created and implemented RestForwarder, refactored FlowAccessAPIForwarder --- engine/access/apiproxy/access_api_proxy.go | 185 ++------ engine/access/rest/rest_server_api.go | 497 +++++++++++++++++++++ module/forwarder/forwarder.go | 144 ++++++ 3 files changed, 673 insertions(+), 153 deletions(-) create mode 100644 module/forwarder/forwarder.go diff --git a/engine/access/apiproxy/access_api_proxy.go b/engine/access/apiproxy/access_api_proxy.go index d72ec5bb5e2..6f0925667ea 100644 --- a/engine/access/apiproxy/access_api_proxy.go +++ b/engine/access/apiproxy/access_api_proxy.go @@ -2,26 +2,17 @@ package apiproxy import ( "context" - "fmt" - "sync" "time" - "google.golang.org/grpc/connectivity" - "google.golang.org/grpc/credentials/insecure" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/status" "github.com/onflow/flow/protobuf/go/flow/access" "github.com/rs/zerolog" - "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/protocol" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/forwarder" "github.com/onflow/flow-go/module/metrics" - "github.com/onflow/flow-go/utils/grpcutils" ) // FlowAccessAPIRouter is a structure that represents the routing proxy algorithm. @@ -51,88 +42,6 @@ func (h *FlowAccessAPIRouter) log(handler, rpc string, err error) { logger.Info().Msg("request succeeded") } -// reconnectingClient returns an active client, or -// creates one, if the last one is not ready anymore. -func (h *FlowAccessAPIForwarder) reconnectingClient(i int) error { - timeout := h.timeout - - if h.connections[i] == nil || h.connections[i].GetState() != connectivity.Ready { - identity := h.ids[i] - var connection *grpc.ClientConn - var err error - if identity.NetworkPubKey == nil { - connection, err = grpc.Dial( - identity.Address, - grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(h.maxMsgSize))), - grpc.WithTransportCredentials(insecure.NewCredentials()), - backend.WithClientUnaryInterceptor(timeout)) - if err != nil { - return err - } - } else { - tlsConfig, err := grpcutils.DefaultClientTLSConfig(identity.NetworkPubKey) - if err != nil { - return fmt.Errorf("failed to get default TLS client config using public flow networking key %s %w", identity.NetworkPubKey.String(), err) - } - - connection, err = grpc.Dial( - identity.Address, - grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(h.maxMsgSize))), - grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), - backend.WithClientUnaryInterceptor(timeout)) - if err != nil { - return fmt.Errorf("cannot connect to %s %w", identity.Address, err) - } - } - connection.Connect() - time.Sleep(1 * time.Second) - state := connection.GetState() - if state != connectivity.Ready && state != connectivity.Connecting { - return fmt.Errorf("%v", state) - } - h.connections[i] = connection - h.upstream[i] = access.NewAccessAPIClient(connection) - } - - return nil -} - -// faultTolerantClient implements an upstream connection that reconnects on errors -// a reasonable amount of time. -func (h *FlowAccessAPIForwarder) faultTolerantClient() (access.AccessAPIClient, error) { - if h.upstream == nil || len(h.upstream) == 0 { - return nil, status.Errorf(codes.Unimplemented, "method not implemented") - } - - // Reasoning: A retry count of three gives an acceptable 5% failure ratio from a 37% failure ratio. - // A bigger number is problematic due to the DNS resolve and connection times, - // plus the need to log and debug each individual connection failure. - // - // This reasoning eliminates the need of making this parameter configurable. - // The logic works rolling over a single connection as well making clean code. - const retryMax = 3 - - h.lock.Lock() - defer h.lock.Unlock() - - var err error - for i := 0; i < retryMax; i++ { - h.roundRobin++ - h.roundRobin = h.roundRobin % len(h.upstream) - err = h.reconnectingClient(h.roundRobin) - if err != nil { - continue - } - state := h.connections[h.roundRobin].GetState() - if state != connectivity.Ready && state != connectivity.Connecting { - continue - } - return h.upstream[h.roundRobin], nil - } - - return nil, status.Errorf(codes.Unavailable, err.Error()) -} - // Ping pings the service. It is special in the sense that it responds successful, // only if all underlying services are ready. func (h *FlowAccessAPIRouter) Ping(context context.Context, req *access.PingRequest) (*access.PingResponse, error) { @@ -292,52 +201,22 @@ func (h *FlowAccessAPIRouter) GetExecutionResultForBlockID(context context.Conte // FlowAccessAPIForwarder forwards all requests to a set of upstream access nodes or observers type FlowAccessAPIForwarder struct { - lock sync.Mutex - roundRobin int - ids flow.IdentityList - upstream []access.AccessAPIClient - connections []*grpc.ClientConn - timeout time.Duration - maxMsgSize uint + forwarder.Forwarder } func NewFlowAccessAPIForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*FlowAccessAPIForwarder, error) { - forwarder := &FlowAccessAPIForwarder{maxMsgSize: maxMsgSize} - err := forwarder.setFlowAccessAPI(identities, timeout) - return forwarder, err -} - -// setFlowAccessAPI sets a backend access API that forwards some requests to an upstream node. -// It is used by Observer services, Blockchain Data Service, etc. -// Make sure that this is just for observation and not a staked participant in the flow network. -// This means that observers see a copy of the data but there is no interaction to ensure integrity from the root block. -func (ret *FlowAccessAPIForwarder) setFlowAccessAPI(accessNodeAddressAndPort flow.IdentityList, timeout time.Duration) error { - ret.timeout = timeout - ret.ids = accessNodeAddressAndPort - ret.upstream = make([]access.AccessAPIClient, accessNodeAddressAndPort.Count()) - ret.connections = make([]*grpc.ClientConn, accessNodeAddressAndPort.Count()) - for i, identity := range accessNodeAddressAndPort { - // Store the faultTolerantClient setup parameters such as address, public, key and timeout, so that - // we can refresh the API on connection loss - ret.ids[i] = identity - - // We fail on any single error on startup, so that - // we identify bootstrapping errors early - err := ret.reconnectingClient(i) - if err != nil { - return err - } - } + commonForwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) - ret.roundRobin = 0 - return nil + forwarder := &FlowAccessAPIForwarder{} + forwarder.Forwarder = commonForwarder + return forwarder, err } // Ping pings the service. It is special in the sense that it responds successful, // only if all underlying services are ready. func (h *FlowAccessAPIForwarder) Ping(context context.Context, req *access.PingRequest) (*access.PingResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -346,7 +225,7 @@ func (h *FlowAccessAPIForwarder) Ping(context context.Context, req *access.PingR func (h *FlowAccessAPIForwarder) GetNodeVersionInfo(context context.Context, req *access.GetNodeVersionInfoRequest) (*access.GetNodeVersionInfoResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -355,7 +234,7 @@ func (h *FlowAccessAPIForwarder) GetNodeVersionInfo(context context.Context, req func (h *FlowAccessAPIForwarder) GetLatestBlockHeader(context context.Context, req *access.GetLatestBlockHeaderRequest) (*access.BlockHeaderResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -364,7 +243,7 @@ func (h *FlowAccessAPIForwarder) GetLatestBlockHeader(context context.Context, r func (h *FlowAccessAPIForwarder) GetBlockHeaderByID(context context.Context, req *access.GetBlockHeaderByIDRequest) (*access.BlockHeaderResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -373,7 +252,7 @@ func (h *FlowAccessAPIForwarder) GetBlockHeaderByID(context context.Context, req func (h *FlowAccessAPIForwarder) GetBlockHeaderByHeight(context context.Context, req *access.GetBlockHeaderByHeightRequest) (*access.BlockHeaderResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -382,7 +261,7 @@ func (h *FlowAccessAPIForwarder) GetBlockHeaderByHeight(context context.Context, func (h *FlowAccessAPIForwarder) GetLatestBlock(context context.Context, req *access.GetLatestBlockRequest) (*access.BlockResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -391,7 +270,7 @@ func (h *FlowAccessAPIForwarder) GetLatestBlock(context context.Context, req *ac func (h *FlowAccessAPIForwarder) GetBlockByID(context context.Context, req *access.GetBlockByIDRequest) (*access.BlockResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -400,7 +279,7 @@ func (h *FlowAccessAPIForwarder) GetBlockByID(context context.Context, req *acce func (h *FlowAccessAPIForwarder) GetBlockByHeight(context context.Context, req *access.GetBlockByHeightRequest) (*access.BlockResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -409,7 +288,7 @@ func (h *FlowAccessAPIForwarder) GetBlockByHeight(context context.Context, req * func (h *FlowAccessAPIForwarder) GetCollectionByID(context context.Context, req *access.GetCollectionByIDRequest) (*access.CollectionResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -418,7 +297,7 @@ func (h *FlowAccessAPIForwarder) GetCollectionByID(context context.Context, req func (h *FlowAccessAPIForwarder) SendTransaction(context context.Context, req *access.SendTransactionRequest) (*access.SendTransactionResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -427,7 +306,7 @@ func (h *FlowAccessAPIForwarder) SendTransaction(context context.Context, req *a func (h *FlowAccessAPIForwarder) GetTransaction(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -436,7 +315,7 @@ func (h *FlowAccessAPIForwarder) GetTransaction(context context.Context, req *ac func (h *FlowAccessAPIForwarder) GetTransactionResult(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResultResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -445,7 +324,7 @@ func (h *FlowAccessAPIForwarder) GetTransactionResult(context context.Context, r func (h *FlowAccessAPIForwarder) GetTransactionResultByIndex(context context.Context, req *access.GetTransactionByIndexRequest) (*access.TransactionResultResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -454,7 +333,7 @@ func (h *FlowAccessAPIForwarder) GetTransactionResultByIndex(context context.Con func (h *FlowAccessAPIForwarder) GetTransactionResultsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionResultsResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -462,7 +341,7 @@ func (h *FlowAccessAPIForwarder) GetTransactionResultsByBlockID(context context. } func (h *FlowAccessAPIForwarder) GetTransactionsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionsResponse, error) { - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -471,7 +350,7 @@ func (h *FlowAccessAPIForwarder) GetTransactionsByBlockID(context context.Contex func (h *FlowAccessAPIForwarder) GetAccount(context context.Context, req *access.GetAccountRequest) (*access.GetAccountResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -480,7 +359,7 @@ func (h *FlowAccessAPIForwarder) GetAccount(context context.Context, req *access func (h *FlowAccessAPIForwarder) GetAccountAtLatestBlock(context context.Context, req *access.GetAccountAtLatestBlockRequest) (*access.AccountResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -489,7 +368,7 @@ func (h *FlowAccessAPIForwarder) GetAccountAtLatestBlock(context context.Context func (h *FlowAccessAPIForwarder) GetAccountAtBlockHeight(context context.Context, req *access.GetAccountAtBlockHeightRequest) (*access.AccountResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -498,7 +377,7 @@ func (h *FlowAccessAPIForwarder) GetAccountAtBlockHeight(context context.Context func (h *FlowAccessAPIForwarder) ExecuteScriptAtLatestBlock(context context.Context, req *access.ExecuteScriptAtLatestBlockRequest) (*access.ExecuteScriptResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -507,7 +386,7 @@ func (h *FlowAccessAPIForwarder) ExecuteScriptAtLatestBlock(context context.Cont func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockID(context context.Context, req *access.ExecuteScriptAtBlockIDRequest) (*access.ExecuteScriptResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -516,7 +395,7 @@ func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockID(context context.Context, func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockHeight(context context.Context, req *access.ExecuteScriptAtBlockHeightRequest) (*access.ExecuteScriptResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -525,7 +404,7 @@ func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockHeight(context context.Cont func (h *FlowAccessAPIForwarder) GetEventsForHeightRange(context context.Context, req *access.GetEventsForHeightRangeRequest) (*access.EventsResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -534,7 +413,7 @@ func (h *FlowAccessAPIForwarder) GetEventsForHeightRange(context context.Context func (h *FlowAccessAPIForwarder) GetEventsForBlockIDs(context context.Context, req *access.GetEventsForBlockIDsRequest) (*access.EventsResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -543,7 +422,7 @@ func (h *FlowAccessAPIForwarder) GetEventsForBlockIDs(context context.Context, r func (h *FlowAccessAPIForwarder) GetNetworkParameters(context context.Context, req *access.GetNetworkParametersRequest) (*access.GetNetworkParametersResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -552,7 +431,7 @@ func (h *FlowAccessAPIForwarder) GetNetworkParameters(context context.Context, r func (h *FlowAccessAPIForwarder) GetLatestProtocolStateSnapshot(context context.Context, req *access.GetLatestProtocolStateSnapshotRequest) (*access.ProtocolStateSnapshotResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } @@ -561,7 +440,7 @@ func (h *FlowAccessAPIForwarder) GetLatestProtocolStateSnapshot(context context. func (h *FlowAccessAPIForwarder) GetExecutionResultForBlockID(context context.Context, req *access.GetExecutionResultForBlockIDRequest) (*access.ExecutionResultForBlockIDResponse, error) { // This is a passthrough request - upstream, err := h.faultTolerantClient() + upstream, err := h.FaultTolerantClient() if err != nil { return nil, err } diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go index ebc2e54f831..9d1f3d1815f 100644 --- a/engine/access/rest/rest_server_api.go +++ b/engine/access/rest/rest_server_api.go @@ -3,13 +3,19 @@ package rest import ( "context" "fmt" + "time" "github.com/rs/zerolog" "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/forwarder" + "github.com/onflow/flow/protobuf/go/flow/entities" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) type RestServerApi interface { @@ -371,3 +377,494 @@ func (h *RequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVers response.Build(params) return response, nil } + +type RestForwarder struct { + log zerolog.Logger + forwarder.Forwarder +} + +// NewRestForwarder returns new RestForwarder. +func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (RestServerApi, error) { + commonForwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) + + forwarder := &RestForwarder{ + log: log, + } + forwarder.Forwarder = commonForwarder + return forwarder, err +} + +// GetTransactionByID gets a transaction by requested ID. +func (f *RestForwarder) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { + var response models.Transaction + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getTransactionRequest := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + } + transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) + if err != nil { + return response, err + } + + var transactionResultResponse *accessproto.TransactionResultResponse + // only lookup result if transaction result is to be expanded + if r.ExpandsResult { + getTransactionResultRequest := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + BlockId: r.BlockID[:], + CollectionId: r.CollectionID[:], + } + transactionResultResponse, err = upstream.GetTransactionResult(context, getTransactionResultRequest) + if err != nil { + return response, err + } + } + flowTransaction, err := convert.MessageToTransaction(transactionResponse.Transaction, chain) + if err != nil { + return response, err + } + + flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) + + response.Build(&flowTransaction, flowTransactionResult, link) + return response, nil +} + +// CreateTransaction creates a new transaction from provided payload. +func (f *RestForwarder) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + var response models.Transaction + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + entitiesTransaction := convert.TransactionToMessage(r.Transaction) + sendTransactionRequest := &accessproto.SendTransactionRequest{ + Transaction: entitiesTransaction, + } + + _, err = upstream.SendTransaction(context, sendTransactionRequest) + if err != nil { + return response, err + } + + response.Build(&r.Transaction, nil, link) + return response, nil +} + +// GetTransactionResultByID retrieves transaction result by the transaction ID. +func (f *RestForwarder) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + var response models.TransactionResult + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getTransactionResult := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + BlockId: r.BlockID[:], + CollectionId: r.CollectionID[:], + } + transactionResultResponse, err := upstream.GetTransactionResult(context, getTransactionResult) + if err != nil { + return response, err + } + + flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) + response.Build(flowTransactionResult, r.ID, link) + return response, nil +} + +// GetBlocksByIDs gets blocks by provided ID or list of IDs. +func (f *RestForwarder) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + blocks := make([]*models.Block, len(r.IDs)) + + upstream, err := f.FaultTolerantClient() + if err != nil { + return blocks, err + } + + for i, id := range r.IDs { + block, err := getForwarderBlock(forID(&id), context, expandFields, upstream, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil +} + +// GetBlocksByHeight gets blocks by provided height. +func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + req, err := r.GetBlockRequest() + if err != nil { + return nil, NewBadRequestError(err) + } + + upstream, err := f.FaultTolerantClient() + if err != nil { + return nil, err + } + + if req.FinalHeight || req.SealedHeight { + block, err := getForwarderBlock(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + + return []*models.Block{block}, nil + } + + // if the query is /blocks/height=1000,1008,1049... + if req.HasHeights() { + blocks := make([]*models.Block, len(req.Heights)) + for i, height := range req.Heights { + block, err := getForwarderBlock(forHeight(height), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil + } + + // support providing end height as "sealed" or "final" + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ + IsSealed: req.EndHeight == request.SealedHeight, + } + blockResponse, err := upstream.GetLatestBlock(r.Context(), getLatestBlockRequest) + if err != nil { + return nil, err + } + + req.EndHeight = blockResponse.Block.BlockHeader.Height // overwrite special value height with fetched + + if req.StartHeight > req.EndHeight { + return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) + } + } + + blocks := make([]*models.Block, 0) + // start and end height inclusive + for i := req.StartHeight; i <= req.EndHeight; i++ { + block, err := getForwarderBlock(forHeight(i), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +// GetBlockPayloadByID gets block payload by ID +func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { + var payload models.BlockPayload + + upstream, err := f.FaultTolerantClient() + if err != nil { + return payload, err + } + + blkProvider := NewBlockForwarderProvider(upstream, forID(&r.ID)) + block, _, statusErr := blkProvider.getBlock(context) + if statusErr != nil { + return payload, statusErr + } + + flowPayload, err := convert.PayloadFromMessage(block) + if err != nil { + return payload, err + } + + err = payload.Build(flowPayload) + if err != nil { + return payload, err + } + + return payload, nil +} + +// GetExecutionResultByID gets execution result by the ID. +func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { /**/ + panic("Need to be implemented after grpc call added") +} + +// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. +func (f *RestForwarder) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + // for each block ID we retrieve execution result + results := make([]models.ExecutionResult, len(r.BlockIDs)) + + upstream, err := f.FaultTolerantClient() + if err != nil { + return results, err + } + + for i, id := range r.BlockIDs { + getExecutionResultForBlockID := &accessproto.GetExecutionResultForBlockIDRequest{ + BlockId: id[:], + } + executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockID) + if err != nil { + return nil, err + } + + var response models.ExecutionResult + flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) + if err != nil { + return nil, err + } + err = response.Build(flowExecResult, link) + if err != nil { + return nil, err + } + results[i] = response + } + + return results, nil +} + +// GetCollectionByID retrieves a collection by ID and builds a response +func (f *RestForwarder) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { + var response models.Collection + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getCollectionByIDRequest := &accessproto.GetCollectionByIDRequest{ + Id: r.ID[:], + } + + collectionResponse, err := upstream.GetCollectionByID(context, getCollectionByIDRequest) + if err != nil { + return response, err + } + + // if we expand transactions in the query retrieve each transaction data + transactions := make([]*entities.Transaction, 0) + if r.ExpandsTransactions { + for _, tid := range collectionResponse.Collection.TransactionIds { + getTransactionRequest := &accessproto.GetTransactionRequest{ + Id: tid, + } + transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) + if err != nil { + return response, err + } + + transactions = append(transactions, transactionResponse.Transaction) + } + } + + err = response.BuildFromGrpc(collectionResponse.Collection, transactions, link, expandFields, chain) + if err != nil { + return response, err + } + + return response, nil +} + +// ExecuteScript handler sends the script from the request to be executed. +func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { + upstream, err := f.FaultTolerantClient() + if err != nil { + return nil, err + } + + if r.BlockID != flow.ZeroID { + executeScriptAtBlockIDRequest := &accessproto.ExecuteScriptAtBlockIDRequest{ + BlockId: r.BlockID[:], + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(context, executeScriptAtBlockIDRequest) + if err != nil { + return nil, err + } + return executeScriptAtBlockIDResponse.Value, nil + } + + // default to sealed height + if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { + executeScriptAtLatestBlockRequest := &accessproto.ExecuteScriptAtLatestBlockRequest{ + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(context, executeScriptAtLatestBlockRequest) + if err != nil { + return nil, err + } + return executeScriptAtLatestBlockResponse.Value, nil + } + + if r.BlockHeight == request.FinalHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: false, + } + getLatestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return nil, err + } + r.BlockHeight = getLatestBlockHeaderResponse.Block.Height + } + + executeScriptAtBlockHeightRequest := &accessproto.ExecuteScriptAtBlockHeightRequest{ + BlockHeight: r.BlockHeight, + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(context, executeScriptAtBlockHeightRequest) + if err != nil { + return nil, err + } + return executeScriptAtBlockHeightResponse.Value, nil +} + +// GetAccount handler retrieves account by address and returns the response. +func (f *RestForwarder) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + var response models.Account + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it + if r.Height == request.FinalHeight || r.Height == request.SealedHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: r.Height == request.SealedHeight, + } + blockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return response, err + } + r.Height = blockHeaderResponse.Block.Height + } + getAccountAtBlockHeightRequest := &accessproto.GetAccountAtBlockHeightRequest{ + Address: r.Address.Bytes(), + BlockHeight: r.Height, + } + + accountResponse, err := upstream.GetAccountAtBlockHeight(context, getAccountAtBlockHeightRequest) + if err != nil { + return response, err + } + + flowAccount, err := convert.MessageToAccount(accountResponse.Account) + if err != nil { + return response, err + } + + err = response.Build(flowAccount, link, expandFields) + return response, err +} + +// GetEvents for the provided block range or list of block IDs filtered by type. +func (f *RestForwarder) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { + // if the request has block IDs provided then return events for block IDs + var blocksEvents models.BlocksEvents + + upstream, err := f.FaultTolerantClient() + if err != nil { + return blocksEvents, err + } + + if len(r.BlockIDs) > 0 { + var blockIds [][]byte + for _, id := range r.BlockIDs { + blockIds = append(blockIds, id[:]) + } + getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ + Type: r.Type, + BlockIds: blockIds, + } + eventsResponse, err := upstream.GetEventsForBlockIDs(context, getEventsForBlockIDsRequest) + if err != nil { + return nil, err + } + + blocksEvents.BuildFromGrpc(eventsResponse.Results) + + return blocksEvents, nil + } + + // if end height is provided with special values then load the height + if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: r.EndHeight == request.SealedHeight, + } + latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return nil, err + } + + r.EndHeight = latestBlockHeaderResponse.Block.Height + // special check after we resolve special height value + if r.StartHeight > r.EndHeight { + return nil, NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) + } + } + + // if request provided block height range then return events for that range + getEventsForHeightRangeRequest := &accessproto.GetEventsForHeightRangeRequest{ + Type: r.Type, + StartHeight: r.StartHeight, + EndHeight: r.EndHeight, + } + eventsResponse, err := upstream.GetEventsForHeightRange(context, getEventsForHeightRangeRequest) + if err != nil { + return nil, err + } + + blocksEvents.BuildFromGrpc(eventsResponse.Results) + return blocksEvents, nil +} + +// GetNetworkParameters returns network-wide parameters of the blockchain +func (f *RestForwarder) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { + var response models.NetworkParameters + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getNetworkParametersRequest := &accessproto.GetNetworkParametersRequest{} + getNetworkParametersResponse, err := upstream.GetNetworkParameters(r.Context(), getNetworkParametersRequest) + if err != nil { + return response, err + } + response.BuildFromGrpc(getNetworkParametersResponse) + return response, nil +} + +// GetNodeVersionInfo returns node version information +func (f *RestForwarder) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { + var response models.NodeVersionInfo + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getNodeVersionInfoRequest := &accessproto.GetNodeVersionInfoRequest{} + getNodeVersionInfoResponse, err := upstream.GetNodeVersionInfo(r.Context(), getNodeVersionInfoRequest) + if err != nil { + return response, err + } + + response.BuildFromGrpc(getNodeVersionInfoResponse.Info) + return response, nil +} diff --git a/module/forwarder/forwarder.go b/module/forwarder/forwarder.go new file mode 100644 index 00000000000..0201c3908cf --- /dev/null +++ b/module/forwarder/forwarder.go @@ -0,0 +1,144 @@ +package forwarder + +import ( + "fmt" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + "github.com/onflow/flow-go/engine/access/rpc/backend" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/grpcutils" + "github.com/onflow/flow/protobuf/go/flow/access" +) + +// Forwarder forwards all requests to a set of upstream access nodes or observers +type Forwarder struct { + lock sync.Mutex + roundRobin int + ids flow.IdentityList + upstream []access.AccessAPIClient + connections []*grpc.ClientConn + timeout time.Duration + maxMsgSize uint +} + +func NewForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (Forwarder, error) { + forwarder := Forwarder{maxMsgSize: maxMsgSize} + err := forwarder.setFlowAccessAPI(identities, timeout) + return forwarder, err +} + +// setFlowAccessAPI sets a backend access API that forwards some requests to an upstream node. +// It is used by Observer services, Blockchain Data Service, etc. +// Make sure that this is just for observation and not a staked participant in the flow network. +// This means that observers see a copy of the data but there is no interaction to ensure integrity from the root block. +func (f *Forwarder) setFlowAccessAPI(accessNodeAddressAndPort flow.IdentityList, timeout time.Duration) error { + f.timeout = timeout + f.ids = accessNodeAddressAndPort + f.upstream = make([]access.AccessAPIClient, accessNodeAddressAndPort.Count()) + f.connections = make([]*grpc.ClientConn, accessNodeAddressAndPort.Count()) + for i, identity := range accessNodeAddressAndPort { + // Store the faultTolerantClient setup parameters such as address, public, key and timeout, so that + // we can refresh the API on connection loss + f.ids[i] = identity + + // We fail on any single error on startup, so that + // we identify bootstrapping errors early + err := f.reconnectingClient(i) + if err != nil { + return err + } + } + + f.roundRobin = 0 + return nil +} + +// reconnectingClient returns an active client, or +// creates one, if the last one is not ready anymore. +func (f *Forwarder) reconnectingClient(i int) error { + timeout := f.timeout + + if f.connections[i] == nil || f.connections[i].GetState() != connectivity.Ready { + identity := f.ids[i] + var connection *grpc.ClientConn + var err error + if identity.NetworkPubKey == nil { + connection, err = grpc.Dial( + identity.Address, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(f.maxMsgSize))), + grpc.WithTransportCredentials(insecure.NewCredentials()), + backend.WithClientUnaryInterceptor(timeout)) + if err != nil { + return err + } + } else { + tlsConfig, err := grpcutils.DefaultClientTLSConfig(identity.NetworkPubKey) + if err != nil { + return fmt.Errorf("failed to get default TLS client config using public flow networking key %s %w", identity.NetworkPubKey.String(), err) + } + + connection, err = grpc.Dial( + identity.Address, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(f.maxMsgSize))), + grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), + backend.WithClientUnaryInterceptor(timeout)) + if err != nil { + return fmt.Errorf("cannot connect to %s %w", identity.Address, err) + } + } + connection.Connect() + time.Sleep(1 * time.Second) + state := connection.GetState() + if state != connectivity.Ready && state != connectivity.Connecting { + return fmt.Errorf("%v", state) + } + f.connections[i] = connection + f.upstream[i] = access.NewAccessAPIClient(connection) + } + + return nil +} + +// FaultTolerantClient implements an upstream connection that reconnects on errors +// a reasonable amount of time. +func (f *Forwarder) FaultTolerantClient() (access.AccessAPIClient, error) { + if f.upstream == nil || len(f.upstream) == 0 { + return nil, status.Errorf(codes.Unimplemented, "method not implemented") + } + + // Reasoning: A retry count of three gives an acceptable 5% failure ratio from a 37% failure ratio. + // A bigger number is problematic due to the DNS resolve and connection times, + // plus the need to log and debug each individual connection failure. + // + // This reasoning eliminates the need of making this parameter configurable. + // The logic works rolling over a single connection as well making clean code. + const retryMax = 3 + + f.lock.Lock() + defer f.lock.Unlock() + + var err error + for i := 0; i < retryMax; i++ { + f.roundRobin++ + f.roundRobin = f.roundRobin % len(f.upstream) + err = f.reconnectingClient(f.roundRobin) + if err != nil { + continue + } + state := f.connections[f.roundRobin].GetState() + if state != connectivity.Ready && state != connectivity.Connecting { + continue + } + return f.upstream[f.roundRobin], nil + } + + return nil, status.Errorf(codes.Unavailable, err.Error()) +} From a685db85e1d924beba8cb1cab66f784ae141f829 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 8 Jun 2023 13:19:57 +0300 Subject: [PATCH 03/30] Updated access api proxy test --- engine/access/apiproxy/access_api_proxy_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/engine/access/apiproxy/access_api_proxy_test.go b/engine/access/apiproxy/access_api_proxy_test.go index 9f5a5aa74b8..f8f2dce72e4 100644 --- a/engine/access/apiproxy/access_api_proxy_test.go +++ b/engine/access/apiproxy/access_api_proxy_test.go @@ -12,6 +12,7 @@ import ( grpcinsecure "google.golang.org/grpc/credentials/insecure" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/forwarder" "github.com/onflow/flow-go/utils/grpcutils" "github.com/onflow/flow-go/utils/unittest" ) @@ -137,7 +138,8 @@ func TestNewFlowCachedAccessAPIProxy(t *testing.T) { // Prepare a proxy that fails due to the second connection being idle l := flow.IdentityList{{Address: unittest.IPPort("11634")}, {Address: unittest.IPPort("11635")}} c := FlowAccessAPIForwarder{} - err = c.setFlowAccessAPI(l, time.Second) + c.Forwarder, err = forwarder.NewForwarder(l, time.Second, grpcutils.DefaultMaxMsgSize) + if err == nil { t.Fatal(fmt.Errorf("should not start with one connection ready")) } @@ -153,7 +155,7 @@ func TestNewFlowCachedAccessAPIProxy(t *testing.T) { // Prepare a proxy l = flow.IdentityList{{Address: unittest.IPPort("11634")}, {Address: unittest.IPPort("11635")}} c = FlowAccessAPIForwarder{} - err = c.setFlowAccessAPI(l, time.Second) + c.Forwarder, err = forwarder.NewForwarder(l, time.Second, grpcutils.DefaultMaxMsgSize) if err != nil { t.Fatal(err) } From 557c6dd0426fb5634ceeb4cbaffcc47873d8dc56 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 16 Jun 2023 11:40:36 +0300 Subject: [PATCH 04/30] Added RestRouter, refactored engine and engine builder --- access/handler.go | 21 ++ apiproxy/access_api_proxy.go | 13 ++ .../node_builder/access_node_builder.go | 17 +- cmd/observer/node_builder/observer_builder.go | 46 ++++- engine/access/apiproxy/access_api_proxy.go | 27 ++- engine/access/backend.go | 93 +++++++++ engine/access/mock/access_api_client.go | 33 ++++ engine/access/mock/access_api_server.go | 26 +++ engine/access/rest/blocks.go | 22 +-- engine/access/rest/models/collection.go | 5 +- engine/access/rest/models/event.go | 1 + engine/access/rest/models/network.go | 1 + .../access/rest/models/node_version_info.go | 1 + engine/access/rest/rest_server_api.go | 182 ++++++++++++++++-- engine/access/rest_api_test.go | 13 +- engine/access/rpc/engine.go | 84 +------- engine/access/rpc/engine_builder.go | 52 +++-- go.mod | 3 + go.sum | 4 +- insecure/go.sum | 2 + integration/go.mod | 2 + integration/go.sum | 4 +- module/forwarder/forwarder.go | 1 + module/mock/grpc_connection_pool_metrics.go | 60 ++++++ 24 files changed, 571 insertions(+), 142 deletions(-) create mode 100644 engine/access/backend.go create mode 100644 module/mock/grpc_connection_pool_metrics.go diff --git a/access/handler.go b/access/handler.go index 404bfa81318..2417d4037ac 100644 --- a/access/handler.go +++ b/access/handler.go @@ -590,6 +590,27 @@ func (h *Handler) GetExecutionResultForBlockID(ctx context.Context, req *access. return executionResultToMessages(result, metadata) } +// GetExecutionResultByID returns the execution result for the given ID. +func (h *Handler) GetExecutionResultByID(ctx context.Context, req *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + metadata := h.buildMetadataResponse() + + blockID := convert.MessageToIdentifier(req.GetId()) + + result, err := h.api.GetExecutionResultByID(ctx, blockID) + if err != nil { + return nil, err + } + + execResult, err := convert.ExecutionResultToMessage(result) + if err != nil { + return nil, err + } + return &access.ExecutionResultByIDResponse{ + ExecutionResult: execResult, + Metadata: metadata, + }, nil +} + func (h *Handler) blockResponse(block *flow.Block, fullResponse bool, status flow.BlockStatus) (*access.BlockResponse, error) { metadata := h.buildMetadataResponse() diff --git a/apiproxy/access_api_proxy.go b/apiproxy/access_api_proxy.go index 8e0b781af5e..c61e8e52d69 100644 --- a/apiproxy/access_api_proxy.go +++ b/apiproxy/access_api_proxy.go @@ -248,6 +248,10 @@ func (h *FlowAccessAPIRouter) GetExecutionResultForBlockID(context context.Conte return h.upstream.GetExecutionResultForBlockID(context, req) } +func (h *FlowAccessAPIRouter) GetExecutionResultByID(context context.Context, req *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + return h.upstream.GetExecutionResultByID(context, req) +} + // FlowAccessAPIForwarder forwards all requests to a set of upstream access nodes or observers type FlowAccessAPIForwarder struct { lock sync.Mutex @@ -466,3 +470,12 @@ func (h *FlowAccessAPIForwarder) GetExecutionResultForBlockID(context context.Co } return upstream.GetExecutionResultForBlockID(context, req) } + +func (h *FlowAccessAPIForwarder) GetExecutionResultByID(context context.Context, req *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + // This is a passthrough request + upstream, err := h.faultTolerantClient() + if err != nil { + return nil, err + } + return upstream.GetExecutionResultByID(context, req) +} diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 03625fe8f50..0482b600e66 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -35,6 +35,7 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" "github.com/onflow/flow-go/crypto" + accessengine "github.com/onflow/flow-go/engine/access" "github.com/onflow/flow-go/engine/access/ingestion" pingeng "github.com/onflow/flow-go/engine/access/ping" "github.com/onflow/flow-go/engine/access/rpc" @@ -991,8 +992,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { return nil }). Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { - engineBuilder, err := rpc.NewBuilder( - node.Logger, + backend, err := accessengine.NewBackend(node.Logger, node.State, builder.rpcConf, builder.CollectionRPC, @@ -1007,11 +1007,22 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.AccessMetrics, builder.collectionGRPCPort, builder.executionGRPCPort, - builder.retryEnabled, + builder.retryEnabled) + if err != nil { + return nil, fmt.Errorf("could not initialize backend: %w", err) + } + + engineBuilder, err := rpc.NewBuilder( + node.Logger, + node.State, + builder.rpcConf, + node.RootChainID, + builder.AccessMetrics, builder.rpcMetricsEnabled, builder.apiRatelimits, builder.apiBurstlimits, builder.Me, + backend, ) if err != nil { return nil, err diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 8b06825b52e..2b82705168d 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "time" @@ -27,7 +28,9 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" "github.com/onflow/flow-go/crypto" + accessengine "github.com/onflow/flow-go/engine/access" "github.com/onflow/flow-go/engine/access/apiproxy" + "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/follower" @@ -847,8 +850,8 @@ func (builder *ObserverServiceBuilder) enqueueConnectWithStakedAN() { func (builder *ObserverServiceBuilder) enqueueRPCServer() { builder.Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { - engineBuilder, err := rpc.NewBuilder( - node.Logger, + accessMetrics := metrics.NewNoopCollector() + accessBackend, err := accessengine.NewBackend(node.Logger, node.State, builder.rpcConf, nil, @@ -860,14 +863,25 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { node.Storage.Receipts, node.Storage.Results, node.RootChainID, - metrics.NewNoopCollector(), + accessMetrics, 0, 0, - false, + false) + if err != nil { + return nil, fmt.Errorf("could not initialize backend: %w", err) + } + + engineBuilder, err := rpc.NewBuilder( + node.Logger, + node.State, + builder.rpcConf, + node.RootChainID, + accessMetrics, builder.rpcMetricsEnabled, builder.apiRatelimits, builder.apiBurstlimits, builder.Me, + accessBackend, ) if err != nil { return nil, err @@ -879,9 +893,11 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { return nil, err } - proxy := &apiproxy.FlowAccessAPIRouter{ + metrics := metrics.NewObserverCollector() + + rpcHandler := &apiproxy.FlowAccessAPIRouter{ Logger: builder.Logger, - Metrics: metrics.NewObserverCollector(), + Metrics: metrics, Upstream: forwarder, Observer: protocol.NewHandler(protocol.New( node.State, @@ -891,9 +907,25 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { )), } + restForwarder, err := rest.NewRestForwarder(builder.Logger, + builder.upstreamIdentities, + builder.apiTimeout, + builder.rpcConf.MaxMsgSize) + if err != nil { + return nil, err + } + + restHandler := &rest.RestRouter{ + Logger: builder.Logger, + Metrics: metrics, + Upstream: restForwarder, + Observer: rest.NewRequestHandler(builder.Logger, accessBackend), + } + // build the rpc engine builder.RpcEng, err = engineBuilder. - WithNewHandler(proxy). + WithRpcHandler(rpcHandler). + WithRestHandler(restHandler). WithLegacy(). Build() if err != nil { diff --git a/engine/access/apiproxy/access_api_proxy.go b/engine/access/apiproxy/access_api_proxy.go index 6f0925667ea..08136531b09 100644 --- a/engine/access/apiproxy/access_api_proxy.go +++ b/engine/access/apiproxy/access_api_proxy.go @@ -199,17 +199,27 @@ func (h *FlowAccessAPIRouter) GetExecutionResultForBlockID(context context.Conte return res, err } +func (h *FlowAccessAPIRouter) GetExecutionResultByID(context context.Context, req *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + res, err := h.Upstream.GetExecutionResultByID(context, req) + h.log("upstream", "GetExecutionResultByID", err) + return res, err +} + // FlowAccessAPIForwarder forwards all requests to a set of upstream access nodes or observers type FlowAccessAPIForwarder struct { forwarder.Forwarder } func NewFlowAccessAPIForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*FlowAccessAPIForwarder, error) { - commonForwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) + forwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) + if err != nil { + return nil, err + } - forwarder := &FlowAccessAPIForwarder{} - forwarder.Forwarder = commonForwarder - return forwarder, err + accessApiForwarder := &FlowAccessAPIForwarder{ + Forwarder: forwarder, + } + return accessApiForwarder, nil } // Ping pings the service. It is special in the sense that it responds successful, @@ -446,3 +456,12 @@ func (h *FlowAccessAPIForwarder) GetExecutionResultForBlockID(context context.Co } return upstream.GetExecutionResultForBlockID(context, req) } + +func (h *FlowAccessAPIForwarder) GetExecutionResultByID(context context.Context, req *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + // This is a passthrough request + upstream, err := h.FaultTolerantClient() + if err != nil { + return nil, err + } + return upstream.GetExecutionResultByID(context, req) +} diff --git a/engine/access/backend.go b/engine/access/backend.go new file mode 100644 index 00000000000..92c8f932b2d --- /dev/null +++ b/engine/access/backend.go @@ -0,0 +1,93 @@ +package access + +import ( + "fmt" + + lru "github.com/hashicorp/golang-lru" + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/engine/access/rpc" + "github.com/onflow/flow-go/engine/access/rpc/backend" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/state/protocol" + "github.com/onflow/flow-go/storage" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" +) + +func NewBackend( + log zerolog.Logger, + state protocol.State, + config rpc.Config, + collectionRPC accessproto.AccessAPIClient, + historicalAccessNodes []accessproto.AccessAPIClient, + blocks storage.Blocks, + headers storage.Headers, + collections storage.Collections, + transactions storage.Transactions, + executionReceipts storage.ExecutionReceipts, + executionResults storage.ExecutionResults, + chainID flow.ChainID, + accessMetrics module.AccessMetrics, + collectionGRPCPort uint, + executionGRPCPort uint, + retryEnabled bool) (*backend.Backend, error) { + + var cache *lru.Cache + cacheSize := config.ConnectionPoolSize + if cacheSize > 0 { + // TODO: remove this fallback after fixing issues with evictions + // It was observed that evictions cause connection errors for in flight requests. This works around + // the issue by forcing hte pool size to be greater than the number of ENs + LNs + if cacheSize < backend.DefaultConnectionPoolSize { + log.Warn().Msg("connection pool size below threshold, setting pool size to default value ") + cacheSize = backend.DefaultConnectionPoolSize + } + var err error + cache, err = lru.NewWithEvict(int(cacheSize), func(_, evictedValue interface{}) { + store := evictedValue.(*backend.CachedClient) + store.Close() + log.Debug().Str("grpc_conn_evicted", store.Address).Msg("closing grpc connection evicted from pool") + if accessMetrics != nil { + accessMetrics.ConnectionFromPoolEvicted() + } + }) + if err != nil { + return nil, fmt.Errorf("could not initialize connection pool cache: %w", err) + } + } + + connectionFactory := &backend.ConnectionFactoryImpl{ + CollectionGRPCPort: collectionGRPCPort, + ExecutionGRPCPort: executionGRPCPort, + CollectionNodeGRPCTimeout: config.CollectionClientTimeout, + ExecutionNodeGRPCTimeout: config.ExecutionClientTimeout, + ConnectionsCache: cache, + CacheSize: cacheSize, + MaxMsgSize: config.MaxMsgSize, + AccessMetrics: accessMetrics, + Log: log, + } + + return backend.New(state, + collectionRPC, + historicalAccessNodes, + blocks, + headers, + collections, + transactions, + executionReceipts, + executionResults, + chainID, + accessMetrics, + connectionFactory, + retryEnabled, + config.MaxHeightRange, + config.PreferredExecutionNodeIDs, + config.FixedExecutionNodeIDs, + log, + backend.DefaultSnapshotHistoryLimit, + config.ArchiveAddressList, + ), nil +} diff --git a/engine/access/mock/access_api_client.go b/engine/access/mock/access_api_client.go index 234e4ffcdee..4e2b1d065c7 100644 --- a/engine/access/mock/access_api_client.go +++ b/engine/access/mock/access_api_client.go @@ -446,6 +446,39 @@ func (_m *AccessAPIClient) GetEventsForHeightRange(ctx context.Context, in *acce return r0, r1 } +// GetExecutionResultByID provides a mock function with given fields: ctx, in, opts +func (_m *AccessAPIClient) GetExecutionResultByID(ctx context.Context, in *access.GetExecutionResultByIDRequest, opts ...grpc.CallOption) (*access.ExecutionResultByIDResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *access.ExecutionResultByIDResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *access.GetExecutionResultByIDRequest, ...grpc.CallOption) (*access.ExecutionResultByIDResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *access.GetExecutionResultByIDRequest, ...grpc.CallOption) *access.ExecutionResultByIDResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*access.ExecutionResultByIDResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *access.GetExecutionResultByIDRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetExecutionResultForBlockID provides a mock function with given fields: ctx, in, opts func (_m *AccessAPIClient) GetExecutionResultForBlockID(ctx context.Context, in *access.GetExecutionResultForBlockIDRequest, opts ...grpc.CallOption) (*access.ExecutionResultForBlockIDResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/engine/access/mock/access_api_server.go b/engine/access/mock/access_api_server.go index 5515698eacd..1a2c3772e44 100644 --- a/engine/access/mock/access_api_server.go +++ b/engine/access/mock/access_api_server.go @@ -353,6 +353,32 @@ func (_m *AccessAPIServer) GetEventsForHeightRange(_a0 context.Context, _a1 *acc return r0, r1 } +// GetExecutionResultByID provides a mock function with given fields: _a0, _a1 +func (_m *AccessAPIServer) GetExecutionResultByID(_a0 context.Context, _a1 *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *access.ExecutionResultByIDResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *access.GetExecutionResultByIDRequest) (*access.ExecutionResultByIDResponse, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *access.GetExecutionResultByIDRequest) *access.ExecutionResultByIDResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*access.ExecutionResultByIDResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *access.GetExecutionResultByIDRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetExecutionResultForBlockID provides a mock function with given fields: _a0, _a1 func (_m *AccessAPIServer) GetExecutionResultForBlockID(_a0 context.Context, _a1 *access.GetExecutionResultForBlockIDRequest) (*access.ExecutionResultForBlockIDResponse, error) { ret := _m.Called(_a0, _a1) diff --git a/engine/access/rest/blocks.go b/engine/access/rest/blocks.go index 1f24f44d717..9adcf0fec14 100644 --- a/engine/access/rest/blocks.go +++ b/engine/access/rest/blocks.go @@ -3,7 +3,6 @@ package rest import ( "context" "fmt" - "net/http" "google.golang.org/grpc/codes" @@ -14,6 +13,7 @@ import ( "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" ) @@ -76,9 +76,9 @@ func getBlock(option blockRequestOption, context context.Context, expandFields m return &block, nil } -func getForwarderBlock(option blockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { +func getBlockFromGrpc(option blockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { // lookup block - blkProvider := NewBlockForwarderProvider(upstream, option) + blkProvider := NewBlockFromGrpcProvider(upstream, option) blk, blockStatus, err := blkProvider.getBlock(context) if err != nil { return nil, err @@ -164,7 +164,7 @@ func forFinalized(queryParam uint64) blockRequestOption { } } -// blockProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to +// blockRequestProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to // look up a block or a block header either by ID or by height type blockRequestProvider struct { blockRequest @@ -211,25 +211,25 @@ func (blkProvider *blockRequestProvider) getBlock(ctx context.Context) (*flow.Bl return blk, status, nil } -// blockProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to +// blockFromGrpcProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to // look up a block or a block header either by ID or by height -type blockForwarderProvider struct { +type blockFromGrpcProvider struct { blockRequest upstream accessproto.AccessAPIClient } -func NewBlockForwarderProvider(upstream accessproto.AccessAPIClient, options ...blockRequestOption) *blockForwarderProvider { - blockForwarderProvider := &blockForwarderProvider{ +func NewBlockFromGrpcProvider(upstream accessproto.AccessAPIClient, options ...blockRequestOption) *blockFromGrpcProvider { + blockFromGrpcProvider := &blockFromGrpcProvider{ upstream: upstream, } for _, o := range options { - o(&blockForwarderProvider.blockRequest) + o(&blockFromGrpcProvider.blockRequest) } - return blockForwarderProvider + return blockFromGrpcProvider } -func (blkProvider *blockForwarderProvider) getBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { +func (blkProvider *blockFromGrpcProvider) getBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { if blkProvider.id != nil { getBlockByIdRequest := &accessproto.GetBlockByIDRequest{ Id: []byte(blkProvider.id.String()), diff --git a/engine/access/rest/models/collection.go b/engine/access/rest/models/collection.go index b42982a4061..d4979146c7c 100644 --- a/engine/access/rest/models/collection.go +++ b/engine/access/rest/models/collection.go @@ -7,6 +7,7 @@ import ( "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow/protobuf/go/flow/entities" ) @@ -69,10 +70,6 @@ func (c *Collection) BuildFromGrpc( var expandable CollectionExpandable var transactions Transactions if expand[ExpandsTransactions] { - var txIds []flow.Identifier - for _, id := range collection.TransactionIds { - txIds = append(txIds, convert.MessageToIdentifier(id)) - } transactions.Build(transactionsBody, link) } else { expandable.Transactions = make([]string, len(collection.TransactionIds)) diff --git a/engine/access/rest/models/event.go b/engine/access/rest/models/event.go index 8cfb3457dc0..d829ec862dc 100644 --- a/engine/access/rest/models/event.go +++ b/engine/access/rest/models/event.go @@ -6,6 +6,7 @@ import ( "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) diff --git a/engine/access/rest/models/network.go b/engine/access/rest/models/network.go index 584e2a73e89..1a6dd9a9816 100644 --- a/engine/access/rest/models/network.go +++ b/engine/access/rest/models/network.go @@ -2,6 +2,7 @@ package models import ( "github.com/onflow/flow-go/access" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) diff --git a/engine/access/rest/models/node_version_info.go b/engine/access/rest/models/node_version_info.go index f51878f158d..782493c0ec9 100644 --- a/engine/access/rest/models/node_version_info.go +++ b/engine/access/rest/models/node_version_info.go @@ -5,6 +5,7 @@ import ( "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/util" + "github.com/onflow/flow/protobuf/go/flow/entities" ) diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go index 9d1f3d1815f..19d6c00440e 100644 --- a/engine/access/rest/rest_server_api.go +++ b/engine/access/rest/rest_server_api.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "google.golang.org/grpc/status" + "github.com/rs/zerolog" "github.com/onflow/flow-go/access" @@ -13,11 +15,123 @@ import ( "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/forwarder" - "github.com/onflow/flow/protobuf/go/flow/entities" + "github.com/onflow/flow-go/module/metrics" accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" ) +// RestRouter is a structure that represents the routing proxy algorithm. +// It splits requests between a local and a remote rest service. +type RestRouter struct { + Logger zerolog.Logger + Metrics *metrics.ObserverCollector + Upstream *RestForwarder + Observer *RequestHandler +} + +func (r *RestRouter) log(handler, rpc string, err error) { + code := status.Code(err) + r.Metrics.RecordRPC(handler, rpc, code) + + logger := r.Logger.With(). + Str("handler", handler). + Str("rest_method", rpc). + Str("rest_code", code.String()). + Logger() + + if err != nil { + logger.Error().Err(err).Msg("request failed") + return + } + + logger.Info().Msg("request succeeded") +} + +func (r *RestRouter) GetTransactionByID(req request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { + res, err := r.Upstream.GetTransactionByID(req, context, link, chain) + r.log("upstream", "GetNodeVersionInfo", err) + return res, err +} + +func (r *RestRouter) CreateTransaction(req request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + res, err := r.Upstream.CreateTransaction(req, context, link) + r.log("upstream", "CreateTransaction", err) + return res, err +} + +func (r *RestRouter) GetTransactionResultByID(req request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + res, err := r.Upstream.GetTransactionResultByID(req, context, link) + r.log("upstream", "GetTransactionResultByID", err) + return res, err +} + +func (r *RestRouter) GetBlocksByIDs(req request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + res, err := r.Observer.GetBlocksByIDs(req, context, expandFields, link) + r.log("observer", "GetBlocksByIDs", err) + return res, err +} + +func (r *RestRouter) GetBlocksByHeight(req *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + res, err := r.Observer.GetBlocksByHeight(req, link) + r.log("observer", "GetBlocksByHeight", err) + return res, err +} + +func (r *RestRouter) GetBlockPayloadByID(req request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) { + res, err := r.Observer.GetBlockPayloadByID(req, context, link) + r.log("observer", "GetBlockPayloadByID", err) + return res, err +} + +func (r *RestRouter) GetExecutionResultByID(req request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + res, err := r.Upstream.GetExecutionResultByID(req, context, link) + r.log("upstream", "GetExecutionResultByID", err) + return res, err +} + +func (r *RestRouter) GetExecutionResultsByBlockIDs(req request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + res, err := r.Upstream.GetExecutionResultsByBlockIDs(req, context, link) + r.log("upstream", "GetExecutionResultsByBlockIDs", err) + return res, err +} + +func (r *RestRouter) GetCollectionByID(req request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { + res, err := r.Upstream.GetCollectionByID(req, context, expandFields, link, chain) + r.log("upstream", "GetCollectionByID", err) + return res, err +} + +func (r *RestRouter) ExecuteScript(req request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { + res, err := r.Upstream.ExecuteScript(req, context, link) + r.log("upstream", "ExecuteScript", err) + return res, err +} + +func (r *RestRouter) GetAccount(req request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + res, err := r.Upstream.GetAccount(req, context, expandFields, link) + r.log("upstream", "GetAccount", err) + return res, err +} + +func (r *RestRouter) GetEvents(req request.GetEvents, context context.Context) (models.BlocksEvents, error) { + res, err := r.Upstream.GetEvents(req, context) + r.log("upstream", "GetEvents", err) + return res, err +} + +func (r *RestRouter) GetNetworkParameters(req *request.Request) (models.NetworkParameters, error) { + res, err := r.Observer.GetNetworkParameters(req) + r.log("observer", "GetNetworkParameters", err) + return res, err +} + +func (r *RestRouter) GetNodeVersionInfo(req *request.Request) (models.NodeVersionInfo, error) { + res, err := r.Observer.GetNodeVersionInfo(req) + r.log("observer", "GetNodeVersionInfo", err) + return res, err +} + type RestServerApi interface { // GetTransactionByID gets a transaction by requested ID. GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) @@ -55,8 +169,16 @@ type RequestHandler struct { backend access.API } +//// NewRequestHandler returns new RequestHandler. +//func NewRequestHandler(log zerolog.Logger, backend access.API) RestServerApi { +// return &RequestHandler{ +// log: log, +// backend: backend, +// } +//} + // NewRequestHandler returns new RequestHandler. -func NewRequestHandler(log zerolog.Logger, backend access.API) RestServerApi { +func NewRequestHandler(log zerolog.Logger, backend access.API) *RequestHandler { return &RequestHandler{ log: log, backend: backend, @@ -384,14 +506,14 @@ type RestForwarder struct { } // NewRestForwarder returns new RestForwarder. -func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (RestServerApi, error) { - commonForwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) +func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*RestForwarder, error) { + forwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) - forwarder := &RestForwarder{ + restForwarder := &RestForwarder{ log: log, } - forwarder.Forwarder = commonForwarder - return forwarder, err + restForwarder.Forwarder = forwarder + return restForwarder, err } // GetTransactionByID gets a transaction by requested ID. @@ -492,7 +614,7 @@ func (f *RestForwarder) GetBlocksByIDs(r request.GetBlockByIDs, context context. } for i, id := range r.IDs { - block, err := getForwarderBlock(forID(&id), context, expandFields, upstream, link) + block, err := getBlockFromGrpc(forID(&id), context, expandFields, upstream, link) if err != nil { return nil, err } @@ -515,7 +637,7 @@ func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGe } if req.FinalHeight || req.SealedHeight { - block, err := getForwarderBlock(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) + block, err := getBlockFromGrpc(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) if err != nil { return nil, err } @@ -527,7 +649,7 @@ func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGe if req.HasHeights() { blocks := make([]*models.Block, len(req.Heights)) for i, height := range req.Heights { - block, err := getForwarderBlock(forHeight(height), r.Context(), r.ExpandFields, upstream, link) + block, err := getBlockFromGrpc(forHeight(height), r.Context(), r.ExpandFields, upstream, link) if err != nil { return nil, err } @@ -557,7 +679,7 @@ func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGe blocks := make([]*models.Block, 0) // start and end height inclusive for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := getForwarderBlock(forHeight(i), r.Context(), r.ExpandFields, upstream, link) + block, err := getBlockFromGrpc(forHeight(i), r.Context(), r.ExpandFields, upstream, link) if err != nil { return nil, err } @@ -576,7 +698,7 @@ func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context c return payload, err } - blkProvider := NewBlockForwarderProvider(upstream, forID(&r.ID)) + blkProvider := NewBlockFromGrpcProvider(upstream, forID(&r.ID)) block, _, statusErr := blkProvider.getBlock(context) if statusErr != nil { return payload, statusErr @@ -596,8 +718,38 @@ func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context c } // GetExecutionResultByID gets execution result by the ID. -func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { /**/ - panic("Need to be implemented after grpc call added") +func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + var response models.ExecutionResult + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + executionResultByIDRequest := &accessproto.GetExecutionResultByIDRequest{ + Id: r.ID[:], + } + + executionResultByIDResponse, err := upstream.GetExecutionResultByID(context, executionResultByIDRequest) + if err != nil { + return response, err + } + + if executionResultByIDResponse == nil { + err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) + return response, NewNotFoundError(err.Error(), err) + } + + flowExecResult, err := convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) + if err != nil { + return response, err + } + err = response.Build(flowExecResult, link) + if err != nil { + return response, err + } + + return response, nil } // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. @@ -677,7 +829,7 @@ func (f *RestForwarder) GetCollectionByID(r request.GetCollection, context conte } // ExecuteScript handler sends the script from the request to be executed. -func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { +func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { upstream, err := f.FaultTolerantClient() if err != nil { return nil, err diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index 5ee8f6d9730..4cb5c5d7c94 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -118,7 +118,7 @@ func (suite *RestAPITestSuite) SetupTest() { RESTListenAddr: unittest.DefaultAddress, } - rpcEngBuilder, err := rpc.NewBuilder( + backend, err := NewBackend( suite.log, suite.state, config, @@ -134,11 +134,20 @@ func (suite *RestAPITestSuite) SetupTest() { suite.metrics, 0, 0, - false, + false) + require.NoError(suite.T(), err) + + rpcEngBuilder, err := rpc.NewBuilder( + suite.log, + suite.state, + config, + suite.chainID, + suite.metrics, false, nil, nil, suite.me, + backend, ) assert.NoError(suite.T(), err) suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index d4c812df997..4fde8fe1acf 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -9,13 +9,11 @@ import ( "sync" "time" - grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" - lru "github.com/hashicorp/golang-lru" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/rs/zerolog" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "github.com/rs/zerolog" + "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rpc/backend" @@ -26,7 +24,8 @@ import ( "github.com/onflow/flow-go/module/events" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/state/protocol" - "github.com/onflow/flow-go/storage" + + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" ) // Config defines the configurable options for the access node server @@ -74,31 +73,23 @@ type Engine struct { unsecureGrpcAddress net.Addr secureGrpcAddress net.Addr restAPIAddress net.Addr + + restHandler rest.RestServerApi } +type Option func(*RPCEngineBuilder) // NewBuilder returns a new RPC engine builder. func NewBuilder(log zerolog.Logger, state protocol.State, config Config, - collectionRPC accessproto.AccessAPIClient, - historicalAccessNodes []accessproto.AccessAPIClient, - blocks storage.Blocks, - headers storage.Headers, - collections storage.Collections, - transactions storage.Transactions, - executionReceipts storage.ExecutionReceipts, - executionResults storage.ExecutionResults, chainID flow.ChainID, accessMetrics module.AccessMetrics, - collectionGRPCPort uint, - executionGRPCPort uint, - retryEnabled bool, rpcMetricsEnabled bool, apiRatelimits map[string]int, // the api rate limit (max calls per second) for each of the Access API e.g. Ping->100, GetTransaction->300 apiBurstLimits map[string]int, // the api burst limit (max calls at the same time) for each of the Access API e.g. Ping->50, GetTransaction->10 me module.Local, + backend *backend.Backend, ) (*RPCEngineBuilder, error) { - log = log.With().Str("engine", "rpc").Logger() // create a GRPC server to serve GRPC clients @@ -137,63 +128,6 @@ func NewBuilder(log zerolog.Logger, // wrap the unsecured server with an HTTP proxy server to serve HTTP clients httpServer := newHTTPProxyServer(unsecureGrpcServer) - var cache *lru.Cache - cacheSize := config.ConnectionPoolSize - if cacheSize > 0 { - // TODO: remove this fallback after fixing issues with evictions - // It was observed that evictions cause connection errors for in flight requests. This works around - // the issue by forcing hte pool size to be greater than the number of ENs + LNs - if cacheSize < backend.DefaultConnectionPoolSize { - log.Warn().Msg("connection pool size below threshold, setting pool size to default value ") - cacheSize = backend.DefaultConnectionPoolSize - } - var err error - cache, err = lru.NewWithEvict(int(cacheSize), func(_, evictedValue interface{}) { - store := evictedValue.(*backend.CachedClient) - store.Close() - log.Debug().Str("grpc_conn_evicted", store.Address).Msg("closing grpc connection evicted from pool") - if accessMetrics != nil { - accessMetrics.ConnectionFromPoolEvicted() - } - }) - if err != nil { - return nil, fmt.Errorf("could not initialize connection pool cache: %w", err) - } - } - - connectionFactory := &backend.ConnectionFactoryImpl{ - CollectionGRPCPort: collectionGRPCPort, - ExecutionGRPCPort: executionGRPCPort, - CollectionNodeGRPCTimeout: config.CollectionClientTimeout, - ExecutionNodeGRPCTimeout: config.ExecutionClientTimeout, - ConnectionsCache: cache, - CacheSize: cacheSize, - MaxMsgSize: config.MaxMsgSize, - AccessMetrics: accessMetrics, - Log: log, - } - - backend := backend.New(state, - collectionRPC, - historicalAccessNodes, - blocks, - headers, - collections, - transactions, - executionReceipts, - executionResults, - chainID, - accessMetrics, - connectionFactory, - retryEnabled, - config.MaxHeightRange, - config.PreferredExecutionNodeIDs, - config.FixedExecutionNodeIDs, - log, - backend.DefaultSnapshotHistoryLimit, - config.ArchiveAddressList, - ) - finalizedCache, finalizedCacheWorker, err := events.NewFinalizedHeaderCache(state) if err != nil { return nil, fmt.Errorf("could not create header cache: %w", err) @@ -384,7 +318,7 @@ func (e *Engine) serveREST(ctx irrecoverable.SignalerContext, ready component.Re e.log.Info().Str("rest_api_address", e.config.RESTListenAddr).Msg("starting REST server on address") - r, err := rest.NewServer(e.backend, e.config.RESTListenAddr, e.log, e.chain, e.restCollector) + r, err := rest.NewServer(e.restHandler, e.config.RESTListenAddr, e.log, e.chain, e.restCollector) if err != nil { e.log.Err(err).Msg("failed to initialize the REST server") ctx.Throw(err) diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index a4694547b03..cdae487a525 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -5,13 +5,14 @@ import ( grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - legacyaccessproto "github.com/onflow/flow/protobuf/go/flow/legacy/access" - "github.com/onflow/flow-go/access" legacyaccess "github.com/onflow/flow-go/access/legacy" "github.com/onflow/flow-go/consensus/hotstuff" + "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/module" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" + legacyaccessproto "github.com/onflow/flow/protobuf/go/flow/legacy/access" ) type RPCEngineBuilder struct { @@ -21,7 +22,7 @@ type RPCEngineBuilder struct { // optional parameters, only one can be set during build phase signerIndicesDecoder hotstuff.BlockSignerDecoder - handler accessproto.AccessAPIServer // Use the parent interface instead of implementation, so that we can assign it to proxy. + rpcHandler accessproto.AccessAPIServer // Use the parent interface instead of implementation, so that we can assign it to proxy. } // NewRPCEngineBuilder helps to build a new RPC engine. @@ -34,8 +35,12 @@ func NewRPCEngineBuilder(engine *Engine, me module.Local, finalizedHeaderCache m } } -func (builder *RPCEngineBuilder) Handler() accessproto.AccessAPIServer { - return builder.handler +func (builder *RPCEngineBuilder) RpcHandler() accessproto.AccessAPIServer { + return builder.rpcHandler +} + +func (builder *RPCEngineBuilder) RestHandler() rest.RestServerApi { + return builder.restHandler } // WithBlockSignerDecoder specifies that signer indices in block headers should be translated @@ -51,15 +56,21 @@ func (builder *RPCEngineBuilder) WithBlockSignerDecoder(signerIndicesDecoder hot return builder } -// WithNewHandler specifies that the given `AccessAPIServer` should be used for serving API queries. +// WithRpcHandler specifies that the given `AccessAPIServer` should be used for serving API queries. // Caution: // you can inject either a `BlockSignerDecoder` (via method `WithBlockSignerDecoder`) -// or an `AccessAPIServer` (via method `WithNewHandler`); but not both. If both are +// or an `AccessAPIServer` (via method `WithRpcHandler`); but not both. If both are // specified, the builder will error during the build step. // // Returns self-reference for chaining. -func (builder *RPCEngineBuilder) WithNewHandler(handler accessproto.AccessAPIServer) *RPCEngineBuilder { - builder.handler = handler +func (builder *RPCEngineBuilder) WithRpcHandler(handler accessproto.AccessAPIServer) *RPCEngineBuilder { + builder.rpcHandler = handler + return builder +} + +// WithRestHandler specifies that the given `RestServerApi` should be used for serving REST queries. +func (builder *RPCEngineBuilder) WithRestHandler(handler rest.RestServerApi) *RPCEngineBuilder { + builder.restHandler = handler return builder } @@ -89,18 +100,25 @@ func (builder *RPCEngineBuilder) WithMetrics() *RPCEngineBuilder { } func (builder *RPCEngineBuilder) Build() (*Engine, error) { - if builder.signerIndicesDecoder != nil && builder.handler != nil { + if builder.signerIndicesDecoder != nil && builder.rpcHandler != nil { return nil, fmt.Errorf("only BlockSignerDecoder (via method `WithBlockSignerDecoder`) or AccessAPIServer (via method `WithNewHandler`) can be specified but not both") } - handler := builder.handler - if handler == nil { + rpcHandler := builder.rpcHandler + if rpcHandler == nil { if builder.signerIndicesDecoder == nil { - handler = access.NewHandler(builder.Engine.backend, builder.Engine.chain, builder.finalizedHeaderCache, builder.me) + rpcHandler = access.NewHandler(builder.Engine.backend, builder.Engine.chain, builder.finalizedHeaderCache, builder.me) } else { - handler = access.NewHandler(builder.Engine.backend, builder.Engine.chain, builder.finalizedHeaderCache, builder.me, access.WithBlockSignerDecoder(builder.signerIndicesDecoder)) + rpcHandler = access.NewHandler(builder.Engine.backend, builder.Engine.chain, builder.finalizedHeaderCache, builder.me, access.WithBlockSignerDecoder(builder.signerIndicesDecoder)) } } - accessproto.RegisterAccessAPIServer(builder.unsecureGrpcServer, handler) - accessproto.RegisterAccessAPIServer(builder.secureGrpcServer, handler) + accessproto.RegisterAccessAPIServer(builder.unsecureGrpcServer, rpcHandler) + accessproto.RegisterAccessAPIServer(builder.secureGrpcServer, rpcHandler) + + restHandler := builder.Engine.restHandler + if restHandler == nil { + restHandler = rest.NewRequestHandler(builder.log, builder.backend) + } + builder.Engine.restHandler = restHandler + return builder.Engine, nil } diff --git a/go.mod b/go.mod index 602fb4c15fd..8a79bff8fd8 100644 --- a/go.mod +++ b/go.mod @@ -278,3 +278,6 @@ require ( lukechampine.com/blake3 v1.1.7 // indirect nhooyr.io/websocket v1.8.6 // indirect ) + +//TODO: Remove when onflow/flow branch will be merged +replace github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e => github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 diff --git a/go.sum b/go.sum index ed305eed14f..284f4b5d690 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,8 @@ github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdII github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 h1:8l0uZ9ut9TowB1qNKbPFt/ar/5mqxhqcp0r+HWv1zps= +github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -1238,8 +1240,6 @@ github.com/onflow/flow-go-sdk v0.40.0 h1:s8uwoyTquN8tjdXpqGmNkXTjf79yUII8JExc5QE github.com/onflow/flow-go-sdk v0.40.0/go.mod h1:34dxXk9Hp/bQw6Zy6+H44Xo0kQU+aJyQoqdDxq00rJM= github.com/onflow/flow-go/crypto v0.24.7 h1:RCLuB83At4z5wkAyUCF7MYEnPoIIOHghJaODuJyEoW0= github.com/onflow/flow-go/crypto v0.24.7/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e h1:QYEd3KWTt309YGBch4IGK6vJ6b7cOGx2NStEnd5NeHM= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 h1:XcSR/n2aSVO7lOEsKScYALcpHlfowLwicZ9yVbL6bnA= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8/go.mod h1:73C8FlT4L/Qe4Cf5iXUNL8b2pvu4zs5dJMMJ5V2TjUI= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/insecure/go.sum b/insecure/go.sum index 129d83cb596..a2e659e29d8 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -92,6 +92,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -1176,6 +1177,7 @@ github.com/onflow/atree v0.5.0 h1:y3lh8hY2fUo8KVE2ALVcz0EiNTq0tXJ6YTXKYVDA+3E= github.com/onflow/atree v0.5.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.38.1 h1:8YpnE1ixAGB8hF3t+slkHGhjfIBJ95dqUS+sEHrM2kY= github.com/onflow/cadence v0.38.1/go.mod h1:SpfjNhPsJxGIHbOthE9JD/e8JFaFY73joYLPsov+PY4= +github.com/onflow/flow v0.3.4 h1:FXUWVdYB90f/rjNcY0Owo30gL790tiYff9Pb/sycXYE= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.3 h1:wV+gcgOY0oJK4HLZQYQoK+mm09rW1XSxf83yqJwj0n4= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.3/go.mod h1:Osvy81E/+tscQM+d3kRFjktcIcZj2bmQ9ESqRQWDEx8= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 h1:X25A1dNajNUtE+KoV76wQ6BR6qI7G65vuuRXxDDqX7E= diff --git a/integration/go.mod b/integration/go.mod index 478283c6530..e207633fdc7 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -325,3 +325,5 @@ require ( replace github.com/onflow/flow-go => ../ replace github.com/onflow/flow-go/insecure => ../insecure + +replace github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e => github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 diff --git a/integration/go.sum b/integration/go.sum index 5aa4af7288b..d81044c4bbf 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -101,6 +101,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBY github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 h1:8l0uZ9ut9TowB1qNKbPFt/ar/5mqxhqcp0r+HWv1zps= +github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= @@ -1318,8 +1320,6 @@ github.com/onflow/flow-go-sdk v0.40.0 h1:s8uwoyTquN8tjdXpqGmNkXTjf79yUII8JExc5QE github.com/onflow/flow-go-sdk v0.40.0/go.mod h1:34dxXk9Hp/bQw6Zy6+H44Xo0kQU+aJyQoqdDxq00rJM= github.com/onflow/flow-go/crypto v0.24.7 h1:RCLuB83At4z5wkAyUCF7MYEnPoIIOHghJaODuJyEoW0= github.com/onflow/flow-go/crypto v0.24.7/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e h1:QYEd3KWTt309YGBch4IGK6vJ6b7cOGx2NStEnd5NeHM= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230428213521-89bcc9e8517e/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 h1:XcSR/n2aSVO7lOEsKScYALcpHlfowLwicZ9yVbL6bnA= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8/go.mod h1:73C8FlT4L/Qe4Cf5iXUNL8b2pvu4zs5dJMMJ5V2TjUI= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/module/forwarder/forwarder.go b/module/forwarder/forwarder.go index 0201c3908cf..6ee4ae4ffd0 100644 --- a/module/forwarder/forwarder.go +++ b/module/forwarder/forwarder.go @@ -15,6 +15,7 @@ import ( "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/grpcutils" + "github.com/onflow/flow/protobuf/go/flow/access" ) diff --git a/module/mock/grpc_connection_pool_metrics.go b/module/mock/grpc_connection_pool_metrics.go new file mode 100644 index 00000000000..2eddb3cf002 --- /dev/null +++ b/module/mock/grpc_connection_pool_metrics.go @@ -0,0 +1,60 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// GRPCConnectionPoolMetrics is an autogenerated mock type for the GRPCConnectionPoolMetrics type +type GRPCConnectionPoolMetrics struct { + mock.Mock +} + +// ConnectionAddedToPool provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) ConnectionAddedToPool() { + _m.Called() +} + +// ConnectionFromPoolEvicted provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) ConnectionFromPoolEvicted() { + _m.Called() +} + +// ConnectionFromPoolInvalidated provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) ConnectionFromPoolInvalidated() { + _m.Called() +} + +// ConnectionFromPoolReused provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) ConnectionFromPoolReused() { + _m.Called() +} + +// ConnectionFromPoolUpdated provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) ConnectionFromPoolUpdated() { + _m.Called() +} + +// NewConnectionEstablished provides a mock function with given fields: +func (_m *GRPCConnectionPoolMetrics) NewConnectionEstablished() { + _m.Called() +} + +// TotalConnectionsInPool provides a mock function with given fields: connectionCount, connectionPoolSize +func (_m *GRPCConnectionPoolMetrics) TotalConnectionsInPool(connectionCount uint, connectionPoolSize uint) { + _m.Called(connectionCount, connectionPoolSize) +} + +type mockConstructorTestingTNewGRPCConnectionPoolMetrics interface { + mock.TestingT + Cleanup(func()) +} + +// NewGRPCConnectionPoolMetrics creates a new instance of GRPCConnectionPoolMetrics. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewGRPCConnectionPoolMetrics(t mockConstructorTestingTNewGRPCConnectionPoolMetrics) *GRPCConnectionPoolMetrics { + mock := &GRPCConnectionPoolMetrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} From 0a8aa0d90c1f829a349d1e9c5da935fd15d2d615 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 16 Jun 2023 12:46:53 +0300 Subject: [PATCH 05/30] Updated tests --- engine/access/rest/accounts_test.go | 211 ++++++++- engine/access/rest/blocks_test.go | 65 ++- engine/access/rest/collections_test.go | 7 +- engine/access/rest/events_test.go | 3 +- engine/access/rest/execution_result_test.go | 18 +- engine/access/rest/mock/rest_server_api.go | 380 +++++++++++++++ engine/access/rest/network_test.go | 3 +- engine/access/rest/node_version_info_test.go | 3 +- engine/access/rest/rest_server_api.go | 2 +- engine/access/rest/scripts_test.go | 17 +- engine/access/rest/test_helpers.go | 80 +++- engine/access/rest/transactions_test.go | 38 +- engine/access/rpc/rate_limit_test.go | 463 ++++++++++--------- engine/access/secure_grpcr_test.go | 14 +- 14 files changed, 1021 insertions(+), 283 deletions(-) create mode 100644 engine/access/rest/mock/rest_server_api.go diff --git a/engine/access/rest/accounts_test.go b/engine/access/rest/accounts_test.go index 61982ff5f9c..0446fc4958d 100644 --- a/engine/access/rest/accounts_test.go +++ b/engine/access/rest/accounts_test.go @@ -35,6 +35,7 @@ func accountURL(t *testing.T, address string, height string) string { func TestGetAccount(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get by address at latest sealed block", func(t *testing.T) { account := accountFixture(t) @@ -53,7 +54,7 @@ func TestGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -73,7 +74,7 @@ func TestGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -88,7 +89,7 @@ func TestGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -103,7 +104,7 @@ func TestGetAccount(t *testing.T) { expected := expectedCondensedResponse(account) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -118,7 +119,7 @@ func TestGetAccount(t *testing.T) { for i, test := range tests { req, _ := http.NewRequest("GET", test.url, nil) - rr, err := executeRequest(req, backend) + rr, err := executeRequest(req, restHandler) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, rr.Code) @@ -127,6 +128,206 @@ func TestGetAccount(t *testing.T) { }) } +//func TestObserverGetAccount(t *testing.T) { +// backend := &mock.API{} +// address := unittest.IPPort("11632") +// +// t.Run("get by address at latest sealed block", func(t *testing.T) { +// account := accountFixture(t) +// entityAccount, err := convert.AccountToMessage(account) +// assert.NoError(t, err) +// +// var height uint64 = 100 +// blockHeader, err := convert.BlockHeaderToMessage(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)), unittest.IdentifierListFixture(4)) //? +// assert.NoError(t, err) +// +// req := getAccountRequest(t, account, sealedHeightQueryParam, expandableFieldKeys, expandableFieldContracts) +// +// done := make(chan int) +// // Bring up 1st upstream server +// mockServer := new(engineaccessmock.AccessAPIServer) +// mockServer.Mock. +// On("GetLatestBlockHeader", mocktestify.Anything, +// &access.GetLatestBlockHeaderRequest{ +// IsSealed: true, +// }). +// Return(&access.BlockHeaderResponse{ +// Block: blockHeader, +// BlockStatus: entities.BlockStatus_BLOCK_SEALED, +// Metadata: nil, +// }, nil) +// +// mockServer.Mock. +// On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ +// Address: account.Address.Bytes(), +// BlockHeight: height, +// }). +// Return(&access.AccountResponse{ +// Account: entityAccount, +// Metadata: nil, +// }, nil) +// expected := expectedExpandedResponse(account) +// +// server, _, err := newGrpcServer(mockServer, "tcp", address, done) +// assert.NoError(t, err) +// +// restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) +// assert.NoError(t, err) +// +// assertOKResponse(t, req, expected, restHandler) +// mocktestify.AssertExpectationsForObjects(t, backend) +// +// server.Stop() +// <-done +// }) +// +// //t.Run("get by address at latest finalized block", func(t *testing.T) { +// // var height uint64 = 100 +// // blockHeader, err := convert.BlockHeaderToMessage(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)), unittest.IdentifierListFixture(4)) +// // assert.NoError(t, err) +// // account := accountFixture(t) +// // entityAccount, err := convert.AccountToMessage(account) +// // assert.NoError(t, err) +// // +// // req := getAccountRequest(t, account, finalHeightQueryParam, expandableFieldKeys, expandableFieldContracts) +// // +// // done := make(chan int) +// // // Bring up 1st upstream server +// // mockServer := new(engineaccessmock.AccessAPIServer) +// // mockServer.Mock. +// // On("GetLatestBlockHeader", mocktestify.Anything, +// // &access.GetLatestBlockHeaderRequest{ +// // IsSealed: false, +// // }). +// // Return(&access.BlockHeaderResponse{ +// // Block: blockHeader, +// // BlockStatus: entities.BlockStatus_BLOCK_FINALIZED, +// // Metadata: nil, +// // }, nil) +// // mockServer.Mock. +// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ +// // Address: account.Address.Bytes(), +// // BlockHeight: height, +// // }). +// // Return(&access.AccountResponse{ +// // Account: entityAccount, +// // Metadata: nil, +// // }, nil) +// // expected := expectedExpandedResponse(account) +// // +// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) +// // assert.NoError(t, err) +// // +// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) +// // assert.NoError(t, err) +// // +// // assertOKResponse(t, req, expected, restHandler) +// // mocktestify.AssertExpectationsForObjects(t, backend) +// // +// // server.Stop() +// // <-done +// //}) +// // +// //t.Run("get by address at height", func(t *testing.T) { +// // var height uint64 = 1337 +// // account := accountFixture(t) +// // entityAccount, err := convert.AccountToMessage(account) +// // assert.NoError(t, err) +// // +// // req := getAccountRequest(t, account, fmt.Sprintf("%d", height), expandableFieldKeys, expandableFieldContracts) +// // +// // done := make(chan int) +// // // Bring up 1st upstream server +// // mockServer := new(engineaccessmock.AccessAPIServer) +// // mockServer.Mock. +// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ +// // Address: account.Address.Bytes(), +// // BlockHeight: height, +// // }). +// // Return(&access.AccountResponse{ +// // Account: entityAccount, +// // Metadata: nil, +// // }, nil) +// // expected := expectedExpandedResponse(account) +// // +// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) +// // assert.NoError(t, err) +// // +// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) +// // assert.NoError(t, err) +// // +// // assertOKResponse(t, req, expected, restHandler) +// // mocktestify.AssertExpectationsForObjects(t, backend) +// // +// // server.Stop() +// // <-done +// //}) +// // +// //t.Run("get by address at height condensed", func(t *testing.T) { +// // var height uint64 = 1337 +// // account := accountFixture(t) +// // entityAccount, err := convert.AccountToMessage(account) +// // assert.NoError(t, err) +// // +// // req := getAccountRequest(t, account, fmt.Sprintf("%d", height)) +// // +// // done := make(chan int) +// // // Bring up 1st upstream server +// // mockServer := new(engineaccessmock.AccessAPIServer) +// // mockServer.Mock. +// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ +// // Address: account.Address.Bytes(), +// // BlockHeight: height, +// // }). +// // Return(&access.AccountResponse{ +// // Account: entityAccount, +// // Metadata: nil, +// // }, nil) +// // expected := expectedCondensedResponse(account) +// // +// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) +// // assert.NoError(t, err) +// // +// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) +// // assert.NoError(t, err) +// // +// // assertOKResponse(t, req, expected, restHandler) +// // mocktestify.AssertExpectationsForObjects(t, backend) +// // +// // server.Stop() +// // <-done +// //}) +// // +// //t.Run("get invalid", func(t *testing.T) { +// // tests := []struct { +// // url string +// // out string +// // }{ +// // {accountURL(t, "123", ""), `{"code":400, "message":"invalid address"}`}, +// // {accountURL(t, unittest.AddressFixture().String(), "foo"), `{"code":400, "message":"invalid height format"}`}, +// // } +// // +// // done := make(chan int) +// // // Bring up 1st upstream server +// // server, _, err := newGrpcServer(new(engineaccessmock.AccessAPIServer), "tcp", address, done) +// // assert.NoError(t, err) +// // +// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) +// // assert.NoError(t, err) +// // for i, test := range tests { +// // req, _ := http.NewRequest("GET", test.url, nil) +// // rr, err := executeRequest(req, restHandler) +// // assert.NoError(t, err) +// // +// // assert.Equal(t, http.StatusBadRequest, rr.Code) +// // assert.JSONEq(t, test.out, rr.Body.String(), fmt.Sprintf("test #%d failed: %v", i, test)) +// // } +// // +// // server.Stop() +// // <-done +// //}) +//} + func expectedExpandedResponse(account *flow.Account) string { return fmt.Sprintf(`{ "address":"%s", diff --git a/engine/access/rest/blocks_test.go b/engine/access/rest/blocks_test.go index 7f977b06d69..536b2b50296 100644 --- a/engine/access/rest/blocks_test.go +++ b/engine/access/rest/blocks_test.go @@ -31,12 +31,13 @@ type testVector struct { expectedResponse string } -// TestGetBlocks tests the get blocks by ID and get blocks by heights API -func TestGetBlocks(t *testing.T) { - backend := &mock.API{} - - blkCnt := 10 - blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) +// ? +func prepareTestVectors(t *testing.T, + blockIDs []string, + heights []string, + blocks []*flow.Block, + executionResults []*flow.ExecutionResult, + blkCnt int) []testVector { singleBlockExpandedResponse := expectedBlockResponsesExpanded(blocks[:1], executionResults[:1], true, flow.BlockStatusUnknown) singleSealedBlockExpandedResponse := expectedBlockResponsesExpanded(blocks[:1], executionResults[:1], true, flow.BlockStatusSealed) @@ -137,9 +138,20 @@ func TestGetBlocks(t *testing.T) { expectedResponse: fmt.Sprintf(`{"code":400, "message": "at most %d IDs can be requested at a time"}`, request.MaxBlockRequestHeightRange), }, } + return testVectors +} + +// TestGetBlocks tests the get blocks by ID and get blocks by heights API +func TestGetBlocks(t *testing.T) { + backend := &mock.API{} + + blkCnt := 10 + blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) + testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) + restHandler := newAccessRestHandler(backend) for _, tv := range testVectors { - responseRec, err := executeRequest(tv.request, backend) + responseRec, err := executeRequest(tv.request, restHandler) assert.NoError(t, err) require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) actualResp := responseRec.Body.String() @@ -147,6 +159,45 @@ func TestGetBlocks(t *testing.T) { } } +// ? +//func TestGetBlockFromObserver(t *testing.T) { +// backend := &mock.API{} +// +// // Bring up upstream server +// blkCnt := 10 +// blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) +// address := unittest.IPPort("11633") +// +// done := make(chan int) +// server, _, err := newGrpcServer(new(engineaccessmock.AccessAPIServer), "tcp", address, done) +// assert.NoError(t, err) +// +// testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) +// +// var b bytes.Buffer +// logger := zerolog.New(&b) +// +// restForwarder, err := NewRestForwarder(logger, +// flow.IdentityList{{Address: address}}, +// time.Second, +// grpcutils.DefaultMaxMsgSize) +// assert.NoError(t, err) +// +// restHandler, err := newObserverRestHandler_v2(backend, ) +// assert.NoError(t, err) +// +// for _, tv := range testVectors { +// responseRec, err := executeRequest(tv.request, restHandler) +// assert.NoError(t, err) +// require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) +// actualResp := responseRec.Body.String() +// require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description) +// +// } +// server.Stop() +// <-done +//} + func requestURL(t *testing.T, ids []string, start string, end string, expandResponse bool, heights ...string) *http.Request { u, _ := url.Parse("/v1/blocks") q := u.Query() diff --git a/engine/access/rest/collections_test.go b/engine/access/rest/collections_test.go index 3981541f3a7..a033fb8a453 100644 --- a/engine/access/rest/collections_test.go +++ b/engine/access/rest/collections_test.go @@ -31,6 +31,7 @@ func getCollectionReq(id string, expandTransactions bool) *http.Request { func TestGetCollections(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get by ID", func(t *testing.T) { inputs := []flow.LightCollection{ @@ -62,7 +63,7 @@ func TestGetCollections(t *testing.T) { }`, col.ID(), col.ID(), transactionsStr) req := getCollectionReq(col.ID().String(), false) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocks.AssertExpectationsForObjects(t, backend) } }) @@ -87,7 +88,7 @@ func TestGetCollections(t *testing.T) { Once() req := getCollectionReq(col.ID().String(), true) - rr, err := executeRequest(req, backend) + rr, err := executeRequest(req, restHandler) assert.NoError(t, err) assert.Equal(t, http.StatusOK, rr.Code) @@ -146,7 +147,7 @@ func TestGetCollections(t *testing.T) { Return(test.mockValue, test.mockErr) } req := getCollectionReq(test.id, false) - assertResponse(t, req, test.status, test.response, backend) + assertResponse(t, req, test.status, test.response, restHandler) } }) } diff --git a/engine/access/rest/events_test.go b/engine/access/rest/events_test.go index 9f0fede2c6c..c5b9be0c00b 100644 --- a/engine/access/rest/events_test.go +++ b/engine/access/rest/events_test.go @@ -23,6 +23,7 @@ import ( func TestGetEvents(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) events := generateEventsMocks(backend, 5) allBlockIDs := make([]string, len(events)) @@ -125,7 +126,7 @@ func TestGetEvents(t *testing.T) { for _, test := range testVectors { t.Run(test.description, func(t *testing.T) { - assertResponse(t, test.request, test.expectedStatus, test.expectedResponse, backend) + assertResponse(t, test.request, test.expectedStatus, test.expectedResponse, restHandler) }) } diff --git a/engine/access/rest/execution_result_test.go b/engine/access/rest/execution_result_test.go index adb3852c668..241f14f6b59 100644 --- a/engine/access/rest/execution_result_test.go +++ b/engine/access/rest/execution_result_test.go @@ -37,9 +37,10 @@ func getResultByIDReq(id string, blockIDs []string) *http.Request { } func TestGetResultByID(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get by ID", func(t *testing.T) { - backend := &mock.API{} result := unittest.ExecutionResultFixture() id := unittest.IdentifierFixture() backend.Mock. @@ -49,12 +50,11 @@ func TestGetResultByID(t *testing.T) { req := getResultByIDReq(id.String(), nil) expected := executionResultExpectedStr(result) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocks.AssertExpectationsForObjects(t, backend) }) t.Run("get by ID not found", func(t *testing.T) { - backend := &mock.API{} id := unittest.IdentifierFixture() backend.Mock. On("GetExecutionResultByID", mocks.Anything, id). @@ -62,14 +62,17 @@ func TestGetResultByID(t *testing.T) { Once() req := getResultByIDReq(id.String(), nil) - assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, backend) + assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, restHandler) mocks.AssertExpectationsForObjects(t, backend) }) } func TestGetResultBlockID(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + t.Run("get by block ID", func(t *testing.T) { - backend := &mock.API{} + blockID := unittest.IdentifierFixture() result := unittest.ExecutionResultFixture(unittest.WithExecutionResultBlockID(blockID)) @@ -81,12 +84,11 @@ func TestGetResultBlockID(t *testing.T) { req := getResultByIDReq("", []string{blockID.String()}) expected := fmt.Sprintf(`[%s]`, executionResultExpectedStr(result)) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocks.AssertExpectationsForObjects(t, backend) }) t.Run("get by block ID not found", func(t *testing.T) { - backend := &mock.API{} blockID := unittest.IdentifierFixture() backend.Mock. On("GetExecutionResultForBlockID", mocks.Anything, blockID). @@ -94,7 +96,7 @@ func TestGetResultBlockID(t *testing.T) { Once() req := getResultByIDReq("", []string{blockID.String()}) - assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, backend) + assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, restHandler) mocks.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/mock/rest_server_api.go b/engine/access/rest/mock/rest_server_api.go new file mode 100644 index 00000000000..fb87307189c --- /dev/null +++ b/engine/access/rest/mock/rest_server_api.go @@ -0,0 +1,380 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import ( + context "context" + + flow "github.com/onflow/flow-go/model/flow" + mock "github.com/stretchr/testify/mock" + + models "github.com/onflow/flow-go/engine/access/rest/models" + + request "github.com/onflow/flow-go/engine/access/rest/request" +) + +// RestServerApi is an autogenerated mock type for the RestServerApi type +type RestServerApi struct { + mock.Mock +} + +// CreateTransaction provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) CreateTransaction(r request.CreateTransaction, _a1 context.Context, link models.LinkGenerator) (models.Transaction, error) { + ret := _m.Called(r, _a1, link) + + var r0 models.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(request.CreateTransaction, context.Context, models.LinkGenerator) (models.Transaction, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.CreateTransaction, context.Context, models.LinkGenerator) models.Transaction); ok { + r0 = rf(r, _a1, link) + } else { + r0 = ret.Get(0).(models.Transaction) + } + + if rf, ok := ret.Get(1).(func(request.CreateTransaction, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteScript provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) ExecuteScript(r request.GetScript, _a1 context.Context, link models.LinkGenerator) ([]byte, error) { + ret := _m.Called(r, _a1, link) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(request.GetScript, context.Context, models.LinkGenerator) ([]byte, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.GetScript, context.Context, models.LinkGenerator) []byte); ok { + r0 = rf(r, _a1, link) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(request.GetScript, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAccount provides a mock function with given fields: r, _a1, expandFields, link +func (_m *RestServerApi) GetAccount(r request.GetAccount, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + ret := _m.Called(r, _a1, expandFields, link) + + var r0 models.Account + var r1 error + if rf, ok := ret.Get(0).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) (models.Account, error)); ok { + return rf(r, _a1, expandFields, link) + } + if rf, ok := ret.Get(0).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) models.Account); ok { + r0 = rf(r, _a1, expandFields, link) + } else { + r0 = ret.Get(0).(models.Account) + } + + if rf, ok := ret.Get(1).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, expandFields, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockPayloadByID provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) GetBlockPayloadByID(r request.GetBlockPayload, _a1 context.Context, link models.LinkGenerator) (models.BlockPayload, error) { + ret := _m.Called(r, _a1, link) + + var r0 models.BlockPayload + var r1 error + if rf, ok := ret.Get(0).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) (models.BlockPayload, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) models.BlockPayload); ok { + r0 = rf(r, _a1, link) + } else { + r0 = ret.Get(0).(models.BlockPayload) + } + + if rf, ok := ret.Get(1).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlocksByHeight provides a mock function with given fields: r, link +func (_m *RestServerApi) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + ret := _m.Called(r, link) + + var r0 []*models.Block + var r1 error + if rf, ok := ret.Get(0).(func(*request.Request, models.LinkGenerator) ([]*models.Block, error)); ok { + return rf(r, link) + } + if rf, ok := ret.Get(0).(func(*request.Request, models.LinkGenerator) []*models.Block); ok { + r0 = rf(r, link) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Block) + } + } + + if rf, ok := ret.Get(1).(func(*request.Request, models.LinkGenerator) error); ok { + r1 = rf(r, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlocksByIDs provides a mock function with given fields: r, _a1, expandFields, link +func (_m *RestServerApi) GetBlocksByIDs(r request.GetBlockByIDs, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + ret := _m.Called(r, _a1, expandFields, link) + + var r0 []*models.Block + var r1 error + if rf, ok := ret.Get(0).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) ([]*models.Block, error)); ok { + return rf(r, _a1, expandFields, link) + } + if rf, ok := ret.Get(0).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) []*models.Block); ok { + r0 = rf(r, _a1, expandFields, link) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Block) + } + } + + if rf, ok := ret.Get(1).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, expandFields, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetCollectionByID provides a mock function with given fields: r, _a1, expandFields, link, chain +func (_m *RestServerApi) GetCollectionByID(r request.GetCollection, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { + ret := _m.Called(r, _a1, expandFields, link, chain) + + var r0 models.Collection + var r1 error + if rf, ok := ret.Get(0).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) (models.Collection, error)); ok { + return rf(r, _a1, expandFields, link, chain) + } + if rf, ok := ret.Get(0).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) models.Collection); ok { + r0 = rf(r, _a1, expandFields, link, chain) + } else { + r0 = ret.Get(0).(models.Collection) + } + + if rf, ok := ret.Get(1).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) error); ok { + r1 = rf(r, _a1, expandFields, link, chain) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEvents provides a mock function with given fields: r, _a1 +func (_m *RestServerApi) GetEvents(r request.GetEvents, _a1 context.Context) (models.BlocksEvents, error) { + ret := _m.Called(r, _a1) + + var r0 models.BlocksEvents + var r1 error + if rf, ok := ret.Get(0).(func(request.GetEvents, context.Context) (models.BlocksEvents, error)); ok { + return rf(r, _a1) + } + if rf, ok := ret.Get(0).(func(request.GetEvents, context.Context) models.BlocksEvents); ok { + r0 = rf(r, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.BlocksEvents) + } + } + + if rf, ok := ret.Get(1).(func(request.GetEvents, context.Context) error); ok { + r1 = rf(r, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionResultByID provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) GetExecutionResultByID(r request.GetExecutionResult, _a1 context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + ret := _m.Called(r, _a1, link) + + var r0 models.ExecutionResult + var r1 error + if rf, ok := ret.Get(0).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) (models.ExecutionResult, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) models.ExecutionResult); ok { + r0 = rf(r, _a1, link) + } else { + r0 = ret.Get(0).(models.ExecutionResult) + } + + if rf, ok := ret.Get(1).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionResultsByBlockIDs provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, _a1 context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + ret := _m.Called(r, _a1, link) + + var r0 []models.ExecutionResult + var r1 error + if rf, ok := ret.Get(0).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) ([]models.ExecutionResult, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) []models.ExecutionResult); ok { + r0 = rf(r, _a1, link) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.ExecutionResult) + } + } + + if rf, ok := ret.Get(1).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNetworkParameters provides a mock function with given fields: r +func (_m *RestServerApi) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { + ret := _m.Called(r) + + var r0 models.NetworkParameters + var r1 error + if rf, ok := ret.Get(0).(func(*request.Request) (models.NetworkParameters, error)); ok { + return rf(r) + } + if rf, ok := ret.Get(0).(func(*request.Request) models.NetworkParameters); ok { + r0 = rf(r) + } else { + r0 = ret.Get(0).(models.NetworkParameters) + } + + if rf, ok := ret.Get(1).(func(*request.Request) error); ok { + r1 = rf(r) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNodeVersionInfo provides a mock function with given fields: r +func (_m *RestServerApi) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { + ret := _m.Called(r) + + var r0 models.NodeVersionInfo + var r1 error + if rf, ok := ret.Get(0).(func(*request.Request) (models.NodeVersionInfo, error)); ok { + return rf(r) + } + if rf, ok := ret.Get(0).(func(*request.Request) models.NodeVersionInfo); ok { + r0 = rf(r) + } else { + r0 = ret.Get(0).(models.NodeVersionInfo) + } + + if rf, ok := ret.Get(1).(func(*request.Request) error); ok { + r1 = rf(r) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionByID provides a mock function with given fields: r, _a1, link, chain +func (_m *RestServerApi) GetTransactionByID(r request.GetTransaction, _a1 context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { + ret := _m.Called(r, _a1, link, chain) + + var r0 models.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) (models.Transaction, error)); ok { + return rf(r, _a1, link, chain) + } + if rf, ok := ret.Get(0).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) models.Transaction); ok { + r0 = rf(r, _a1, link, chain) + } else { + r0 = ret.Get(0).(models.Transaction) + } + + if rf, ok := ret.Get(1).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) error); ok { + r1 = rf(r, _a1, link, chain) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionResultByID provides a mock function with given fields: r, _a1, link +func (_m *RestServerApi) GetTransactionResultByID(r request.GetTransactionResult, _a1 context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + ret := _m.Called(r, _a1, link) + + var r0 models.TransactionResult + var r1 error + if rf, ok := ret.Get(0).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) (models.TransactionResult, error)); ok { + return rf(r, _a1, link) + } + if rf, ok := ret.Get(0).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) models.TransactionResult); ok { + r0 = rf(r, _a1, link) + } else { + r0 = ret.Get(0).(models.TransactionResult) + } + + if rf, ok := ret.Get(1).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) error); ok { + r1 = rf(r, _a1, link) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewRestServerApi interface { + mock.TestingT + Cleanup(func()) +} + +// NewRestServerApi creates a new instance of RestServerApi. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewRestServerApi(t mockConstructorTestingTNewRestServerApi) *RestServerApi { + mock := &RestServerApi{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/engine/access/rest/network_test.go b/engine/access/rest/network_test.go index c4ce7492476..f8013c86685 100644 --- a/engine/access/rest/network_test.go +++ b/engine/access/rest/network_test.go @@ -23,6 +23,7 @@ func networkURL(t *testing.T) string { func TestGetNetworkParameters(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get network parameters on mainnet", func(t *testing.T) { @@ -38,7 +39,7 @@ func TestGetNetworkParameters(t *testing.T) { expected := networkParametersExpectedStr(flow.Mainnet) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/node_version_info_test.go b/engine/access/rest/node_version_info_test.go index 4140089a280..b2543950ea0 100644 --- a/engine/access/rest/node_version_info_test.go +++ b/engine/access/rest/node_version_info_test.go @@ -24,6 +24,7 @@ func nodeVersionInfoURL(t *testing.T) string { func TestGetNodeVersionInfo(t *testing.T) { backend := mock.NewAPI(t) + restHandler := newAccessRestHandler(backend) t.Run("get node version info", func(t *testing.T) { req := getNodeVersionInfoRequest(t) @@ -41,7 +42,7 @@ func TestGetNodeVersionInfo(t *testing.T) { expected := nodeVersionInfoExpectedStr(params) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) mocktestify.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go index 19d6c00440e..ed29d90b6aa 100644 --- a/engine/access/rest/rest_server_api.go +++ b/engine/access/rest/rest_server_api.go @@ -26,7 +26,7 @@ import ( type RestRouter struct { Logger zerolog.Logger Metrics *metrics.ObserverCollector - Upstream *RestForwarder + Upstream RestServerApi Observer *RequestHandler } diff --git a/engine/access/rest/scripts_test.go b/engine/access/rest/scripts_test.go index 7e3271c1d81..d69886e1d20 100644 --- a/engine/access/rest/scripts_test.go +++ b/engine/access/rest/scripts_test.go @@ -45,9 +45,10 @@ func TestScripts(t *testing.T) { "script": util.ToBase64(validCode), "arguments": []string{util.ToBase64(validArgs)}, } + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get by Latest height", func(t *testing.T) { - backend := &mock.API{} backend.Mock. On("ExecuteScriptAtLatestBlock", mocks.Anything, validCode, [][]byte{validArgs}). Return([]byte("hello world"), nil) @@ -56,11 +57,10 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), backend) + ), restHandler) }) t.Run("get by height", func(t *testing.T) { - backend := &mock.API{} height := uint64(1337) backend.Mock. @@ -71,11 +71,10 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), backend) + ), restHandler) }) t.Run("get by ID", func(t *testing.T) { - backend := &mock.API{} id, _ := flow.HexStringToIdentifier("222dc5dd51b9e4910f687e475f892f495f3352362ba318b53e318b4d78131312") backend.Mock. @@ -86,11 +85,10 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), backend) + ), restHandler) }) t.Run("get error", func(t *testing.T) { - backend := &mock.API{} backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, uint64(1337), validCode, [][]byte{validArgs}). Return(nil, status.Error(codes.Internal, "internal server error")) @@ -101,12 +99,11 @@ func TestScripts(t *testing.T) { req, http.StatusBadRequest, `{"code":400, "message":"Invalid Flow request: internal server error"}`, - backend, + restHandler, ) }) t.Run("get invalid", func(t *testing.T) { - backend := &mock.API{} backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, mocks.Anything, mocks.Anything, mocks.Anything). Return(nil, nil) @@ -126,7 +123,7 @@ func TestScripts(t *testing.T) { for _, test := range tests { req := scriptReq(test.id, test.height, test.body) - assertResponse(t, req, http.StatusBadRequest, test.out, backend) + assertResponse(t, req, http.StatusBadRequest, test.out, restHandler) } }) } diff --git a/engine/access/rest/test_helpers.go b/engine/access/rest/test_helpers.go index 88170769c99..fc62c8b6feb 100644 --- a/engine/access/rest/test_helpers.go +++ b/engine/access/rest/test_helpers.go @@ -3,17 +3,26 @@ package rest import ( "bytes" "fmt" + "net" "net/http" "net/http/httptest" "testing" + "time" + + "google.golang.org/grpc" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/access/mock" + engineaccessmock "github.com/onflow/flow-go/engine/access/mock" + restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/utils/grpcutils" + + "github.com/onflow/flow/protobuf/go/flow/access" ) const ( @@ -26,11 +35,12 @@ const ( heightQueryParam = "height" ) -func executeRequest(req *http.Request, backend *mock.API) (*httptest.ResponseRecorder, error) { +func executeRequest(req *http.Request, restHandler RestServerApi) (*httptest.ResponseRecorder, error) { var b bytes.Buffer logger := zerolog.New(&b) - restCollector := metrics.NewNoopCollector() - router, err := newRouter(backend, logger, flow.Testnet.Chain(), restCollector) + metrics := metrics.NewNoopCollector() + + router, err := newRouter(restHandler, logger, flow.Testnet.Chain(), metrics) if err != nil { return nil, err } @@ -40,14 +50,68 @@ func executeRequest(req *http.Request, backend *mock.API) (*httptest.ResponseRec return rr, nil } -func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend *mock.API) { - assertResponse(t, req, http.StatusOK, expectedRespBody, backend) +func newAccessRestHandler(backend *mock.API) RestServerApi { + var b bytes.Buffer + logger := zerolog.New(&b) + + return NewRequestHandler(logger, backend) } -func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend *mock.API) { - rr, err := executeRequest(req, backend) - assert.NoError(t, err) +func newObserverRestHandler_v2(backend *mock.API, restForwarder *restmock.RestServerApi) (RestServerApi, error) { + var b bytes.Buffer + logger := zerolog.New(&b) + observerCollector := metrics.NewObserverCollector() // + + return &RestRouter{ + Logger: logger, + Metrics: observerCollector, + Upstream: restForwarder, + Observer: NewRequestHandler(logger, backend), + }, nil +} + +func newObserverRestHandler(backend *mock.API, identities flow.IdentityList) (RestServerApi, error) { + var b bytes.Buffer + logger := zerolog.New(&b) + observerCollector := metrics.NewObserverCollector() + restForwarder, err := NewRestForwarder(logger, + identities, + time.Second, + grpcutils.DefaultMaxMsgSize) + if err != nil { + return nil, err + } + + return &RestRouter{ + Logger: logger, + Metrics: observerCollector, + Upstream: restForwarder, + Observer: NewRequestHandler(logger, backend), + }, nil +} + +func newGrpcServer(mockServer *engineaccessmock.AccessAPIServer, network string, address string, done chan int) (*grpc.Server, *net.Listener, error) { + l, err := net.Listen(network, address) + if err != nil { + return nil, nil, err + } + s := grpc.NewServer() + go func(done chan int) { + access.RegisterAccessAPIServer(s, mockServer) + _ = s.Serve(l) + done <- 1 + }(done) + return s, &l, nil +} + +func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, restHandler RestServerApi) { + assertResponse(t, req, http.StatusOK, expectedRespBody, restHandler) +} + +func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, restHandler RestServerApi) { + rr, err := executeRequest(req, restHandler) + assert.NoError(t, err) actualResponseBody := rr.Body.String() require.JSONEq(t, expectedRespBody, diff --git a/engine/access/rest/transactions_test.go b/engine/access/rest/transactions_test.go index 26710c747e5..c35980d4347 100644 --- a/engine/access/rest/transactions_test.go +++ b/engine/access/rest/transactions_test.go @@ -102,9 +102,11 @@ func validCreateBody(tx flow.TransactionBody) map[string]interface{} { } func TestGetTransactions(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("get by ID without results", func(t *testing.T) { - backend := &mock.API{} + tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -145,7 +147,7 @@ func TestGetTransactions(t *testing.T) { }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("Get by ID with results", func(t *testing.T) { @@ -214,19 +216,16 @@ func TestGetTransactions(t *testing.T) { } }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ReferenceBlockID, txr.CollectionID, tx.ID(), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("get by ID Invalid", func(t *testing.T) { - backend := &mock.API{} - req := getTransactionReq("invalid", false, "", "") expected := `{"code":400, "message":"invalid ID format"}` - assertResponse(t, req, http.StatusBadRequest, expected, backend) + assertResponse(t, req, http.StatusBadRequest, expected, restHandler) }) t.Run("get by ID non-existing", func(t *testing.T) { - backend := &mock.API{} tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -235,7 +234,7 @@ func TestGetTransactions(t *testing.T) { Return(nil, status.Error(codes.NotFound, "transaction not found")) expected := `{"code":404, "message":"Flow resource not found: transaction not found"}` - assertResponse(t, req, http.StatusNotFound, expected, backend) + assertResponse(t, req, http.StatusNotFound, expected, restHandler) }) } @@ -276,15 +275,17 @@ func TestGetTransactionResult(t *testing.T) { } }`, bid.String(), cid.String(), id.String(), util.ToBase64(txr.Events[0].Payload), id.String()) + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + t.Run("get by transaction ID", func(t *testing.T) { - backend := &mock.API{} req := getTransactionResultReq(id.String(), "", "") backend.Mock. On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, flow.ZeroID). Return(txr, nil) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("get by block ID", func(t *testing.T) { @@ -295,7 +296,7 @@ func TestGetTransactionResult(t *testing.T) { On("GetTransactionResult", mocks.Anything, id, bid, flow.ZeroID). Return(txr, nil) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("get by collection ID", func(t *testing.T) { @@ -306,7 +307,7 @@ func TestGetTransactionResult(t *testing.T) { On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, cid). Return(txr, nil) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("get execution statuses", func(t *testing.T) { @@ -353,23 +354,23 @@ func TestGetTransactionResult(t *testing.T) { "_self": "/v1/transaction_results/%s" } }`, bid.String(), cid.String(), err, cases.Title(language.English).String(strings.ToLower(txResult.Status.String())), txResult.ErrorMessage, id.String()) - assertOKResponse(t, req, expectedResp, backend) + assertOKResponse(t, req, expectedResp, restHandler) } }) t.Run("get by ID Invalid", func(t *testing.T) { - backend := &mock.API{} req := getTransactionResultReq("invalid", "", "") expected := `{"code":400, "message":"invalid ID format"}` - assertResponse(t, req, http.StatusBadRequest, expected, backend) + assertResponse(t, req, http.StatusBadRequest, expected, restHandler) }) } func TestCreateTransaction(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) t.Run("create", func(t *testing.T) { - backend := &mock.API{} tx := unittest.TransactionBodyFixture() tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()} tx.Arguments = [][]uint8{} @@ -417,11 +418,10 @@ func TestCreateTransaction(t *testing.T) { } }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.PayloadSignatures[0].Signature), util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, backend) + assertOKResponse(t, req, expected, restHandler) }) t.Run("post invalid transaction", func(t *testing.T) { - backend := &mock.API{} tests := []struct { inputField string inputValue string @@ -445,7 +445,7 @@ func TestCreateTransaction(t *testing.T) { testTx[test.inputField] = test.inputValue req := createTransactionReq(testTx) - assertResponse(t, req, http.StatusBadRequest, test.output, backend) + assertResponse(t, req, http.StatusBadRequest, test.output, restHandler) } }) } diff --git a/engine/access/rpc/rate_limit_test.go b/engine/access/rpc/rate_limit_test.go index 3cce6e97fda..4cbe5a6e495 100644 --- a/engine/access/rpc/rate_limit_test.go +++ b/engine/access/rpc/rate_limit_test.go @@ -1,219 +1,248 @@ package rpc -import ( - "context" - "fmt" - "io" - "os" - "testing" - "time" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/status" - - accessmock "github.com/onflow/flow-go/engine/access/mock" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/irrecoverable" - "github.com/onflow/flow-go/module/metrics" - module "github.com/onflow/flow-go/module/mock" - "github.com/onflow/flow-go/network" - protocol "github.com/onflow/flow-go/state/protocol/mock" - storagemock "github.com/onflow/flow-go/storage/mock" - "github.com/onflow/flow-go/utils/grpcutils" - "github.com/onflow/flow-go/utils/unittest" -) - -type RateLimitTestSuite struct { - suite.Suite - state *protocol.State - snapshot *protocol.Snapshot - epochQuery *protocol.EpochQuery - log zerolog.Logger - net *network.Network - request *module.Requester - collClient *accessmock.AccessAPIClient - execClient *accessmock.ExecutionAPIClient - me *module.Local - chainID flow.ChainID - metrics *metrics.NoopCollector - rpcEng *Engine - client accessproto.AccessAPIClient - closer io.Closer - - // storage - blocks *storagemock.Blocks - headers *storagemock.Headers - collections *storagemock.Collections - transactions *storagemock.Transactions - receipts *storagemock.ExecutionReceipts - - // test rate limit - rateLimit int - burstLimit int - - ctx irrecoverable.SignalerContext - cancel context.CancelFunc -} - -func (suite *RateLimitTestSuite) SetupTest() { - suite.log = zerolog.New(os.Stdout) - suite.net = new(network.Network) - suite.state = new(protocol.State) - suite.snapshot = new(protocol.Snapshot) - - suite.epochQuery = new(protocol.EpochQuery) - suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() - suite.state.On("Final").Return(suite.snapshot, nil).Maybe() - suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() - suite.blocks = new(storagemock.Blocks) - suite.headers = new(storagemock.Headers) - suite.transactions = new(storagemock.Transactions) - suite.collections = new(storagemock.Collections) - suite.receipts = new(storagemock.ExecutionReceipts) - - suite.collClient = new(accessmock.AccessAPIClient) - suite.execClient = new(accessmock.ExecutionAPIClient) - - suite.request = new(module.Requester) - suite.request.On("EntityByID", mock.Anything, mock.Anything) - - suite.me = new(module.Local) - - accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) - suite.me. - On("NodeID"). - Return(accessIdentity.NodeID) - - suite.chainID = flow.Testnet - suite.metrics = metrics.NewNoopCollector() - - config := Config{ - UnsecureGRPCListenAddr: unittest.DefaultAddress, - SecureGRPCListenAddr: unittest.DefaultAddress, - HTTPListenAddr: unittest.DefaultAddress, - } - - // set the rate limit to test with - suite.rateLimit = 2 - // set the burst limit to test with - suite.burstLimit = 2 - - apiRateLimt := map[string]int{ - "Ping": suite.rateLimit, - } - - apiBurstLimt := map[string]int{ - "Ping": suite.rateLimit, - } - - block := unittest.BlockHeaderFixture() - suite.snapshot.On("Head").Return(block, nil) - - rpcEngBuilder, err := NewBuilder(suite.log, suite.state, config, suite.collClient, nil, suite.blocks, suite.headers, suite.collections, suite.transactions, nil, - nil, suite.chainID, suite.metrics, 0, 0, false, false, apiRateLimt, apiBurstLimt, suite.me) - require.NoError(suite.T(), err) - suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() - require.NoError(suite.T(), err) - suite.ctx, suite.cancel = irrecoverable.NewMockSignalerContextWithCancel(suite.T(), context.Background()) - suite.rpcEng.Start(suite.ctx) - // wait for the server to startup - unittest.RequireCloseBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second, "engine not ready at startup") - - // create the access api client - suite.client, suite.closer, err = accessAPIClient(suite.rpcEng.UnsecureGRPCAddress().String()) - require.NoError(suite.T(), err) -} - -func (suite *RateLimitTestSuite) TearDownTest() { - if suite.cancel != nil { - suite.cancel() - } - // close the client - if suite.closer != nil { - suite.closer.Close() - } - // close the server - unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Done(), 2*time.Second) -} - -func TestRateLimit(t *testing.T) { - suite.Run(t, new(RateLimitTestSuite)) -} - -// TestRatelimitingWithoutBurst tests that rate limit is correctly applied to an Access API call -func (suite *RateLimitTestSuite) TestRatelimitingWithoutBurst() { - - req := &accessproto.PingRequest{} - ctx := context.Background() - - // expect 2 upstream calls - suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) - suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) - - requestCnt := 0 - // requests within the burst should succeed - for requestCnt < suite.rateLimit { - resp, err := suite.client.Ping(ctx, req) - assert.NoError(suite.T(), err) - assert.NotNil(suite.T(), resp) - // sleep to prevent burst - time.Sleep(100 * time.Millisecond) - requestCnt++ - } - - // request more than the limit should fail - _, err := suite.client.Ping(ctx, req) - suite.assertRateLimitError(err) -} - -// TestRatelimitingWithBurst tests that burst limit is correctly applied to an Access API call -func (suite *RateLimitTestSuite) TestRatelimitingWithBurst() { - - req := &accessproto.PingRequest{} - ctx := context.Background() - - // expect rpc.defaultBurst number of upstream calls - suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) - suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) - - requestCnt := 0 - // generate a permissible burst of request and assert that they succeed - for requestCnt < suite.burstLimit { - resp, err := suite.client.Ping(ctx, req) - assert.NoError(suite.T(), err) - assert.NotNil(suite.T(), resp) - requestCnt++ - } - - // request more than the permissible burst and assert that it fails - _, err := suite.client.Ping(ctx, req) - suite.assertRateLimitError(err) -} - -func (suite *RateLimitTestSuite) assertRateLimitError(err error) { - assert.Error(suite.T(), err) - status, ok := status.FromError(err) - assert.True(suite.T(), ok) - assert.Equal(suite.T(), codes.ResourceExhausted, status.Code()) -} - -func accessAPIClient(address string) (accessproto.AccessAPIClient, io.Closer, error) { - conn, err := grpc.Dial( - address, - grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), - grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return nil, nil, fmt.Errorf("failed to connect to address %s: %w", address, err) - } - client := accessproto.NewAccessAPIClient(conn) - closer := io.Closer(conn) - return client, closer, nil -} +// import ( +// "context" +// "fmt" +// "io" +// "os" +// "testing" +// "time" + +// "github.com/rs/zerolog" +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/mock" +// "github.com/stretchr/testify/require" +// "github.com/stretchr/testify/suite" +// "google.golang.org/grpc" +// "google.golang.org/grpc/codes" +// "google.golang.org/grpc/credentials/insecure" +// "google.golang.org/grpc/status" + +// accessengine "github.com/onflow/flow-go/engine/access" +// accessmock "github.com/onflow/flow-go/engine/access/mock" +// "github.com/onflow/flow-go/model/flow" +// "github.com/onflow/flow-go/module/irrecoverable" +// "github.com/onflow/flow-go/module/metrics" +// module "github.com/onflow/flow-go/module/mock" +// "github.com/onflow/flow-go/network" +// protocol "github.com/onflow/flow-go/state/protocol/mock" +// storagemock "github.com/onflow/flow-go/storage/mock" +// "github.com/onflow/flow-go/utils/grpcutils" +// "github.com/onflow/flow-go/utils/unittest" +// accessproto "github.com/onflow/flow/protobuf/go/flow/access" +// ) + +// type RateLimitTestSuite struct { +// suite.Suite +// state *protocol.State +// snapshot *protocol.Snapshot +// epochQuery *protocol.EpochQuery +// log zerolog.Logger +// net *network.Network +// request *module.Requester +// collClient *accessmock.AccessAPIClient +// execClient *accessmock.ExecutionAPIClient +// me *module.Local +// chainID flow.ChainID +// metrics *metrics.NoopCollector +// rpcEng *Engine +// client accessproto.AccessAPIClient +// closer io.Closer + +// // storage +// blocks *storagemock.Blocks +// headers *storagemock.Headers +// collections *storagemock.Collections +// transactions *storagemock.Transactions +// receipts *storagemock.ExecutionReceipts + +// // test rate limit +// rateLimit int +// burstLimit int + +// ctx irrecoverable.SignalerContext +// cancel context.CancelFunc +// } + +// func (suite *RateLimitTestSuite) SetupTest() { +// suite.log = zerolog.New(os.Stdout) +// suite.net = new(network.Network) +// suite.state = new(protocol.State) +// suite.snapshot = new(protocol.Snapshot) + +// suite.epochQuery = new(protocol.EpochQuery) +// suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() +// suite.state.On("Final").Return(suite.snapshot, nil).Maybe() +// suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() +// suite.blocks = new(storagemock.Blocks) +// suite.headers = new(storagemock.Headers) +// suite.transactions = new(storagemock.Transactions) +// suite.collections = new(storagemock.Collections) +// suite.receipts = new(storagemock.ExecutionReceipts) + +// suite.collClient = new(accessmock.AccessAPIClient) +// suite.execClient = new(accessmock.ExecutionAPIClient) + +// suite.request = new(module.Requester) +// suite.request.On("EntityByID", mock.Anything, mock.Anything) + +// suite.me = new(module.Local) + +// accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) +// suite.me. +// On("NodeID"). +// Return(accessIdentity.NodeID) + +// suite.chainID = flow.Testnet +// suite.metrics = metrics.NewNoopCollector() + +// config := Config{ +// UnsecureGRPCListenAddr: unittest.DefaultAddress, +// SecureGRPCListenAddr: unittest.DefaultAddress, +// HTTPListenAddr: unittest.DefaultAddress, +// } + +// // set the rate limit to test with +// suite.rateLimit = 2 +// // set the burst limit to test with +// suite.burstLimit = 2 + +// apiRateLimt := map[string]int{ +// "Ping": suite.rateLimit, +// } + +// apiBurstLimt := map[string]int{ +// "Ping": suite.rateLimit, +// } + +// block := unittest.BlockHeaderFixture() +// suite.snapshot.On("Head").Return(block, nil) + +// backend, err := accessengine.NewBackend( +// suite.log, +// suite.state, +// config, +// suite.collClient, +// nil, +// suite.blocks, +// suite.headers, +// suite.collections, +// suite.transactions, +// nil, +// nil, +// suite.chainID, +// suite.metrics, +// 0, +// 0, +// false) +// require.NoError(suite.T(), err) + +// rpcEngBuilder, err := NewBuilder( +// suite.log, +// suite.state, +// config, +// suite.chainID, +// suite.metrics, +// false, +// apiRateLimt, +// apiBurstLimt, +// suite.me, +// backend) +// require.NoError(suite.T(), err) +// suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() +// require.NoError(suite.T(), err) +// suite.ctx, suite.cancel = irrecoverable.NewMockSignalerContextWithCancel(suite.T(), context.Background()) +// suite.rpcEng.Start(suite.ctx) +// // wait for the server to startup +// unittest.RequireCloseBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second, "engine not ready at startup") + +// // create the access api client +// suite.client, suite.closer, err = accessAPIClient(suite.rpcEng.UnsecureGRPCAddress().String()) +// require.NoError(suite.T(), err) +// } + +// func (suite *RateLimitTestSuite) TearDownTest() { +// if suite.cancel != nil { +// suite.cancel() +// } +// // close the client +// if suite.closer != nil { +// suite.closer.Close() +// } +// // close the server +// unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Done(), 2*time.Second) +// } + +// func TestRateLimit(t *testing.T) { +// suite.Run(t, new(RateLimitTestSuite)) +// } + +// // TestRatelimitingWithoutBurst tests that rate limit is correctly applied to an Access API call +// func (suite *RateLimitTestSuite) TestRatelimitingWithoutBurst() { + +// req := &accessproto.PingRequest{} +// ctx := context.Background() + +// // expect 2 upstream calls +// suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) +// suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) + +// requestCnt := 0 +// // requests within the burst should succeed +// for requestCnt < suite.rateLimit { +// resp, err := suite.client.Ping(ctx, req) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), resp) +// // sleep to prevent burst +// time.Sleep(100 * time.Millisecond) +// requestCnt++ +// } + +// // request more than the limit should fail +// _, err := suite.client.Ping(ctx, req) +// suite.assertRateLimitError(err) +// } + +// // TestRatelimitingWithBurst tests that burst limit is correctly applied to an Access API call +// func (suite *RateLimitTestSuite) TestRatelimitingWithBurst() { + +// req := &accessproto.PingRequest{} +// ctx := context.Background() + +// // expect rpc.defaultBurst number of upstream calls +// suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) +// suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) + +// requestCnt := 0 +// // generate a permissible burst of request and assert that they succeed +// for requestCnt < suite.burstLimit { +// resp, err := suite.client.Ping(ctx, req) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), resp) +// requestCnt++ +// } + +// // request more than the permissible burst and assert that it fails +// _, err := suite.client.Ping(ctx, req) +// suite.assertRateLimitError(err) +// } + +// func (suite *RateLimitTestSuite) assertRateLimitError(err error) { +// assert.Error(suite.T(), err) +// status, ok := status.FromError(err) +// assert.True(suite.T(), ok) +// assert.Equal(suite.T(), codes.ResourceExhausted, status.Code()) +// } + +// func accessAPIClient(address string) (accessproto.AccessAPIClient, io.Closer, error) { +// conn, err := grpc.Dial( +// address, +// grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), +// grpc.WithTransportCredentials(insecure.NewCredentials())) +// if err != nil { +// return nil, nil, fmt.Errorf("failed to connect to address %s: %w", address, err) +// } +// client := accessproto.NewAccessAPIClient(conn) +// closer := io.Closer(conn) +// return client, closer, nil +// } diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index b82160668db..3c96ca2740c 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -11,6 +11,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -108,7 +109,7 @@ func (suite *SecureGRPCTestSuite) SetupTest() { block := unittest.BlockHeaderFixture() suite.snapshot.On("Head").Return(block, nil) - rpcEngBuilder, err := rpc.NewBuilder( + backend, err := NewBackend( suite.log, suite.state, config, @@ -124,11 +125,20 @@ func (suite *SecureGRPCTestSuite) SetupTest() { suite.metrics, 0, 0, - false, + false) + require.NoError(suite.T(), err) + + rpcEngBuilder, err := rpc.NewBuilder( + suite.log, + suite.state, + config, + suite.chainID, + suite.metrics, false, nil, nil, suite.me, + backend, ) assert.NoError(suite.T(), err) suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() From ee5b7c561178f3a745a7fb53ffce5b8e5b55960c Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Mon, 19 Jun 2023 15:18:14 +0300 Subject: [PATCH 06/30] Refactored tests ande added new tests for rest servise. Linted. --- .../node_builder/access_node_builder.go | 17 +- cmd/observer/node_builder/observer_builder.go | 28 +- engine/access/apiproxy/access_api_proxy.go | 7 +- engine/access/backend.go | 93 ---- engine/access/rest/accounts_test.go | 409 +++++++------- engine/access/rest/blocks_test.go | 67 +-- engine/access/rest/handler.go | 9 +- engine/access/rest/rest_server_api.go | 19 +- engine/access/rest/scripts_test.go | 17 +- engine/access/rest/test_helpers.go | 48 +- engine/access/rest/transactions_test.go | 28 +- engine/access/rest_api_test.go | 15 +- engine/access/rpc/backend/backend.go | 84 +++ engine/access/rpc/engine_builder.go | 2 +- engine/access/rpc/rate_limit_test.go | 500 +++++++++--------- engine/access/secure_grpcr_test.go | 14 +- module/forwarder/forwarder.go | 4 +- 17 files changed, 684 insertions(+), 677 deletions(-) delete mode 100644 engine/access/backend.go diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 0482b600e66..12e6e03a683 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -35,7 +35,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" "github.com/onflow/flow-go/crypto" - accessengine "github.com/onflow/flow-go/engine/access" "github.com/onflow/flow-go/engine/access/ingestion" pingeng "github.com/onflow/flow-go/engine/access/ping" "github.com/onflow/flow-go/engine/access/rpc" @@ -992,9 +991,9 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { return nil }). Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { - backend, err := accessengine.NewBackend(node.Logger, + config := builder.rpcConf + backend, err := backend.NewBackend(node.Logger, node.State, - builder.rpcConf, builder.CollectionRPC, builder.HistoricalAccessRPCs, node.Storage.Blocks, @@ -1007,7 +1006,15 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.AccessMetrics, builder.collectionGRPCPort, builder.executionGRPCPort, - builder.retryEnabled) + builder.retryEnabled, + config.MaxHeightRange, + config.ExecutionClientTimeout, + config.CollectionClientTimeout, + config.ConnectionPoolSize, + config.MaxHeightRange, + config.PreferredExecutionNodeIDs, + config.FixedExecutionNodeIDs, + config.ArchiveAddressList) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) } @@ -1015,7 +1022,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { engineBuilder, err := rpc.NewBuilder( node.Logger, node.State, - builder.rpcConf, + config, node.RootChainID, builder.AccessMetrics, builder.rpcMetricsEnabled, diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 2b82705168d..ddef152feda 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -28,7 +28,6 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/verification" recovery "github.com/onflow/flow-go/consensus/recovery/protocol" "github.com/onflow/flow-go/crypto" - accessengine "github.com/onflow/flow-go/engine/access" "github.com/onflow/flow-go/engine/access/apiproxy" "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rpc" @@ -851,9 +850,9 @@ func (builder *ObserverServiceBuilder) enqueueConnectWithStakedAN() { func (builder *ObserverServiceBuilder) enqueueRPCServer() { builder.Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { accessMetrics := metrics.NewNoopCollector() - accessBackend, err := accessengine.NewBackend(node.Logger, + config := builder.rpcConf + accessBackend, err := backend.NewBackend(node.Logger, node.State, - builder.rpcConf, nil, nil, node.Storage.Blocks, @@ -866,7 +865,16 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { accessMetrics, 0, 0, - false) + false, + config.MaxHeightRange, + config.ExecutionClientTimeout, + config.CollectionClientTimeout, + config.ConnectionPoolSize, + config.MaxHeightRange, + config.PreferredExecutionNodeIDs, + config.FixedExecutionNodeIDs, + config.ArchiveAddressList) + if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) } @@ -874,7 +882,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { engineBuilder, err := rpc.NewBuilder( node.Logger, node.State, - builder.rpcConf, + config, node.RootChainID, accessMetrics, builder.rpcMetricsEnabled, @@ -888,16 +896,16 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { } // upstream access node forwarder - forwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, builder.rpcConf.MaxMsgSize) + forwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) if err != nil { return nil, err } - metrics := metrics.NewObserverCollector() + observerCollector := metrics.NewObserverCollector() rpcHandler := &apiproxy.FlowAccessAPIRouter{ Logger: builder.Logger, - Metrics: metrics, + Metrics: observerCollector, Upstream: forwarder, Observer: protocol.NewHandler(protocol.New( node.State, @@ -910,14 +918,14 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { restForwarder, err := rest.NewRestForwarder(builder.Logger, builder.upstreamIdentities, builder.apiTimeout, - builder.rpcConf.MaxMsgSize) + config.MaxMsgSize) if err != nil { return nil, err } restHandler := &rest.RestRouter{ Logger: builder.Logger, - Metrics: metrics, + Metrics: observerCollector, Upstream: restForwarder, Observer: rest.NewRequestHandler(builder.Logger, accessBackend), } diff --git a/engine/access/apiproxy/access_api_proxy.go b/engine/access/apiproxy/access_api_proxy.go index 08136531b09..2b345420229 100644 --- a/engine/access/apiproxy/access_api_proxy.go +++ b/engine/access/apiproxy/access_api_proxy.go @@ -207,7 +207,7 @@ func (h *FlowAccessAPIRouter) GetExecutionResultByID(context context.Context, re // FlowAccessAPIForwarder forwards all requests to a set of upstream access nodes or observers type FlowAccessAPIForwarder struct { - forwarder.Forwarder + *forwarder.Forwarder } func NewFlowAccessAPIForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*FlowAccessAPIForwarder, error) { @@ -216,10 +216,9 @@ func NewFlowAccessAPIForwarder(identities flow.IdentityList, timeout time.Durati return nil, err } - accessApiForwarder := &FlowAccessAPIForwarder{ + return &FlowAccessAPIForwarder{ Forwarder: forwarder, - } - return accessApiForwarder, nil + }, nil } // Ping pings the service. It is special in the sense that it responds successful, diff --git a/engine/access/backend.go b/engine/access/backend.go deleted file mode 100644 index 92c8f932b2d..00000000000 --- a/engine/access/backend.go +++ /dev/null @@ -1,93 +0,0 @@ -package access - -import ( - "fmt" - - lru "github.com/hashicorp/golang-lru" - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/engine/access/rpc" - "github.com/onflow/flow-go/engine/access/rpc/backend" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module" - "github.com/onflow/flow-go/state/protocol" - "github.com/onflow/flow-go/storage" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" -) - -func NewBackend( - log zerolog.Logger, - state protocol.State, - config rpc.Config, - collectionRPC accessproto.AccessAPIClient, - historicalAccessNodes []accessproto.AccessAPIClient, - blocks storage.Blocks, - headers storage.Headers, - collections storage.Collections, - transactions storage.Transactions, - executionReceipts storage.ExecutionReceipts, - executionResults storage.ExecutionResults, - chainID flow.ChainID, - accessMetrics module.AccessMetrics, - collectionGRPCPort uint, - executionGRPCPort uint, - retryEnabled bool) (*backend.Backend, error) { - - var cache *lru.Cache - cacheSize := config.ConnectionPoolSize - if cacheSize > 0 { - // TODO: remove this fallback after fixing issues with evictions - // It was observed that evictions cause connection errors for in flight requests. This works around - // the issue by forcing hte pool size to be greater than the number of ENs + LNs - if cacheSize < backend.DefaultConnectionPoolSize { - log.Warn().Msg("connection pool size below threshold, setting pool size to default value ") - cacheSize = backend.DefaultConnectionPoolSize - } - var err error - cache, err = lru.NewWithEvict(int(cacheSize), func(_, evictedValue interface{}) { - store := evictedValue.(*backend.CachedClient) - store.Close() - log.Debug().Str("grpc_conn_evicted", store.Address).Msg("closing grpc connection evicted from pool") - if accessMetrics != nil { - accessMetrics.ConnectionFromPoolEvicted() - } - }) - if err != nil { - return nil, fmt.Errorf("could not initialize connection pool cache: %w", err) - } - } - - connectionFactory := &backend.ConnectionFactoryImpl{ - CollectionGRPCPort: collectionGRPCPort, - ExecutionGRPCPort: executionGRPCPort, - CollectionNodeGRPCTimeout: config.CollectionClientTimeout, - ExecutionNodeGRPCTimeout: config.ExecutionClientTimeout, - ConnectionsCache: cache, - CacheSize: cacheSize, - MaxMsgSize: config.MaxMsgSize, - AccessMetrics: accessMetrics, - Log: log, - } - - return backend.New(state, - collectionRPC, - historicalAccessNodes, - blocks, - headers, - collections, - transactions, - executionReceipts, - executionResults, - chainID, - accessMetrics, - connectionFactory, - retryEnabled, - config.MaxHeightRange, - config.PreferredExecutionNodeIDs, - config.FixedExecutionNodeIDs, - log, - backend.DefaultSnapshotHistoryLimit, - config.ArchiveAddressList, - ), nil -} diff --git a/engine/access/rest/accounts_test.go b/engine/access/rest/accounts_test.go index 0446fc4958d..d248bd312c8 100644 --- a/engine/access/rest/accounts_test.go +++ b/engine/access/rest/accounts_test.go @@ -13,6 +13,9 @@ import ( "github.com/onflow/flow-go/access/mock" "github.com/onflow/flow-go/engine/access/rest/middleware" + restmock "github.com/onflow/flow-go/engine/access/rest/mock" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" ) @@ -33,7 +36,7 @@ func accountURL(t *testing.T, address string, height string) string { return u.String() } -func TestGetAccount(t *testing.T) { +func TestAccessGetAccount(t *testing.T) { backend := &mock.API{} restHandler := newAccessRestHandler(backend) @@ -128,205 +131,210 @@ func TestGetAccount(t *testing.T) { }) } -//func TestObserverGetAccount(t *testing.T) { -// backend := &mock.API{} -// address := unittest.IPPort("11632") -// -// t.Run("get by address at latest sealed block", func(t *testing.T) { -// account := accountFixture(t) -// entityAccount, err := convert.AccountToMessage(account) -// assert.NoError(t, err) -// -// var height uint64 = 100 -// blockHeader, err := convert.BlockHeaderToMessage(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)), unittest.IdentifierListFixture(4)) //? -// assert.NoError(t, err) -// -// req := getAccountRequest(t, account, sealedHeightQueryParam, expandableFieldKeys, expandableFieldContracts) -// -// done := make(chan int) -// // Bring up 1st upstream server -// mockServer := new(engineaccessmock.AccessAPIServer) -// mockServer.Mock. -// On("GetLatestBlockHeader", mocktestify.Anything, -// &access.GetLatestBlockHeaderRequest{ -// IsSealed: true, -// }). -// Return(&access.BlockHeaderResponse{ -// Block: blockHeader, -// BlockStatus: entities.BlockStatus_BLOCK_SEALED, -// Metadata: nil, -// }, nil) -// -// mockServer.Mock. -// On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ -// Address: account.Address.Bytes(), -// BlockHeight: height, -// }). -// Return(&access.AccountResponse{ -// Account: entityAccount, -// Metadata: nil, -// }, nil) -// expected := expectedExpandedResponse(account) -// -// server, _, err := newGrpcServer(mockServer, "tcp", address, done) -// assert.NoError(t, err) -// -// restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) -// assert.NoError(t, err) -// -// assertOKResponse(t, req, expected, restHandler) -// mocktestify.AssertExpectationsForObjects(t, backend) -// -// server.Stop() -// <-done -// }) -// -// //t.Run("get by address at latest finalized block", func(t *testing.T) { -// // var height uint64 = 100 -// // blockHeader, err := convert.BlockHeaderToMessage(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)), unittest.IdentifierListFixture(4)) -// // assert.NoError(t, err) -// // account := accountFixture(t) -// // entityAccount, err := convert.AccountToMessage(account) -// // assert.NoError(t, err) -// // -// // req := getAccountRequest(t, account, finalHeightQueryParam, expandableFieldKeys, expandableFieldContracts) -// // -// // done := make(chan int) -// // // Bring up 1st upstream server -// // mockServer := new(engineaccessmock.AccessAPIServer) -// // mockServer.Mock. -// // On("GetLatestBlockHeader", mocktestify.Anything, -// // &access.GetLatestBlockHeaderRequest{ -// // IsSealed: false, -// // }). -// // Return(&access.BlockHeaderResponse{ -// // Block: blockHeader, -// // BlockStatus: entities.BlockStatus_BLOCK_FINALIZED, -// // Metadata: nil, -// // }, nil) -// // mockServer.Mock. -// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ -// // Address: account.Address.Bytes(), -// // BlockHeight: height, -// // }). -// // Return(&access.AccountResponse{ -// // Account: entityAccount, -// // Metadata: nil, -// // }, nil) -// // expected := expectedExpandedResponse(account) -// // -// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) -// // assert.NoError(t, err) -// // -// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) -// // assert.NoError(t, err) -// // -// // assertOKResponse(t, req, expected, restHandler) -// // mocktestify.AssertExpectationsForObjects(t, backend) -// // -// // server.Stop() -// // <-done -// //}) -// // -// //t.Run("get by address at height", func(t *testing.T) { -// // var height uint64 = 1337 -// // account := accountFixture(t) -// // entityAccount, err := convert.AccountToMessage(account) -// // assert.NoError(t, err) -// // -// // req := getAccountRequest(t, account, fmt.Sprintf("%d", height), expandableFieldKeys, expandableFieldContracts) -// // -// // done := make(chan int) -// // // Bring up 1st upstream server -// // mockServer := new(engineaccessmock.AccessAPIServer) -// // mockServer.Mock. -// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ -// // Address: account.Address.Bytes(), -// // BlockHeight: height, -// // }). -// // Return(&access.AccountResponse{ -// // Account: entityAccount, -// // Metadata: nil, -// // }, nil) -// // expected := expectedExpandedResponse(account) -// // -// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) -// // assert.NoError(t, err) -// // -// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) -// // assert.NoError(t, err) -// // -// // assertOKResponse(t, req, expected, restHandler) -// // mocktestify.AssertExpectationsForObjects(t, backend) -// // -// // server.Stop() -// // <-done -// //}) -// // -// //t.Run("get by address at height condensed", func(t *testing.T) { -// // var height uint64 = 1337 -// // account := accountFixture(t) -// // entityAccount, err := convert.AccountToMessage(account) -// // assert.NoError(t, err) -// // -// // req := getAccountRequest(t, account, fmt.Sprintf("%d", height)) -// // -// // done := make(chan int) -// // // Bring up 1st upstream server -// // mockServer := new(engineaccessmock.AccessAPIServer) -// // mockServer.Mock. -// // On("GetAccountAtBlockHeight", mocktestify.Anything, &access.GetAccountAtBlockHeightRequest{ -// // Address: account.Address.Bytes(), -// // BlockHeight: height, -// // }). -// // Return(&access.AccountResponse{ -// // Account: entityAccount, -// // Metadata: nil, -// // }, nil) -// // expected := expectedCondensedResponse(account) -// // -// // server, _, err := newGrpcServer(mockServer, "tcp", address, done) -// // assert.NoError(t, err) -// // -// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) -// // assert.NoError(t, err) -// // -// // assertOKResponse(t, req, expected, restHandler) -// // mocktestify.AssertExpectationsForObjects(t, backend) -// // -// // server.Stop() -// // <-done -// //}) -// // -// //t.Run("get invalid", func(t *testing.T) { -// // tests := []struct { -// // url string -// // out string -// // }{ -// // {accountURL(t, "123", ""), `{"code":400, "message":"invalid address"}`}, -// // {accountURL(t, unittest.AddressFixture().String(), "foo"), `{"code":400, "message":"invalid height format"}`}, -// // } -// // -// // done := make(chan int) -// // // Bring up 1st upstream server -// // server, _, err := newGrpcServer(new(engineaccessmock.AccessAPIServer), "tcp", address, done) -// // assert.NoError(t, err) -// // -// // restHandler, err := newObserverRestHandler(backend, flow.IdentityList{{Address: address}}) -// // assert.NoError(t, err) -// // for i, test := range tests { -// // req, _ := http.NewRequest("GET", test.url, nil) -// // rr, err := executeRequest(req, restHandler) -// // assert.NoError(t, err) -// // -// // assert.Equal(t, http.StatusBadRequest, rr.Code) -// // assert.JSONEq(t, test.out, rr.Body.String(), fmt.Sprintf("test #%d failed: %v", i, test)) -// // } -// // -// // server.Stop() -// // <-done -// //}) -//} +// TestObserverGetAccount tests the get account from observer node +func TestObserverGetAccount(t *testing.T) { + backend := &mock.API{} + restForwarder := &restmock.RestServerApi{} + + restHandler, err := newObserverRestHandler(backend, restForwarder) + assert.NoError(t, err) + + t.Run("get by address at latest sealed block", func(t *testing.T) { + account := accountFixture(t) + + req := getAccountRequest(t, account, sealedHeightQueryParam, expandableFieldKeys, expandableFieldContracts) + + accountKeys := make([]models.AccountPublicKey, 1) + + sigAlgo := models.SigningAlgorithm("ECDSA_P256") + hashAlgo := models.HashingAlgorithm("SHA3_256") + + accountKeys[0] = models.AccountPublicKey{ + Index: "0", + PublicKey: account.Keys[0].PublicKey.String(), + SigningAlgorithm: &sigAlgo, + HashingAlgorithm: &hashAlgo, + SequenceNumber: "0", + Weight: "1000", + Revoked: false, + } + + restForwarder.Mock.On("GetAccount", + request.GetAccount{ + Address: account.Address, + Height: request.SealedHeight, + }, + mocktestify.Anything, + mocktestify.Anything, + mocktestify.Anything). + Return(models.Account{ + Address: account.Address.String(), + Balance: fmt.Sprintf("%d", account.Balance), + Keys: accountKeys, + Contracts: map[string]string{ + "contract1": "Y29udHJhY3Qx", + "contract2": "Y29udHJhY3Qy", + }, + Expandable: &models.AccountExpandable{}, + Links: &models.Links{ + Self: fmt.Sprintf("/v1/accounts/%s", account.Address), + }, + }, nil) + + expected := expectedExpandedResponse(account) + + assertOKResponse(t, req, expected, restHandler) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get by address at latest finalized block", func(t *testing.T) { + account := accountFixture(t) + + req := getAccountRequest(t, account, finalHeightQueryParam, expandableFieldKeys, expandableFieldContracts) + + accountKeys := make([]models.AccountPublicKey, 1) + + sigAlgo := models.SigningAlgorithm("ECDSA_P256") + hashAlgo := models.HashingAlgorithm("SHA3_256") + + accountKeys[0] = models.AccountPublicKey{ + Index: "0", + PublicKey: account.Keys[0].PublicKey.String(), + SigningAlgorithm: &sigAlgo, + HashingAlgorithm: &hashAlgo, + SequenceNumber: "0", + Weight: "1000", + Revoked: false, + } + + restForwarder.Mock.On("GetAccount", + request.GetAccount{ + Address: account.Address, + Height: request.FinalHeight, + }, + mocktestify.Anything, + mocktestify.Anything, + mocktestify.Anything). + Return(models.Account{ + Address: account.Address.String(), + Balance: fmt.Sprintf("%d", account.Balance), + Keys: accountKeys, + Contracts: map[string]string{ + "contract1": "Y29udHJhY3Qx", + "contract2": "Y29udHJhY3Qy", + }, + Expandable: &models.AccountExpandable{}, + Links: &models.Links{ + Self: fmt.Sprintf("/v1/accounts/%s", account.Address), + }, + }, nil) + + expected := expectedExpandedResponse(account) + + assertOKResponse(t, req, expected, restHandler) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get by address at height", func(t *testing.T) { + var height uint64 = 1337 + account := accountFixture(t) + + req := getAccountRequest(t, account, fmt.Sprintf("%d", height), expandableFieldKeys, expandableFieldContracts) + accountKeys := make([]models.AccountPublicKey, 1) + + sigAlgo := models.SigningAlgorithm("ECDSA_P256") + hashAlgo := models.HashingAlgorithm("SHA3_256") + + accountKeys[0] = models.AccountPublicKey{ + Index: "0", + PublicKey: account.Keys[0].PublicKey.String(), + SigningAlgorithm: &sigAlgo, + HashingAlgorithm: &hashAlgo, + SequenceNumber: "0", + Weight: "1000", + Revoked: false, + } + + restForwarder.Mock.On("GetAccount", + request.GetAccount{ + Address: account.Address, + Height: height, + }, + mocktestify.Anything, + mocktestify.Anything, + mocktestify.Anything). + Return(models.Account{ + Address: account.Address.String(), + Balance: fmt.Sprintf("%d", account.Balance), + Keys: accountKeys, + Contracts: map[string]string{ + "contract1": "Y29udHJhY3Qx", + "contract2": "Y29udHJhY3Qy", + }, + Expandable: &models.AccountExpandable{}, + Links: &models.Links{ + Self: fmt.Sprintf("/v1/accounts/%s", account.Address), + }, + }, nil) + + expected := expectedExpandedResponse(account) + + assertOKResponse(t, req, expected, restHandler) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get by address at height condensed", func(t *testing.T) { + var height uint64 = 1337 + account := accountFixture(t) + + req := getAccountRequest(t, account, fmt.Sprintf("%d", height)) + + restForwarder.Mock.On("GetAccount", + request.GetAccount{ + Address: account.Address, + Height: height, + }, + mocktestify.Anything, + mocktestify.Anything, + mocktestify.Anything). + Return(models.Account{ + Address: account.Address.String(), + Balance: fmt.Sprintf("%d", account.Balance), + Contracts: map[string]string{}, + Expandable: &models.AccountExpandable{ + Keys: expandableFieldKeys, + Contracts: expandableFieldContracts, + }, + Links: &models.Links{ + Self: fmt.Sprintf("/v1/accounts/%s", account.Address), + }, + }, nil) + + expected := expectedCondensedResponse(account) + + assertOKResponse(t, req, expected, restHandler) + mocktestify.AssertExpectationsForObjects(t, backend) + }) + + t.Run("get invalid", func(t *testing.T) { + tests := []struct { + url string + out string + }{ + {accountURL(t, "123", ""), `{"code":400, "message":"invalid address"}`}, + {accountURL(t, unittest.AddressFixture().String(), "foo"), `{"code":400, "message":"invalid height format"}`}, + } + + for i, test := range tests { + req, _ := http.NewRequest("GET", test.url, nil) + rr, err := executeRequest(req, restHandler) + assert.NoError(t, err) + + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.JSONEq(t, test.out, rr.Body.String(), fmt.Sprintf("test #%d failed: %v", i, test)) + } + }) +} func expectedExpandedResponse(account *flow.Account) string { return fmt.Sprintf(`{ @@ -366,6 +374,7 @@ func getAccountRequest(t *testing.T, account *flow.Account, height string, expan q.Add(middleware.ExpandQueryParam, fieldParam) req.URL.RawQuery = q.Encode() } + require.NoError(t, err) return req } diff --git a/engine/access/rest/blocks_test.go b/engine/access/rest/blocks_test.go index 536b2b50296..2ea9a1f3f6b 100644 --- a/engine/access/rest/blocks_test.go +++ b/engine/access/rest/blocks_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" + restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/util" @@ -31,7 +32,6 @@ type testVector struct { expectedResponse string } -// ? func prepareTestVectors(t *testing.T, blockIDs []string, heights []string, @@ -141,8 +141,8 @@ func prepareTestVectors(t *testing.T, return testVectors } -// TestGetBlocks tests the get blocks by ID and get blocks by heights API -func TestGetBlocks(t *testing.T) { +// TestGetBlocks tests the get blocks by ID and get blocks by heights API from access node +func TestAccessGetBlocks(t *testing.T) { backend := &mock.API{} blkCnt := 10 @@ -159,44 +159,29 @@ func TestGetBlocks(t *testing.T) { } } -// ? -//func TestGetBlockFromObserver(t *testing.T) { -// backend := &mock.API{} -// -// // Bring up upstream server -// blkCnt := 10 -// blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) -// address := unittest.IPPort("11633") -// -// done := make(chan int) -// server, _, err := newGrpcServer(new(engineaccessmock.AccessAPIServer), "tcp", address, done) -// assert.NoError(t, err) -// -// testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) -// -// var b bytes.Buffer -// logger := zerolog.New(&b) -// -// restForwarder, err := NewRestForwarder(logger, -// flow.IdentityList{{Address: address}}, -// time.Second, -// grpcutils.DefaultMaxMsgSize) -// assert.NoError(t, err) -// -// restHandler, err := newObserverRestHandler_v2(backend, ) -// assert.NoError(t, err) -// -// for _, tv := range testVectors { -// responseRec, err := executeRequest(tv.request, restHandler) -// assert.NoError(t, err) -// require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) -// actualResp := responseRec.Body.String() -// require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description) -// -// } -// server.Stop() -// <-done -//} +// TestObserverGetBlocks tests the get blocks by ID and get blocks by heights API from observer node +func TestObserverGetBlocks(t *testing.T) { + backend := &mock.API{} + restForwarder := &restmock.RestServerApi{} + + // Bring up upstream server + blkCnt := 10 + blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) + + testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) + + restHandler, err := newObserverRestHandler(backend, restForwarder) + assert.NoError(t, err) + + for _, tv := range testVectors { + responseRec, err := executeRequest(tv.request, restHandler) + assert.NoError(t, err) + require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) + actualResp := responseRec.Body.String() + require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description) + + } +} func requestURL(t *testing.T, ids []string, start string, end string, expandResponse bool, heights ...string) *http.Request { u, _ := url.Parse("/v1/blocks") diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index 74e5663172f..025e9eb0d01 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -6,15 +6,16 @@ import ( "fmt" "net/http" + "github.com/rs/zerolog" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/util" fvmErrors "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/model/flow" - - "github.com/rs/zerolog" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) const MaxRequestSize = 2 << 20 // 2MB diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go index ed29d90b6aa..d37edf7f731 100644 --- a/engine/access/rest/rest_server_api.go +++ b/engine/access/rest/rest_server_api.go @@ -132,6 +132,7 @@ func (r *RestRouter) GetNodeVersionInfo(req *request.Request) (models.NodeVersio return res, err } +// RestServerApi is the server API for REST service. type RestServerApi interface { // GetTransactionByID gets a transaction by requested ID. GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) @@ -163,20 +164,13 @@ type RestServerApi interface { GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) } +// RequestHandler is a structure that represents local requests type RequestHandler struct { RestServerApi log zerolog.Logger backend access.API } -//// NewRequestHandler returns new RequestHandler. -//func NewRequestHandler(log zerolog.Logger, backend access.API) RestServerApi { -// return &RequestHandler{ -// log: log, -// backend: backend, -// } -//} - // NewRequestHandler returns new RequestHandler. func NewRequestHandler(log zerolog.Logger, backend access.API) *RequestHandler { return &RequestHandler{ @@ -500,19 +494,20 @@ func (h *RequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVers return response, nil } +// RestForwarder handles the request forwarding to upstream type RestForwarder struct { log zerolog.Logger - forwarder.Forwarder + *forwarder.Forwarder } // NewRestForwarder returns new RestForwarder. func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*RestForwarder, error) { - forwarder, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) + f, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) restForwarder := &RestForwarder{ - log: log, + log: log, + Forwarder: f, } - restForwarder.Forwarder = forwarder return restForwarder, err } diff --git a/engine/access/rest/scripts_test.go b/engine/access/rest/scripts_test.go index d69886e1d20..8cb19415ae3 100644 --- a/engine/access/rest/scripts_test.go +++ b/engine/access/rest/scripts_test.go @@ -45,10 +45,11 @@ func TestScripts(t *testing.T) { "script": util.ToBase64(validCode), "arguments": []string{util.ToBase64(validArgs)}, } - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("get by Latest height", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + backend.Mock. On("ExecuteScriptAtLatestBlock", mocks.Anything, validCode, [][]byte{validArgs}). Return([]byte("hello world"), nil) @@ -63,6 +64,9 @@ func TestScripts(t *testing.T) { t.Run("get by height", func(t *testing.T) { height := uint64(1337) + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, height, validCode, [][]byte{validArgs}). Return([]byte("hello world"), nil) @@ -77,6 +81,9 @@ func TestScripts(t *testing.T) { t.Run("get by ID", func(t *testing.T) { id, _ := flow.HexStringToIdentifier("222dc5dd51b9e4910f687e475f892f495f3352362ba318b53e318b4d78131312") + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + backend.Mock. On("ExecuteScriptAtBlockID", mocks.Anything, id, validCode, [][]byte{validArgs}). Return([]byte("hello world"), nil) @@ -89,6 +96,9 @@ func TestScripts(t *testing.T) { }) t.Run("get error", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, uint64(1337), validCode, [][]byte{validArgs}). Return(nil, status.Error(codes.Internal, "internal server error")) @@ -104,6 +114,9 @@ func TestScripts(t *testing.T) { }) t.Run("get invalid", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, mocks.Anything, mocks.Anything, mocks.Anything). Return(nil, nil) diff --git a/engine/access/rest/test_helpers.go b/engine/access/rest/test_helpers.go index fc62c8b6feb..a9723728a42 100644 --- a/engine/access/rest/test_helpers.go +++ b/engine/access/rest/test_helpers.go @@ -3,26 +3,18 @@ package rest import ( "bytes" "fmt" - "net" "net/http" "net/http/httptest" "testing" - "time" - - "google.golang.org/grpc" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/onflow/flow-go/access/mock" - engineaccessmock "github.com/onflow/flow-go/engine/access/mock" restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" - "github.com/onflow/flow-go/utils/grpcutils" - - "github.com/onflow/flow/protobuf/go/flow/access" ) const ( @@ -57,10 +49,11 @@ func newAccessRestHandler(backend *mock.API) RestServerApi { return NewRequestHandler(logger, backend) } -func newObserverRestHandler_v2(backend *mock.API, restForwarder *restmock.RestServerApi) (RestServerApi, error) { +func newObserverRestHandler(backend *mock.API, restForwarder *restmock.RestServerApi) (RestServerApi, error) { var b bytes.Buffer logger := zerolog.New(&b) - observerCollector := metrics.NewObserverCollector() // + observerCollector := metrics.NewObserverCollector() //TODO: + //metrics := metrics.NewNoopCollector() return &RestRouter{ Logger: logger, @@ -70,41 +63,6 @@ func newObserverRestHandler_v2(backend *mock.API, restForwarder *restmock.RestSe }, nil } -func newObserverRestHandler(backend *mock.API, identities flow.IdentityList) (RestServerApi, error) { - var b bytes.Buffer - logger := zerolog.New(&b) - observerCollector := metrics.NewObserverCollector() - - restForwarder, err := NewRestForwarder(logger, - identities, - time.Second, - grpcutils.DefaultMaxMsgSize) - if err != nil { - return nil, err - } - - return &RestRouter{ - Logger: logger, - Metrics: observerCollector, - Upstream: restForwarder, - Observer: NewRequestHandler(logger, backend), - }, nil -} - -func newGrpcServer(mockServer *engineaccessmock.AccessAPIServer, network string, address string, done chan int) (*grpc.Server, *net.Listener, error) { - l, err := net.Listen(network, address) - if err != nil { - return nil, nil, err - } - s := grpc.NewServer() - go func(done chan int) { - access.RegisterAccessAPIServer(s, mockServer) - _ = s.Serve(l) - done <- 1 - }(done) - return s, &l, nil -} - func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, restHandler RestServerApi) { assertResponse(t, req, http.StatusOK, expectedRespBody, restHandler) } diff --git a/engine/access/rest/transactions_test.go b/engine/access/rest/transactions_test.go index c35980d4347..36a0cfe9885 100644 --- a/engine/access/rest/transactions_test.go +++ b/engine/access/rest/transactions_test.go @@ -102,10 +102,9 @@ func validCreateBody(tx flow.TransactionBody) map[string]interface{} { } func TestGetTransactions(t *testing.T) { - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) - t.Run("get by ID without results", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -152,6 +151,8 @@ func TestGetTransactions(t *testing.T) { t.Run("Get by ID with results", func(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + tx := unittest.TransactionFixture() txr := transactionResultFixture(tx) @@ -220,12 +221,18 @@ func TestGetTransactions(t *testing.T) { }) t.Run("get by ID Invalid", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + req := getTransactionReq("invalid", false, "", "") expected := `{"code":400, "message":"invalid ID format"}` assertResponse(t, req, http.StatusBadRequest, expected, restHandler) }) t.Run("get by ID non-existing", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -275,10 +282,10 @@ func TestGetTransactionResult(t *testing.T) { } }`, bid.String(), cid.String(), id.String(), util.ToBase64(txr.Events[0].Payload), id.String()) - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) - t.Run("get by transaction ID", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + req := getTransactionResultReq(id.String(), "", "") backend.Mock. @@ -290,6 +297,8 @@ func TestGetTransactionResult(t *testing.T) { t.Run("get by block ID", func(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + req := getTransactionResultReq(id.String(), bid.String(), "") backend.Mock. @@ -301,6 +310,8 @@ func TestGetTransactionResult(t *testing.T) { t.Run("get by collection ID", func(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + req := getTransactionResultReq(id.String(), "", cid.String()) backend.Mock. @@ -312,6 +323,8 @@ func TestGetTransactionResult(t *testing.T) { t.Run("get execution statuses", func(t *testing.T) { backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + testVectors := map[*access.TransactionResult]string{{ Status: flow.TransactionStatusExpired, ErrorMessage: "", @@ -359,6 +372,9 @@ func TestGetTransactionResult(t *testing.T) { }) t.Run("get by ID Invalid", func(t *testing.T) { + backend := &mock.API{} + restHandler := newAccessRestHandler(backend) + req := getTransactionResultReq("invalid", "", "") expected := `{"code":400, "message":"invalid ID format"}` diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index 4cb5c5d7c94..fafb33f7d6b 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -22,6 +22,7 @@ import ( "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rpc" + "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" @@ -118,10 +119,9 @@ func (suite *RestAPITestSuite) SetupTest() { RESTListenAddr: unittest.DefaultAddress, } - backend, err := NewBackend( + backend, err := backend.NewBackend( suite.log, suite.state, - config, suite.collClient, nil, suite.blocks, @@ -134,7 +134,16 @@ func (suite *RestAPITestSuite) SetupTest() { suite.metrics, 0, 0, - false) + false, + 0, + 0, + 0, + 0, + 0, + nil, + nil, + nil) + require.NoError(suite.T(), err) rpcEngBuilder, err := rpc.NewBuilder( diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index bde0ceffda6..5ebdf139f47 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -189,6 +189,90 @@ func New( return b } +func NewBackend( + log zerolog.Logger, + state protocol.State, + collectionRPC accessproto.AccessAPIClient, + historicalAccessNodes []accessproto.AccessAPIClient, + blocks storage.Blocks, + headers storage.Headers, + collections storage.Collections, + transactions storage.Transactions, + executionReceipts storage.ExecutionReceipts, + executionResults storage.ExecutionResults, + chainID flow.ChainID, + accessMetrics module.AccessMetrics, + collectionGRPCPort uint, + executionGRPCPort uint, + retryEnabled bool, + maxMsgSize uint, + executionClientTimeout time.Duration, + collectionClientTimeout time.Duration, + connectionPoolSize uint, + maxHeightRange uint, + preferredExecutionNodeIDs []string, + fixedExecutionNodeIDs, + archiveAddressList []string, +) (*Backend, error) { + + var cache *lru.Cache + cacheSize := connectionPoolSize + if cacheSize > 0 { + // TODO: remove this fallback after fixing issues with evictions + // It was observed that evictions cause connection errors for in flight requests. This works around + // the issue by forcing hte pool size to be greater than the number of ENs + LNs + if cacheSize < DefaultConnectionPoolSize { + log.Warn().Msg("connection pool size below threshold, setting pool size to default value ") + cacheSize = DefaultConnectionPoolSize + } + var err error + cache, err = lru.NewWithEvict(int(cacheSize), func(_, evictedValue interface{}) { + store := evictedValue.(*CachedClient) + store.Close() + log.Debug().Str("grpc_conn_evicted", store.Address).Msg("closing grpc connection evicted from pool") + if accessMetrics != nil { + accessMetrics.ConnectionFromPoolEvicted() + } + }) + if err != nil { + return nil, fmt.Errorf("could not initialize connection pool cache: %w", err) + } + } + + connectionFactory := &ConnectionFactoryImpl{ + CollectionGRPCPort: collectionGRPCPort, + ExecutionGRPCPort: executionGRPCPort, + CollectionNodeGRPCTimeout: collectionClientTimeout, + ExecutionNodeGRPCTimeout: executionClientTimeout, + ConnectionsCache: cache, + CacheSize: cacheSize, + MaxMsgSize: maxMsgSize, + AccessMetrics: accessMetrics, + Log: log, + } + + return New(state, + collectionRPC, + historicalAccessNodes, + blocks, + headers, + collections, + transactions, + executionReceipts, + executionResults, + chainID, + accessMetrics, + connectionFactory, + retryEnabled, + maxHeightRange, + preferredExecutionNodeIDs, + fixedExecutionNodeIDs, + log, + DefaultSnapshotHistoryLimit, + archiveAddressList, + ), nil +} + func identifierList(ids []string) (flow.IdentifierList, error) { idList := make(flow.IdentifierList, len(ids)) for i, idStr := range ids { diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index cdae487a525..fbd925cee68 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -68,7 +68,7 @@ func (builder *RPCEngineBuilder) WithRpcHandler(handler accessproto.AccessAPISer return builder } -// WithRestHandler specifies that the given `RestServerApi` should be used for serving REST queries. +// WithRestHandler specifies that the given `RestServerApi` should be used for REST. func (builder *RPCEngineBuilder) WithRestHandler(handler rest.RestServerApi) *RPCEngineBuilder { builder.restHandler = handler return builder diff --git a/engine/access/rpc/rate_limit_test.go b/engine/access/rpc/rate_limit_test.go index 4cbe5a6e495..a9dadb34839 100644 --- a/engine/access/rpc/rate_limit_test.go +++ b/engine/access/rpc/rate_limit_test.go @@ -1,248 +1,256 @@ package rpc -// import ( -// "context" -// "fmt" -// "io" -// "os" -// "testing" -// "time" - -// "github.com/rs/zerolog" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/mock" -// "github.com/stretchr/testify/require" -// "github.com/stretchr/testify/suite" -// "google.golang.org/grpc" -// "google.golang.org/grpc/codes" -// "google.golang.org/grpc/credentials/insecure" -// "google.golang.org/grpc/status" - -// accessengine "github.com/onflow/flow-go/engine/access" -// accessmock "github.com/onflow/flow-go/engine/access/mock" -// "github.com/onflow/flow-go/model/flow" -// "github.com/onflow/flow-go/module/irrecoverable" -// "github.com/onflow/flow-go/module/metrics" -// module "github.com/onflow/flow-go/module/mock" -// "github.com/onflow/flow-go/network" -// protocol "github.com/onflow/flow-go/state/protocol/mock" -// storagemock "github.com/onflow/flow-go/storage/mock" -// "github.com/onflow/flow-go/utils/grpcutils" -// "github.com/onflow/flow-go/utils/unittest" -// accessproto "github.com/onflow/flow/protobuf/go/flow/access" -// ) - -// type RateLimitTestSuite struct { -// suite.Suite -// state *protocol.State -// snapshot *protocol.Snapshot -// epochQuery *protocol.EpochQuery -// log zerolog.Logger -// net *network.Network -// request *module.Requester -// collClient *accessmock.AccessAPIClient -// execClient *accessmock.ExecutionAPIClient -// me *module.Local -// chainID flow.ChainID -// metrics *metrics.NoopCollector -// rpcEng *Engine -// client accessproto.AccessAPIClient -// closer io.Closer - -// // storage -// blocks *storagemock.Blocks -// headers *storagemock.Headers -// collections *storagemock.Collections -// transactions *storagemock.Transactions -// receipts *storagemock.ExecutionReceipts - -// // test rate limit -// rateLimit int -// burstLimit int - -// ctx irrecoverable.SignalerContext -// cancel context.CancelFunc -// } - -// func (suite *RateLimitTestSuite) SetupTest() { -// suite.log = zerolog.New(os.Stdout) -// suite.net = new(network.Network) -// suite.state = new(protocol.State) -// suite.snapshot = new(protocol.Snapshot) - -// suite.epochQuery = new(protocol.EpochQuery) -// suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() -// suite.state.On("Final").Return(suite.snapshot, nil).Maybe() -// suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() -// suite.blocks = new(storagemock.Blocks) -// suite.headers = new(storagemock.Headers) -// suite.transactions = new(storagemock.Transactions) -// suite.collections = new(storagemock.Collections) -// suite.receipts = new(storagemock.ExecutionReceipts) - -// suite.collClient = new(accessmock.AccessAPIClient) -// suite.execClient = new(accessmock.ExecutionAPIClient) - -// suite.request = new(module.Requester) -// suite.request.On("EntityByID", mock.Anything, mock.Anything) - -// suite.me = new(module.Local) - -// accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) -// suite.me. -// On("NodeID"). -// Return(accessIdentity.NodeID) - -// suite.chainID = flow.Testnet -// suite.metrics = metrics.NewNoopCollector() - -// config := Config{ -// UnsecureGRPCListenAddr: unittest.DefaultAddress, -// SecureGRPCListenAddr: unittest.DefaultAddress, -// HTTPListenAddr: unittest.DefaultAddress, -// } - -// // set the rate limit to test with -// suite.rateLimit = 2 -// // set the burst limit to test with -// suite.burstLimit = 2 - -// apiRateLimt := map[string]int{ -// "Ping": suite.rateLimit, -// } - -// apiBurstLimt := map[string]int{ -// "Ping": suite.rateLimit, -// } - -// block := unittest.BlockHeaderFixture() -// suite.snapshot.On("Head").Return(block, nil) - -// backend, err := accessengine.NewBackend( -// suite.log, -// suite.state, -// config, -// suite.collClient, -// nil, -// suite.blocks, -// suite.headers, -// suite.collections, -// suite.transactions, -// nil, -// nil, -// suite.chainID, -// suite.metrics, -// 0, -// 0, -// false) -// require.NoError(suite.T(), err) - -// rpcEngBuilder, err := NewBuilder( -// suite.log, -// suite.state, -// config, -// suite.chainID, -// suite.metrics, -// false, -// apiRateLimt, -// apiBurstLimt, -// suite.me, -// backend) -// require.NoError(suite.T(), err) -// suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() -// require.NoError(suite.T(), err) -// suite.ctx, suite.cancel = irrecoverable.NewMockSignalerContextWithCancel(suite.T(), context.Background()) -// suite.rpcEng.Start(suite.ctx) -// // wait for the server to startup -// unittest.RequireCloseBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second, "engine not ready at startup") - -// // create the access api client -// suite.client, suite.closer, err = accessAPIClient(suite.rpcEng.UnsecureGRPCAddress().String()) -// require.NoError(suite.T(), err) -// } - -// func (suite *RateLimitTestSuite) TearDownTest() { -// if suite.cancel != nil { -// suite.cancel() -// } -// // close the client -// if suite.closer != nil { -// suite.closer.Close() -// } -// // close the server -// unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Done(), 2*time.Second) -// } - -// func TestRateLimit(t *testing.T) { -// suite.Run(t, new(RateLimitTestSuite)) -// } - -// // TestRatelimitingWithoutBurst tests that rate limit is correctly applied to an Access API call -// func (suite *RateLimitTestSuite) TestRatelimitingWithoutBurst() { - -// req := &accessproto.PingRequest{} -// ctx := context.Background() - -// // expect 2 upstream calls -// suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) -// suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) - -// requestCnt := 0 -// // requests within the burst should succeed -// for requestCnt < suite.rateLimit { -// resp, err := suite.client.Ping(ctx, req) -// assert.NoError(suite.T(), err) -// assert.NotNil(suite.T(), resp) -// // sleep to prevent burst -// time.Sleep(100 * time.Millisecond) -// requestCnt++ -// } - -// // request more than the limit should fail -// _, err := suite.client.Ping(ctx, req) -// suite.assertRateLimitError(err) -// } - -// // TestRatelimitingWithBurst tests that burst limit is correctly applied to an Access API call -// func (suite *RateLimitTestSuite) TestRatelimitingWithBurst() { - -// req := &accessproto.PingRequest{} -// ctx := context.Background() - -// // expect rpc.defaultBurst number of upstream calls -// suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) -// suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) - -// requestCnt := 0 -// // generate a permissible burst of request and assert that they succeed -// for requestCnt < suite.burstLimit { -// resp, err := suite.client.Ping(ctx, req) -// assert.NoError(suite.T(), err) -// assert.NotNil(suite.T(), resp) -// requestCnt++ -// } - -// // request more than the permissible burst and assert that it fails -// _, err := suite.client.Ping(ctx, req) -// suite.assertRateLimitError(err) -// } - -// func (suite *RateLimitTestSuite) assertRateLimitError(err error) { -// assert.Error(suite.T(), err) -// status, ok := status.FromError(err) -// assert.True(suite.T(), ok) -// assert.Equal(suite.T(), codes.ResourceExhausted, status.Code()) -// } - -// func accessAPIClient(address string) (accessproto.AccessAPIClient, io.Closer, error) { -// conn, err := grpc.Dial( -// address, -// grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), -// grpc.WithTransportCredentials(insecure.NewCredentials())) -// if err != nil { -// return nil, nil, fmt.Errorf("failed to connect to address %s: %w", address, err) -// } -// client := accessproto.NewAccessAPIClient(conn) -// closer := io.Closer(conn) -// return client, closer, nil -// } +import ( + "context" + "fmt" + "io" + "os" + "testing" + "time" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + accessmock "github.com/onflow/flow-go/engine/access/mock" + "github.com/onflow/flow-go/engine/access/rpc/backend" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/irrecoverable" + "github.com/onflow/flow-go/module/metrics" + module "github.com/onflow/flow-go/module/mock" + "github.com/onflow/flow-go/network" + protocol "github.com/onflow/flow-go/state/protocol/mock" + storagemock "github.com/onflow/flow-go/storage/mock" + "github.com/onflow/flow-go/utils/grpcutils" + "github.com/onflow/flow-go/utils/unittest" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" +) + +type RateLimitTestSuite struct { + suite.Suite + state *protocol.State + snapshot *protocol.Snapshot + epochQuery *protocol.EpochQuery + log zerolog.Logger + net *network.Network + request *module.Requester + collClient *accessmock.AccessAPIClient + execClient *accessmock.ExecutionAPIClient + me *module.Local + chainID flow.ChainID + metrics *metrics.NoopCollector + rpcEng *Engine + client accessproto.AccessAPIClient + closer io.Closer + + // storage + blocks *storagemock.Blocks + headers *storagemock.Headers + collections *storagemock.Collections + transactions *storagemock.Transactions + receipts *storagemock.ExecutionReceipts + + // test rate limit + rateLimit int + burstLimit int + + ctx irrecoverable.SignalerContext + cancel context.CancelFunc +} + +func (suite *RateLimitTestSuite) SetupTest() { + suite.log = zerolog.New(os.Stdout) + suite.net = new(network.Network) + suite.state = new(protocol.State) + suite.snapshot = new(protocol.Snapshot) + + suite.epochQuery = new(protocol.EpochQuery) + suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() + suite.state.On("Final").Return(suite.snapshot, nil).Maybe() + suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() + suite.blocks = new(storagemock.Blocks) + suite.headers = new(storagemock.Headers) + suite.transactions = new(storagemock.Transactions) + suite.collections = new(storagemock.Collections) + suite.receipts = new(storagemock.ExecutionReceipts) + + suite.collClient = new(accessmock.AccessAPIClient) + suite.execClient = new(accessmock.ExecutionAPIClient) + + suite.request = new(module.Requester) + suite.request.On("EntityByID", mock.Anything, mock.Anything) + + suite.me = new(module.Local) + + accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) + suite.me. + On("NodeID"). + Return(accessIdentity.NodeID) + + suite.chainID = flow.Testnet + suite.metrics = metrics.NewNoopCollector() + + config := Config{ + UnsecureGRPCListenAddr: unittest.DefaultAddress, + SecureGRPCListenAddr: unittest.DefaultAddress, + HTTPListenAddr: unittest.DefaultAddress, + } + + // set the rate limit to test with + suite.rateLimit = 2 + // set the burst limit to test with + suite.burstLimit = 2 + + apiRateLimt := map[string]int{ + "Ping": suite.rateLimit, + } + + apiBurstLimt := map[string]int{ + "Ping": suite.rateLimit, + } + + block := unittest.BlockHeaderFixture() + suite.snapshot.On("Head").Return(block, nil) + + backend, err := backend.NewBackend( + suite.log, + suite.state, + suite.collClient, + nil, + suite.blocks, + suite.headers, + suite.collections, + suite.transactions, + nil, + nil, + suite.chainID, + suite.metrics, + 0, + 0, + false, + 0, + 0, + 0, + 0, + 0, + nil, + nil, + nil) + require.NoError(suite.T(), err) + + rpcEngBuilder, err := NewBuilder( + suite.log, + suite.state, + config, + suite.chainID, + suite.metrics, + false, + apiRateLimt, + apiBurstLimt, + suite.me, + backend) + require.NoError(suite.T(), err) + suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() + require.NoError(suite.T(), err) + suite.ctx, suite.cancel = irrecoverable.NewMockSignalerContextWithCancel(suite.T(), context.Background()) + suite.rpcEng.Start(suite.ctx) + // wait for the server to startup + unittest.RequireCloseBefore(suite.T(), suite.rpcEng.Ready(), 2*time.Second, "engine not ready at startup") + + // create the access api client + suite.client, suite.closer, err = accessAPIClient(suite.rpcEng.UnsecureGRPCAddress().String()) + require.NoError(suite.T(), err) +} + +func (suite *RateLimitTestSuite) TearDownTest() { + if suite.cancel != nil { + suite.cancel() + } + // close the client + if suite.closer != nil { + suite.closer.Close() + } + // close the server + unittest.AssertClosesBefore(suite.T(), suite.rpcEng.Done(), 2*time.Second) +} + +func TestRateLimit(t *testing.T) { + suite.Run(t, new(RateLimitTestSuite)) +} + +// TestRatelimitingWithoutBurst tests that rate limit is correctly applied to an Access API call +func (suite *RateLimitTestSuite) TestRatelimitingWithoutBurst() { + + req := &accessproto.PingRequest{} + ctx := context.Background() + + // expect 2 upstream calls + suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) + suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.rateLimit) + + requestCnt := 0 + // requests within the burst should succeed + for requestCnt < suite.rateLimit { + resp, err := suite.client.Ping(ctx, req) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), resp) + // sleep to prevent burst + time.Sleep(100 * time.Millisecond) + requestCnt++ + } + + // request more than the limit should fail + _, err := suite.client.Ping(ctx, req) + suite.assertRateLimitError(err) +} + +// TestRatelimitingWithBurst tests that burst limit is correctly applied to an Access API call +func (suite *RateLimitTestSuite) TestRatelimitingWithBurst() { + + req := &accessproto.PingRequest{} + ctx := context.Background() + + // expect rpc.defaultBurst number of upstream calls + suite.execClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) + suite.collClient.On("Ping", mock.Anything, mock.Anything).Return(nil, nil).Times(suite.burstLimit) + + requestCnt := 0 + // generate a permissible burst of request and assert that they succeed + for requestCnt < suite.burstLimit { + resp, err := suite.client.Ping(ctx, req) + assert.NoError(suite.T(), err) + assert.NotNil(suite.T(), resp) + requestCnt++ + } + + // request more than the permissible burst and assert that it fails + _, err := suite.client.Ping(ctx, req) + suite.assertRateLimitError(err) +} + +func (suite *RateLimitTestSuite) assertRateLimitError(err error) { + assert.Error(suite.T(), err) + status, ok := status.FromError(err) + assert.True(suite.T(), ok) + assert.Equal(suite.T(), codes.ResourceExhausted, status.Code()) +} + +func accessAPIClient(address string) (accessproto.AccessAPIClient, io.Closer, error) { + conn, err := grpc.Dial( + address, + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, nil, fmt.Errorf("failed to connect to address %s: %w", address, err) + } + client := accessproto.NewAccessAPIClient(conn) + closer := io.Closer(conn) + return client, closer, nil +} diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index 3c96ca2740c..73b6d32349a 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -19,6 +19,7 @@ import ( "github.com/onflow/flow-go/crypto" accessmock "github.com/onflow/flow-go/engine/access/mock" "github.com/onflow/flow-go/engine/access/rpc" + "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/module/metrics" @@ -109,10 +110,9 @@ func (suite *SecureGRPCTestSuite) SetupTest() { block := unittest.BlockHeaderFixture() suite.snapshot.On("Head").Return(block, nil) - backend, err := NewBackend( + backend, err := backend.NewBackend( suite.log, suite.state, - config, suite.collClient, nil, suite.blocks, @@ -125,7 +125,15 @@ func (suite *SecureGRPCTestSuite) SetupTest() { suite.metrics, 0, 0, - false) + false, + 0, + 0, + 0, + 0, + 0, + nil, + nil, + nil) require.NoError(suite.T(), err) rpcEngBuilder, err := rpc.NewBuilder( diff --git a/module/forwarder/forwarder.go b/module/forwarder/forwarder.go index 6ee4ae4ffd0..b5cf6244d44 100644 --- a/module/forwarder/forwarder.go +++ b/module/forwarder/forwarder.go @@ -30,8 +30,8 @@ type Forwarder struct { maxMsgSize uint } -func NewForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (Forwarder, error) { - forwarder := Forwarder{maxMsgSize: maxMsgSize} +func NewForwarder(identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*Forwarder, error) { + forwarder := &Forwarder{maxMsgSize: maxMsgSize} err := forwarder.setFlowAccessAPI(identities, timeout) return forwarder, err } From 23b4cd00f62f3361d80c689be387705208ac7549 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Wed, 21 Jun 2023 15:15:37 +0300 Subject: [PATCH 07/30] Added observer rest integration test, linted --- engine/access/rest/blocks_test.go | 1 - engine/access/rest/handler.go | 5 + engine/access/rest/rest_server_api.go | 6 +- engine/access/rest/test_helpers.go | 6 +- insecure/go.sum | 2 - integration/testnet/network.go | 3 + integration/tests/access/observer_test.go | 208 ++++++++++++++++++++-- module/metrics/noop.go | 6 + module/metrics/observer.go | 5 + 9 files changed, 218 insertions(+), 24 deletions(-) diff --git a/engine/access/rest/blocks_test.go b/engine/access/rest/blocks_test.go index 2ea9a1f3f6b..7e337e07e2a 100644 --- a/engine/access/rest/blocks_test.go +++ b/engine/access/rest/blocks_test.go @@ -179,7 +179,6 @@ func TestObserverGetBlocks(t *testing.T) { require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) actualResp := responseRec.Body.String() require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description) - } } diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index 025e9eb0d01..03d8695fbb7 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -123,6 +123,11 @@ func (h *Handler) errorHandler(w http.ResponseWriter, err error, errorLogger zer h.errorResponse(w, http.StatusBadRequest, msg, errorLogger) return } + if se.Code() == codes.Unavailable { + msg := fmt.Sprintf("Invalid Upstream request: %s", se.Message()) + h.errorResponse(w, http.StatusServiceUnavailable, msg, errorLogger) + return + } } // stop going further - catch all error diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go index d37edf7f731..27d85960026 100644 --- a/engine/access/rest/rest_server_api.go +++ b/engine/access/rest/rest_server_api.go @@ -25,7 +25,7 @@ import ( // It splits requests between a local and a remote rest service. type RestRouter struct { Logger zerolog.Logger - Metrics *metrics.ObserverCollector + Metrics metrics.ObserverMetrics Upstream RestServerApi Observer *RequestHandler } @@ -427,7 +427,7 @@ func (h *RequestHandler) GetAccount(r request.GetAccount, context context.Contex account, err := h.backend.GetAccountAtBlockHeight(context, r.Address, r.Height) if err != nil { - return response, err + return response, NewNotFoundError("not found account at block height", err) } err = response.Build(account, link, expandFields) @@ -906,7 +906,7 @@ func (f *RestForwarder) GetAccount(r request.GetAccount, context context.Context accountResponse, err := upstream.GetAccountAtBlockHeight(context, getAccountAtBlockHeightRequest) if err != nil { - return response, err + return response, NewNotFoundError("not found account at block height", err) } flowAccount, err := convert.MessageToAccount(accountResponse.Account) diff --git a/engine/access/rest/test_helpers.go b/engine/access/rest/test_helpers.go index a9723728a42..8ce8c2f50d2 100644 --- a/engine/access/rest/test_helpers.go +++ b/engine/access/rest/test_helpers.go @@ -30,9 +30,8 @@ const ( func executeRequest(req *http.Request, restHandler RestServerApi) (*httptest.ResponseRecorder, error) { var b bytes.Buffer logger := zerolog.New(&b) - metrics := metrics.NewNoopCollector() - router, err := newRouter(restHandler, logger, flow.Testnet.Chain(), metrics) + router, err := newRouter(restHandler, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) if err != nil { return nil, err } @@ -52,8 +51,7 @@ func newAccessRestHandler(backend *mock.API) RestServerApi { func newObserverRestHandler(backend *mock.API, restForwarder *restmock.RestServerApi) (RestServerApi, error) { var b bytes.Buffer logger := zerolog.New(&b) - observerCollector := metrics.NewObserverCollector() //TODO: - //metrics := metrics.NewNoopCollector() + observerCollector := metrics.NewNoopCollector() return &RestRouter{ Logger: logger, diff --git a/insecure/go.sum b/insecure/go.sum index a2e659e29d8..129d83cb596 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -92,7 +92,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -1177,7 +1176,6 @@ github.com/onflow/atree v0.5.0 h1:y3lh8hY2fUo8KVE2ALVcz0EiNTq0tXJ6YTXKYVDA+3E= github.com/onflow/atree v0.5.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.38.1 h1:8YpnE1ixAGB8hF3t+slkHGhjfIBJ95dqUS+sEHrM2kY= github.com/onflow/cadence v0.38.1/go.mod h1:SpfjNhPsJxGIHbOthE9JD/e8JFaFY73joYLPsov+PY4= -github.com/onflow/flow v0.3.4 h1:FXUWVdYB90f/rjNcY0Owo30gL790tiYff9Pb/sycXYE= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.3 h1:wV+gcgOY0oJK4HLZQYQoK+mm09rW1XSxf83yqJwj0n4= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.3/go.mod h1:Osvy81E/+tscQM+d3kRFjktcIcZj2bmQ9ESqRQWDEx8= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 h1:X25A1dNajNUtE+KoV76wQ6BR6qI7G65vuuRXxDDqX7E= diff --git a/integration/testnet/network.go b/integration/testnet/network.go index 1520725b335..e16f6bcf79e 100644 --- a/integration/testnet/network.go +++ b/integration/testnet/network.go @@ -714,6 +714,9 @@ func (net *FlowNetwork) addObserver(t *testing.T, conf ObserverConfig) { nodeContainer.exposePort(AdminPort, testingdock.RandomPort(t)) nodeContainer.AddFlag("admin-addr", nodeContainer.ContainerAddr(AdminPort)) + nodeContainer.exposePort(RESTPort, testingdock.RandomPort(t)) + nodeContainer.AddFlag("rest-addr", nodeContainer.ContainerAddr(RESTPort)) + nodeContainer.opts.HealthCheck = testingdock.HealthCheckCustom(nodeContainer.HealthcheckCallback()) suiteContainer := net.suite.Container(containerOpts) diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 29b96da49e6..e36642eb662 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -2,21 +2,25 @@ package access import ( "context" + "fmt" + "net/http" + "strings" "testing" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/status" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/integration/testnet" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) func TestObserver(t *testing.T) { @@ -25,9 +29,10 @@ func TestObserver(t *testing.T) { type ObserverSuite struct { suite.Suite - net *testnet.FlowNetwork - teardown func() - local map[string]struct{} + net *testnet.FlowNetwork + teardown func() + localRpc map[string]struct{} + localRest map[string]struct{} cancel context.CancelFunc } @@ -44,7 +49,7 @@ func (s *ObserverSuite) TearDownTest() { } func (s *ObserverSuite) SetupTest() { - s.local = map[string]struct{}{ + s.localRpc = map[string]struct{}{ "Ping": {}, "GetLatestBlockHeader": {}, "GetBlockHeaderByID": {}, @@ -56,6 +61,14 @@ func (s *ObserverSuite) SetupTest() { "GetNetworkParameters": {}, } + s.localRest = map[string]struct{}{ + "getBlocksByIDs": {}, + "getBlocksByHeight": {}, + "getBlockPayloadByID": {}, + "getNetworkParameters": {}, + "getNodeVersionInfo": {}, + } + nodeConfigs := []testnet.NodeConfig{ // access node with unstaked nodes supported testnet.NewNodeConfig(flow.RoleAccess, testnet.WithLogLevel(zerolog.InfoLevel), testnet.WithAdditionalFlag("--supports-observer=true")), @@ -90,11 +103,11 @@ func (s *ObserverSuite) SetupTest() { s.net.Start(ctx) } -// TestObserver runs the following tests: +// TestObserverRPC runs the following tests: // 1. CompareRPCs: verifies that the observer client returns the same errors as the access client for rpcs proxied to the upstream AN // 2. HandledByUpstream: stops the upstream AN and verifies that the observer client returns errors for all rpcs handled by the upstream // 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all other queries -func (s *ObserverSuite) TestObserver() { +func (s *ObserverSuite) TestObserverRPC() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -111,7 +124,7 @@ func (s *ObserverSuite) TestObserver() { // verify that both clients return the same errors for proxied rpcs for _, rpc := range s.getRPCs() { // skip rpcs handled locally by observer - if _, local := s.local[rpc.name]; local { + if _, local := s.localRpc[rpc.name]; local { continue } t.Run(rpc.name, func(t *testing.T) { @@ -129,7 +142,7 @@ func (s *ObserverSuite) TestObserver() { t.Run("HandledByUpstream", func(t *testing.T) { // verify that we receive Unavailable errors from all rpcs handled upstream for _, rpc := range s.getRPCs() { - if _, local := s.local[rpc.name]; local { + if _, local := s.localRpc[rpc.name]; local { continue } t.Run(rpc.name, func(t *testing.T) { @@ -142,7 +155,7 @@ func (s *ObserverSuite) TestObserver() { t.Run("HandledByObserver", func(t *testing.T) { // verify that we receive NotFound or no error from all rpcs handled locally for _, rpc := range s.getRPCs() { - if _, local := s.local[rpc.name]; !local { + if _, local := s.localRpc[rpc.name]; !local { continue } t.Run(rpc.name, func(t *testing.T) { @@ -154,7 +167,86 @@ func (s *ObserverSuite) TestObserver() { }) } }) +} +// TestObserverRest runs the following tests: +// 1. CompareRPCs: verifies that the observer client returns the same errors as the access client for rests proxied to the upstream AN +// 2. HandledByUpstream: stops the upstream AN and verifies that the observer client returns errors for all rests handled by the upstream +// 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all other queries +func (s *ObserverSuite) TestObserverRest() { + t := s.T() + + accessAddr := s.net.ContainerByName(testnet.PrimaryAN).Addr(testnet.RESTPort) + observerAddr := s.net.ContainerByName("observer_1").Addr(testnet.RESTPort) + + httpClient := http.DefaultClient + makeHttpCall := func(method string, url string) (*http.Response, error) { + switch method { + case http.MethodGet: + return httpClient.Get(url) + case http.MethodPost: + return httpClient.Post(url, "application/json", strings.NewReader("{}")) + } + panic("not supported") + } + makeObserverCall := func(method string, path string) (*http.Response, error) { + return makeHttpCall(method, "http://"+observerAddr+"/v1"+path) + } + makeAccessCall := func(method string, path string) (*http.Response, error) { + return makeHttpCall(method, "http://"+accessAddr+"/v1"+path) + } + + t.Run("CompareEndpoints", func(t *testing.T) { + // verify that both clients return the same errors for proxied rests + for _, endpoint := range s.getRestEndpoints() { + // skip rest handled locally by observer + if _, local := s.localRest[endpoint.name]; local { + continue + } + t.Run(endpoint.name, func(t *testing.T) { + accessResp, accessErr := makeAccessCall(endpoint.method, endpoint.path) + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + assert.NoError(t, accessErr) + assert.NoError(t, observerErr) + assert.Equal(t, accessResp.Status, observerResp.Status) + }) + } + }) + + // stop the upstream access container + err := s.net.StopContainerByName(context.Background(), testnet.PrimaryAN) + require.NoError(t, err) + + t.Run("HandledByUpstream", func(t *testing.T) { + // verify that we receive StatusInternalServerError, StatusServiceUnavailable, StatusBadRequest errors from all rests handled upstream + for _, endpoint := range s.getRestEndpoints() { + if _, local := s.localRest[endpoint.name]; local { + continue + } + t.Run(endpoint.name, func(t *testing.T) { + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + require.NoError(t, observerErr) + assert.Contains(t, [...]int{ + http.StatusInternalServerError, + http.StatusServiceUnavailable, + http.StatusBadRequest}, observerResp.StatusCode) + }) + } + }) + + t.Run("HandledByObserver", func(t *testing.T) { + // verify that we receive NotFound or no error from all rests handled locally + for _, endpoint := range s.getRestEndpoints() { + if _, local := s.localRest[endpoint.name]; !local { + continue + } + t.Run(endpoint.name, func(t *testing.T) { + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + require.NoError(t, observerErr) + assert.Contains(t, [...]int{http.StatusNotFound, http.StatusOK}, observerResp.StatusCode) + }) + } + }) } func (s *ObserverSuite) getAccessClient() (accessproto.AccessAPIClient, error) { @@ -287,3 +379,91 @@ func (s *ObserverSuite) getRPCs() []RPCTest { }}, } } + +type RestEndpointTest struct { + name string + method string + path string +} + +func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { + transactionId := unittest.IdentifierFixture().String() + account, _ := unittest.AccountFixture() + block := unittest.BlockFixture() + executionResult := unittest.ExecutionResultFixture() + collection := unittest.CollectionFixture(2) + blockEvents := unittest.BlockEventsFixture(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(uint64(2))), 2) + + return []RestEndpointTest{ + { + name: "getTransactionByID", + method: http.MethodGet, + path: "/transactions/" + transactionId, + }, + { + name: "createTransaction", + method: http.MethodPost, + path: "/transactions", + }, + { + name: "getTransactionResultByID", + method: http.MethodGet, + path: fmt.Sprintf("/transaction_results/%s?block_id=%s&collection_id=%s", transactionId, block.ID().String(), collection.ID().String()), + }, + { + name: "getBlocksByIDs", + method: http.MethodGet, + path: "/blocks/" + block.ID().String(), + }, + { + name: "getBlocksByHeight", + method: http.MethodGet, + path: "/blocks?height=0", + }, + { + name: "getBlockPayloadByID", + method: http.MethodGet, + path: "/blocks/" + block.ID().String() + "/payload", + }, + { + name: "getExecutionResultByID", + method: http.MethodGet, + path: "/execution_results/" + executionResult.ID().String(), + }, + { + name: "getExecutionResultByBlockID", + method: http.MethodGet, + path: "/execution_results?block_id=" + block.ID().String(), + }, + { + name: "getCollectionByID", + method: http.MethodGet, + path: "/collections/" + collection.ID().String(), + }, + { + name: "executeScript", + method: http.MethodPost, + path: "/scripts", + }, + { + name: "getAccount", + method: http.MethodGet, + path: "/accounts/" + account.Address.HexWithPrefix(), + }, + { + name: "getEvents", + method: http.MethodGet, + path: fmt.Sprintf("/events?type=%s&start_height=%d&end_height=%d", blockEvents.Events[0].Type, 1, 3), + }, + { + name: "getNetworkParameters", + method: http.MethodGet, + path: "/network/parameters", + }, + { + name: "getNodeVersionInfo", + method: http.MethodGet, + path: "/node_version_info", + }, + } +} diff --git a/module/metrics/noop.go b/module/metrics/noop.go index eddfe1a1a26..4c6ae18c255 100644 --- a/module/metrics/noop.go +++ b/module/metrics/noop.go @@ -4,6 +4,8 @@ import ( "context" "time" + "google.golang.org/grpc/codes" + "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -297,3 +299,7 @@ func (nc *NoopCollector) AsyncProcessingStarted(string) func (nc *NoopCollector) AsyncProcessingFinished(string, time.Duration) {} func (nc *NoopCollector) OnMisbehaviorReported(string, string) {} + +var _ ObserverMetrics = (*NoopCollector)(nil) + +func (nc *NoopCollector) RecordRPC(handler, rpc string, code codes.Code) {} diff --git a/module/metrics/observer.go b/module/metrics/observer.go index 4e885c9bf4c..95116b9f8f1 100644 --- a/module/metrics/observer.go +++ b/module/metrics/observer.go @@ -6,7 +6,12 @@ import ( "google.golang.org/grpc/codes" ) +type ObserverMetrics interface { + RecordRPC(handler, rpc string, code codes.Code) +} + type ObserverCollector struct { + ObserverMetrics rpcs *prometheus.CounterVec } From ea525fdb28daf407a5331922473747ded0199339 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Wed, 21 Jun 2023 17:53:15 +0300 Subject: [PATCH 08/30] Fixed MaxMsgSize argument --- cmd/access/node_builder/access_node_builder.go | 2 +- cmd/observer/node_builder/observer_builder.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 12e6e03a683..844d39289dd 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -1007,7 +1007,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.collectionGRPCPort, builder.executionGRPCPort, builder.retryEnabled, - config.MaxHeightRange, + config.MaxMsgSize, config.ExecutionClientTimeout, config.CollectionClientTimeout, config.ConnectionPoolSize, diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index ddef152feda..95637f7f4e5 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -866,7 +866,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { 0, 0, false, - config.MaxHeightRange, + config.MaxMsgSize, config.ExecutionClientTimeout, config.CollectionClientTimeout, config.ConnectionPoolSize, From 8c8cd4875468a54795a860ef49029ae42af2fb4e Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 22 Jun 2023 17:43:45 +0300 Subject: [PATCH 09/30] Created Config structure for backend --- .../node_builder/access_node_builder.go | 55 +++++++++---------- cmd/observer/node_builder/observer_builder.go | 39 ++++++------- .../export_report.json | 6 -- engine/access/rest_api_test.go | 9 +-- engine/access/rpc/backend/backend.go | 36 +++++++----- engine/access/rpc/engine.go | 33 +++++------ engine/access/rpc/rate_limit_test.go | 8 +-- engine/access/secure_grpcr_test.go | 8 +-- 8 files changed, 86 insertions(+), 108 deletions(-) delete mode 100644 cmd/util/cmd/execution-state-extract/export_report.json diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 0a933d2ea0a..38e36baaa2f 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -147,20 +147,22 @@ func DefaultAccessNodeConfig() *AccessNodeConfig { collectionGRPCPort: 9000, executionGRPCPort: 9000, rpcConf: rpc.Config{ - UnsecureGRPCListenAddr: "0.0.0.0:9000", - SecureGRPCListenAddr: "0.0.0.0:9001", - HTTPListenAddr: "0.0.0.0:8000", - RESTListenAddr: "", - CollectionAddr: "", - HistoricalAccessAddrs: "", - CollectionClientTimeout: 3 * time.Second, - ExecutionClientTimeout: 3 * time.Second, - ConnectionPoolSize: backend.DefaultConnectionPoolSize, - MaxHeightRange: backend.DefaultMaxHeightRange, - PreferredExecutionNodeIDs: nil, - FixedExecutionNodeIDs: nil, - ArchiveAddressList: nil, - MaxMsgSize: grpcutils.DefaultMaxMsgSize, + UnsecureGRPCListenAddr: "0.0.0.0:9000", + SecureGRPCListenAddr: "0.0.0.0:9001", + HTTPListenAddr: "0.0.0.0:8000", + RESTListenAddr: "", + CollectionAddr: "", + HistoricalAccessAddrs: "", + BackendConfig: backend.Config{ + CollectionClientTimeout: 3 * time.Second, + ExecutionClientTimeout: 3 * time.Second, + ConnectionPoolSize: backend.DefaultConnectionPoolSize, + MaxHeightRange: backend.DefaultMaxHeightRange, + PreferredExecutionNodeIDs: nil, + FixedExecutionNodeIDs: nil, + ArchiveAddressList: nil, + }, + MaxMsgSize: grpcutils.DefaultMaxMsgSize, }, stateStreamConf: state_stream.Config{ MaxExecutionDataMsgSize: grpcutils.DefaultMaxMsgSize, @@ -662,15 +664,15 @@ func (builder *FlowAccessNodeBuilder) extraFlags() { flags.StringVar(&builder.rpcConf.RESTListenAddr, "rest-addr", defaultConfig.rpcConf.RESTListenAddr, "the address the REST server listens on (if empty the REST server will not be started)") flags.StringVarP(&builder.rpcConf.CollectionAddr, "static-collection-ingress-addr", "", defaultConfig.rpcConf.CollectionAddr, "the address (of the collection node) to send transactions to") flags.StringVarP(&builder.ExecutionNodeAddress, "script-addr", "s", defaultConfig.ExecutionNodeAddress, "the address (of the execution node) forward the script to") - flags.StringSliceVar(&builder.rpcConf.ArchiveAddressList, "archive-address-list", defaultConfig.rpcConf.ArchiveAddressList, "the list of address of the archive node to forward the script queries to") + flags.StringSliceVar(&builder.rpcConf.BackendConfig.ArchiveAddressList, "archive-address-list", defaultConfig.rpcConf.BackendConfig.ArchiveAddressList, "the list of address of the archive node to forward the script queries to") flags.StringVarP(&builder.rpcConf.HistoricalAccessAddrs, "historical-access-addr", "", defaultConfig.rpcConf.HistoricalAccessAddrs, "comma separated rpc addresses for historical access nodes") - flags.DurationVar(&builder.rpcConf.CollectionClientTimeout, "collection-client-timeout", defaultConfig.rpcConf.CollectionClientTimeout, "grpc client timeout for a collection node") - flags.DurationVar(&builder.rpcConf.ExecutionClientTimeout, "execution-client-timeout", defaultConfig.rpcConf.ExecutionClientTimeout, "grpc client timeout for an execution node") - flags.UintVar(&builder.rpcConf.ConnectionPoolSize, "connection-pool-size", defaultConfig.rpcConf.ConnectionPoolSize, "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") + flags.DurationVar(&builder.rpcConf.BackendConfig.CollectionClientTimeout, "collection-client-timeout", defaultConfig.rpcConf.BackendConfig.CollectionClientTimeout, "grpc client timeout for a collection node") + flags.DurationVar(&builder.rpcConf.BackendConfig.ExecutionClientTimeout, "execution-client-timeout", defaultConfig.rpcConf.BackendConfig.ExecutionClientTimeout, "grpc client timeout for an execution node") + flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, "connection-pool-size", defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") flags.UintVar(&builder.rpcConf.MaxMsgSize, "rpc-max-message-size", grpcutils.DefaultMaxMsgSize, "the maximum message size in bytes for messages sent or received over grpc") - flags.UintVar(&builder.rpcConf.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.MaxHeightRange, "maximum size for height range requests") - flags.StringSliceVar(&builder.rpcConf.PreferredExecutionNodeIDs, "preferred-execution-node-ids", defaultConfig.rpcConf.PreferredExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call e.g. b4a4dbdcd443d...,fb386a6a... etc.") - flags.StringSliceVar(&builder.rpcConf.FixedExecutionNodeIDs, "fixed-execution-node-ids", defaultConfig.rpcConf.FixedExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call if no matching preferred execution id is found e.g. b4a4dbdcd443d...,fb386a6a... etc.") + flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.BackendConfig.MaxHeightRange, "maximum size for height range requests") + flags.StringSliceVar(&builder.rpcConf.BackendConfig.PreferredExecutionNodeIDs, "preferred-execution-node-ids", defaultConfig.rpcConf.BackendConfig.PreferredExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call e.g. b4a4dbdcd443d...,fb386a6a... etc.") + flags.StringSliceVar(&builder.rpcConf.BackendConfig.FixedExecutionNodeIDs, "fixed-execution-node-ids", defaultConfig.rpcConf.BackendConfig.FixedExecutionNodeIDs, "comma separated list of execution nodes ids to choose from when making an upstream call if no matching preferred execution id is found e.g. b4a4dbdcd443d...,fb386a6a... etc.") flags.BoolVar(&builder.logTxTimeToFinalized, "log-tx-time-to-finalized", defaultConfig.logTxTimeToFinalized, "log transaction time to finalized") flags.BoolVar(&builder.logTxTimeToExecuted, "log-tx-time-to-executed", defaultConfig.logTxTimeToExecuted, "log transaction time to executed") flags.BoolVar(&builder.logTxTimeToFinalizedExecuted, "log-tx-time-to-finalized-executed", defaultConfig.logTxTimeToFinalizedExecuted, "log transaction time to finalized and executed") @@ -910,7 +912,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.rpcConf.CollectionAddr, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(builder.rpcConf.MaxMsgSize))), grpc.WithTransportCredentials(insecure.NewCredentials()), - backend.WithClientUnaryInterceptor(builder.rpcConf.CollectionClientTimeout)) + backend.WithClientUnaryInterceptor(builder.rpcConf.BackendConfig.CollectionClientTimeout)) if err != nil { return err } @@ -989,6 +991,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { }). Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { config := builder.rpcConf + backend, err := backend.NewBackend(node.Logger, node.State, builder.CollectionRPC, @@ -1005,13 +1008,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.executionGRPCPort, builder.retryEnabled, config.MaxMsgSize, - config.ExecutionClientTimeout, - config.CollectionClientTimeout, - config.ConnectionPoolSize, - config.MaxHeightRange, - config.PreferredExecutionNodeIDs, - config.FixedExecutionNodeIDs, - config.ArchiveAddressList) + builder.rpcConf.BackendConfig) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) } diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index d0754647f29..9e7ef69fb9a 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -113,19 +113,22 @@ type ObserverServiceConfig struct { func DefaultObserverServiceConfig() *ObserverServiceConfig { return &ObserverServiceConfig{ rpcConf: rpc.Config{ - UnsecureGRPCListenAddr: "0.0.0.0:9000", - SecureGRPCListenAddr: "0.0.0.0:9001", - HTTPListenAddr: "0.0.0.0:8000", - RESTListenAddr: "", - CollectionAddr: "", - HistoricalAccessAddrs: "", - CollectionClientTimeout: 3 * time.Second, - ExecutionClientTimeout: 3 * time.Second, - MaxHeightRange: backend.DefaultMaxHeightRange, - PreferredExecutionNodeIDs: nil, - FixedExecutionNodeIDs: nil, - ArchiveAddressList: nil, - MaxMsgSize: grpcutils.DefaultMaxMsgSize, + UnsecureGRPCListenAddr: "0.0.0.0:9000", + SecureGRPCListenAddr: "0.0.0.0:9001", + HTTPListenAddr: "0.0.0.0:8000", + RESTListenAddr: "", + CollectionAddr: "", + HistoricalAccessAddrs: "", + BackendConfig: backend.Config{ + CollectionClientTimeout: 3 * time.Second, + ExecutionClientTimeout: 3 * time.Second, + ConnectionPoolSize: backend.DefaultConnectionPoolSize, + MaxHeightRange: backend.DefaultMaxHeightRange, + PreferredExecutionNodeIDs: nil, + FixedExecutionNodeIDs: nil, + ArchiveAddressList: nil, + }, + MaxMsgSize: grpcutils.DefaultMaxMsgSize, }, rpcMetricsEnabled: false, apiRatelimits: nil, @@ -450,7 +453,7 @@ func (builder *ObserverServiceBuilder) extraFlags() { flags.StringVarP(&builder.rpcConf.HTTPListenAddr, "http-addr", "h", defaultConfig.rpcConf.HTTPListenAddr, "the address the http proxy server listens on") flags.StringVar(&builder.rpcConf.RESTListenAddr, "rest-addr", defaultConfig.rpcConf.RESTListenAddr, "the address the REST server listens on (if empty the REST server will not be started)") flags.UintVar(&builder.rpcConf.MaxMsgSize, "rpc-max-message-size", defaultConfig.rpcConf.MaxMsgSize, "the maximum message size in bytes for messages sent or received over grpc") - flags.UintVar(&builder.rpcConf.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.MaxHeightRange, "maximum size for height range requests") + flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.BackendConfig.MaxHeightRange, "maximum size for height range requests") flags.StringToIntVar(&builder.apiRatelimits, "api-rate-limits", defaultConfig.apiRatelimits, "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") flags.StringToIntVar(&builder.apiBurstlimits, "api-burst-limits", defaultConfig.apiBurstlimits, "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") flags.StringVar(&builder.observerNetworkingKeyPath, "observer-networking-key-path", defaultConfig.observerNetworkingKeyPath, "path to the networking key for observer") @@ -867,13 +870,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { 0, false, config.MaxMsgSize, - config.ExecutionClientTimeout, - config.CollectionClientTimeout, - config.ConnectionPoolSize, - config.MaxHeightRange, - config.PreferredExecutionNodeIDs, - config.FixedExecutionNodeIDs, - config.ArchiveAddressList) + builder.rpcConf.BackendConfig) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) diff --git a/cmd/util/cmd/execution-state-extract/export_report.json b/cmd/util/cmd/execution-state-extract/export_report.json deleted file mode 100644 index 55627d0684d..00000000000 --- a/cmd/util/cmd/execution-state-extract/export_report.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "EpochCounter": 0, - "PreviousStateCommitment": "bf63e3eff31913c99688ddbac17d5d6bddeb9574b2bf947dca47bce2d164b8dd", - "CurrentStateCommitment": "bf63e3eff31913c99688ddbac17d5d6bddeb9574b2bf947dca47bce2d164b8dd", - "ReportSucceeded": true -} \ No newline at end of file diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index fafb33f7d6b..7b418fd33ee 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -136,13 +136,8 @@ func (suite *RestAPITestSuite) SetupTest() { 0, false, 0, - 0, - 0, - 0, - 0, - nil, - nil, - nil) + config.BackendConfig, + ) require.NoError(suite.T(), err) diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index 5ebdf139f47..d5a29d04adb 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -9,7 +9,6 @@ import ( "google.golang.org/grpc/status" lru "github.com/hashicorp/golang-lru" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/rs/zerolog" "github.com/onflow/flow-go/access" @@ -21,6 +20,8 @@ import ( "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/storage" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) // maxExecutionNodesCnt is the max number of execution nodes that will be contacted to complete an execution api request @@ -78,6 +79,17 @@ type Backend struct { connFactory ConnectionFactory } +// Config defines the configurable options for creating Backend +type Config struct { + ExecutionClientTimeout time.Duration // execution API GRPC client timeout + CollectionClientTimeout time.Duration // collection API GRPC client timeout + ConnectionPoolSize uint // size of the cache for storing collection and execution connections + MaxHeightRange uint // max size of height range requests + PreferredExecutionNodeIDs []string // preferred list of upstream execution node IDs + FixedExecutionNodeIDs []string // fixed list of execution node IDs to choose from if no node ID can be chosen from the PreferredExecutionNodeIDs + ArchiveAddressList []string // the archive node address list to send script executions. when configured, script executions will be all sent to the archive node +} + func New( state protocol.State, collectionRPC accessproto.AccessAPIClient, @@ -206,17 +218,11 @@ func NewBackend( executionGRPCPort uint, retryEnabled bool, maxMsgSize uint, - executionClientTimeout time.Duration, - collectionClientTimeout time.Duration, - connectionPoolSize uint, - maxHeightRange uint, - preferredExecutionNodeIDs []string, - fixedExecutionNodeIDs, - archiveAddressList []string, + config Config, ) (*Backend, error) { var cache *lru.Cache - cacheSize := connectionPoolSize + cacheSize := config.ConnectionPoolSize if cacheSize > 0 { // TODO: remove this fallback after fixing issues with evictions // It was observed that evictions cause connection errors for in flight requests. This works around @@ -242,8 +248,8 @@ func NewBackend( connectionFactory := &ConnectionFactoryImpl{ CollectionGRPCPort: collectionGRPCPort, ExecutionGRPCPort: executionGRPCPort, - CollectionNodeGRPCTimeout: collectionClientTimeout, - ExecutionNodeGRPCTimeout: executionClientTimeout, + CollectionNodeGRPCTimeout: config.CollectionClientTimeout, + ExecutionNodeGRPCTimeout: config.ExecutionClientTimeout, ConnectionsCache: cache, CacheSize: cacheSize, MaxMsgSize: maxMsgSize, @@ -264,12 +270,12 @@ func NewBackend( accessMetrics, connectionFactory, retryEnabled, - maxHeightRange, - preferredExecutionNodeIDs, - fixedExecutionNodeIDs, + config.MaxHeightRange, + config.PreferredExecutionNodeIDs, + config.FixedExecutionNodeIDs, log, DefaultSnapshotHistoryLimit, - archiveAddressList, + config.ArchiveAddressList, ), nil } diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index 4fde8fe1acf..875a9389e6d 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "sync" - "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -32,21 +31,23 @@ import ( // A secure GRPC server here implies a server that presents a self-signed TLS certificate and a client that authenticates // the server via a pre-shared public key type Config struct { - UnsecureGRPCListenAddr string // the non-secure GRPC server address as ip:port - SecureGRPCListenAddr string // the secure GRPC server address as ip:port - TransportCredentials credentials.TransportCredentials // the secure GRPC credentials - HTTPListenAddr string // the HTTP web proxy address as ip:port - RESTListenAddr string // the REST server address as ip:port (if empty the REST server will not be started) - CollectionAddr string // the address of the upstream collection node - HistoricalAccessAddrs string // the list of all access nodes from previous spork - MaxMsgSize uint // GRPC max message size - ExecutionClientTimeout time.Duration // execution API GRPC client timeout - CollectionClientTimeout time.Duration // collection API GRPC client timeout - ConnectionPoolSize uint // size of the cache for storing collection and execution connections - MaxHeightRange uint // max size of height range requests - PreferredExecutionNodeIDs []string // preferred list of upstream execution node IDs - FixedExecutionNodeIDs []string // fixed list of execution node IDs to choose from if no node node ID can be chosen from the PreferredExecutionNodeIDs - ArchiveAddressList []string // the archive node address list to send script executions. when configured, script executions will be all sent to the archive node + UnsecureGRPCListenAddr string // the non-secure GRPC server address as ip:port + SecureGRPCListenAddr string // the secure GRPC server address as ip:port + TransportCredentials credentials.TransportCredentials // the secure GRPC credentials + HTTPListenAddr string // the HTTP web proxy address as ip:port + RESTListenAddr string // the REST server address as ip:port (if empty the REST server will not be started) + CollectionAddr string // the address of the upstream collection node + HistoricalAccessAddrs string // the list of all access nodes from previous spork + + BackendConfig backend.Config // configurable options for creating Backend + MaxMsgSize uint // GRPC max message size + //ExecutionClientTimeout time.Duration // execution API GRPC client timeout + //CollectionClientTimeout time.Duration // collection API GRPC client timeout + //ConnectionPoolSize uint // size of the cache for storing collection and execution connections + //MaxHeightRange uint // max size of height range requests + //PreferredExecutionNodeIDs []string // preferred list of upstream execution node IDs + //FixedExecutionNodeIDs []string // fixed list of execution node IDs to choose from if no node node ID can be chosen from the PreferredExecutionNodeIDs + //ArchiveAddressList []string // the archive node address list to send script executions. when configured, script executions will be all sent to the archive node } // Engine exposes the server with a simplified version of the Access API. diff --git a/engine/access/rpc/rate_limit_test.go b/engine/access/rpc/rate_limit_test.go index a9dadb34839..d0b31b19118 100644 --- a/engine/access/rpc/rate_limit_test.go +++ b/engine/access/rpc/rate_limit_test.go @@ -136,13 +136,7 @@ func (suite *RateLimitTestSuite) SetupTest() { 0, false, 0, - 0, - 0, - 0, - 0, - nil, - nil, - nil) + config.BackendConfig) require.NoError(suite.T(), err) rpcEngBuilder, err := NewBuilder( diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index 73b6d32349a..86d018e0548 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -127,13 +127,7 @@ func (suite *SecureGRPCTestSuite) SetupTest() { 0, false, 0, - 0, - 0, - 0, - 0, - nil, - nil, - nil) + config.BackendConfig) require.NoError(suite.T(), err) rpcEngBuilder, err := rpc.NewBuilder( From 2368ca2486ae299bc3ff238b7d93bccdecf5ef69 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 23 Jun 2023 13:39:39 +0300 Subject: [PATCH 10/30] Removed old unnecessary comment, added small upgrade for ObserverCollector structure --- cmd/access/node_builder/access_node_builder.go | 2 +- cmd/observer/node_builder/observer_builder.go | 2 +- engine/access/rpc/engine.go | 7 ------- module/metrics/observer.go | 3 ++- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 38e36baaa2f..6222c1d4b53 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -1008,7 +1008,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.executionGRPCPort, builder.retryEnabled, config.MaxMsgSize, - builder.rpcConf.BackendConfig) + config.BackendConfig) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) } diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 9e7ef69fb9a..ac4d1ed6823 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -870,7 +870,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { 0, false, config.MaxMsgSize, - builder.rpcConf.BackendConfig) + config.BackendConfig) if err != nil { return nil, fmt.Errorf("could not initialize backend: %w", err) diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index 875a9389e6d..e30e7d7a405 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -41,13 +41,6 @@ type Config struct { BackendConfig backend.Config // configurable options for creating Backend MaxMsgSize uint // GRPC max message size - //ExecutionClientTimeout time.Duration // execution API GRPC client timeout - //CollectionClientTimeout time.Duration // collection API GRPC client timeout - //ConnectionPoolSize uint // size of the cache for storing collection and execution connections - //MaxHeightRange uint // max size of height range requests - //PreferredExecutionNodeIDs []string // preferred list of upstream execution node IDs - //FixedExecutionNodeIDs []string // fixed list of execution node IDs to choose from if no node node ID can be chosen from the PreferredExecutionNodeIDs - //ArchiveAddressList []string // the archive node address list to send script executions. when configured, script executions will be all sent to the archive node } // Engine exposes the server with a simplified version of the Access API. diff --git a/module/metrics/observer.go b/module/metrics/observer.go index 95116b9f8f1..375aa66a2ac 100644 --- a/module/metrics/observer.go +++ b/module/metrics/observer.go @@ -11,10 +11,11 @@ type ObserverMetrics interface { } type ObserverCollector struct { - ObserverMetrics rpcs *prometheus.CounterVec } +var _ ObserverMetrics = (*ObserverCollector)(nil) + func NewObserverCollector() *ObserverCollector { return &ObserverCollector{ rpcs: promauto.NewCounterVec(prometheus.CounterOpts{ From 8854978fddf7b0fdacb8c1ec474201318eeffc6f Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 23 Jun 2023 17:04:25 +0300 Subject: [PATCH 11/30] Refactored rest api --- cmd/observer/node_builder/observer_builder.go | 7 +- .../export_report.json | 6 + engine/access/rest/api/api.go | 41 + engine/access/rest/apiproxy/forwarder.go | 604 ++++++++++ engine/access/rest/apiproxy/router.go | 126 ++ engine/access/rest/handler.go | 9 +- engine/access/rest/{ => models}/error.go | 2 +- engine/access/rest/rest_server_api.go | 1017 ----------------- engine/access/rest/router.go | 32 +- engine/access/rest/{ => routes}/accounts.go | 7 +- engine/access/rest/{ => routes}/blocks.go | 108 +- .../access/rest/{ => routes}/collections.go | 7 +- engine/access/rest/{ => routes}/events.go | 11 +- .../rest/{ => routes}/execution_result.go | 11 +- engine/access/rest/{ => routes}/network.go | 5 +- .../rest/{ => routes}/node_version_info.go | 5 +- engine/access/rest/{ => routes}/scripts.go | 7 +- .../access/rest/{ => routes}/transactions.go | 15 +- engine/access/rest/server.go | 5 +- engine/access/rest/server_request_handler.go | 346 ++++++ .../access/rest/{ => tests}/accounts_test.go | 2 +- engine/access/rest/{ => tests}/blocks_test.go | 2 +- .../rest/{ => tests}/collections_test.go | 2 +- engine/access/rest/{ => tests}/events_test.go | 20 +- .../rest/{ => tests}/execution_result_test.go | 2 +- .../access/rest/{ => tests}/network_test.go | 2 +- .../{ => tests}/node_version_info_test.go | 2 +- .../access/rest/{ => tests}/scripts_test.go | 2 +- .../access/rest/{ => tests}/test_helpers.go | 23 +- .../rest/{ => tests}/transactions_test.go | 2 +- engine/access/rest_api_test.go | 9 +- engine/access/rpc/engine.go | 3 +- engine/access/rpc/engine_builder.go | 7 +- 33 files changed, 1260 insertions(+), 1189 deletions(-) create mode 100644 cmd/util/cmd/execution-state-extract/export_report.json create mode 100644 engine/access/rest/api/api.go create mode 100644 engine/access/rest/apiproxy/forwarder.go create mode 100644 engine/access/rest/apiproxy/router.go rename engine/access/rest/{ => models}/error.go (98%) delete mode 100644 engine/access/rest/rest_server_api.go rename engine/access/rest/{ => routes}/accounts.go (59%) rename engine/access/rest/{ => routes}/blocks.go (61%) rename engine/access/rest/{ => routes}/collections.go (59%) rename engine/access/rest/{ => routes}/events.go (51%) rename engine/access/rest/{ => routes}/execution_result.go (56%) rename engine/access/rest/{ => routes}/network.go (56%) rename engine/access/rest/{ => routes}/node_version_info.go (54%) rename engine/access/rest/{ => routes}/scripts.go (57%) rename engine/access/rest/{ => routes}/transactions.go (55%) create mode 100644 engine/access/rest/server_request_handler.go rename engine/access/rest/{ => tests}/accounts_test.go (99%) rename engine/access/rest/{ => tests}/blocks_test.go (99%) rename engine/access/rest/{ => tests}/collections_test.go (99%) rename engine/access/rest/{ => tests}/events_test.go (98%) rename engine/access/rest/{ => tests}/execution_result_test.go (99%) rename engine/access/rest/{ => tests}/network_test.go (98%) rename engine/access/rest/{ => tests}/node_version_info_test.go (99%) rename engine/access/rest/{ => tests}/scripts_test.go (99%) rename engine/access/rest/{ => tests}/test_helpers.go (68%) rename engine/access/rest/{ => tests}/transactions_test.go (99%) diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index ac4d1ed6823..1920aec0106 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -30,6 +30,7 @@ import ( "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/apiproxy" "github.com/onflow/flow-go/engine/access/rest" + restapiproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/follower" @@ -912,7 +913,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { )), } - restForwarder, err := rest.NewRestForwarder(builder.Logger, + restForwarder, err := restapiproxy.NewRestForwarder(builder.Logger, builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) @@ -920,11 +921,11 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { return nil, err } - restHandler := &rest.RestRouter{ + restHandler := &restapiproxy.RestRouter{ Logger: builder.Logger, Metrics: observerCollector, Upstream: restForwarder, - Observer: rest.NewRequestHandler(builder.Logger, accessBackend), + Observer: rest.NewServerRequestHandler(builder.Logger, accessBackend), } // build the rpc engine diff --git a/cmd/util/cmd/execution-state-extract/export_report.json b/cmd/util/cmd/execution-state-extract/export_report.json new file mode 100644 index 00000000000..3c4a27478db --- /dev/null +++ b/cmd/util/cmd/execution-state-extract/export_report.json @@ -0,0 +1,6 @@ +{ + "EpochCounter": 0, + "PreviousStateCommitment": "8536ee2769a5b35be123a54e45a23d2eaf3fa9f3df3bde6a713c87c286b9ec40", + "CurrentStateCommitment": "8536ee2769a5b35be123a54e45a23d2eaf3fa9f3df3bde6a713c87c286b9ec40", + "ReportSucceeded": true +} \ No newline at end of file diff --git a/engine/access/rest/api/api.go b/engine/access/rest/api/api.go new file mode 100644 index 00000000000..aea539e0571 --- /dev/null +++ b/engine/access/rest/api/api.go @@ -0,0 +1,41 @@ +package api + +import ( + "context" + + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/model/flow" +) + +// RestServerApi is the server API for REST service. +type RestServerApi interface { + // GetTransactionByID gets a transaction by requested ID. + GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) + // CreateTransaction creates a new transaction from provided payload. + CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) + // GetTransactionResultByID retrieves transaction result by the transaction ID. + GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) + // GetBlocksByIDs gets blocks by provided ID or list of IDs. + GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) + // GetBlocksByHeight gets blocks by provided height. + GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) + // GetBlockPayloadByID gets block payload by ID + GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) + // GetExecutionResultByID gets execution result by the ID. + GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) + // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. + GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) + // GetCollectionByID retrieves a collection by ID and builds a response + GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) + // ExecuteScript handler sends the script from the request to be executed. + ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) + // GetAccount handler retrieves account by address and returns the response. + GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) + // GetEvents for the provided block range or list of block IDs filtered by type. + GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) + // GetNetworkParameters returns network-wide parameters of the blockchain + GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) + // GetNodeVersionInfo returns node version information + GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) +} diff --git a/engine/access/rest/apiproxy/forwarder.go b/engine/access/rest/apiproxy/forwarder.go new file mode 100644 index 00000000000..6a62e302973 --- /dev/null +++ b/engine/access/rest/apiproxy/forwarder.go @@ -0,0 +1,604 @@ +package apiproxy + +import ( + "context" + "fmt" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/engine/access/rest/routes" + "github.com/onflow/flow-go/engine/common/rpc/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/forwarder" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" +) + +// RestForwarder handles the request forwarding to upstream +type RestForwarder struct { + log zerolog.Logger + *forwarder.Forwarder +} + +var _ api.RestServerApi = (*RestForwarder)(nil) + +// NewRestForwarder returns new RestForwarder. +func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*RestForwarder, error) { + f, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) + + restForwarder := &RestForwarder{ + log: log, + Forwarder: f, + } + return restForwarder, err +} + +// GetTransactionByID gets a transaction by requested ID. +func (f *RestForwarder) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { + var response models.Transaction + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getTransactionRequest := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + } + transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) + if err != nil { + return response, err + } + + var transactionResultResponse *accessproto.TransactionResultResponse + // only lookup result if transaction result is to be expanded + if r.ExpandsResult { + getTransactionResultRequest := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + BlockId: r.BlockID[:], + CollectionId: r.CollectionID[:], + } + transactionResultResponse, err = upstream.GetTransactionResult(context, getTransactionResultRequest) + if err != nil { + return response, err + } + } + flowTransaction, err := convert.MessageToTransaction(transactionResponse.Transaction, chain) + if err != nil { + return response, err + } + + flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) + + response.Build(&flowTransaction, flowTransactionResult, link) + return response, nil +} + +// CreateTransaction creates a new transaction from provided payload. +func (f *RestForwarder) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + var response models.Transaction + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + entitiesTransaction := convert.TransactionToMessage(r.Transaction) + sendTransactionRequest := &accessproto.SendTransactionRequest{ + Transaction: entitiesTransaction, + } + + _, err = upstream.SendTransaction(context, sendTransactionRequest) + if err != nil { + return response, err + } + + response.Build(&r.Transaction, nil, link) + return response, nil +} + +// GetTransactionResultByID retrieves transaction result by the transaction ID. +func (f *RestForwarder) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + var response models.TransactionResult + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getTransactionResult := &accessproto.GetTransactionRequest{ + Id: r.ID[:], + BlockId: r.BlockID[:], + CollectionId: r.CollectionID[:], + } + transactionResultResponse, err := upstream.GetTransactionResult(context, getTransactionResult) + if err != nil { + return response, err + } + + flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) + response.Build(flowTransactionResult, r.ID, link) + return response, nil +} + +// GetBlocksByIDs gets blocks by provided ID or list of IDs. +func (f *RestForwarder) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + blocks := make([]*models.Block, len(r.IDs)) + + upstream, err := f.FaultTolerantClient() + if err != nil { + return blocks, err + } + + for i, id := range r.IDs { + block, err := getBlockFromGrpc(routes.ForID(&id), context, expandFields, upstream, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil +} + +// GetBlocksByHeight gets blocks by provided height. +func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + req, err := r.GetBlockRequest() + if err != nil { + return nil, models.NewBadRequestError(err) + } + + upstream, err := f.FaultTolerantClient() + if err != nil { + return nil, err + } + + if req.FinalHeight || req.SealedHeight { + block, err := getBlockFromGrpc(routes.ForFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + + return []*models.Block{block}, nil + } + + // if the query is /blocks/height=1000,1008,1049... + if req.HasHeights() { + blocks := make([]*models.Block, len(req.Heights)) + for i, height := range req.Heights { + block, err := getBlockFromGrpc(routes.ForHeight(height), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil + } + + // support providing end height as "sealed" or "final" + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ + IsSealed: req.EndHeight == request.SealedHeight, + } + blockResponse, err := upstream.GetLatestBlock(r.Context(), getLatestBlockRequest) + if err != nil { + return nil, err + } + + req.EndHeight = blockResponse.Block.BlockHeader.Height // overwrite special value height with fetched + + if req.StartHeight > req.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) + } + } + + blocks := make([]*models.Block, 0) + // start and end height inclusive + for i := req.StartHeight; i <= req.EndHeight; i++ { + block, err := getBlockFromGrpc(routes.ForHeight(i), r.Context(), r.ExpandFields, upstream, link) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +// GetBlockPayloadByID gets block payload by ID +func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { + var payload models.BlockPayload + + upstream, err := f.FaultTolerantClient() + if err != nil { + return payload, err + } + + blkProvider := routes.NewBlockFromGrpcProvider(upstream, routes.ForID(&r.ID)) + block, _, statusErr := blkProvider.GetBlock(context) + if statusErr != nil { + return payload, statusErr + } + + flowPayload, err := convert.PayloadFromMessage(block) + if err != nil { + return payload, err + } + + err = payload.Build(flowPayload) + if err != nil { + return payload, err + } + + return payload, nil +} + +// GetExecutionResultByID gets execution result by the ID. +func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + var response models.ExecutionResult + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + executionResultByIDRequest := &accessproto.GetExecutionResultByIDRequest{ + Id: r.ID[:], + } + + executionResultByIDResponse, err := upstream.GetExecutionResultByID(context, executionResultByIDRequest) + if err != nil { + return response, err + } + + if executionResultByIDResponse == nil { + err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) + return response, models.NewNotFoundError(err.Error(), err) + } + + flowExecResult, err := convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) + if err != nil { + return response, err + } + err = response.Build(flowExecResult, link) + if err != nil { + return response, err + } + + return response, nil +} + +// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. +func (f *RestForwarder) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + // for each block ID we retrieve execution result + results := make([]models.ExecutionResult, len(r.BlockIDs)) + + upstream, err := f.FaultTolerantClient() + if err != nil { + return results, err + } + + for i, id := range r.BlockIDs { + getExecutionResultForBlockID := &accessproto.GetExecutionResultForBlockIDRequest{ + BlockId: id[:], + } + executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockID) + if err != nil { + return nil, err + } + + var response models.ExecutionResult + flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) + if err != nil { + return nil, err + } + err = response.Build(flowExecResult, link) + if err != nil { + return nil, err + } + results[i] = response + } + + return results, nil +} + +// GetCollectionByID retrieves a collection by ID and builds a response +func (f *RestForwarder) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { + var response models.Collection + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getCollectionByIDRequest := &accessproto.GetCollectionByIDRequest{ + Id: r.ID[:], + } + + collectionResponse, err := upstream.GetCollectionByID(context, getCollectionByIDRequest) + if err != nil { + return response, err + } + + // if we expand transactions in the query retrieve each transaction data + transactions := make([]*entities.Transaction, 0) + if r.ExpandsTransactions { + for _, tid := range collectionResponse.Collection.TransactionIds { + getTransactionRequest := &accessproto.GetTransactionRequest{ + Id: tid, + } + transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) + if err != nil { + return response, err + } + + transactions = append(transactions, transactionResponse.Transaction) + } + } + + err = response.BuildFromGrpc(collectionResponse.Collection, transactions, link, expandFields, chain) + if err != nil { + return response, err + } + + return response, nil +} + +// ExecuteScript handler sends the script from the request to be executed. +func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { + upstream, err := f.FaultTolerantClient() + if err != nil { + return nil, err + } + + if r.BlockID != flow.ZeroID { + executeScriptAtBlockIDRequest := &accessproto.ExecuteScriptAtBlockIDRequest{ + BlockId: r.BlockID[:], + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(context, executeScriptAtBlockIDRequest) + if err != nil { + return nil, err + } + return executeScriptAtBlockIDResponse.Value, nil + } + + // default to sealed height + if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { + executeScriptAtLatestBlockRequest := &accessproto.ExecuteScriptAtLatestBlockRequest{ + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(context, executeScriptAtLatestBlockRequest) + if err != nil { + return nil, err + } + return executeScriptAtLatestBlockResponse.Value, nil + } + + if r.BlockHeight == request.FinalHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: false, + } + getLatestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return nil, err + } + r.BlockHeight = getLatestBlockHeaderResponse.Block.Height + } + + executeScriptAtBlockHeightRequest := &accessproto.ExecuteScriptAtBlockHeightRequest{ + BlockHeight: r.BlockHeight, + Script: r.Script.Source, + Arguments: r.Script.Args, + } + executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(context, executeScriptAtBlockHeightRequest) + if err != nil { + return nil, err + } + return executeScriptAtBlockHeightResponse.Value, nil +} + +// GetAccount handler retrieves account by address and returns the response. +func (f *RestForwarder) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + var response models.Account + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it + if r.Height == request.FinalHeight || r.Height == request.SealedHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: r.Height == request.SealedHeight, + } + blockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return response, err + } + r.Height = blockHeaderResponse.Block.Height + } + getAccountAtBlockHeightRequest := &accessproto.GetAccountAtBlockHeightRequest{ + Address: r.Address.Bytes(), + BlockHeight: r.Height, + } + + accountResponse, err := upstream.GetAccountAtBlockHeight(context, getAccountAtBlockHeightRequest) + if err != nil { + return response, models.NewNotFoundError("not found account at block height", err) + } + + flowAccount, err := convert.MessageToAccount(accountResponse.Account) + if err != nil { + return response, err + } + + err = response.Build(flowAccount, link, expandFields) + return response, err +} + +// GetEvents for the provided block range or list of block IDs filtered by type. +func (f *RestForwarder) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { + // if the request has block IDs provided then return events for block IDs + var blocksEvents models.BlocksEvents + + upstream, err := f.FaultTolerantClient() + if err != nil { + return blocksEvents, err + } + + if len(r.BlockIDs) > 0 { + var blockIds [][]byte + for _, id := range r.BlockIDs { + blockIds = append(blockIds, id[:]) + } + getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ + Type: r.Type, + BlockIds: blockIds, + } + eventsResponse, err := upstream.GetEventsForBlockIDs(context, getEventsForBlockIDsRequest) + if err != nil { + return nil, err + } + + blocksEvents.BuildFromGrpc(eventsResponse.Results) + + return blocksEvents, nil + } + + // if end height is provided with special values then load the height + if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: r.EndHeight == request.SealedHeight, + } + latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) + if err != nil { + return nil, err + } + + r.EndHeight = latestBlockHeaderResponse.Block.Height + // special check after we resolve special height value + if r.StartHeight > r.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) + } + } + + // if request provided block height range then return events for that range + getEventsForHeightRangeRequest := &accessproto.GetEventsForHeightRangeRequest{ + Type: r.Type, + StartHeight: r.StartHeight, + EndHeight: r.EndHeight, + } + eventsResponse, err := upstream.GetEventsForHeightRange(context, getEventsForHeightRangeRequest) + if err != nil { + return nil, err + } + + blocksEvents.BuildFromGrpc(eventsResponse.Results) + return blocksEvents, nil +} + +// GetNetworkParameters returns network-wide parameters of the blockchain +func (f *RestForwarder) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { + var response models.NetworkParameters + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getNetworkParametersRequest := &accessproto.GetNetworkParametersRequest{} + getNetworkParametersResponse, err := upstream.GetNetworkParameters(r.Context(), getNetworkParametersRequest) + if err != nil { + return response, err + } + response.BuildFromGrpc(getNetworkParametersResponse) + return response, nil +} + +// GetNodeVersionInfo returns node version information +func (f *RestForwarder) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { + var response models.NodeVersionInfo + + upstream, err := f.FaultTolerantClient() + if err != nil { + return response, err + } + + getNodeVersionInfoRequest := &accessproto.GetNodeVersionInfoRequest{} + getNodeVersionInfoResponse, err := upstream.GetNodeVersionInfo(r.Context(), getNodeVersionInfoRequest) + if err != nil { + return response, err + } + + response.BuildFromGrpc(getNodeVersionInfoResponse.Info) + return response, nil +} + +func getBlockFromGrpc(option routes.BlockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { + // lookup block + blkProvider := routes.NewBlockFromGrpcProvider(upstream, option) + blk, blockStatus, err := blkProvider.GetBlock(context) + if err != nil { + return nil, err + } + + // lookup execution result + // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) + var block models.Block + getExecutionResultForBlockIDRequest := &accessproto.GetExecutionResultForBlockIDRequest{ + BlockId: blk.Id, + } + + executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockIDRequest) + if err != nil { + return nil, err + } + + flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) + if err != nil { + return nil, err + } + + flowBlock, err := convert.MessageToBlock(blk) + if err != nil { + return nil, err + } + + flowBlockStatus, err := convert.MessagesToBlockStatus(blockStatus) + if err != nil { + return nil, err + } + + if err != nil { + // handle case where execution result is not yet available + if se, ok := status.FromError(err); ok { + if se.Code() == codes.NotFound { + err := block.Build(flowBlock, nil, link, flowBlockStatus, expandFields) + if err != nil { + return nil, err + } + return &block, nil + } + } + return nil, err + } + + err = block.Build(flowBlock, flowExecResult, link, flowBlockStatus, expandFields) + if err != nil { + return nil, err + } + return &block, nil +} diff --git a/engine/access/rest/apiproxy/router.go b/engine/access/rest/apiproxy/router.go new file mode 100644 index 00000000000..beb74b6ca41 --- /dev/null +++ b/engine/access/rest/apiproxy/router.go @@ -0,0 +1,126 @@ +package apiproxy + +import ( + "context" + + "google.golang.org/grpc/status" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/metrics" +) + +// RestRouter is a structure that represents the routing proxy algorithm for observer node. +// It splits requests between a local requests and request forwarding to upstream service. +type RestRouter struct { + Logger zerolog.Logger + Metrics metrics.ObserverMetrics + Upstream api.RestServerApi + Observer api.RestServerApi +} + +func (r *RestRouter) log(handler, rpc string, err error) { + code := status.Code(err) + r.Metrics.RecordRPC(handler, rpc, code) + + logger := r.Logger.With(). + Str("handler", handler). + Str("rest_method", rpc). + Str("rest_code", code.String()). + Logger() + + if err != nil { + logger.Error().Err(err).Msg("request failed") + return + } + + logger.Info().Msg("request succeeded") +} + +func (r *RestRouter) GetTransactionByID(req request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { + res, err := r.Upstream.GetTransactionByID(req, context, link, chain) + r.log("upstream", "GetNodeVersionInfo", err) + return res, err +} + +func (r *RestRouter) CreateTransaction(req request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + res, err := r.Upstream.CreateTransaction(req, context, link) + r.log("upstream", "CreateTransaction", err) + return res, err +} + +func (r *RestRouter) GetTransactionResultByID(req request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + res, err := r.Upstream.GetTransactionResultByID(req, context, link) + r.log("upstream", "GetTransactionResultByID", err) + return res, err +} + +func (r *RestRouter) GetBlocksByIDs(req request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + res, err := r.Observer.GetBlocksByIDs(req, context, expandFields, link) + r.log("observer", "GetBlocksByIDs", err) + return res, err +} + +func (r *RestRouter) GetBlocksByHeight(req *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + res, err := r.Observer.GetBlocksByHeight(req, link) + r.log("observer", "GetBlocksByHeight", err) + return res, err +} + +func (r *RestRouter) GetBlockPayloadByID(req request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) { + res, err := r.Observer.GetBlockPayloadByID(req, context, link) + r.log("observer", "GetBlockPayloadByID", err) + return res, err +} + +func (r *RestRouter) GetExecutionResultByID(req request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + res, err := r.Upstream.GetExecutionResultByID(req, context, link) + r.log("upstream", "GetExecutionResultByID", err) + return res, err +} + +func (r *RestRouter) GetExecutionResultsByBlockIDs(req request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + res, err := r.Upstream.GetExecutionResultsByBlockIDs(req, context, link) + r.log("upstream", "GetExecutionResultsByBlockIDs", err) + return res, err +} + +func (r *RestRouter) GetCollectionByID(req request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { + res, err := r.Upstream.GetCollectionByID(req, context, expandFields, link, chain) + r.log("upstream", "GetCollectionByID", err) + return res, err +} + +func (r *RestRouter) ExecuteScript(req request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { + res, err := r.Upstream.ExecuteScript(req, context, link) + r.log("upstream", "ExecuteScript", err) + return res, err +} + +func (r *RestRouter) GetAccount(req request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + res, err := r.Upstream.GetAccount(req, context, expandFields, link) + r.log("upstream", "GetAccount", err) + return res, err +} + +func (r *RestRouter) GetEvents(req request.GetEvents, context context.Context) (models.BlocksEvents, error) { + res, err := r.Upstream.GetEvents(req, context) + r.log("upstream", "GetEvents", err) + return res, err +} + +func (r *RestRouter) GetNetworkParameters(req *request.Request) (models.NetworkParameters, error) { + res, err := r.Observer.GetNetworkParameters(req) + r.log("observer", "GetNetworkParameters", err) + return res, err +} + +func (r *RestRouter) GetNodeVersionInfo(req *request.Request) (models.NodeVersionInfo, error) { + res, err := r.Observer.GetNodeVersionInfo(req) + r.log("observer", "GetNodeVersionInfo", err) + return res, err +} diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index 03d8695fbb7..c56b5ccf114 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -11,6 +11,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/util" @@ -24,7 +25,7 @@ const MaxRequestSize = 2 << 20 // 2MB // it fetches necessary resources and returns an error or response model. type ApiHandlerFunc func( r *request.Request, - srv RestServerApi, + srv api.RestServerApi, generator models.LinkGenerator, ) (interface{}, error) @@ -33,7 +34,7 @@ type ApiHandlerFunc func( // wraps functionality for handling error and responses outside of endpoint handling. type Handler struct { logger zerolog.Logger - restServerAPI RestServerApi + restServerAPI api.RestServerApi linkGenerator models.LinkGenerator apiHandlerFunc ApiHandlerFunc chain flow.Chain @@ -41,7 +42,7 @@ type Handler struct { func NewHandler( logger zerolog.Logger, - restServerAPI RestServerApi, + restServerAPI api.RestServerApi, handlerFunc ApiHandlerFunc, generator models.LinkGenerator, chain flow.Chain, @@ -92,7 +93,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) errorHandler(w http.ResponseWriter, err error, errorLogger zerolog.Logger) { // rest status type error should be returned with status and user message provided - var statusErr StatusError + var statusErr models.StatusError if errors.As(err, &statusErr) { h.errorResponse(w, statusErr.Status(), statusErr.UserMessage(), errorLogger) return diff --git a/engine/access/rest/error.go b/engine/access/rest/models/error.go similarity index 98% rename from engine/access/rest/error.go rename to engine/access/rest/models/error.go index 7403510ba55..2247b38743b 100644 --- a/engine/access/rest/error.go +++ b/engine/access/rest/models/error.go @@ -1,4 +1,4 @@ -package rest +package models import "net/http" diff --git a/engine/access/rest/rest_server_api.go b/engine/access/rest/rest_server_api.go deleted file mode 100644 index 27d85960026..00000000000 --- a/engine/access/rest/rest_server_api.go +++ /dev/null @@ -1,1017 +0,0 @@ -package rest - -import ( - "context" - "fmt" - "time" - - "google.golang.org/grpc/status" - - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/engine/common/rpc/convert" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/forwarder" - "github.com/onflow/flow-go/module/metrics" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/onflow/flow/protobuf/go/flow/entities" -) - -// RestRouter is a structure that represents the routing proxy algorithm. -// It splits requests between a local and a remote rest service. -type RestRouter struct { - Logger zerolog.Logger - Metrics metrics.ObserverMetrics - Upstream RestServerApi - Observer *RequestHandler -} - -func (r *RestRouter) log(handler, rpc string, err error) { - code := status.Code(err) - r.Metrics.RecordRPC(handler, rpc, code) - - logger := r.Logger.With(). - Str("handler", handler). - Str("rest_method", rpc). - Str("rest_code", code.String()). - Logger() - - if err != nil { - logger.Error().Err(err).Msg("request failed") - return - } - - logger.Info().Msg("request succeeded") -} - -func (r *RestRouter) GetTransactionByID(req request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { - res, err := r.Upstream.GetTransactionByID(req, context, link, chain) - r.log("upstream", "GetNodeVersionInfo", err) - return res, err -} - -func (r *RestRouter) CreateTransaction(req request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - res, err := r.Upstream.CreateTransaction(req, context, link) - r.log("upstream", "CreateTransaction", err) - return res, err -} - -func (r *RestRouter) GetTransactionResultByID(req request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - res, err := r.Upstream.GetTransactionResultByID(req, context, link) - r.log("upstream", "GetTransactionResultByID", err) - return res, err -} - -func (r *RestRouter) GetBlocksByIDs(req request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - res, err := r.Observer.GetBlocksByIDs(req, context, expandFields, link) - r.log("observer", "GetBlocksByIDs", err) - return res, err -} - -func (r *RestRouter) GetBlocksByHeight(req *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - res, err := r.Observer.GetBlocksByHeight(req, link) - r.log("observer", "GetBlocksByHeight", err) - return res, err -} - -func (r *RestRouter) GetBlockPayloadByID(req request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) { - res, err := r.Observer.GetBlockPayloadByID(req, context, link) - r.log("observer", "GetBlockPayloadByID", err) - return res, err -} - -func (r *RestRouter) GetExecutionResultByID(req request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - res, err := r.Upstream.GetExecutionResultByID(req, context, link) - r.log("upstream", "GetExecutionResultByID", err) - return res, err -} - -func (r *RestRouter) GetExecutionResultsByBlockIDs(req request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - res, err := r.Upstream.GetExecutionResultsByBlockIDs(req, context, link) - r.log("upstream", "GetExecutionResultsByBlockIDs", err) - return res, err -} - -func (r *RestRouter) GetCollectionByID(req request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { - res, err := r.Upstream.GetCollectionByID(req, context, expandFields, link, chain) - r.log("upstream", "GetCollectionByID", err) - return res, err -} - -func (r *RestRouter) ExecuteScript(req request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { - res, err := r.Upstream.ExecuteScript(req, context, link) - r.log("upstream", "ExecuteScript", err) - return res, err -} - -func (r *RestRouter) GetAccount(req request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - res, err := r.Upstream.GetAccount(req, context, expandFields, link) - r.log("upstream", "GetAccount", err) - return res, err -} - -func (r *RestRouter) GetEvents(req request.GetEvents, context context.Context) (models.BlocksEvents, error) { - res, err := r.Upstream.GetEvents(req, context) - r.log("upstream", "GetEvents", err) - return res, err -} - -func (r *RestRouter) GetNetworkParameters(req *request.Request) (models.NetworkParameters, error) { - res, err := r.Observer.GetNetworkParameters(req) - r.log("observer", "GetNetworkParameters", err) - return res, err -} - -func (r *RestRouter) GetNodeVersionInfo(req *request.Request) (models.NodeVersionInfo, error) { - res, err := r.Observer.GetNodeVersionInfo(req) - r.log("observer", "GetNodeVersionInfo", err) - return res, err -} - -// RestServerApi is the server API for REST service. -type RestServerApi interface { - // GetTransactionByID gets a transaction by requested ID. - GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) - // CreateTransaction creates a new transaction from provided payload. - CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) - // GetTransactionResultByID retrieves transaction result by the transaction ID. - GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) - // GetBlocksByIDs gets blocks by provided ID or list of IDs. - GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) - // GetBlocksByHeight gets blocks by provided height. - GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) - // GetBlockPayloadByID gets block payload by ID - GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) - // GetExecutionResultByID gets execution result by the ID. - GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) - // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. - GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) - // GetCollectionByID retrieves a collection by ID and builds a response - GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) - // ExecuteScript handler sends the script from the request to be executed. - ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) - // GetAccount handler retrieves account by address and returns the response. - GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) - // GetEvents for the provided block range or list of block IDs filtered by type. - GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) - // GetNetworkParameters returns network-wide parameters of the blockchain - GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) - // GetNodeVersionInfo returns node version information - GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) -} - -// RequestHandler is a structure that represents local requests -type RequestHandler struct { - RestServerApi - log zerolog.Logger - backend access.API -} - -// NewRequestHandler returns new RequestHandler. -func NewRequestHandler(log zerolog.Logger, backend access.API) *RequestHandler { - return &RequestHandler{ - log: log, - backend: backend, - } -} - -// GetTransactionByID gets a transaction by requested ID. -func (h *RequestHandler) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, _ flow.Chain) (models.Transaction, error) { - var response models.Transaction - - tx, err := h.backend.GetTransaction(context, r.ID) - if err != nil { - return response, err - } - - var txr *access.TransactionResult - // only lookup result if transaction result is to be expanded - if r.ExpandsResult { - txr, err = h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) - if err != nil { - return response, err - } - } - - response.Build(tx, txr, link) - return response, nil -} - -// CreateTransaction creates a new transaction from provided payload. -func (h *RequestHandler) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - var response models.Transaction - - err := h.backend.SendTransaction(context, &r.Transaction) - if err != nil { - return response, err - } - - response.Build(&r.Transaction, nil, link) - return response, nil -} - -// GetTransactionResultByID retrieves transaction result by the transaction ID. -func (h *RequestHandler) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - var response models.TransactionResult - - txr, err := h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) - if err != nil { - return response, err - } - - response.Build(txr, r.ID, link) - return response, nil -} - -// GetBlocksByIDs gets blocks by provided ID or list of IDs. -func (h *RequestHandler) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - blocks := make([]*models.Block, len(r.IDs)) - - for i, id := range r.IDs { - block, err := getBlock(forID(&id), context, expandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil -} - -// GetBlocksByHeight gets blocks by provided height. -func (h *RequestHandler) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - req, err := r.GetBlockRequest() - if err != nil { - return nil, NewBadRequestError(err) - } - - if req.FinalHeight || req.SealedHeight { - block, err := getBlock(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - - return []*models.Block{block}, nil - } - - // if the query is /blocks/height=1000,1008,1049... - if req.HasHeights() { - blocks := make([]*models.Block, len(req.Heights)) - for i, height := range req.Heights { - block, err := getBlock(forHeight(height), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil - } - - // support providing end height as "sealed" or "final" - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - latest, _, err := h.backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - req.EndHeight = latest.Header.Height // overwrite special value height with fetched - - if req.StartHeight > req.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) - } - } - - blocks := make([]*models.Block, 0) - // start and end height inclusive - for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := getBlock(forHeight(i), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -// GetBlockPayloadByID gets block payload by ID -func (h *RequestHandler) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { - var payload models.BlockPayload - - blkProvider := NewBlockRequestProvider(h.backend, forID(&r.ID)) - blk, _, statusErr := blkProvider.getBlock(context) - if statusErr != nil { - return payload, statusErr - } - - err := payload.Build(blk.Payload) - if err != nil { - return payload, err - } - - return payload, nil -} - -// GetExecutionResultByID gets execution result by the ID. -func (h *RequestHandler) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - var response models.ExecutionResult - - res, err := h.backend.GetExecutionResultByID(context, r.ID) - if err != nil { - return response, err - } - - if res == nil { - err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) - return response, NewNotFoundError(err.Error(), err) - } - - err = response.Build(res, link) - if err != nil { - return response, err - } - - return response, nil -} - -// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func (h *RequestHandler) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - // for each block ID we retrieve execution result - results := make([]models.ExecutionResult, len(r.BlockIDs)) - for i, id := range r.BlockIDs { - res, err := h.backend.GetExecutionResultForBlockID(context, id) - if err != nil { - return nil, err - } - - var response models.ExecutionResult - err = response.Build(res, link) - if err != nil { - return nil, err - } - results[i] = response - } - - return results, nil -} - -// GetCollectionByID retrieves a collection by ID and builds a response -func (h *RequestHandler) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, _ flow.Chain) (models.Collection, error) { - var response models.Collection - - collection, err := h.backend.GetCollectionByID(context, r.ID) - if err != nil { - return response, err - } - - // if we expand transactions in the query retrieve each transaction data - transactions := make([]*flow.TransactionBody, 0) - if r.ExpandsTransactions { - for _, tid := range collection.Transactions { - tx, err := h.backend.GetTransaction(context, tid) - if err != nil { - return response, err - } - - transactions = append(transactions, tx) - } - } - - err = response.Build(collection, transactions, link, expandFields) - if err != nil { - return response, err - } - - return response, nil -} - -// ExecuteScript handler sends the script from the request to be executed. -func (h *RequestHandler) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { - if r.BlockID != flow.ZeroID { - return h.backend.ExecuteScriptAtBlockID(context, r.BlockID, r.Script.Source, r.Script.Args) - } - - // default to sealed height - if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { - return h.backend.ExecuteScriptAtLatestBlock(context, r.Script.Source, r.Script.Args) - } - - if r.BlockHeight == request.FinalHeight { - finalBlock, _, err := h.backend.GetLatestBlockHeader(context, false) - if err != nil { - return nil, err - } - r.BlockHeight = finalBlock.Height - } - - return h.backend.ExecuteScriptAtBlockHeight(context, r.BlockHeight, r.Script.Source, r.Script.Args) -} - -// GetAccount handler retrieves account by address and returns the response. -func (h *RequestHandler) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - var response models.Account - - // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it - if r.Height == request.FinalHeight || r.Height == request.SealedHeight { - header, _, err := h.backend.GetLatestBlockHeader(context, r.Height == request.SealedHeight) - if err != nil { - return response, err - } - r.Height = header.Height - } - - account, err := h.backend.GetAccountAtBlockHeight(context, r.Address, r.Height) - if err != nil { - return response, NewNotFoundError("not found account at block height", err) - } - - err = response.Build(account, link, expandFields) - return response, err -} - -// GetEvents for the provided block range or list of block IDs filtered by type. -func (h *RequestHandler) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { - // if the request has block IDs provided then return events for block IDs - var blocksEvents models.BlocksEvents - if len(r.BlockIDs) > 0 { - events, err := h.backend.GetEventsForBlockIDs(context, r.Type, r.BlockIDs) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil - } - - // if end height is provided with special values then load the height - if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { - latest, _, err := h.backend.GetLatestBlockHeader(context, r.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - r.EndHeight = latest.Height - // special check after we resolve special height value - if r.StartHeight > r.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) - } - } - - // if request provided block height range then return events for that range - events, err := h.backend.GetEventsForHeightRange(context, r.Type, r.StartHeight, r.EndHeight) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil -} - -// GetNetworkParameters returns network-wide parameters of the blockchain -func (h *RequestHandler) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { - params := h.backend.GetNetworkParameters(r.Context()) - - var response models.NetworkParameters - response.Build(¶ms) - return response, nil -} - -// GetNodeVersionInfo returns node version information -func (h *RequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { - var response models.NodeVersionInfo - - params, err := h.backend.GetNodeVersionInfo(r.Context()) - if err != nil { - return response, err - } - - response.Build(params) - return response, nil -} - -// RestForwarder handles the request forwarding to upstream -type RestForwarder struct { - log zerolog.Logger - *forwarder.Forwarder -} - -// NewRestForwarder returns new RestForwarder. -func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*RestForwarder, error) { - f, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) - - restForwarder := &RestForwarder{ - log: log, - Forwarder: f, - } - return restForwarder, err -} - -// GetTransactionByID gets a transaction by requested ID. -func (f *RestForwarder) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { - var response models.Transaction - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getTransactionRequest := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - } - transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) - if err != nil { - return response, err - } - - var transactionResultResponse *accessproto.TransactionResultResponse - // only lookup result if transaction result is to be expanded - if r.ExpandsResult { - getTransactionResultRequest := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - BlockId: r.BlockID[:], - CollectionId: r.CollectionID[:], - } - transactionResultResponse, err = upstream.GetTransactionResult(context, getTransactionResultRequest) - if err != nil { - return response, err - } - } - flowTransaction, err := convert.MessageToTransaction(transactionResponse.Transaction, chain) - if err != nil { - return response, err - } - - flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) - - response.Build(&flowTransaction, flowTransactionResult, link) - return response, nil -} - -// CreateTransaction creates a new transaction from provided payload. -func (f *RestForwarder) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - var response models.Transaction - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - entitiesTransaction := convert.TransactionToMessage(r.Transaction) - sendTransactionRequest := &accessproto.SendTransactionRequest{ - Transaction: entitiesTransaction, - } - - _, err = upstream.SendTransaction(context, sendTransactionRequest) - if err != nil { - return response, err - } - - response.Build(&r.Transaction, nil, link) - return response, nil -} - -// GetTransactionResultByID retrieves transaction result by the transaction ID. -func (f *RestForwarder) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - var response models.TransactionResult - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getTransactionResult := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - BlockId: r.BlockID[:], - CollectionId: r.CollectionID[:], - } - transactionResultResponse, err := upstream.GetTransactionResult(context, getTransactionResult) - if err != nil { - return response, err - } - - flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) - response.Build(flowTransactionResult, r.ID, link) - return response, nil -} - -// GetBlocksByIDs gets blocks by provided ID or list of IDs. -func (f *RestForwarder) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - blocks := make([]*models.Block, len(r.IDs)) - - upstream, err := f.FaultTolerantClient() - if err != nil { - return blocks, err - } - - for i, id := range r.IDs { - block, err := getBlockFromGrpc(forID(&id), context, expandFields, upstream, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil -} - -// GetBlocksByHeight gets blocks by provided height. -func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - req, err := r.GetBlockRequest() - if err != nil { - return nil, NewBadRequestError(err) - } - - upstream, err := f.FaultTolerantClient() - if err != nil { - return nil, err - } - - if req.FinalHeight || req.SealedHeight { - block, err := getBlockFromGrpc(forFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - - return []*models.Block{block}, nil - } - - // if the query is /blocks/height=1000,1008,1049... - if req.HasHeights() { - blocks := make([]*models.Block, len(req.Heights)) - for i, height := range req.Heights { - block, err := getBlockFromGrpc(forHeight(height), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil - } - - // support providing end height as "sealed" or "final" - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ - IsSealed: req.EndHeight == request.SealedHeight, - } - blockResponse, err := upstream.GetLatestBlock(r.Context(), getLatestBlockRequest) - if err != nil { - return nil, err - } - - req.EndHeight = blockResponse.Block.BlockHeader.Height // overwrite special value height with fetched - - if req.StartHeight > req.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) - } - } - - blocks := make([]*models.Block, 0) - // start and end height inclusive - for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := getBlockFromGrpc(forHeight(i), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -// GetBlockPayloadByID gets block payload by ID -func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { - var payload models.BlockPayload - - upstream, err := f.FaultTolerantClient() - if err != nil { - return payload, err - } - - blkProvider := NewBlockFromGrpcProvider(upstream, forID(&r.ID)) - block, _, statusErr := blkProvider.getBlock(context) - if statusErr != nil { - return payload, statusErr - } - - flowPayload, err := convert.PayloadFromMessage(block) - if err != nil { - return payload, err - } - - err = payload.Build(flowPayload) - if err != nil { - return payload, err - } - - return payload, nil -} - -// GetExecutionResultByID gets execution result by the ID. -func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - var response models.ExecutionResult - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - executionResultByIDRequest := &accessproto.GetExecutionResultByIDRequest{ - Id: r.ID[:], - } - - executionResultByIDResponse, err := upstream.GetExecutionResultByID(context, executionResultByIDRequest) - if err != nil { - return response, err - } - - if executionResultByIDResponse == nil { - err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) - return response, NewNotFoundError(err.Error(), err) - } - - flowExecResult, err := convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) - if err != nil { - return response, err - } - err = response.Build(flowExecResult, link) - if err != nil { - return response, err - } - - return response, nil -} - -// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func (f *RestForwarder) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - // for each block ID we retrieve execution result - results := make([]models.ExecutionResult, len(r.BlockIDs)) - - upstream, err := f.FaultTolerantClient() - if err != nil { - return results, err - } - - for i, id := range r.BlockIDs { - getExecutionResultForBlockID := &accessproto.GetExecutionResultForBlockIDRequest{ - BlockId: id[:], - } - executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockID) - if err != nil { - return nil, err - } - - var response models.ExecutionResult - flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) - if err != nil { - return nil, err - } - err = response.Build(flowExecResult, link) - if err != nil { - return nil, err - } - results[i] = response - } - - return results, nil -} - -// GetCollectionByID retrieves a collection by ID and builds a response -func (f *RestForwarder) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { - var response models.Collection - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getCollectionByIDRequest := &accessproto.GetCollectionByIDRequest{ - Id: r.ID[:], - } - - collectionResponse, err := upstream.GetCollectionByID(context, getCollectionByIDRequest) - if err != nil { - return response, err - } - - // if we expand transactions in the query retrieve each transaction data - transactions := make([]*entities.Transaction, 0) - if r.ExpandsTransactions { - for _, tid := range collectionResponse.Collection.TransactionIds { - getTransactionRequest := &accessproto.GetTransactionRequest{ - Id: tid, - } - transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) - if err != nil { - return response, err - } - - transactions = append(transactions, transactionResponse.Transaction) - } - } - - err = response.BuildFromGrpc(collectionResponse.Collection, transactions, link, expandFields, chain) - if err != nil { - return response, err - } - - return response, nil -} - -// ExecuteScript handler sends the script from the request to be executed. -func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { - upstream, err := f.FaultTolerantClient() - if err != nil { - return nil, err - } - - if r.BlockID != flow.ZeroID { - executeScriptAtBlockIDRequest := &accessproto.ExecuteScriptAtBlockIDRequest{ - BlockId: r.BlockID[:], - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(context, executeScriptAtBlockIDRequest) - if err != nil { - return nil, err - } - return executeScriptAtBlockIDResponse.Value, nil - } - - // default to sealed height - if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { - executeScriptAtLatestBlockRequest := &accessproto.ExecuteScriptAtLatestBlockRequest{ - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(context, executeScriptAtLatestBlockRequest) - if err != nil { - return nil, err - } - return executeScriptAtLatestBlockResponse.Value, nil - } - - if r.BlockHeight == request.FinalHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: false, - } - getLatestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return nil, err - } - r.BlockHeight = getLatestBlockHeaderResponse.Block.Height - } - - executeScriptAtBlockHeightRequest := &accessproto.ExecuteScriptAtBlockHeightRequest{ - BlockHeight: r.BlockHeight, - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(context, executeScriptAtBlockHeightRequest) - if err != nil { - return nil, err - } - return executeScriptAtBlockHeightResponse.Value, nil -} - -// GetAccount handler retrieves account by address and returns the response. -func (f *RestForwarder) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - var response models.Account - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it - if r.Height == request.FinalHeight || r.Height == request.SealedHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: r.Height == request.SealedHeight, - } - blockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return response, err - } - r.Height = blockHeaderResponse.Block.Height - } - getAccountAtBlockHeightRequest := &accessproto.GetAccountAtBlockHeightRequest{ - Address: r.Address.Bytes(), - BlockHeight: r.Height, - } - - accountResponse, err := upstream.GetAccountAtBlockHeight(context, getAccountAtBlockHeightRequest) - if err != nil { - return response, NewNotFoundError("not found account at block height", err) - } - - flowAccount, err := convert.MessageToAccount(accountResponse.Account) - if err != nil { - return response, err - } - - err = response.Build(flowAccount, link, expandFields) - return response, err -} - -// GetEvents for the provided block range or list of block IDs filtered by type. -func (f *RestForwarder) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { - // if the request has block IDs provided then return events for block IDs - var blocksEvents models.BlocksEvents - - upstream, err := f.FaultTolerantClient() - if err != nil { - return blocksEvents, err - } - - if len(r.BlockIDs) > 0 { - var blockIds [][]byte - for _, id := range r.BlockIDs { - blockIds = append(blockIds, id[:]) - } - getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ - Type: r.Type, - BlockIds: blockIds, - } - eventsResponse, err := upstream.GetEventsForBlockIDs(context, getEventsForBlockIDsRequest) - if err != nil { - return nil, err - } - - blocksEvents.BuildFromGrpc(eventsResponse.Results) - - return blocksEvents, nil - } - - // if end height is provided with special values then load the height - if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: r.EndHeight == request.SealedHeight, - } - latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return nil, err - } - - r.EndHeight = latestBlockHeaderResponse.Block.Height - // special check after we resolve special height value - if r.StartHeight > r.EndHeight { - return nil, NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) - } - } - - // if request provided block height range then return events for that range - getEventsForHeightRangeRequest := &accessproto.GetEventsForHeightRangeRequest{ - Type: r.Type, - StartHeight: r.StartHeight, - EndHeight: r.EndHeight, - } - eventsResponse, err := upstream.GetEventsForHeightRange(context, getEventsForHeightRangeRequest) - if err != nil { - return nil, err - } - - blocksEvents.BuildFromGrpc(eventsResponse.Results) - return blocksEvents, nil -} - -// GetNetworkParameters returns network-wide parameters of the blockchain -func (f *RestForwarder) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { - var response models.NetworkParameters - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getNetworkParametersRequest := &accessproto.GetNetworkParametersRequest{} - getNetworkParametersResponse, err := upstream.GetNetworkParameters(r.Context(), getNetworkParametersRequest) - if err != nil { - return response, err - } - response.BuildFromGrpc(getNetworkParametersResponse) - return response, nil -} - -// GetNodeVersionInfo returns node version information -func (f *RestForwarder) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { - var response models.NodeVersionInfo - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getNodeVersionInfoRequest := &accessproto.GetNodeVersionInfoRequest{} - getNodeVersionInfoResponse, err := upstream.GetNodeVersionInfo(r.Context(), getNodeVersionInfoRequest) - if err != nil { - return response, err - } - - response.BuildFromGrpc(getNodeVersionInfoResponse.Info) - return response, nil -} diff --git a/engine/access/rest/router.go b/engine/access/rest/router.go index 9bb04239b66..2253d56033c 100644 --- a/engine/access/rest/router.go +++ b/engine/access/rest/router.go @@ -6,13 +6,15 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/middleware" "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) -func newRouter(serverAPI RestServerApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { +func NewRouter(serverAPI api.RestServerApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { router := mux.NewRouter().StrictSlash(true) v1SubRouter := router.PathPrefix("/v1").Subrouter() @@ -46,70 +48,70 @@ var Routes = []route{{ Method: http.MethodGet, Pattern: "/transactions/{id}", Name: "getTransactionByID", - Handler: GetTransactionByID, + Handler: routes.GetTransactionByID, }, { Method: http.MethodPost, Pattern: "/transactions", Name: "createTransaction", - Handler: CreateTransaction, + Handler: routes.CreateTransaction, }, { Method: http.MethodGet, Pattern: "/transaction_results/{id}", Name: "getTransactionResultByID", - Handler: GetTransactionResultByID, + Handler: routes.GetTransactionResultByID, }, { Method: http.MethodGet, Pattern: "/blocks/{id}", Name: "getBlocksByIDs", - Handler: GetBlocksByIDs, + Handler: routes.GetBlocksByIDs, }, { Method: http.MethodGet, Pattern: "/blocks", Name: "getBlocksByHeight", - Handler: GetBlocksByHeight, + Handler: routes.GetBlocksByHeight, }, { Method: http.MethodGet, Pattern: "/blocks/{id}/payload", Name: "getBlockPayloadByID", - Handler: GetBlockPayloadByID, + Handler: routes.GetBlockPayloadByID, }, { Method: http.MethodGet, Pattern: "/execution_results/{id}", Name: "getExecutionResultByID", - Handler: GetExecutionResultByID, + Handler: routes.GetExecutionResultByID, }, { Method: http.MethodGet, Pattern: "/execution_results", Name: "getExecutionResultByBlockID", - Handler: GetExecutionResultsByBlockIDs, + Handler: routes.GetExecutionResultsByBlockIDs, }, { Method: http.MethodGet, Pattern: "/collections/{id}", Name: "getCollectionByID", - Handler: GetCollectionByID, + Handler: routes.GetCollectionByID, }, { Method: http.MethodPost, Pattern: "/scripts", Name: "executeScript", - Handler: ExecuteScript, + Handler: routes.ExecuteScript, }, { Method: http.MethodGet, Pattern: "/accounts/{address}", Name: "getAccount", - Handler: GetAccount, + Handler: routes.GetAccount, }, { Method: http.MethodGet, Pattern: "/events", Name: "getEvents", - Handler: GetEvents, + Handler: routes.GetEvents, }, { Method: http.MethodGet, Pattern: "/network/parameters", Name: "getNetworkParameters", - Handler: GetNetworkParameters, + Handler: routes.GetNetworkParameters, }, { Method: http.MethodGet, Pattern: "/node_version_info", Name: "getNodeVersionInfo", - Handler: GetNodeVersionInfo, + Handler: routes.GetNodeVersionInfo, }} diff --git a/engine/access/rest/accounts.go b/engine/access/rest/routes/accounts.go similarity index 59% rename from engine/access/rest/accounts.go rename to engine/access/rest/routes/accounts.go index a7e194de9e3..76ff9d7fcb5 100644 --- a/engine/access/rest/accounts.go +++ b/engine/access/rest/routes/accounts.go @@ -1,15 +1,16 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetAccount handler retrieves account by address and returns the response -func GetAccount(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetAccount(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetAccountRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetAccount(req, r.Context(), r.ExpandFields, link) diff --git a/engine/access/rest/blocks.go b/engine/access/rest/routes/blocks.go similarity index 61% rename from engine/access/rest/blocks.go rename to engine/access/rest/routes/blocks.go index 9adcf0fec14..b6676d7076d 100644 --- a/engine/access/rest/blocks.go +++ b/engine/access/rest/routes/blocks.go @@ -1,4 +1,4 @@ -package rest +package routes import ( "context" @@ -9,9 +9,9 @@ import ( "google.golang.org/grpc/status" "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" accessproto "github.com/onflow/flow/protobuf/go/flow/access" @@ -19,34 +19,34 @@ import ( ) // GetBlocksByIDs gets blocks by provided ID or list of IDs. -func GetBlocksByIDs(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByIDs(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockByIDsRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetBlocksByIDs(req, r.Context(), r.ExpandFields, link) } // GetBlocksByHeight gets blocks by height. -func GetBlocksByHeight(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByHeight(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { return srv.GetBlocksByHeight(r, link) } // GetBlockPayloadByID gets block payload by ID -func GetBlockPayloadByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetBlockPayloadByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockPayloadRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetBlockPayloadByID(req, r.Context(), link) } -func getBlock(option blockRequestOption, context context.Context, expandFields map[string]bool, backend access.API, link models.LinkGenerator) (*models.Block, error) { +func GetBlock(option BlockRequestOption, context context.Context, expandFields map[string]bool, backend access.API, link models.LinkGenerator) (*models.Block, error) { // lookup block blkProvider := NewBlockRequestProvider(backend, option) - blk, blockStatus, err := blkProvider.getBlock(context) + blk, blockStatus, err := blkProvider.GetBlock(context) if err != nil { return nil, err } @@ -76,62 +76,6 @@ func getBlock(option blockRequestOption, context context.Context, expandFields m return &block, nil } -func getBlockFromGrpc(option blockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { - // lookup block - blkProvider := NewBlockFromGrpcProvider(upstream, option) - blk, blockStatus, err := blkProvider.getBlock(context) - if err != nil { - return nil, err - } - - // lookup execution result - // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) - var block models.Block - getExecutionResultForBlockIDRequest := &accessproto.GetExecutionResultForBlockIDRequest{ - BlockId: blk.Id, - } - - executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockIDRequest) - if err != nil { - return nil, err - } - - flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) - if err != nil { - return nil, err - } - - flowBlock, err := convert.MessageToBlock(blk) - if err != nil { - return nil, err - } - - flowBlockStatus, err := convert.MessagesToBlockStatus(blockStatus) - if err != nil { - return nil, err - } - - if err != nil { - // handle case where execution result is not yet available - if se, ok := status.FromError(err); ok { - if se.Code() == codes.NotFound { - err := block.Build(flowBlock, nil, link, flowBlockStatus, expandFields) - if err != nil { - return nil, err - } - return &block, nil - } - } - return nil, err - } - - err = block.Build(flowBlock, flowExecResult, link, flowBlockStatus, expandFields) - if err != nil { - return nil, err - } - return &block, nil -} - type blockRequest struct { id *flow.Identifier height uint64 @@ -139,20 +83,20 @@ type blockRequest struct { sealed bool } -type blockRequestOption func(blkRequest *blockRequest) +type BlockRequestOption func(blkRequest *blockRequest) -func forID(id *flow.Identifier) blockRequestOption { +func ForID(id *flow.Identifier) BlockRequestOption { return func(blockRequest *blockRequest) { blockRequest.id = id } } -func forHeight(height uint64) blockRequestOption { +func ForHeight(height uint64) BlockRequestOption { return func(blockRequest *blockRequest) { blockRequest.height = height } } -func forFinalized(queryParam uint64) blockRequestOption { +func ForFinalized(queryParam uint64) BlockRequestOption { return func(blockRequest *blockRequest) { switch queryParam { case request.SealedHeight: @@ -171,7 +115,7 @@ type blockRequestProvider struct { backend access.API } -func NewBlockRequestProvider(backend access.API, options ...blockRequestOption) *blockRequestProvider { +func NewBlockRequestProvider(backend access.API, options ...BlockRequestOption) *blockRequestProvider { blockRequestProvider := &blockRequestProvider{ backend: backend, } @@ -182,11 +126,11 @@ func NewBlockRequestProvider(backend access.API, options ...blockRequestOption) return blockRequestProvider } -func (blkProvider *blockRequestProvider) getBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { +func (blkProvider *blockRequestProvider) GetBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { if blkProvider.id != nil { blk, _, err := blkProvider.backend.GetBlockByID(ctx, *blkProvider.id) if err != nil { // unfortunately backend returns internal error status if not found - return nil, flow.BlockStatusUnknown, NewNotFoundError( + return nil, flow.BlockStatusUnknown, models.NewNotFoundError( fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err, ) } @@ -197,29 +141,29 @@ func (blkProvider *blockRequestProvider) getBlock(ctx context.Context) (*flow.Bl blk, status, err := blkProvider.backend.GetLatestBlock(ctx, blkProvider.sealed) if err != nil { // cannot be a 'not found' error since final and sealed block should always be found - return nil, flow.BlockStatusUnknown, NewRestError(http.StatusInternalServerError, "block lookup failed", err) + return nil, flow.BlockStatusUnknown, models.NewRestError(http.StatusInternalServerError, "block lookup failed", err) } return blk, status, nil } blk, status, err := blkProvider.backend.GetBlockByHeight(ctx, blkProvider.height) if err != nil { // unfortunately backend returns internal error status if not found - return nil, flow.BlockStatusUnknown, NewNotFoundError( + return nil, flow.BlockStatusUnknown, models.NewNotFoundError( fmt.Sprintf("error looking up block at height %d", blkProvider.height), err, ) } return blk, status, nil } -// blockFromGrpcProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to +// BlockFromGrpcProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to // look up a block or a block header either by ID or by height -type blockFromGrpcProvider struct { +type BlockFromGrpcProvider struct { blockRequest upstream accessproto.AccessAPIClient } -func NewBlockFromGrpcProvider(upstream accessproto.AccessAPIClient, options ...blockRequestOption) *blockFromGrpcProvider { - blockFromGrpcProvider := &blockFromGrpcProvider{ +func NewBlockFromGrpcProvider(upstream accessproto.AccessAPIClient, options ...BlockRequestOption) *BlockFromGrpcProvider { + blockFromGrpcProvider := &BlockFromGrpcProvider{ upstream: upstream, } @@ -229,14 +173,14 @@ func NewBlockFromGrpcProvider(upstream accessproto.AccessAPIClient, options ...b return blockFromGrpcProvider } -func (blkProvider *blockFromGrpcProvider) getBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { +func (blkProvider *BlockFromGrpcProvider) GetBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { if blkProvider.id != nil { getBlockByIdRequest := &accessproto.GetBlockByIDRequest{ Id: []byte(blkProvider.id.String()), } blockResponse, err := blkProvider.upstream.GetBlockByID(ctx, getBlockByIdRequest) if err != nil { // unfortunately grpc returns internal error status if not found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewNotFoundError( + return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewNotFoundError( fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err, ) } @@ -250,7 +194,7 @@ func (blkProvider *blockFromGrpcProvider) getBlock(ctx context.Context) (*entiti blockResponse, err := blkProvider.upstream.GetLatestBlock(ctx, getLatestBlockRequest) if err != nil { // cannot be a 'not found' error since final and sealed block should always be found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewRestError(http.StatusInternalServerError, "block lookup failed", err) + return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewRestError(http.StatusInternalServerError, "block lookup failed", err) } return blockResponse.Block, blockResponse.BlockStatus, nil } @@ -261,7 +205,7 @@ func (blkProvider *blockFromGrpcProvider) getBlock(ctx context.Context) (*entiti } blockResponse, err := blkProvider.upstream.GetBlockByHeight(ctx, getBlockByHeight) if err != nil { // unfortunately grpc returns internal error status if not found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, NewNotFoundError( + return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewNotFoundError( fmt.Sprintf("error looking up block at height %d", blkProvider.height), err, ) } diff --git a/engine/access/rest/collections.go b/engine/access/rest/routes/collections.go similarity index 59% rename from engine/access/rest/collections.go rename to engine/access/rest/routes/collections.go index be1b751b348..97a38005961 100644 --- a/engine/access/rest/collections.go +++ b/engine/access/rest/routes/collections.go @@ -1,15 +1,16 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetCollectionByID retrieves a collection by ID and builds a response -func GetCollectionByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetCollectionByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetCollectionRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetCollectionByID(req, r.Context(), r.ExpandFields, link, r.Chain) diff --git a/engine/access/rest/events.go b/engine/access/rest/routes/events.go similarity index 51% rename from engine/access/rest/events.go rename to engine/access/rest/routes/events.go index e1e3eb6c7ec..fd728bc1188 100644 --- a/engine/access/rest/events.go +++ b/engine/access/rest/routes/events.go @@ -1,18 +1,19 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) -const blockQueryParam = "block_ids" -const eventTypeQuery = "type" +const BlockQueryParam = "block_ids" +const EventTypeQuery = "type" // GetEvents for the provided block range or list of block IDs filtered by type. -func GetEvents(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { +func GetEvents(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetEventsRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetEvents(req, r.Context()) diff --git a/engine/access/rest/execution_result.go b/engine/access/rest/routes/execution_result.go similarity index 56% rename from engine/access/rest/execution_result.go rename to engine/access/rest/routes/execution_result.go index 1b9af6c586d..4c1ba8aab7e 100644 --- a/engine/access/rest/execution_result.go +++ b/engine/access/rest/routes/execution_result.go @@ -1,25 +1,26 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func GetExecutionResultsByBlockIDs(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultsByBlockIDs(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultByBlockIDsRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetExecutionResultsByBlockIDs(req, r.Context(), link) } // GetExecutionResultByID gets execution result by the ID. -func GetExecutionResultByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetExecutionResultByID(req, r.Context(), link) diff --git a/engine/access/rest/network.go b/engine/access/rest/routes/network.go similarity index 56% rename from engine/access/rest/network.go rename to engine/access/rest/routes/network.go index 1fe904e29dd..a409fd4b893 100644 --- a/engine/access/rest/network.go +++ b/engine/access/rest/routes/network.go @@ -1,11 +1,12 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNetworkParameters returns network-wide parameters of the blockchain -func GetNetworkParameters(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { +func GetNetworkParameters(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { return srv.GetNetworkParameters(r) } diff --git a/engine/access/rest/node_version_info.go b/engine/access/rest/routes/node_version_info.go similarity index 54% rename from engine/access/rest/node_version_info.go rename to engine/access/rest/routes/node_version_info.go index a1a04977835..14fbd8bbf26 100644 --- a/engine/access/rest/node_version_info.go +++ b/engine/access/rest/routes/node_version_info.go @@ -1,11 +1,12 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNodeVersionInfo returns node version information -func GetNodeVersionInfo(r *request.Request, srv RestServerApi, _ models.LinkGenerator) (interface{}, error) { +func GetNodeVersionInfo(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { return srv.GetNodeVersionInfo(r) } diff --git a/engine/access/rest/scripts.go b/engine/access/rest/routes/scripts.go similarity index 57% rename from engine/access/rest/scripts.go rename to engine/access/rest/routes/scripts.go index 827bd25b13a..1debbb07934 100644 --- a/engine/access/rest/scripts.go +++ b/engine/access/rest/routes/scripts.go @@ -1,15 +1,16 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // ExecuteScript handler sends the script from the request to be executed. -func ExecuteScript(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func ExecuteScript(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetScriptRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.ExecuteScript(req, r.Context(), link) diff --git a/engine/access/rest/transactions.go b/engine/access/rest/routes/transactions.go similarity index 55% rename from engine/access/rest/transactions.go rename to engine/access/rest/routes/transactions.go index 1acdccfa1e8..9f9f49d8cf1 100644 --- a/engine/access/rest/transactions.go +++ b/engine/access/rest/routes/transactions.go @@ -1,35 +1,36 @@ -package rest +package routes import ( + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetTransactionByID gets a transaction by requested ID. -func GetTransactionByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetTransactionByID(req, r.Context(), link, r.Chain) } // GetTransactionResultByID retrieves transaction result by the transaction ID. -func GetTransactionResultByID(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionResultByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionResultRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.GetTransactionResultByID(req, r.Context(), link) } // CreateTransaction creates a new transaction from provided payload. -func CreateTransaction(r *request.Request, srv RestServerApi, link models.LinkGenerator) (interface{}, error) { +func CreateTransaction(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { req, err := r.CreateTransactionRequest() if err != nil { - return nil, NewBadRequestError(err) + return nil, models.NewBadRequestError(err) } return srv.CreateTransaction(req, r.Context(), link) diff --git a/engine/access/rest/server.go b/engine/access/rest/server.go index f196fb1a4a9..9dc7112e6b6 100644 --- a/engine/access/rest/server.go +++ b/engine/access/rest/server.go @@ -7,13 +7,14 @@ import ( "github.com/rs/cors" "github.com/rs/zerolog" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) // NewServer returns an HTTP server initialized with the REST API handler -func NewServer(serverAPI RestServerApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { - router, err := newRouter(serverAPI, logger, chain, restCollector) +func NewServer(serverAPI api.RestServerApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { + router, err := NewRouter(serverAPI, logger, chain, restCollector) if err != nil { return nil, err } diff --git a/engine/access/rest/server_request_handler.go b/engine/access/rest/server_request_handler.go new file mode 100644 index 00000000000..12d45550705 --- /dev/null +++ b/engine/access/rest/server_request_handler.go @@ -0,0 +1,346 @@ +package rest + +import ( + "context" + "fmt" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/engine/access/rest/routes" + "github.com/onflow/flow-go/model/flow" +) + +// ServerRequestHandler is a structure that represents local requests +type ServerRequestHandler struct { + log zerolog.Logger + backend access.API +} + +var _ api.RestServerApi = (*ServerRequestHandler)(nil) + +// NewServerRequestHandler returns new ServerRequestHandler. +func NewServerRequestHandler(log zerolog.Logger, backend access.API) *ServerRequestHandler { + return &ServerRequestHandler{ + log: log, + backend: backend, + } +} + +// GetTransactionByID gets a transaction by requested ID. +func (h *ServerRequestHandler) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, _ flow.Chain) (models.Transaction, error) { + var response models.Transaction + + tx, err := h.backend.GetTransaction(context, r.ID) + if err != nil { + return response, err + } + + var txr *access.TransactionResult + // only lookup result if transaction result is to be expanded + if r.ExpandsResult { + txr, err = h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) + if err != nil { + return response, err + } + } + + response.Build(tx, txr, link) + return response, nil +} + +// CreateTransaction creates a new transaction from provided payload. +func (h *ServerRequestHandler) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { + var response models.Transaction + + err := h.backend.SendTransaction(context, &r.Transaction) + if err != nil { + return response, err + } + + response.Build(&r.Transaction, nil, link) + return response, nil +} + +// GetTransactionResultByID retrieves transaction result by the transaction ID. +func (h *ServerRequestHandler) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { + var response models.TransactionResult + + txr, err := h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) + if err != nil { + return response, err + } + + response.Build(txr, r.ID, link) + return response, nil +} + +// GetBlocksByIDs gets blocks by provided ID or list of IDs. +func (h *ServerRequestHandler) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { + blocks := make([]*models.Block, len(r.IDs)) + + for i, id := range r.IDs { + block, err := routes.GetBlock(routes.ForID(&id), context, expandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil +} + +// GetBlocksByHeight gets blocks by provided height. +func (h *ServerRequestHandler) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { + req, err := r.GetBlockRequest() + if err != nil { + return nil, models.NewBadRequestError(err) + } + + if req.FinalHeight || req.SealedHeight { + block, err := routes.GetBlock(routes.ForFinalized(req.Heights[0]), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + + return []*models.Block{block}, nil + } + + // if the query is /blocks/height=1000,1008,1049... + if req.HasHeights() { + blocks := make([]*models.Block, len(req.Heights)) + for i, height := range req.Heights { + block, err := routes.GetBlock(routes.ForHeight(height), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil + } + + // support providing end height as "sealed" or "final" + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + latest, _, err := h.backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + req.EndHeight = latest.Header.Height // overwrite special value height with fetched + + if req.StartHeight > req.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) + } + } + + blocks := make([]*models.Block, 0) + // start and end height inclusive + for i := req.StartHeight; i <= req.EndHeight; i++ { + block, err := routes.GetBlock(routes.ForHeight(i), r.Context(), r.ExpandFields, h.backend, link) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +// GetBlockPayloadByID gets block payload by ID +func (h *ServerRequestHandler) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { + var payload models.BlockPayload + + blkProvider := routes.NewBlockRequestProvider(h.backend, routes.ForID(&r.ID)) + blk, _, statusErr := blkProvider.GetBlock(context) + if statusErr != nil { + return payload, statusErr + } + + err := payload.Build(blk.Payload) + if err != nil { + return payload, err + } + + return payload, nil +} + +// GetExecutionResultByID gets execution result by the ID. +func (h *ServerRequestHandler) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { + var response models.ExecutionResult + + res, err := h.backend.GetExecutionResultByID(context, r.ID) + if err != nil { + return response, err + } + + if res == nil { + err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) + return response, models.NewNotFoundError(err.Error(), err) + } + + err = response.Build(res, link) + if err != nil { + return response, err + } + + return response, nil +} + +// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. +func (h *ServerRequestHandler) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { + // for each block ID we retrieve execution result + results := make([]models.ExecutionResult, len(r.BlockIDs)) + for i, id := range r.BlockIDs { + res, err := h.backend.GetExecutionResultForBlockID(context, id) + if err != nil { + return nil, err + } + + var response models.ExecutionResult + err = response.Build(res, link) + if err != nil { + return nil, err + } + results[i] = response + } + + return results, nil +} + +// GetCollectionByID retrieves a collection by ID and builds a response +func (h *ServerRequestHandler) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, _ flow.Chain) (models.Collection, error) { + var response models.Collection + + collection, err := h.backend.GetCollectionByID(context, r.ID) + if err != nil { + return response, err + } + + // if we expand transactions in the query retrieve each transaction data + transactions := make([]*flow.TransactionBody, 0) + if r.ExpandsTransactions { + for _, tid := range collection.Transactions { + tx, err := h.backend.GetTransaction(context, tid) + if err != nil { + return response, err + } + + transactions = append(transactions, tx) + } + } + + err = response.Build(collection, transactions, link, expandFields) + if err != nil { + return response, err + } + + return response, nil +} + +// ExecuteScript handler sends the script from the request to be executed. +func (h *ServerRequestHandler) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { + if r.BlockID != flow.ZeroID { + return h.backend.ExecuteScriptAtBlockID(context, r.BlockID, r.Script.Source, r.Script.Args) + } + + // default to sealed height + if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { + return h.backend.ExecuteScriptAtLatestBlock(context, r.Script.Source, r.Script.Args) + } + + if r.BlockHeight == request.FinalHeight { + finalBlock, _, err := h.backend.GetLatestBlockHeader(context, false) + if err != nil { + return nil, err + } + r.BlockHeight = finalBlock.Height + } + + return h.backend.ExecuteScriptAtBlockHeight(context, r.BlockHeight, r.Script.Source, r.Script.Args) +} + +// GetAccount handler retrieves account by address and returns the response. +func (h *ServerRequestHandler) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { + var response models.Account + + // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it + if r.Height == request.FinalHeight || r.Height == request.SealedHeight { + header, _, err := h.backend.GetLatestBlockHeader(context, r.Height == request.SealedHeight) + if err != nil { + return response, err + } + r.Height = header.Height + } + + account, err := h.backend.GetAccountAtBlockHeight(context, r.Address, r.Height) + if err != nil { + return response, models.NewNotFoundError("not found account at block height", err) + } + + err = response.Build(account, link, expandFields) + return response, err +} + +// GetEvents for the provided block range or list of block IDs filtered by type. +func (h *ServerRequestHandler) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { + // if the request has block IDs provided then return events for block IDs + var blocksEvents models.BlocksEvents + if len(r.BlockIDs) > 0 { + events, err := h.backend.GetEventsForBlockIDs(context, r.Type, r.BlockIDs) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil + } + + // if end height is provided with special values then load the height + if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { + latest, _, err := h.backend.GetLatestBlockHeader(context, r.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + r.EndHeight = latest.Height + // special check after we resolve special height value + if r.StartHeight > r.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) + } + } + + // if request provided block height range then return events for that range + events, err := h.backend.GetEventsForHeightRange(context, r.Type, r.StartHeight, r.EndHeight) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil +} + +// GetNetworkParameters returns network-wide parameters of the blockchain +func (h *ServerRequestHandler) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { + params := h.backend.GetNetworkParameters(r.Context()) + + var response models.NetworkParameters + response.Build(¶ms) + return response, nil +} + +// GetNodeVersionInfo returns node version information +func (h *ServerRequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { + var response models.NodeVersionInfo + + params, err := h.backend.GetNodeVersionInfo(r.Context()) + if err != nil { + return response, err + } + + response.Build(params) + return response, nil +} diff --git a/engine/access/rest/accounts_test.go b/engine/access/rest/tests/accounts_test.go similarity index 99% rename from engine/access/rest/accounts_test.go rename to engine/access/rest/tests/accounts_test.go index d248bd312c8..222928bc846 100644 --- a/engine/access/rest/accounts_test.go +++ b/engine/access/rest/tests/accounts_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "fmt" diff --git a/engine/access/rest/blocks_test.go b/engine/access/rest/tests/blocks_test.go similarity index 99% rename from engine/access/rest/blocks_test.go rename to engine/access/rest/tests/blocks_test.go index 7e337e07e2a..f2cab63c3c0 100644 --- a/engine/access/rest/blocks_test.go +++ b/engine/access/rest/tests/blocks_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "fmt" diff --git a/engine/access/rest/collections_test.go b/engine/access/rest/tests/collections_test.go similarity index 99% rename from engine/access/rest/collections_test.go rename to engine/access/rest/tests/collections_test.go index a033fb8a453..77a97957ae8 100644 --- a/engine/access/rest/collections_test.go +++ b/engine/access/rest/tests/collections_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "encoding/json" diff --git a/engine/access/rest/events_test.go b/engine/access/rest/tests/events_test.go similarity index 98% rename from engine/access/rest/events_test.go rename to engine/access/rest/tests/events_test.go index c5b9be0c00b..806b7860b96 100644 --- a/engine/access/rest/events_test.go +++ b/engine/access/rest/tests/events_test.go @@ -1,24 +1,26 @@ -package rest +package tests import ( "encoding/json" "fmt" + "net/http" "net/url" "strings" "testing" "time" - "github.com/onflow/flow-go/engine/access/rest/util" - - "github.com/onflow/flow-go/access/mock" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/utils/unittest" - mocks "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + + "github.com/onflow/flow-go/access/mock" + "github.com/onflow/flow-go/engine/access/rest/routes" + "github.com/onflow/flow-go/engine/access/rest/util" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) func TestGetEvents(t *testing.T) { @@ -137,7 +139,7 @@ func getEventReq(t *testing.T, eventType string, start string, end string, block q := u.Query() if len(blockIDs) > 0 { - q.Add(blockQueryParam, strings.Join(blockIDs, ",")) + q.Add(routes.BlockQueryParam, strings.Join(blockIDs, ",")) } if start != "" && end != "" { @@ -145,7 +147,7 @@ func getEventReq(t *testing.T, eventType string, start string, end string, block q.Add(endHeightQueryParam, end) } - q.Add(eventTypeQuery, eventType) + q.Add(routes.EventTypeQuery, eventType) u.RawQuery = q.Encode() diff --git a/engine/access/rest/execution_result_test.go b/engine/access/rest/tests/execution_result_test.go similarity index 99% rename from engine/access/rest/execution_result_test.go rename to engine/access/rest/tests/execution_result_test.go index 241f14f6b59..f978f9bd299 100644 --- a/engine/access/rest/execution_result_test.go +++ b/engine/access/rest/tests/execution_result_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "fmt" diff --git a/engine/access/rest/network_test.go b/engine/access/rest/tests/network_test.go similarity index 98% rename from engine/access/rest/network_test.go rename to engine/access/rest/tests/network_test.go index f8013c86685..4af5b501c06 100644 --- a/engine/access/rest/network_test.go +++ b/engine/access/rest/tests/network_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "fmt" diff --git a/engine/access/rest/node_version_info_test.go b/engine/access/rest/tests/node_version_info_test.go similarity index 99% rename from engine/access/rest/node_version_info_test.go rename to engine/access/rest/tests/node_version_info_test.go index b2543950ea0..a4935a29438 100644 --- a/engine/access/rest/node_version_info_test.go +++ b/engine/access/rest/tests/node_version_info_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "fmt" diff --git a/engine/access/rest/scripts_test.go b/engine/access/rest/tests/scripts_test.go similarity index 99% rename from engine/access/rest/scripts_test.go rename to engine/access/rest/tests/scripts_test.go index 8cb19415ae3..0c93106b38a 100644 --- a/engine/access/rest/scripts_test.go +++ b/engine/access/rest/tests/scripts_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "bytes" diff --git a/engine/access/rest/test_helpers.go b/engine/access/rest/tests/test_helpers.go similarity index 68% rename from engine/access/rest/test_helpers.go rename to engine/access/rest/tests/test_helpers.go index 8ce8c2f50d2..4ce4fbc2f50 100644 --- a/engine/access/rest/test_helpers.go +++ b/engine/access/rest/tests/test_helpers.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "bytes" @@ -12,6 +12,9 @@ import ( "github.com/stretchr/testify/require" "github.com/onflow/flow-go/access/mock" + "github.com/onflow/flow-go/engine/access/rest" + "github.com/onflow/flow-go/engine/access/rest/api" + restproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" @@ -27,11 +30,11 @@ const ( heightQueryParam = "height" ) -func executeRequest(req *http.Request, restHandler RestServerApi) (*httptest.ResponseRecorder, error) { +func executeRequest(req *http.Request, restHandler api.RestServerApi) (*httptest.ResponseRecorder, error) { var b bytes.Buffer logger := zerolog.New(&b) - router, err := newRouter(restHandler, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) + router, err := rest.NewRouter(restHandler, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) if err != nil { return nil, err } @@ -41,31 +44,31 @@ func executeRequest(req *http.Request, restHandler RestServerApi) (*httptest.Res return rr, nil } -func newAccessRestHandler(backend *mock.API) RestServerApi { +func newAccessRestHandler(backend *mock.API) api.RestServerApi { var b bytes.Buffer logger := zerolog.New(&b) - return NewRequestHandler(logger, backend) + return rest.NewServerRequestHandler(logger, backend) } -func newObserverRestHandler(backend *mock.API, restForwarder *restmock.RestServerApi) (RestServerApi, error) { +func newObserverRestHandler(backend *mock.API, restForwarder *restmock.RestServerApi) (api.RestServerApi, error) { var b bytes.Buffer logger := zerolog.New(&b) observerCollector := metrics.NewNoopCollector() - return &RestRouter{ + return &restproxy.RestRouter{ Logger: logger, Metrics: observerCollector, Upstream: restForwarder, - Observer: NewRequestHandler(logger, backend), + Observer: rest.NewServerRequestHandler(logger, backend), }, nil } -func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, restHandler RestServerApi) { +func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, restHandler api.RestServerApi) { assertResponse(t, req, http.StatusOK, expectedRespBody, restHandler) } -func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, restHandler RestServerApi) { +func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, restHandler api.RestServerApi) { rr, err := executeRequest(req, restHandler) assert.NoError(t, err) actualResponseBody := rr.Body.String() diff --git a/engine/access/rest/transactions_test.go b/engine/access/rest/tests/transactions_test.go similarity index 99% rename from engine/access/rest/transactions_test.go rename to engine/access/rest/tests/transactions_test.go index 36a0cfe9885..f4570d3a191 100644 --- a/engine/access/rest/transactions_test.go +++ b/engine/access/rest/tests/transactions_test.go @@ -1,4 +1,4 @@ -package rest +package tests import ( "bytes" diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index 7b418fd33ee..7e099f00bf3 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -21,6 +21,7 @@ import ( accessmock "github.com/onflow/flow-go/engine/access/mock" "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/engine/access/rest/tests" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/model/flow" @@ -396,13 +397,13 @@ func assertError(t *testing.T, resp *http.Response, err error, expectedCode int, func optionsForBlockByID() *restclient.BlocksApiBlocksIdGetOpts { return &restclient.BlocksApiBlocksIdGetOpts{ - Expand: optional.NewInterface([]string{rest.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id"}), } } func optionsForBlockByStartEndHeight(startHeight, endHeight uint64) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{rest.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), StartHeight: optional.NewInterface(startHeight), EndHeight: optional.NewInterface(endHeight), @@ -411,7 +412,7 @@ func optionsForBlockByStartEndHeight(startHeight, endHeight uint64) *restclient. func optionsForBlockByHeights(heights []uint64) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{rest.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), Height: optional.NewInterface(heights), } @@ -419,7 +420,7 @@ func optionsForBlockByHeights(heights []uint64) *restclient.BlocksApiBlocksGetOp func optionsForFinalizedBlock(finalOrSealed string) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{rest.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), Height: optional.NewInterface(finalOrSealed), } diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index e30e7d7a405..42b4aec9021 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -15,6 +15,7 @@ import ( "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/engine/access/rest" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/rpc" "github.com/onflow/flow-go/model/flow" @@ -68,7 +69,7 @@ type Engine struct { secureGrpcAddress net.Addr restAPIAddress net.Addr - restHandler rest.RestServerApi + restHandler api.RestServerApi } type Option func(*RPCEngineBuilder) diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index fbd925cee68..79db9853c29 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -9,6 +9,7 @@ import ( legacyaccess "github.com/onflow/flow-go/access/legacy" "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/engine/access/rest" + restapi "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/module" accessproto "github.com/onflow/flow/protobuf/go/flow/access" @@ -39,7 +40,7 @@ func (builder *RPCEngineBuilder) RpcHandler() accessproto.AccessAPIServer { return builder.rpcHandler } -func (builder *RPCEngineBuilder) RestHandler() rest.RestServerApi { +func (builder *RPCEngineBuilder) RestHandler() restapi.RestServerApi { return builder.restHandler } @@ -69,7 +70,7 @@ func (builder *RPCEngineBuilder) WithRpcHandler(handler accessproto.AccessAPISer } // WithRestHandler specifies that the given `RestServerApi` should be used for REST. -func (builder *RPCEngineBuilder) WithRestHandler(handler rest.RestServerApi) *RPCEngineBuilder { +func (builder *RPCEngineBuilder) WithRestHandler(handler restapi.RestServerApi) *RPCEngineBuilder { builder.restHandler = handler return builder } @@ -116,7 +117,7 @@ func (builder *RPCEngineBuilder) Build() (*Engine, error) { restHandler := builder.Engine.restHandler if restHandler == nil { - restHandler = rest.NewRequestHandler(builder.log, builder.backend) + restHandler = rest.NewServerRequestHandler(builder.log, builder.backend) } builder.Engine.restHandler = restHandler From 2db0debcba19a95d0eb1a00487e7bba4d054bb31 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 23 Jun 2023 18:05:22 +0300 Subject: [PATCH 12/30] Added more comments --- engine/access/rest/apiproxy/forwarder.go | 2 +- engine/access/rest/apiproxy/router.go | 2 +- engine/access/rest/tests/accounts_test.go | 17 ++++++++++++++++- engine/access/rest/tests/blocks_test.go | 21 ++++++++++++++++++--- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/engine/access/rest/apiproxy/forwarder.go b/engine/access/rest/apiproxy/forwarder.go index 6a62e302973..d5b65102196 100644 --- a/engine/access/rest/apiproxy/forwarder.go +++ b/engine/access/rest/apiproxy/forwarder.go @@ -23,7 +23,7 @@ import ( "github.com/onflow/flow/protobuf/go/flow/entities" ) -// RestForwarder handles the request forwarding to upstream +// RestForwarder - structure which handles requests to an upstream access node using gRPC API. type RestForwarder struct { log zerolog.Logger *forwarder.Forwarder diff --git a/engine/access/rest/apiproxy/router.go b/engine/access/rest/apiproxy/router.go index beb74b6ca41..b3a3e8db086 100644 --- a/engine/access/rest/apiproxy/router.go +++ b/engine/access/rest/apiproxy/router.go @@ -15,7 +15,7 @@ import ( ) // RestRouter is a structure that represents the routing proxy algorithm for observer node. -// It splits requests between a local requests and request forwarding to upstream service. +// It splits requests between a local requests and forward requests which can't be handled locally to an upstream access node. type RestRouter struct { Logger zerolog.Logger Metrics metrics.ObserverMetrics diff --git a/engine/access/rest/tests/accounts_test.go b/engine/access/rest/tests/accounts_test.go index 222928bc846..c9a247f8c15 100644 --- a/engine/access/rest/tests/accounts_test.go +++ b/engine/access/rest/tests/accounts_test.go @@ -36,6 +36,14 @@ func accountURL(t *testing.T, address string, height string) string { return u.String() } +// TestAccessGetAccount tests local getAccount request. +// +// Runs the following tests: +// 1. Get account by address at latest sealed block. +// 2. Get account by address at latest finalized block. +// 3. Get account by address at height. +// 4. Get account by address at height condensed. +// 5. Get invalid account. func TestAccessGetAccount(t *testing.T) { backend := &mock.API{} restHandler := newAccessRestHandler(backend) @@ -131,7 +139,14 @@ func TestAccessGetAccount(t *testing.T) { }) } -// TestObserverGetAccount tests the get account from observer node +// TestObserverGetAccount tests the get account request forwarding to an upstream. +// +// Runs the following tests: +// 1. Get account by address at latest sealed block. +// 2. Get account by address at latest finalized block. +// 3. Get account by address at height. +// 4. Get account by address at height condensed. +// 5. Get invalid account. func TestObserverGetAccount(t *testing.T) { backend := &mock.API{} restForwarder := &restmock.RestServerApi{} diff --git a/engine/access/rest/tests/blocks_test.go b/engine/access/rest/tests/blocks_test.go index f2cab63c3c0..0b8fd2c66c5 100644 --- a/engine/access/rest/tests/blocks_test.go +++ b/engine/access/rest/tests/blocks_test.go @@ -141,7 +141,7 @@ func prepareTestVectors(t *testing.T, return testVectors } -// TestGetBlocks tests the get blocks by ID and get blocks by heights API from access node +// TestGetBlocks tests local get blocks by ID and get blocks by heights API func TestAccessGetBlocks(t *testing.T) { backend := &mock.API{} @@ -159,12 +159,27 @@ func TestAccessGetBlocks(t *testing.T) { } } -// TestObserverGetBlocks tests the get blocks by ID and get blocks by heights API from observer node +// TestObserverGetBlocks tests requests forwarding for get blocks by ID and get blocks by heights. +// +// Check the following cases: +// 1. Get single expanded block by ID. +// 2. Get multiple expanded blocks by IDs +// 3. Get single condensed block by ID. +// 4. Get multiple condensed blocks by IDs. +// 5. Get single expanded block by height. +// 6. Get multiple expanded blocks by heights. +// 7. Get multiple expanded blocks by start and end height. +// 8. Get block by ID not found. +// 9. Get block by height not found. +// 10. Get block by end height less than start height. +// 11. Get block by both heights and start and end height. +// 12. Get block with missing height param. +// 13. Get block with missing height values. +// 14. Get block by more than maximum permissible number of IDs. func TestObserverGetBlocks(t *testing.T) { backend := &mock.API{} restForwarder := &restmock.RestServerApi{} - // Bring up upstream server blkCnt := 10 blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) From 264f86ff1b6332738f19130b19f24cd6eb82b47d Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Sat, 24 Jun 2023 00:03:31 +0300 Subject: [PATCH 13/30] Updated rest README --- engine/access/rest/README.md | 19 +++++++++++++------ engine/access/rest/server_request_handler.go | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/engine/access/rest/README.md b/engine/access/rest/README.md index fd7b970493d..a972fe8c385 100644 --- a/engine/access/rest/README.md +++ b/engine/access/rest/README.md @@ -6,10 +6,15 @@ available on our [docs site](https://docs.onflow.org/http-api/). ## Packages -- `rest`: The HTTP handlers for all the request, server generator and the select filter. +- `rest`: The HTTP handlers for the server generator and the select filter, implementation of handling local requests. - `middleware`: The common [middlewares](https://github.com/gorilla/mux#middleware) that all request pass through. - `models`: The generated models using openapi generators and implementation of model builders. - `request`: Implementation of API requests that provide validation for input data and build request models. +- `routes`: The common HTTP handlers for all the requests. +- `api`: The server API interface for REST service. +- `apiproxy`: Implementation of proxy router which splits requests for observer node between local and request +forwarding to upstream, implementation of handling request forwarding to an upstream access node using gRPC API. +- `tests`: Test for each request. ## Request lifecycle @@ -37,17 +42,19 @@ make generate-openapi ### Adding New API Endpoints -A new endpoint can be added by first implementing a new request handler, a request handle is a function in the rest +A new endpoint can be added by first implementing a new request handler, a request handle is a function in the routes package that complies with function interfaced defined as: ```go type ApiHandlerFunc func ( r *request.Request, -backend access.API, +srv api.RestServerApi, generator models.LinkGenerator, ) (interface{}, error) ``` -That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Adding a -new API endpoint also requires for a new request builder to be implemented and added in request package. Make sure to -not forget about adding tests for each of the API handler. +That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Then needs +to be added new request to `RestServerApi` interface and implemented it for local API service to the `RequestHandler` and for +request forwarding to the `RestForwarder`. After that new function needs to be added to the `RestRouter` for representing +the routing proxy algorithm. Adding a new API endpoint also requires for a new request builder to be implemented and added +in request package. Make sure to not forget about adding tests for each of the API handler. diff --git a/engine/access/rest/server_request_handler.go b/engine/access/rest/server_request_handler.go index 12d45550705..257c9f82deb 100644 --- a/engine/access/rest/server_request_handler.go +++ b/engine/access/rest/server_request_handler.go @@ -14,7 +14,7 @@ import ( "github.com/onflow/flow-go/model/flow" ) -// ServerRequestHandler is a structure that represents local requests +// ServerRequestHandler is a structure that represents handling local requests. type ServerRequestHandler struct { log zerolog.Logger backend access.API From 12f69d2674077e2cc0d3c8c53cae9b9b070c2b66 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Mon, 26 Jun 2023 15:52:41 +0300 Subject: [PATCH 14/30] Moved forwarder package to engine/common/grpc/forwarder, added constructor for backend cache, updated tests --- .../node_builder/access_node_builder.go | 37 +++++++++--- cmd/observer/node_builder/observer_builder.go | 38 +++++++++--- .../export_report.json | 6 -- engine/access/apiproxy/access_api_proxy.go | 2 +- .../access/apiproxy/access_api_proxy_test.go | 2 +- engine/access/rest/apiproxy/forwarder.go | 2 +- engine/access/rest_api_test.go | 15 +++-- engine/access/rpc/backend/backend.go | 58 ++----------------- engine/access/rpc/rate_limit_test.go | 13 +++-- engine/access/secure_grpcr_test.go | 14 ++--- .../common/grpc}/forwarder/forwarder.go | 0 11 files changed, 88 insertions(+), 99 deletions(-) delete mode 100644 cmd/util/cmd/execution-state-extract/export_report.json rename {module => engine/common/grpc}/forwarder/forwarder.go (100%) diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 6222c1d4b53..fc69272d16a 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -991,8 +991,29 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { }). Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { config := builder.rpcConf + backendConfig := config.BackendConfig + accessMetrics := builder.AccessMetrics - backend, err := backend.NewBackend(node.Logger, + cache, cacheSize, err := backend.NewCache(node.Logger, + accessMetrics, + backendConfig.ConnectionPoolSize) + if err != nil { + return nil, fmt.Errorf("could not initialize backend cache: %w", err) + } + + connFactory := &backend.ConnectionFactoryImpl{ + CollectionGRPCPort: builder.collectionGRPCPort, + ExecutionGRPCPort: builder.executionGRPCPort, + CollectionNodeGRPCTimeout: backendConfig.CollectionClientTimeout, + ExecutionNodeGRPCTimeout: backendConfig.ExecutionClientTimeout, + ConnectionsCache: cache, + CacheSize: cacheSize, + MaxMsgSize: config.MaxMsgSize, + AccessMetrics: accessMetrics, + Log: node.Logger, + } + + backend := backend.New( node.State, builder.CollectionRPC, builder.HistoricalAccessRPCs, @@ -1004,14 +1025,14 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { node.Storage.Results, node.RootChainID, builder.AccessMetrics, - builder.collectionGRPCPort, - builder.executionGRPCPort, + connFactory, builder.retryEnabled, - config.MaxMsgSize, - config.BackendConfig) - if err != nil { - return nil, fmt.Errorf("could not initialize backend: %w", err) - } + backendConfig.MaxHeightRange, + backendConfig.PreferredExecutionNodeIDs, + backendConfig.FixedExecutionNodeIDs, + node.Logger, + backend.DefaultSnapshotHistoryLimit, + backendConfig.ArchiveAddressList) engineBuilder, err := rpc.NewBuilder( node.Logger, diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 1920aec0106..7a93d9e2afa 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -855,7 +855,28 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { builder.Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { accessMetrics := metrics.NewNoopCollector() config := builder.rpcConf - accessBackend, err := backend.NewBackend(node.Logger, + backendConfig := config.BackendConfig + + cache, cacheSize, err := backend.NewCache(node.Logger, + accessMetrics, + config.BackendConfig.ConnectionPoolSize) + if err != nil { + return nil, fmt.Errorf("could not initialize backend cache: %w", err) + } + + connFactory := &backend.ConnectionFactoryImpl{ + CollectionGRPCPort: 0, + ExecutionGRPCPort: 0, + CollectionNodeGRPCTimeout: backendConfig.CollectionClientTimeout, + ExecutionNodeGRPCTimeout: backendConfig.ExecutionClientTimeout, + ConnectionsCache: cache, + CacheSize: cacheSize, + MaxMsgSize: config.MaxMsgSize, + AccessMetrics: accessMetrics, + Log: node.Logger, + } + + accessBackend := backend.New( node.State, nil, nil, @@ -867,15 +888,14 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { node.Storage.Results, node.RootChainID, accessMetrics, - 0, - 0, + connFactory, false, - config.MaxMsgSize, - config.BackendConfig) - - if err != nil { - return nil, fmt.Errorf("could not initialize backend: %w", err) - } + backendConfig.MaxHeightRange, + backendConfig.PreferredExecutionNodeIDs, + backendConfig.FixedExecutionNodeIDs, + node.Logger, + backend.DefaultSnapshotHistoryLimit, + backendConfig.ArchiveAddressList) engineBuilder, err := rpc.NewBuilder( node.Logger, diff --git a/cmd/util/cmd/execution-state-extract/export_report.json b/cmd/util/cmd/execution-state-extract/export_report.json deleted file mode 100644 index 3c4a27478db..00000000000 --- a/cmd/util/cmd/execution-state-extract/export_report.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "EpochCounter": 0, - "PreviousStateCommitment": "8536ee2769a5b35be123a54e45a23d2eaf3fa9f3df3bde6a713c87c286b9ec40", - "CurrentStateCommitment": "8536ee2769a5b35be123a54e45a23d2eaf3fa9f3df3bde6a713c87c286b9ec40", - "ReportSucceeded": true -} \ No newline at end of file diff --git a/engine/access/apiproxy/access_api_proxy.go b/engine/access/apiproxy/access_api_proxy.go index 2b345420229..f5898686fc6 100644 --- a/engine/access/apiproxy/access_api_proxy.go +++ b/engine/access/apiproxy/access_api_proxy.go @@ -9,9 +9,9 @@ import ( "github.com/onflow/flow/protobuf/go/flow/access" "github.com/rs/zerolog" + "github.com/onflow/flow-go/engine/common/grpc/forwarder" "github.com/onflow/flow-go/engine/protocol" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/forwarder" "github.com/onflow/flow-go/module/metrics" ) diff --git a/engine/access/apiproxy/access_api_proxy_test.go b/engine/access/apiproxy/access_api_proxy_test.go index f8f2dce72e4..d20c5ee705d 100644 --- a/engine/access/apiproxy/access_api_proxy_test.go +++ b/engine/access/apiproxy/access_api_proxy_test.go @@ -11,8 +11,8 @@ import ( "google.golang.org/grpc" grpcinsecure "google.golang.org/grpc/credentials/insecure" + "github.com/onflow/flow-go/engine/common/grpc/forwarder" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/forwarder" "github.com/onflow/flow-go/utils/grpcutils" "github.com/onflow/flow-go/utils/unittest" ) diff --git a/engine/access/rest/apiproxy/forwarder.go b/engine/access/rest/apiproxy/forwarder.go index d5b65102196..164db705b47 100644 --- a/engine/access/rest/apiproxy/forwarder.go +++ b/engine/access/rest/apiproxy/forwarder.go @@ -15,9 +15,9 @@ import ( "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/routes" + "github.com/onflow/flow-go/engine/common/grpc/forwarder" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/forwarder" accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index 7e099f00bf3..61c4f75de0b 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -120,8 +120,7 @@ func (suite *RestAPITestSuite) SetupTest() { RESTListenAddr: unittest.DefaultAddress, } - backend, err := backend.NewBackend( - suite.log, + backend := backend.New( suite.state, suite.collClient, nil, @@ -133,14 +132,14 @@ func (suite *RestAPITestSuite) SetupTest() { suite.executionResults, suite.chainID, suite.metrics, - 0, - 0, + nil, false, 0, - config.BackendConfig, - ) - - require.NoError(suite.T(), err) + nil, + nil, + suite.log, + 0, + nil) rpcEngBuilder, err := rpc.NewBuilder( suite.log, diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index d5a29d04adb..97d23f9a5ab 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -201,28 +201,14 @@ func New( return b } -func NewBackend( +func NewCache( log zerolog.Logger, - state protocol.State, - collectionRPC accessproto.AccessAPIClient, - historicalAccessNodes []accessproto.AccessAPIClient, - blocks storage.Blocks, - headers storage.Headers, - collections storage.Collections, - transactions storage.Transactions, - executionReceipts storage.ExecutionReceipts, - executionResults storage.ExecutionResults, - chainID flow.ChainID, accessMetrics module.AccessMetrics, - collectionGRPCPort uint, - executionGRPCPort uint, - retryEnabled bool, - maxMsgSize uint, - config Config, -) (*Backend, error) { + connectionPoolSize uint, +) (*lru.Cache, uint, error) { var cache *lru.Cache - cacheSize := config.ConnectionPoolSize + cacheSize := connectionPoolSize if cacheSize > 0 { // TODO: remove this fallback after fixing issues with evictions // It was observed that evictions cause connection errors for in flight requests. This works around @@ -241,42 +227,10 @@ func NewBackend( } }) if err != nil { - return nil, fmt.Errorf("could not initialize connection pool cache: %w", err) + return nil, 0, fmt.Errorf("could not initialize connection pool cache: %w", err) } } - - connectionFactory := &ConnectionFactoryImpl{ - CollectionGRPCPort: collectionGRPCPort, - ExecutionGRPCPort: executionGRPCPort, - CollectionNodeGRPCTimeout: config.CollectionClientTimeout, - ExecutionNodeGRPCTimeout: config.ExecutionClientTimeout, - ConnectionsCache: cache, - CacheSize: cacheSize, - MaxMsgSize: maxMsgSize, - AccessMetrics: accessMetrics, - Log: log, - } - - return New(state, - collectionRPC, - historicalAccessNodes, - blocks, - headers, - collections, - transactions, - executionReceipts, - executionResults, - chainID, - accessMetrics, - connectionFactory, - retryEnabled, - config.MaxHeightRange, - config.PreferredExecutionNodeIDs, - config.FixedExecutionNodeIDs, - log, - DefaultSnapshotHistoryLimit, - config.ArchiveAddressList, - ), nil + return cache, cacheSize, nil } func identifierList(ids []string) (flow.IdentifierList, error) { diff --git a/engine/access/rpc/rate_limit_test.go b/engine/access/rpc/rate_limit_test.go index d0b31b19118..87551c96d5d 100644 --- a/engine/access/rpc/rate_limit_test.go +++ b/engine/access/rpc/rate_limit_test.go @@ -119,8 +119,7 @@ func (suite *RateLimitTestSuite) SetupTest() { block := unittest.BlockHeaderFixture() suite.snapshot.On("Head").Return(block, nil) - backend, err := backend.NewBackend( - suite.log, + backend := backend.New( suite.state, suite.collClient, nil, @@ -132,12 +131,14 @@ func (suite *RateLimitTestSuite) SetupTest() { nil, suite.chainID, suite.metrics, - 0, - 0, + nil, false, 0, - config.BackendConfig) - require.NoError(suite.T(), err) + nil, + nil, + suite.log, + 0, + nil) rpcEngBuilder, err := NewBuilder( suite.log, diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index 86d018e0548..a5975a7e92c 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -11,7 +11,6 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -110,8 +109,7 @@ func (suite *SecureGRPCTestSuite) SetupTest() { block := unittest.BlockHeaderFixture() suite.snapshot.On("Head").Return(block, nil) - backend, err := backend.NewBackend( - suite.log, + backend := backend.New( suite.state, suite.collClient, nil, @@ -123,12 +121,14 @@ func (suite *SecureGRPCTestSuite) SetupTest() { nil, suite.chainID, suite.metrics, - 0, - 0, + nil, false, 0, - config.BackendConfig) - require.NoError(suite.T(), err) + nil, + nil, + suite.log, + 0, + nil) rpcEngBuilder, err := rpc.NewBuilder( suite.log, diff --git a/module/forwarder/forwarder.go b/engine/common/grpc/forwarder/forwarder.go similarity index 100% rename from module/forwarder/forwarder.go rename to engine/common/grpc/forwarder/forwarder.go From 0e76e55a09a24c425cebd6e50ddc604a5a8f94ab Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 29 Jun 2023 12:31:05 +0300 Subject: [PATCH 15/30] Refactored request forwarding according to suggestions from code review --- .../node_builder/access_node_builder.go | 4 +- cmd/observer/node_builder/observer_builder.go | 25 +- engine/access/rest/README.md | 13 +- engine/access/rest/api/api.go | 61 +- engine/access/rest/apiproxy/forwarder.go | 604 ------------------ .../rest/apiproxy/rest_proxy_handler.go | 336 ++++++++++ engine/access/rest/apiproxy/router.go | 126 ---- engine/access/rest/handler.go | 10 +- engine/access/rest/mock/rest_backend_api.go | 505 +++++++++++++++ engine/access/rest/mock/rest_server_api.go | 380 ----------- engine/access/rest/models/collection.go | 47 -- engine/access/rest/models/event.go | 28 - engine/access/rest/models/network.go | 6 - .../access/rest/models/node_version_info.go | 11 - engine/access/rest/router.go | 4 +- engine/access/rest/routes/accounts.go | 20 +- engine/access/rest/routes/blocks.go | 211 +++--- engine/access/rest/routes/collections.go | 29 +- engine/access/rest/routes/events.go | 39 +- engine/access/rest/routes/execution_result.go | 42 +- engine/access/rest/routes/network.go | 8 +- .../access/rest/routes/node_version_info.go | 11 +- engine/access/rest/routes/scripts.go | 22 +- engine/access/rest/routes/transactions.go | 43 +- engine/access/rest/server.go | 2 +- engine/access/rest/server_request_handler.go | 346 ---------- engine/access/rest/tests/accounts_test.go | 180 +----- engine/access/rest/tests/blocks_test.go | 42 +- engine/access/rest/tests/collections_test.go | 7 +- engine/access/rest/tests/events_test.go | 3 +- .../rest/tests/execution_result_test.go | 18 +- engine/access/rest/tests/network_test.go | 3 +- .../rest/tests/node_version_info_test.go | 3 +- engine/access/rest/tests/scripts_test.go | 21 +- engine/access/rest/tests/test_helpers.go | 35 +- engine/access/rest/tests/transactions_test.go | 32 +- engine/access/rpc/backend/backend.go | 1 + engine/access/rpc/engine.go | 2 +- engine/access/rpc/engine_builder.go | 9 +- engine/common/rpc/convert/convert.go | 22 + 40 files changed, 1301 insertions(+), 2010 deletions(-) delete mode 100644 engine/access/rest/apiproxy/forwarder.go create mode 100644 engine/access/rest/apiproxy/rest_proxy_handler.go delete mode 100644 engine/access/rest/apiproxy/router.go create mode 100644 engine/access/rest/mock/rest_backend_api.go delete mode 100644 engine/access/rest/mock/rest_server_api.go delete mode 100644 engine/access/rest/server_request_handler.go diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index fc69272d16a..c13a6db6f40 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -994,7 +994,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { backendConfig := config.BackendConfig accessMetrics := builder.AccessMetrics - cache, cacheSize, err := backend.NewCache(node.Logger, + backendCache, cacheSize, err := backend.NewCache(node.Logger, accessMetrics, backendConfig.ConnectionPoolSize) if err != nil { @@ -1006,7 +1006,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { ExecutionGRPCPort: builder.executionGRPCPort, CollectionNodeGRPCTimeout: backendConfig.CollectionClientTimeout, ExecutionNodeGRPCTimeout: backendConfig.ExecutionClientTimeout, - ConnectionsCache: cache, + ConnectionsCache: backendCache, CacheSize: cacheSize, MaxMsgSize: config.MaxMsgSize, AccessMetrics: accessMetrics, diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 7a93d9e2afa..5c8e4922c7e 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "strings" "time" @@ -29,11 +28,11 @@ import ( recovery "github.com/onflow/flow-go/consensus/recovery/protocol" "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/apiproxy" - "github.com/onflow/flow-go/engine/access/rest" restapiproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/follower" + "github.com/onflow/flow-go/engine/common/grpc/forwarder" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/engine/protocol" "github.com/onflow/flow-go/model/encodable" @@ -857,7 +856,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { config := builder.rpcConf backendConfig := config.BackendConfig - cache, cacheSize, err := backend.NewCache(node.Logger, + backendCache, cacheSize, err := backend.NewCache(node.Logger, accessMetrics, config.BackendConfig.ConnectionPoolSize) if err != nil { @@ -869,7 +868,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { ExecutionGRPCPort: 0, CollectionNodeGRPCTimeout: backendConfig.CollectionClientTimeout, ExecutionNodeGRPCTimeout: backendConfig.ExecutionClientTimeout, - ConnectionsCache: cache, + ConnectionsCache: backendCache, CacheSize: cacheSize, MaxMsgSize: config.MaxMsgSize, AccessMetrics: accessMetrics, @@ -914,7 +913,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { } // upstream access node forwarder - forwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) + rpcForwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) if err != nil { return nil, err } @@ -924,7 +923,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { rpcHandler := &apiproxy.FlowAccessAPIRouter{ Logger: builder.Logger, Metrics: observerCollector, - Upstream: forwarder, + Upstream: rpcForwarder, Observer: protocol.NewHandler(protocol.New( node.State, node.Storage.Blocks, @@ -932,8 +931,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { backend.NewNetworkAPI(node.State, node.RootChainID, backend.DefaultSnapshotHistoryLimit), )), } - - restForwarder, err := restapiproxy.NewRestForwarder(builder.Logger, + frw, err := forwarder.NewForwarder( builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) @@ -941,12 +939,13 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { return nil, err } - restHandler := &restapiproxy.RestRouter{ - Logger: builder.Logger, - Metrics: observerCollector, - Upstream: restForwarder, - Observer: rest.NewServerRequestHandler(builder.Logger, accessBackend), + restHandler := &restapiproxy.RestProxyHandler{ + Logger: builder.Logger, + Metrics: observerCollector, + Chain: node.RootChainID.Chain(), } + restHandler.API = accessBackend + restHandler.Forwarder = frw // build the rpc engine builder.RpcEng, err = engineBuilder. diff --git a/engine/access/rest/README.md b/engine/access/rest/README.md index a972fe8c385..633acf65707 100644 --- a/engine/access/rest/README.md +++ b/engine/access/rest/README.md @@ -12,8 +12,8 @@ available on our [docs site](https://docs.onflow.org/http-api/). - `request`: Implementation of API requests that provide validation for input data and build request models. - `routes`: The common HTTP handlers for all the requests. - `api`: The server API interface for REST service. -- `apiproxy`: Implementation of proxy router which splits requests for observer node between local and request -forwarding to upstream, implementation of handling request forwarding to an upstream access node using gRPC API. +- `apiproxy`: Implementation of proxy backend handler which includes the local backend and forwards the methods which +can't be handled locally to an upstream using gRPC API. - `tests`: Test for each request. ## Request lifecycle @@ -48,13 +48,12 @@ package that complies with function interfaced defined as: ```go type ApiHandlerFunc func ( r *request.Request, -srv api.RestServerApi, +backend api.RestBackendApi, generator models.LinkGenerator, ) (interface{}, error) ``` That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Then needs -to be added new request to `RestServerApi` interface and implemented it for local API service to the `RequestHandler` and for -request forwarding to the `RestForwarder`. After that new function needs to be added to the `RestRouter` for representing -the routing proxy algorithm. Adding a new API endpoint also requires for a new request builder to be implemented and added -in request package. Make sure to not forget about adding tests for each of the API handler. +to be added new request to `RestBackendApi` interface and overrides the method if it should be proxied to the backend +handler `RestProxyHandler` for request forwarding. Adding a new API endpoint also requires for a new request builder to +be implemented and added in request package. Make sure to not forget about adding tests for each of the API handler. diff --git a/engine/access/rest/api/api.go b/engine/access/rest/api/api.go index aea539e0571..81d45cccb6e 100644 --- a/engine/access/rest/api/api.go +++ b/engine/access/rest/api/api.go @@ -3,39 +3,36 @@ package api import ( "context" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/model/flow" ) -// RestServerApi is the server API for REST service. -type RestServerApi interface { - // GetTransactionByID gets a transaction by requested ID. - GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) - // CreateTransaction creates a new transaction from provided payload. - CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) - // GetTransactionResultByID retrieves transaction result by the transaction ID. - GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) - // GetBlocksByIDs gets blocks by provided ID or list of IDs. - GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) - // GetBlocksByHeight gets blocks by provided height. - GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) - // GetBlockPayloadByID gets block payload by ID - GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) - // GetExecutionResultByID gets execution result by the ID. - GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) - // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. - GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) - // GetCollectionByID retrieves a collection by ID and builds a response - GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) - // ExecuteScript handler sends the script from the request to be executed. - ExecuteScript(r request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) - // GetAccount handler retrieves account by address and returns the response. - GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) - // GetEvents for the provided block range or list of block IDs filtered by type. - GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) - // GetNetworkParameters returns network-wide parameters of the blockchain - GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) - // GetNodeVersionInfo returns node version information - GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) +// RestBackendApi is the backend API for REST service. +type RestBackendApi interface { + GetNetworkParameters(ctx context.Context) access.NetworkParameters + GetNodeVersionInfo(ctx context.Context) (*access.NodeVersionInfo, error) + + GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) + + GetLatestBlock(ctx context.Context, isSealed bool) (*flow.Block, flow.BlockStatus, error) + GetBlockByHeight(ctx context.Context, height uint64) (*flow.Block, flow.BlockStatus, error) + GetBlockByID(ctx context.Context, id flow.Identifier) (*flow.Block, flow.BlockStatus, error) + + GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) + + SendTransaction(ctx context.Context, tx *flow.TransactionBody) error + GetTransaction(ctx context.Context, id flow.Identifier) (*flow.TransactionBody, error) + GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) + + GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) + + ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments [][]byte) ([]byte, error) + ExecuteScriptAtBlockHeight(ctx context.Context, blockHeight uint64, script []byte, arguments [][]byte) ([]byte, error) + ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments [][]byte) ([]byte, error) + + GetEventsForHeightRange(ctx context.Context, eventType string, startHeight, endHeight uint64) ([]flow.BlockEvents, error) + GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) + + GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) + GetExecutionResultByID(ctx context.Context, id flow.Identifier) (*flow.ExecutionResult, error) } diff --git a/engine/access/rest/apiproxy/forwarder.go b/engine/access/rest/apiproxy/forwarder.go deleted file mode 100644 index 164db705b47..00000000000 --- a/engine/access/rest/apiproxy/forwarder.go +++ /dev/null @@ -1,604 +0,0 @@ -package apiproxy - -import ( - "context" - "fmt" - "time" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/rest/api" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/engine/access/rest/routes" - "github.com/onflow/flow-go/engine/common/grpc/forwarder" - "github.com/onflow/flow-go/engine/common/rpc/convert" - "github.com/onflow/flow-go/model/flow" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/onflow/flow/protobuf/go/flow/entities" -) - -// RestForwarder - structure which handles requests to an upstream access node using gRPC API. -type RestForwarder struct { - log zerolog.Logger - *forwarder.Forwarder -} - -var _ api.RestServerApi = (*RestForwarder)(nil) - -// NewRestForwarder returns new RestForwarder. -func NewRestForwarder(log zerolog.Logger, identities flow.IdentityList, timeout time.Duration, maxMsgSize uint) (*RestForwarder, error) { - f, err := forwarder.NewForwarder(identities, timeout, maxMsgSize) - - restForwarder := &RestForwarder{ - log: log, - Forwarder: f, - } - return restForwarder, err -} - -// GetTransactionByID gets a transaction by requested ID. -func (f *RestForwarder) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { - var response models.Transaction - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getTransactionRequest := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - } - transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) - if err != nil { - return response, err - } - - var transactionResultResponse *accessproto.TransactionResultResponse - // only lookup result if transaction result is to be expanded - if r.ExpandsResult { - getTransactionResultRequest := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - BlockId: r.BlockID[:], - CollectionId: r.CollectionID[:], - } - transactionResultResponse, err = upstream.GetTransactionResult(context, getTransactionResultRequest) - if err != nil { - return response, err - } - } - flowTransaction, err := convert.MessageToTransaction(transactionResponse.Transaction, chain) - if err != nil { - return response, err - } - - flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) - - response.Build(&flowTransaction, flowTransactionResult, link) - return response, nil -} - -// CreateTransaction creates a new transaction from provided payload. -func (f *RestForwarder) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - var response models.Transaction - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - entitiesTransaction := convert.TransactionToMessage(r.Transaction) - sendTransactionRequest := &accessproto.SendTransactionRequest{ - Transaction: entitiesTransaction, - } - - _, err = upstream.SendTransaction(context, sendTransactionRequest) - if err != nil { - return response, err - } - - response.Build(&r.Transaction, nil, link) - return response, nil -} - -// GetTransactionResultByID retrieves transaction result by the transaction ID. -func (f *RestForwarder) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - var response models.TransactionResult - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getTransactionResult := &accessproto.GetTransactionRequest{ - Id: r.ID[:], - BlockId: r.BlockID[:], - CollectionId: r.CollectionID[:], - } - transactionResultResponse, err := upstream.GetTransactionResult(context, getTransactionResult) - if err != nil { - return response, err - } - - flowTransactionResult := access.MessageToTransactionResult(transactionResultResponse) - response.Build(flowTransactionResult, r.ID, link) - return response, nil -} - -// GetBlocksByIDs gets blocks by provided ID or list of IDs. -func (f *RestForwarder) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - blocks := make([]*models.Block, len(r.IDs)) - - upstream, err := f.FaultTolerantClient() - if err != nil { - return blocks, err - } - - for i, id := range r.IDs { - block, err := getBlockFromGrpc(routes.ForID(&id), context, expandFields, upstream, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil -} - -// GetBlocksByHeight gets blocks by provided height. -func (f *RestForwarder) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - req, err := r.GetBlockRequest() - if err != nil { - return nil, models.NewBadRequestError(err) - } - - upstream, err := f.FaultTolerantClient() - if err != nil { - return nil, err - } - - if req.FinalHeight || req.SealedHeight { - block, err := getBlockFromGrpc(routes.ForFinalized(req.Heights[0]), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - - return []*models.Block{block}, nil - } - - // if the query is /blocks/height=1000,1008,1049... - if req.HasHeights() { - blocks := make([]*models.Block, len(req.Heights)) - for i, height := range req.Heights { - block, err := getBlockFromGrpc(routes.ForHeight(height), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil - } - - // support providing end height as "sealed" or "final" - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ - IsSealed: req.EndHeight == request.SealedHeight, - } - blockResponse, err := upstream.GetLatestBlock(r.Context(), getLatestBlockRequest) - if err != nil { - return nil, err - } - - req.EndHeight = blockResponse.Block.BlockHeader.Height // overwrite special value height with fetched - - if req.StartHeight > req.EndHeight { - return nil, models.NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) - } - } - - blocks := make([]*models.Block, 0) - // start and end height inclusive - for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := getBlockFromGrpc(routes.ForHeight(i), r.Context(), r.ExpandFields, upstream, link) - if err != nil { - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -// GetBlockPayloadByID gets block payload by ID -func (f *RestForwarder) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { - var payload models.BlockPayload - - upstream, err := f.FaultTolerantClient() - if err != nil { - return payload, err - } - - blkProvider := routes.NewBlockFromGrpcProvider(upstream, routes.ForID(&r.ID)) - block, _, statusErr := blkProvider.GetBlock(context) - if statusErr != nil { - return payload, statusErr - } - - flowPayload, err := convert.PayloadFromMessage(block) - if err != nil { - return payload, err - } - - err = payload.Build(flowPayload) - if err != nil { - return payload, err - } - - return payload, nil -} - -// GetExecutionResultByID gets execution result by the ID. -func (f *RestForwarder) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - var response models.ExecutionResult - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - executionResultByIDRequest := &accessproto.GetExecutionResultByIDRequest{ - Id: r.ID[:], - } - - executionResultByIDResponse, err := upstream.GetExecutionResultByID(context, executionResultByIDRequest) - if err != nil { - return response, err - } - - if executionResultByIDResponse == nil { - err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) - return response, models.NewNotFoundError(err.Error(), err) - } - - flowExecResult, err := convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) - if err != nil { - return response, err - } - err = response.Build(flowExecResult, link) - if err != nil { - return response, err - } - - return response, nil -} - -// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func (f *RestForwarder) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - // for each block ID we retrieve execution result - results := make([]models.ExecutionResult, len(r.BlockIDs)) - - upstream, err := f.FaultTolerantClient() - if err != nil { - return results, err - } - - for i, id := range r.BlockIDs { - getExecutionResultForBlockID := &accessproto.GetExecutionResultForBlockIDRequest{ - BlockId: id[:], - } - executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockID) - if err != nil { - return nil, err - } - - var response models.ExecutionResult - flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) - if err != nil { - return nil, err - } - err = response.Build(flowExecResult, link) - if err != nil { - return nil, err - } - results[i] = response - } - - return results, nil -} - -// GetCollectionByID retrieves a collection by ID and builds a response -func (f *RestForwarder) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { - var response models.Collection - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getCollectionByIDRequest := &accessproto.GetCollectionByIDRequest{ - Id: r.ID[:], - } - - collectionResponse, err := upstream.GetCollectionByID(context, getCollectionByIDRequest) - if err != nil { - return response, err - } - - // if we expand transactions in the query retrieve each transaction data - transactions := make([]*entities.Transaction, 0) - if r.ExpandsTransactions { - for _, tid := range collectionResponse.Collection.TransactionIds { - getTransactionRequest := &accessproto.GetTransactionRequest{ - Id: tid, - } - transactionResponse, err := upstream.GetTransaction(context, getTransactionRequest) - if err != nil { - return response, err - } - - transactions = append(transactions, transactionResponse.Transaction) - } - } - - err = response.BuildFromGrpc(collectionResponse.Collection, transactions, link, expandFields, chain) - if err != nil { - return response, err - } - - return response, nil -} - -// ExecuteScript handler sends the script from the request to be executed. -func (f *RestForwarder) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { - upstream, err := f.FaultTolerantClient() - if err != nil { - return nil, err - } - - if r.BlockID != flow.ZeroID { - executeScriptAtBlockIDRequest := &accessproto.ExecuteScriptAtBlockIDRequest{ - BlockId: r.BlockID[:], - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(context, executeScriptAtBlockIDRequest) - if err != nil { - return nil, err - } - return executeScriptAtBlockIDResponse.Value, nil - } - - // default to sealed height - if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { - executeScriptAtLatestBlockRequest := &accessproto.ExecuteScriptAtLatestBlockRequest{ - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(context, executeScriptAtLatestBlockRequest) - if err != nil { - return nil, err - } - return executeScriptAtLatestBlockResponse.Value, nil - } - - if r.BlockHeight == request.FinalHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: false, - } - getLatestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return nil, err - } - r.BlockHeight = getLatestBlockHeaderResponse.Block.Height - } - - executeScriptAtBlockHeightRequest := &accessproto.ExecuteScriptAtBlockHeightRequest{ - BlockHeight: r.BlockHeight, - Script: r.Script.Source, - Arguments: r.Script.Args, - } - executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(context, executeScriptAtBlockHeightRequest) - if err != nil { - return nil, err - } - return executeScriptAtBlockHeightResponse.Value, nil -} - -// GetAccount handler retrieves account by address and returns the response. -func (f *RestForwarder) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - var response models.Account - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it - if r.Height == request.FinalHeight || r.Height == request.SealedHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: r.Height == request.SealedHeight, - } - blockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return response, err - } - r.Height = blockHeaderResponse.Block.Height - } - getAccountAtBlockHeightRequest := &accessproto.GetAccountAtBlockHeightRequest{ - Address: r.Address.Bytes(), - BlockHeight: r.Height, - } - - accountResponse, err := upstream.GetAccountAtBlockHeight(context, getAccountAtBlockHeightRequest) - if err != nil { - return response, models.NewNotFoundError("not found account at block height", err) - } - - flowAccount, err := convert.MessageToAccount(accountResponse.Account) - if err != nil { - return response, err - } - - err = response.Build(flowAccount, link, expandFields) - return response, err -} - -// GetEvents for the provided block range or list of block IDs filtered by type. -func (f *RestForwarder) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { - // if the request has block IDs provided then return events for block IDs - var blocksEvents models.BlocksEvents - - upstream, err := f.FaultTolerantClient() - if err != nil { - return blocksEvents, err - } - - if len(r.BlockIDs) > 0 { - var blockIds [][]byte - for _, id := range r.BlockIDs { - blockIds = append(blockIds, id[:]) - } - getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ - Type: r.Type, - BlockIds: blockIds, - } - eventsResponse, err := upstream.GetEventsForBlockIDs(context, getEventsForBlockIDsRequest) - if err != nil { - return nil, err - } - - blocksEvents.BuildFromGrpc(eventsResponse.Results) - - return blocksEvents, nil - } - - // if end height is provided with special values then load the height - if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: r.EndHeight == request.SealedHeight, - } - latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(context, getLatestBlockHeaderRequest) - if err != nil { - return nil, err - } - - r.EndHeight = latestBlockHeaderResponse.Block.Height - // special check after we resolve special height value - if r.StartHeight > r.EndHeight { - return nil, models.NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) - } - } - - // if request provided block height range then return events for that range - getEventsForHeightRangeRequest := &accessproto.GetEventsForHeightRangeRequest{ - Type: r.Type, - StartHeight: r.StartHeight, - EndHeight: r.EndHeight, - } - eventsResponse, err := upstream.GetEventsForHeightRange(context, getEventsForHeightRangeRequest) - if err != nil { - return nil, err - } - - blocksEvents.BuildFromGrpc(eventsResponse.Results) - return blocksEvents, nil -} - -// GetNetworkParameters returns network-wide parameters of the blockchain -func (f *RestForwarder) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { - var response models.NetworkParameters - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getNetworkParametersRequest := &accessproto.GetNetworkParametersRequest{} - getNetworkParametersResponse, err := upstream.GetNetworkParameters(r.Context(), getNetworkParametersRequest) - if err != nil { - return response, err - } - response.BuildFromGrpc(getNetworkParametersResponse) - return response, nil -} - -// GetNodeVersionInfo returns node version information -func (f *RestForwarder) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { - var response models.NodeVersionInfo - - upstream, err := f.FaultTolerantClient() - if err != nil { - return response, err - } - - getNodeVersionInfoRequest := &accessproto.GetNodeVersionInfoRequest{} - getNodeVersionInfoResponse, err := upstream.GetNodeVersionInfo(r.Context(), getNodeVersionInfoRequest) - if err != nil { - return response, err - } - - response.BuildFromGrpc(getNodeVersionInfoResponse.Info) - return response, nil -} - -func getBlockFromGrpc(option routes.BlockRequestOption, context context.Context, expandFields map[string]bool, upstream accessproto.AccessAPIClient, link models.LinkGenerator) (*models.Block, error) { - // lookup block - blkProvider := routes.NewBlockFromGrpcProvider(upstream, option) - blk, blockStatus, err := blkProvider.GetBlock(context) - if err != nil { - return nil, err - } - - // lookup execution result - // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) - var block models.Block - getExecutionResultForBlockIDRequest := &accessproto.GetExecutionResultForBlockIDRequest{ - BlockId: blk.Id, - } - - executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(context, getExecutionResultForBlockIDRequest) - if err != nil { - return nil, err - } - - flowExecResult, err := convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) - if err != nil { - return nil, err - } - - flowBlock, err := convert.MessageToBlock(blk) - if err != nil { - return nil, err - } - - flowBlockStatus, err := convert.MessagesToBlockStatus(blockStatus) - if err != nil { - return nil, err - } - - if err != nil { - // handle case where execution result is not yet available - if se, ok := status.FromError(err); ok { - if se.Code() == codes.NotFound { - err := block.Build(flowBlock, nil, link, flowBlockStatus, expandFields) - if err != nil { - return nil, err - } - return &block, nil - } - } - return nil, err - } - - err = block.Build(flowBlock, flowExecResult, link, flowBlockStatus, expandFields) - if err != nil { - return nil, err - } - return &block, nil -} diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go new file mode 100644 index 00000000000..d4bc6546a40 --- /dev/null +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -0,0 +1,336 @@ +package apiproxy + +import ( + "context" + + "google.golang.org/grpc/status" + + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/models" + "github.com/onflow/flow-go/engine/common/grpc/forwarder" + "github.com/onflow/flow-go/engine/common/rpc/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/module/metrics" + + accessproto "github.com/onflow/flow/protobuf/go/flow/access" +) + +// RestProxyHandler is a structure that represents the proxy algorithm for observer node. +// It includes the local backend and forwards the methods which can't be handled locally to an upstream using gRPC API. +type RestProxyHandler struct { + access.API + *forwarder.Forwarder + Logger zerolog.Logger + Metrics metrics.ObserverMetrics + Chain flow.Chain +} + +func (r *RestProxyHandler) log(handler, rpc string, err error) { + code := status.Code(err) + r.Metrics.RecordRPC(handler, rpc, code) + + logger := r.Logger.With(). + Str("handler", handler). + Str("rest_method", rpc). + Str("rest_code", code.String()). + Logger() + + if err != nil { + logger.Error().Err(err).Msg("request failed") + return + } + + logger.Info().Msg("request succeeded") +} + +// GetLatestBlockHeader returns the latest block header and block status, if isSealed = true - returns the latest seal header. +func (r *RestProxyHandler) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { //Uliana getAccount та GetEvents були з Upstream + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, flow.BlockStatusUnknown, err + } + + getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ + IsSealed: isSealed, + } + latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(ctx, getLatestBlockHeaderRequest) + if err != nil { + return nil, flow.BlockStatusUnknown, err + } + blockHeader, err := convert.MessageToBlockHeader(latestBlockHeaderResponse.Block) + if err != nil { + return nil, flow.BlockStatusUnknown, err + } + blockStatus, err := convert.MessagesToBlockStatus(latestBlockHeaderResponse.BlockStatus) + if err != nil { + return nil, flow.BlockStatusUnknown, err + } + + r.log("upstream", "GetLatestBlockHeader", err) + return blockHeader, blockStatus, nil +} + +// GetCollectionByID returns a collection by ID. +func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getCollectionByIDRequest := &accessproto.GetCollectionByIDRequest{ + Id: id[:], + } + + collectionResponse, err := upstream.GetCollectionByID(ctx, getCollectionByIDRequest) + if err != nil { + return nil, err + } + + transactions := make([]flow.Identifier, len(collectionResponse.Collection.TransactionIds)) + for _, txId := range collectionResponse.Collection.TransactionIds { + transactions = append(transactions, convert.MessageToIdentifier(txId)) + } + + r.log("upstream", "GetCollectionByID", err) + return &flow.LightCollection{ + Transactions: transactions, + }, nil +} + +// SendTransaction sends already created transaction. +func (r *RestProxyHandler) SendTransaction(ctx context.Context, tx *flow.TransactionBody) error { + upstream, err := r.FaultTolerantClient() + if err != nil { + return err + } + + transaction := convert.TransactionToMessage(*tx) + sendTransactionRequest := &accessproto.SendTransactionRequest{ + Transaction: transaction, + } + + _, err = upstream.SendTransaction(ctx, sendTransactionRequest) + if err != nil { + return err + } + + r.log("upstream", "SendTransaction", err) + return nil + +} + +// GetTransaction returns transaction by ID. +func (r *RestProxyHandler) GetTransaction(ctx context.Context, id flow.Identifier) (*flow.TransactionBody, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getTransactionRequest := &accessproto.GetTransactionRequest{ + Id: id[:], + } + transactionResponse, err := upstream.GetTransaction(ctx, getTransactionRequest) + if err != nil { + return nil, err + } + + transactionBody, err := convert.MessageToTransaction(transactionResponse.Transaction, r.Chain) + if err != nil { + return nil, err + } + + r.log("upstream", "GetTransaction", err) + return &transactionBody, nil +} + +// GetTransactionResult returns transaction result by the transaction ID. +func (r *RestProxyHandler) GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getTransactionResultRequest := &accessproto.GetTransactionRequest{ + Id: id[:], + BlockId: blockID[:], + CollectionId: collectionID[:], + } + + transactionResultResponse, err := upstream.GetTransactionResult(ctx, getTransactionResultRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "GetTransactionResult", err) + return access.MessageToTransactionResult(transactionResultResponse), nil +} + +// GetAccountAtBlockHeight returns account by account address and block height. +func (r *RestProxyHandler) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getAccountAtBlockHeightRequest := &accessproto.GetAccountAtBlockHeightRequest{ + Address: address.Bytes(), + BlockHeight: height, + } + + accountResponse, err := upstream.GetAccountAtBlockHeight(ctx, getAccountAtBlockHeightRequest) + if err != nil { + return nil, models.NewNotFoundError("not found account at block height", err) + } + + r.log("upstream", "GetAccountAtBlockHeight", err) + return convert.MessageToAccount(accountResponse.Account) +} + +// ExecuteScriptAtLatestBlock executes script at latest block. +func (r *RestProxyHandler) ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments [][]byte) ([]byte, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + executeScriptAtLatestBlockRequest := &accessproto.ExecuteScriptAtLatestBlockRequest{ + Script: script, + Arguments: arguments, + } + executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(ctx, executeScriptAtLatestBlockRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "ExecuteScriptAtLatestBlock", err) + return executeScriptAtLatestBlockResponse.Value, nil +} + +// ExecuteScriptAtBlockHeight executes script at the given block height . +func (r *RestProxyHandler) ExecuteScriptAtBlockHeight(ctx context.Context, blockHeight uint64, script []byte, arguments [][]byte) ([]byte, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + executeScriptAtBlockHeightRequest := &accessproto.ExecuteScriptAtBlockHeightRequest{ + BlockHeight: blockHeight, + Script: script, + Arguments: arguments, + } + executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(ctx, executeScriptAtBlockHeightRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "ExecuteScriptAtBlockHeight", err) + return executeScriptAtBlockHeightResponse.Value, nil +} + +// ExecuteScriptAtBlockID executes script at the given block id . +func (r *RestProxyHandler) ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments [][]byte) ([]byte, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + executeScriptAtBlockIDRequest := &accessproto.ExecuteScriptAtBlockIDRequest{ + BlockId: blockID[:], + Script: script, + Arguments: arguments, + } + executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(ctx, executeScriptAtBlockIDRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "ExecuteScriptAtBlockID", err) + return executeScriptAtBlockIDResponse.Value, nil +} + +// GetEventsForHeightRange returns events by their name in the specified blocks heights. +func (r *RestProxyHandler) GetEventsForHeightRange(ctx context.Context, eventType string, startHeight, endHeight uint64) ([]flow.BlockEvents, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getEventsForHeightRangeRequest := &accessproto.GetEventsForHeightRangeRequest{ + Type: eventType, + StartHeight: startHeight, + EndHeight: endHeight, + } + eventsResponse, err := upstream.GetEventsForHeightRange(ctx, getEventsForHeightRangeRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "GetEventsForHeightRange", err) + return convert.MessagesToBlockEvents(eventsResponse.Results), nil +} + +// GetEventsForBlockIDs returns events by their name in the specified block IDs. +func (r *RestProxyHandler) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + var blockIds [][]byte + for _, id := range blockIDs { + blockIds = append(blockIds, id[:]) + } + + getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ + Type: eventType, + BlockIds: blockIds, + } + eventsResponse, err := upstream.GetEventsForBlockIDs(ctx, getEventsForBlockIDsRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "GetEventsForBlockIDs", err) + return convert.MessagesToBlockEvents(eventsResponse.Results), nil +} + +// GetExecutionResultForBlockID gets execution result by provided block ID. +func (r *RestProxyHandler) GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + getExecutionResultForBlockID := &accessproto.GetExecutionResultForBlockIDRequest{ + BlockId: blockID[:], + } + executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(ctx, getExecutionResultForBlockID) + if err != nil { + return nil, err + } + + r.log("upsteram", "GetExecutionResultForBlockID", err) + return convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) +} + +// GetExecutionResultByID gets execution result by its ID. +func (r *RestProxyHandler) GetExecutionResultByID(ctx context.Context, id flow.Identifier) (*flow.ExecutionResult, error) { + upstream, err := r.FaultTolerantClient() + if err != nil { + return nil, err + } + + executionResultByIDRequest := &accessproto.GetExecutionResultByIDRequest{ + Id: id[:], + } + + executionResultByIDResponse, err := upstream.GetExecutionResultByID(ctx, executionResultByIDRequest) + if err != nil { + return nil, err + } + + r.log("upstream", "GetExecutionResultByID", err) + return convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) +} diff --git a/engine/access/rest/apiproxy/router.go b/engine/access/rest/apiproxy/router.go deleted file mode 100644 index b3a3e8db086..00000000000 --- a/engine/access/rest/apiproxy/router.go +++ /dev/null @@ -1,126 +0,0 @@ -package apiproxy - -import ( - "context" - - "google.golang.org/grpc/status" - - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/engine/access/rest/api" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/metrics" -) - -// RestRouter is a structure that represents the routing proxy algorithm for observer node. -// It splits requests between a local requests and forward requests which can't be handled locally to an upstream access node. -type RestRouter struct { - Logger zerolog.Logger - Metrics metrics.ObserverMetrics - Upstream api.RestServerApi - Observer api.RestServerApi -} - -func (r *RestRouter) log(handler, rpc string, err error) { - code := status.Code(err) - r.Metrics.RecordRPC(handler, rpc, code) - - logger := r.Logger.With(). - Str("handler", handler). - Str("rest_method", rpc). - Str("rest_code", code.String()). - Logger() - - if err != nil { - logger.Error().Err(err).Msg("request failed") - return - } - - logger.Info().Msg("request succeeded") -} - -func (r *RestRouter) GetTransactionByID(req request.GetTransaction, context context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { - res, err := r.Upstream.GetTransactionByID(req, context, link, chain) - r.log("upstream", "GetNodeVersionInfo", err) - return res, err -} - -func (r *RestRouter) CreateTransaction(req request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - res, err := r.Upstream.CreateTransaction(req, context, link) - r.log("upstream", "CreateTransaction", err) - return res, err -} - -func (r *RestRouter) GetTransactionResultByID(req request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - res, err := r.Upstream.GetTransactionResultByID(req, context, link) - r.log("upstream", "GetTransactionResultByID", err) - return res, err -} - -func (r *RestRouter) GetBlocksByIDs(req request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - res, err := r.Observer.GetBlocksByIDs(req, context, expandFields, link) - r.log("observer", "GetBlocksByIDs", err) - return res, err -} - -func (r *RestRouter) GetBlocksByHeight(req *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - res, err := r.Observer.GetBlocksByHeight(req, link) - r.log("observer", "GetBlocksByHeight", err) - return res, err -} - -func (r *RestRouter) GetBlockPayloadByID(req request.GetBlockPayload, context context.Context, link models.LinkGenerator) (models.BlockPayload, error) { - res, err := r.Observer.GetBlockPayloadByID(req, context, link) - r.log("observer", "GetBlockPayloadByID", err) - return res, err -} - -func (r *RestRouter) GetExecutionResultByID(req request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - res, err := r.Upstream.GetExecutionResultByID(req, context, link) - r.log("upstream", "GetExecutionResultByID", err) - return res, err -} - -func (r *RestRouter) GetExecutionResultsByBlockIDs(req request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - res, err := r.Upstream.GetExecutionResultsByBlockIDs(req, context, link) - r.log("upstream", "GetExecutionResultsByBlockIDs", err) - return res, err -} - -func (r *RestRouter) GetCollectionByID(req request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { - res, err := r.Upstream.GetCollectionByID(req, context, expandFields, link, chain) - r.log("upstream", "GetCollectionByID", err) - return res, err -} - -func (r *RestRouter) ExecuteScript(req request.GetScript, context context.Context, link models.LinkGenerator) ([]byte, error) { - res, err := r.Upstream.ExecuteScript(req, context, link) - r.log("upstream", "ExecuteScript", err) - return res, err -} - -func (r *RestRouter) GetAccount(req request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - res, err := r.Upstream.GetAccount(req, context, expandFields, link) - r.log("upstream", "GetAccount", err) - return res, err -} - -func (r *RestRouter) GetEvents(req request.GetEvents, context context.Context) (models.BlocksEvents, error) { - res, err := r.Upstream.GetEvents(req, context) - r.log("upstream", "GetEvents", err) - return res, err -} - -func (r *RestRouter) GetNetworkParameters(req *request.Request) (models.NetworkParameters, error) { - res, err := r.Observer.GetNetworkParameters(req) - r.log("observer", "GetNetworkParameters", err) - return res, err -} - -func (r *RestRouter) GetNodeVersionInfo(req *request.Request) (models.NodeVersionInfo, error) { - res, err := r.Observer.GetNodeVersionInfo(req) - r.log("observer", "GetNodeVersionInfo", err) - return res, err -} diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index c56b5ccf114..c025a0c4c24 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -25,7 +25,7 @@ const MaxRequestSize = 2 << 20 // 2MB // it fetches necessary resources and returns an error or response model. type ApiHandlerFunc func( r *request.Request, - srv api.RestServerApi, + backend api.RestBackendApi, generator models.LinkGenerator, ) (interface{}, error) @@ -34,7 +34,7 @@ type ApiHandlerFunc func( // wraps functionality for handling error and responses outside of endpoint handling. type Handler struct { logger zerolog.Logger - restServerAPI api.RestServerApi + backend api.RestBackendApi linkGenerator models.LinkGenerator apiHandlerFunc ApiHandlerFunc chain flow.Chain @@ -42,14 +42,14 @@ type Handler struct { func NewHandler( logger zerolog.Logger, - restServerAPI api.RestServerApi, + backend api.RestBackendApi, handlerFunc ApiHandlerFunc, generator models.LinkGenerator, chain flow.Chain, ) *Handler { return &Handler{ logger: logger, - restServerAPI: restServerAPI, + backend: backend, apiHandlerFunc: handlerFunc, linkGenerator: generator, chain: chain, @@ -74,7 +74,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { decoratedRequest := request.Decorate(r, h.chain) // execute handler function and check for error - response, err := h.apiHandlerFunc(decoratedRequest, h.restServerAPI, h.linkGenerator) + response, err := h.apiHandlerFunc(decoratedRequest, h.backend, h.linkGenerator) if err != nil { h.errorHandler(w, err, errLog) return diff --git a/engine/access/rest/mock/rest_backend_api.go b/engine/access/rest/mock/rest_backend_api.go new file mode 100644 index 00000000000..61c3a938efa --- /dev/null +++ b/engine/access/rest/mock/rest_backend_api.go @@ -0,0 +1,505 @@ +// Code generated by mockery v2.21.4. DO NOT EDIT. + +package mock + +import ( + access "github.com/onflow/flow-go/access" + + context "context" + + flow "github.com/onflow/flow-go/model/flow" + + mock "github.com/stretchr/testify/mock" +) + +// RestBackendApi is an autogenerated mock type for the RestBackendApi type +type RestBackendApi struct { + mock.Mock +} + +// ExecuteScriptAtBlockHeight provides a mock function with given fields: ctx, blockHeight, script, arguments +func (_m *RestBackendApi) ExecuteScriptAtBlockHeight(ctx context.Context, blockHeight uint64, script []byte, arguments [][]byte) ([]byte, error) { + ret := _m.Called(ctx, blockHeight, script, arguments) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, [][]byte) ([]byte, error)); ok { + return rf(ctx, blockHeight, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, [][]byte) []byte); ok { + r0 = rf(ctx, blockHeight, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, []byte, [][]byte) error); ok { + r1 = rf(ctx, blockHeight, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteScriptAtBlockID provides a mock function with given fields: ctx, blockID, script, arguments +func (_m *RestBackendApi) ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments [][]byte) ([]byte, error) { + ret := _m.Called(ctx, blockID, script, arguments) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, [][]byte) ([]byte, error)); ok { + return rf(ctx, blockID, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, [][]byte) []byte); ok { + r0 = rf(ctx, blockID, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, []byte, [][]byte) error); ok { + r1 = rf(ctx, blockID, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ExecuteScriptAtLatestBlock provides a mock function with given fields: ctx, script, arguments +func (_m *RestBackendApi) ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments [][]byte) ([]byte, error) { + ret := _m.Called(ctx, script, arguments) + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, [][]byte) ([]byte, error)); ok { + return rf(ctx, script, arguments) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte, [][]byte) []byte); ok { + r0 = rf(ctx, script, arguments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte, [][]byte) error); ok { + r1 = rf(ctx, script, arguments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetAccountAtBlockHeight provides a mock function with given fields: ctx, address, height +func (_m *RestBackendApi) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) { + ret := _m.Called(ctx, address, height) + + var r0 *flow.Account + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) (*flow.Account, error)); ok { + return rf(ctx, address, height) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) *flow.Account); ok { + r0 = rf(ctx, address, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Account) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Address, uint64) error); ok { + r1 = rf(ctx, address, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBlockByHeight provides a mock function with given fields: ctx, height +func (_m *RestBackendApi) GetBlockByHeight(ctx context.Context, height uint64) (*flow.Block, flow.BlockStatus, error) { + ret := _m.Called(ctx, height) + + var r0 *flow.Block + var r1 flow.BlockStatus + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (*flow.Block, flow.BlockStatus, error)); ok { + return rf(ctx, height) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) *flow.Block); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) flow.BlockStatus); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Get(1).(flow.BlockStatus) + } + + if rf, ok := ret.Get(2).(func(context.Context, uint64) error); ok { + r2 = rf(ctx, height) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetBlockByID provides a mock function with given fields: ctx, id +func (_m *RestBackendApi) GetBlockByID(ctx context.Context, id flow.Identifier) (*flow.Block, flow.BlockStatus, error) { + ret := _m.Called(ctx, id) + + var r0 *flow.Block + var r1 flow.BlockStatus + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.Block, flow.BlockStatus, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.Block); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) flow.BlockStatus); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Get(1).(flow.BlockStatus) + } + + if rf, ok := ret.Get(2).(func(context.Context, flow.Identifier) error); ok { + r2 = rf(ctx, id) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetCollectionByID provides a mock function with given fields: ctx, id +func (_m *RestBackendApi) GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) { + ret := _m.Called(ctx, id) + + var r0 *flow.LightCollection + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.LightCollection, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.LightCollection); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.LightCollection) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEventsForBlockIDs provides a mock function with given fields: ctx, eventType, blockIDs +func (_m *RestBackendApi) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) { + ret := _m.Called(ctx, eventType, blockIDs) + + var r0 []flow.BlockEvents + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) ([]flow.BlockEvents, error)); ok { + return rf(ctx, eventType, blockIDs) + } + if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) []flow.BlockEvents); ok { + r0 = rf(ctx, eventType, blockIDs) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, []flow.Identifier) error); ok { + r1 = rf(ctx, eventType, blockIDs) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEventsForHeightRange provides a mock function with given fields: ctx, eventType, startHeight, endHeight +func (_m *RestBackendApi) GetEventsForHeightRange(ctx context.Context, eventType string, startHeight uint64, endHeight uint64) ([]flow.BlockEvents, error) { + ret := _m.Called(ctx, eventType, startHeight, endHeight) + + var r0 []flow.BlockEvents + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) ([]flow.BlockEvents, error)); ok { + return rf(ctx, eventType, startHeight, endHeight) + } + if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) []flow.BlockEvents); ok { + r0 = rf(ctx, eventType, startHeight, endHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]flow.BlockEvents) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { + r1 = rf(ctx, eventType, startHeight, endHeight) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionResultByID provides a mock function with given fields: ctx, id +func (_m *RestBackendApi) GetExecutionResultByID(ctx context.Context, id flow.Identifier) (*flow.ExecutionResult, error) { + ret := _m.Called(ctx, id) + + var r0 *flow.ExecutionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionResult, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionResult); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.ExecutionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetExecutionResultForBlockID provides a mock function with given fields: ctx, blockID +func (_m *RestBackendApi) GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) { + ret := _m.Called(ctx, blockID) + + var r0 *flow.ExecutionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionResult, error)); ok { + return rf(ctx, blockID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionResult); ok { + r0 = rf(ctx, blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.ExecutionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, blockID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLatestBlock provides a mock function with given fields: ctx, isSealed +func (_m *RestBackendApi) GetLatestBlock(ctx context.Context, isSealed bool) (*flow.Block, flow.BlockStatus, error) { + ret := _m.Called(ctx, isSealed) + + var r0 *flow.Block + var r1 flow.BlockStatus + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.Block, flow.BlockStatus, error)); ok { + return rf(ctx, isSealed) + } + if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.Block); ok { + r0 = rf(ctx, isSealed) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Block) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, bool) flow.BlockStatus); ok { + r1 = rf(ctx, isSealed) + } else { + r1 = ret.Get(1).(flow.BlockStatus) + } + + if rf, ok := ret.Get(2).(func(context.Context, bool) error); ok { + r2 = rf(ctx, isSealed) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetLatestBlockHeader provides a mock function with given fields: ctx, isSealed +func (_m *RestBackendApi) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { + ret := _m.Called(ctx, isSealed) + + var r0 *flow.Header + var r1 flow.BlockStatus + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.Header, flow.BlockStatus, error)); ok { + return rf(ctx, isSealed) + } + if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.Header); ok { + r0 = rf(ctx, isSealed) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.Header) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, bool) flow.BlockStatus); ok { + r1 = rf(ctx, isSealed) + } else { + r1 = ret.Get(1).(flow.BlockStatus) + } + + if rf, ok := ret.Get(2).(func(context.Context, bool) error); ok { + r2 = rf(ctx, isSealed) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// GetNetworkParameters provides a mock function with given fields: ctx +func (_m *RestBackendApi) GetNetworkParameters(ctx context.Context) access.NetworkParameters { + ret := _m.Called(ctx) + + var r0 access.NetworkParameters + if rf, ok := ret.Get(0).(func(context.Context) access.NetworkParameters); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(access.NetworkParameters) + } + + return r0 +} + +// GetNodeVersionInfo provides a mock function with given fields: ctx +func (_m *RestBackendApi) GetNodeVersionInfo(ctx context.Context) (*access.NodeVersionInfo, error) { + ret := _m.Called(ctx) + + var r0 *access.NodeVersionInfo + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*access.NodeVersionInfo, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *access.NodeVersionInfo); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*access.NodeVersionInfo) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransaction provides a mock function with given fields: ctx, id +func (_m *RestBackendApi) GetTransaction(ctx context.Context, id flow.Identifier) (*flow.TransactionBody, error) { + ret := _m.Called(ctx, id) + + var r0 *flow.TransactionBody + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.TransactionBody, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.TransactionBody); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.TransactionBody) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTransactionResult provides a mock function with given fields: ctx, id, blockID, collectionID +func (_m *RestBackendApi) GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) { + ret := _m.Called(ctx, id, blockID, collectionID) + + var r0 *access.TransactionResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) (*access.TransactionResult, error)); ok { + return rf(ctx, id, blockID, collectionID) + } + if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) *access.TransactionResult); ok { + r0 = rf(ctx, id, blockID, collectionID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*access.TransactionResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) error); ok { + r1 = rf(ctx, id, blockID, collectionID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendTransaction provides a mock function with given fields: ctx, tx +func (_m *RestBackendApi) SendTransaction(ctx context.Context, tx *flow.TransactionBody) error { + ret := _m.Called(ctx, tx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *flow.TransactionBody) error); ok { + r0 = rf(ctx, tx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewRestBackendApi interface { + mock.TestingT + Cleanup(func()) +} + +// NewRestBackendApi creates a new instance of RestBackendApi. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewRestBackendApi(t mockConstructorTestingTNewRestBackendApi) *RestBackendApi { + mock := &RestBackendApi{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/engine/access/rest/mock/rest_server_api.go b/engine/access/rest/mock/rest_server_api.go deleted file mode 100644 index fb87307189c..00000000000 --- a/engine/access/rest/mock/rest_server_api.go +++ /dev/null @@ -1,380 +0,0 @@ -// Code generated by mockery v2.21.4. DO NOT EDIT. - -package mock - -import ( - context "context" - - flow "github.com/onflow/flow-go/model/flow" - mock "github.com/stretchr/testify/mock" - - models "github.com/onflow/flow-go/engine/access/rest/models" - - request "github.com/onflow/flow-go/engine/access/rest/request" -) - -// RestServerApi is an autogenerated mock type for the RestServerApi type -type RestServerApi struct { - mock.Mock -} - -// CreateTransaction provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) CreateTransaction(r request.CreateTransaction, _a1 context.Context, link models.LinkGenerator) (models.Transaction, error) { - ret := _m.Called(r, _a1, link) - - var r0 models.Transaction - var r1 error - if rf, ok := ret.Get(0).(func(request.CreateTransaction, context.Context, models.LinkGenerator) (models.Transaction, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.CreateTransaction, context.Context, models.LinkGenerator) models.Transaction); ok { - r0 = rf(r, _a1, link) - } else { - r0 = ret.Get(0).(models.Transaction) - } - - if rf, ok := ret.Get(1).(func(request.CreateTransaction, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ExecuteScript provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) ExecuteScript(r request.GetScript, _a1 context.Context, link models.LinkGenerator) ([]byte, error) { - ret := _m.Called(r, _a1, link) - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(request.GetScript, context.Context, models.LinkGenerator) ([]byte, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.GetScript, context.Context, models.LinkGenerator) []byte); ok { - r0 = rf(r, _a1, link) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(request.GetScript, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetAccount provides a mock function with given fields: r, _a1, expandFields, link -func (_m *RestServerApi) GetAccount(r request.GetAccount, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - ret := _m.Called(r, _a1, expandFields, link) - - var r0 models.Account - var r1 error - if rf, ok := ret.Get(0).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) (models.Account, error)); ok { - return rf(r, _a1, expandFields, link) - } - if rf, ok := ret.Get(0).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) models.Account); ok { - r0 = rf(r, _a1, expandFields, link) - } else { - r0 = ret.Get(0).(models.Account) - } - - if rf, ok := ret.Get(1).(func(request.GetAccount, context.Context, map[string]bool, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, expandFields, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetBlockPayloadByID provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) GetBlockPayloadByID(r request.GetBlockPayload, _a1 context.Context, link models.LinkGenerator) (models.BlockPayload, error) { - ret := _m.Called(r, _a1, link) - - var r0 models.BlockPayload - var r1 error - if rf, ok := ret.Get(0).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) (models.BlockPayload, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) models.BlockPayload); ok { - r0 = rf(r, _a1, link) - } else { - r0 = ret.Get(0).(models.BlockPayload) - } - - if rf, ok := ret.Get(1).(func(request.GetBlockPayload, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetBlocksByHeight provides a mock function with given fields: r, link -func (_m *RestServerApi) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - ret := _m.Called(r, link) - - var r0 []*models.Block - var r1 error - if rf, ok := ret.Get(0).(func(*request.Request, models.LinkGenerator) ([]*models.Block, error)); ok { - return rf(r, link) - } - if rf, ok := ret.Get(0).(func(*request.Request, models.LinkGenerator) []*models.Block); ok { - r0 = rf(r, link) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*models.Block) - } - } - - if rf, ok := ret.Get(1).(func(*request.Request, models.LinkGenerator) error); ok { - r1 = rf(r, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetBlocksByIDs provides a mock function with given fields: r, _a1, expandFields, link -func (_m *RestServerApi) GetBlocksByIDs(r request.GetBlockByIDs, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - ret := _m.Called(r, _a1, expandFields, link) - - var r0 []*models.Block - var r1 error - if rf, ok := ret.Get(0).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) ([]*models.Block, error)); ok { - return rf(r, _a1, expandFields, link) - } - if rf, ok := ret.Get(0).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) []*models.Block); ok { - r0 = rf(r, _a1, expandFields, link) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*models.Block) - } - } - - if rf, ok := ret.Get(1).(func(request.GetBlockByIDs, context.Context, map[string]bool, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, expandFields, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetCollectionByID provides a mock function with given fields: r, _a1, expandFields, link, chain -func (_m *RestServerApi) GetCollectionByID(r request.GetCollection, _a1 context.Context, expandFields map[string]bool, link models.LinkGenerator, chain flow.Chain) (models.Collection, error) { - ret := _m.Called(r, _a1, expandFields, link, chain) - - var r0 models.Collection - var r1 error - if rf, ok := ret.Get(0).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) (models.Collection, error)); ok { - return rf(r, _a1, expandFields, link, chain) - } - if rf, ok := ret.Get(0).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) models.Collection); ok { - r0 = rf(r, _a1, expandFields, link, chain) - } else { - r0 = ret.Get(0).(models.Collection) - } - - if rf, ok := ret.Get(1).(func(request.GetCollection, context.Context, map[string]bool, models.LinkGenerator, flow.Chain) error); ok { - r1 = rf(r, _a1, expandFields, link, chain) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetEvents provides a mock function with given fields: r, _a1 -func (_m *RestServerApi) GetEvents(r request.GetEvents, _a1 context.Context) (models.BlocksEvents, error) { - ret := _m.Called(r, _a1) - - var r0 models.BlocksEvents - var r1 error - if rf, ok := ret.Get(0).(func(request.GetEvents, context.Context) (models.BlocksEvents, error)); ok { - return rf(r, _a1) - } - if rf, ok := ret.Get(0).(func(request.GetEvents, context.Context) models.BlocksEvents); ok { - r0 = rf(r, _a1) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(models.BlocksEvents) - } - } - - if rf, ok := ret.Get(1).(func(request.GetEvents, context.Context) error); ok { - r1 = rf(r, _a1) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetExecutionResultByID provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) GetExecutionResultByID(r request.GetExecutionResult, _a1 context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - ret := _m.Called(r, _a1, link) - - var r0 models.ExecutionResult - var r1 error - if rf, ok := ret.Get(0).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) (models.ExecutionResult, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) models.ExecutionResult); ok { - r0 = rf(r, _a1, link) - } else { - r0 = ret.Get(0).(models.ExecutionResult) - } - - if rf, ok := ret.Get(1).(func(request.GetExecutionResult, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetExecutionResultsByBlockIDs provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, _a1 context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - ret := _m.Called(r, _a1, link) - - var r0 []models.ExecutionResult - var r1 error - if rf, ok := ret.Get(0).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) ([]models.ExecutionResult, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) []models.ExecutionResult); ok { - r0 = rf(r, _a1, link) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]models.ExecutionResult) - } - } - - if rf, ok := ret.Get(1).(func(request.GetExecutionResultByBlockIDs, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetNetworkParameters provides a mock function with given fields: r -func (_m *RestServerApi) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { - ret := _m.Called(r) - - var r0 models.NetworkParameters - var r1 error - if rf, ok := ret.Get(0).(func(*request.Request) (models.NetworkParameters, error)); ok { - return rf(r) - } - if rf, ok := ret.Get(0).(func(*request.Request) models.NetworkParameters); ok { - r0 = rf(r) - } else { - r0 = ret.Get(0).(models.NetworkParameters) - } - - if rf, ok := ret.Get(1).(func(*request.Request) error); ok { - r1 = rf(r) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetNodeVersionInfo provides a mock function with given fields: r -func (_m *RestServerApi) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { - ret := _m.Called(r) - - var r0 models.NodeVersionInfo - var r1 error - if rf, ok := ret.Get(0).(func(*request.Request) (models.NodeVersionInfo, error)); ok { - return rf(r) - } - if rf, ok := ret.Get(0).(func(*request.Request) models.NodeVersionInfo); ok { - r0 = rf(r) - } else { - r0 = ret.Get(0).(models.NodeVersionInfo) - } - - if rf, ok := ret.Get(1).(func(*request.Request) error); ok { - r1 = rf(r) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTransactionByID provides a mock function with given fields: r, _a1, link, chain -func (_m *RestServerApi) GetTransactionByID(r request.GetTransaction, _a1 context.Context, link models.LinkGenerator, chain flow.Chain) (models.Transaction, error) { - ret := _m.Called(r, _a1, link, chain) - - var r0 models.Transaction - var r1 error - if rf, ok := ret.Get(0).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) (models.Transaction, error)); ok { - return rf(r, _a1, link, chain) - } - if rf, ok := ret.Get(0).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) models.Transaction); ok { - r0 = rf(r, _a1, link, chain) - } else { - r0 = ret.Get(0).(models.Transaction) - } - - if rf, ok := ret.Get(1).(func(request.GetTransaction, context.Context, models.LinkGenerator, flow.Chain) error); ok { - r1 = rf(r, _a1, link, chain) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTransactionResultByID provides a mock function with given fields: r, _a1, link -func (_m *RestServerApi) GetTransactionResultByID(r request.GetTransactionResult, _a1 context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - ret := _m.Called(r, _a1, link) - - var r0 models.TransactionResult - var r1 error - if rf, ok := ret.Get(0).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) (models.TransactionResult, error)); ok { - return rf(r, _a1, link) - } - if rf, ok := ret.Get(0).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) models.TransactionResult); ok { - r0 = rf(r, _a1, link) - } else { - r0 = ret.Get(0).(models.TransactionResult) - } - - if rf, ok := ret.Get(1).(func(request.GetTransactionResult, context.Context, models.LinkGenerator) error); ok { - r1 = rf(r, _a1, link) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewRestServerApi interface { - mock.TestingT - Cleanup(func()) -} - -// NewRestServerApi creates a new instance of RestServerApi. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRestServerApi(t mockConstructorTestingTNewRestServerApi) *RestServerApi { - mock := &RestServerApi{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/engine/access/rest/models/collection.go b/engine/access/rest/models/collection.go index d4979146c7c..c5076fdc7db 100644 --- a/engine/access/rest/models/collection.go +++ b/engine/access/rest/models/collection.go @@ -1,14 +1,10 @@ package models import ( - "encoding/hex" "fmt" "github.com/onflow/flow-go/engine/access/rest/util" - "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" - - "github.com/onflow/flow/protobuf/go/flow/entities" ) const ExpandsTransactions = "transactions" @@ -46,49 +42,6 @@ func (c *Collection) Build( return nil } -func (c *Collection) BuildFromGrpc( - collection *entities.Collection, - txs []*entities.Transaction, - link LinkGenerator, - expand map[string]bool, - chain flow.Chain) error { - - self, err := SelfLink(convert.MessageToIdentifier(collection.Id), link.CollectionLink) - if err != nil { - return err - } - - transactionsBody := make([]*flow.TransactionBody, 0) - for _, tx := range txs { - flowTransaction, err := convert.MessageToTransaction(tx, chain) - if err != nil { - return err - } - transactionsBody = append(transactionsBody, &flowTransaction) - } - - var expandable CollectionExpandable - var transactions Transactions - if expand[ExpandsTransactions] { - transactions.Build(transactionsBody, link) - } else { - expandable.Transactions = make([]string, len(collection.TransactionIds)) - for i, id := range collection.TransactionIds { - expandable.Transactions[i], err = link.TransactionLink(convert.MessageToIdentifier(id)) - if err != nil { - return err - } - } - } - - c.Id = hex.EncodeToString(collection.Id) - c.Transactions = transactions - c.Links = self - c.Expandable = &expandable - - return nil -} - func (c *CollectionGuarantee) Build(guarantee *flow.CollectionGuarantee) { c.CollectionId = guarantee.CollectionID.String() c.SignerIndices = fmt.Sprintf("%x", guarantee.SignerIndices) diff --git a/engine/access/rest/models/event.go b/engine/access/rest/models/event.go index d829ec862dc..929dbb3f42c 100644 --- a/engine/access/rest/models/event.go +++ b/engine/access/rest/models/event.go @@ -1,13 +1,8 @@ package models import ( - "encoding/hex" - "github.com/onflow/flow-go/engine/access/rest/util" - "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) func (e *Event) Build(event flow.Event) { @@ -53,26 +48,3 @@ func (b *BlocksEvents) Build(blocksEvents []flow.BlockEvents) { *b = evs } - -func (b *BlocksEvents) BuildFromGrpc(blocksEvents []*accessproto.EventsResponse_Result) { - evs := make([]BlockEvents, 0) - for _, ev := range blocksEvents { - var blockEvent BlockEvents - blockEvent.BuildFromGrpc(ev) - evs = append(evs, blockEvent) - } - - *b = evs -} - -func (b *BlockEvents) BuildFromGrpc(blockEvents *accessproto.EventsResponse_Result) { - b.BlockHeight = util.FromUint64(blockEvents.BlockHeight) - b.BlockId = hex.EncodeToString(blockEvents.BlockId) - b.BlockTimestamp = blockEvents.BlockTimestamp.AsTime() - - var events Events - flowEvents := convert.MessagesToEvents(blockEvents.Events) - events.Build(flowEvents) - b.Events = events - -} diff --git a/engine/access/rest/models/network.go b/engine/access/rest/models/network.go index 1a6dd9a9816..927b5a23362 100644 --- a/engine/access/rest/models/network.go +++ b/engine/access/rest/models/network.go @@ -2,14 +2,8 @@ package models import ( "github.com/onflow/flow-go/access" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" ) func (t *NetworkParameters) Build(params *access.NetworkParameters) { t.ChainId = params.ChainID.String() } - -func (t *NetworkParameters) BuildFromGrpc(response *accessproto.GetNetworkParametersResponse) { - t.ChainId = response.ChainId -} diff --git a/engine/access/rest/models/node_version_info.go b/engine/access/rest/models/node_version_info.go index 782493c0ec9..6a85e9f8d42 100644 --- a/engine/access/rest/models/node_version_info.go +++ b/engine/access/rest/models/node_version_info.go @@ -1,12 +1,8 @@ package models import ( - "encoding/hex" - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/util" - - "github.com/onflow/flow/protobuf/go/flow/entities" ) func (t *NodeVersionInfo) Build(params *access.NodeVersionInfo) { @@ -15,10 +11,3 @@ func (t *NodeVersionInfo) Build(params *access.NodeVersionInfo) { t.SporkId = params.SporkId.String() t.ProtocolVersion = util.FromUint64(params.ProtocolVersion) } - -func (t *NodeVersionInfo) BuildFromGrpc(params *entities.NodeVersionInfo) { - t.Semver = params.Semver - t.Commit = params.Commit - t.SporkId = hex.EncodeToString(params.SporkId) - t.ProtocolVersion = util.FromUint64(params.ProtocolVersion) -} diff --git a/engine/access/rest/router.go b/engine/access/rest/router.go index 2253d56033c..ef1f9665a91 100644 --- a/engine/access/rest/router.go +++ b/engine/access/rest/router.go @@ -14,7 +14,7 @@ import ( "github.com/onflow/flow-go/module" ) -func NewRouter(serverAPI api.RestServerApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { +func NewRouter(backend api.RestBackendApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { router := mux.NewRouter().StrictSlash(true) v1SubRouter := router.PathPrefix("/v1").Subrouter() @@ -27,7 +27,7 @@ func NewRouter(serverAPI api.RestServerApi, logger zerolog.Logger, chain flow.Ch linkGenerator := models.NewLinkGeneratorImpl(v1SubRouter) for _, r := range Routes { - h := NewHandler(logger, serverAPI, r.Handler, linkGenerator, chain) + h := NewHandler(logger, backend, r.Handler, linkGenerator, chain) v1SubRouter. Methods(r.Method). Path(r.Pattern). diff --git a/engine/access/rest/routes/accounts.go b/engine/access/rest/routes/accounts.go index 76ff9d7fcb5..d9f16562f1d 100644 --- a/engine/access/rest/routes/accounts.go +++ b/engine/access/rest/routes/accounts.go @@ -7,11 +7,27 @@ import ( ) // GetAccount handler retrieves account by address and returns the response -func GetAccount(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetAccount(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetAccountRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetAccount(req, r.Context(), r.ExpandFields, link) + // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it + if req.Height == request.FinalHeight || req.Height == request.SealedHeight { + header, _, err := backend.GetLatestBlockHeader(r.Context(), req.Height == request.SealedHeight) + if err != nil { + return nil, err + } + req.Height = header.Height + } + + account, err := backend.GetAccountAtBlockHeight(r.Context(), req.Address, req.Height) + if err != nil { + return nil, err + } + + var response models.Account + err = response.Build(account, link, r.ExpandFields) + return response, err } diff --git a/engine/access/rest/routes/blocks.go b/engine/access/rest/routes/blocks.go index b6676d7076d..7f0537a0c07 100644 --- a/engine/access/rest/routes/blocks.go +++ b/engine/access/rest/routes/blocks.go @@ -8,45 +8,115 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" - - accessproto "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/onflow/flow/protobuf/go/flow/entities" ) // GetBlocksByIDs gets blocks by provided ID or list of IDs. -func GetBlocksByIDs(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByIDs(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockByIDsRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetBlocksByIDs(req, r.Context(), r.ExpandFields, link) + blocks := make([]*models.Block, len(req.IDs)) + + for i, id := range req.IDs { + block, err := getBlock(forID(&id), r, backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil } // GetBlocksByHeight gets blocks by height. -func GetBlocksByHeight(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { - return srv.GetBlocksByHeight(r, link) +func GetBlocksByHeight(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { + req, err := r.GetBlockRequest() + if err != nil { + return nil, models.NewBadRequestError(err) + } + + if req.FinalHeight || req.SealedHeight { + block, err := getBlock(forFinalized(req.Heights[0]), r, backend, link) + if err != nil { + return nil, err + } + + return []*models.Block{block}, nil + } + + // if the query is /blocks/height=1000,1008,1049... + if req.HasHeights() { + blocks := make([]*models.Block, len(req.Heights)) + for i, h := range req.Heights { + block, err := getBlock(forHeight(h), r, backend, link) + if err != nil { + return nil, err + } + blocks[i] = block + } + + return blocks, nil + } + + // support providing end height as "sealed" or "final" + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + latest, _, err := backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + req.EndHeight = latest.Header.Height // overwrite special value height with fetched + + if req.StartHeight > req.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) + } + } + + blocks := make([]*models.Block, 0) + // start and end height inclusive + for i := req.StartHeight; i <= req.EndHeight; i++ { + block, err := getBlock(forHeight(i), r, backend, link) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil } // GetBlockPayloadByID gets block payload by ID -func GetBlockPayloadByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetBlockPayloadByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockPayloadRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetBlockPayloadByID(req, r.Context(), link) + blkProvider := NewBlockProvider(backend, forID(&req.ID)) + blk, _, statusErr := blkProvider.getBlock(r.Context()) + if statusErr != nil { + return nil, statusErr + } + + var payload models.BlockPayload + err = payload.Build(blk.Payload) + if err != nil { + return nil, err + } + + return payload, nil } -func GetBlock(option BlockRequestOption, context context.Context, expandFields map[string]bool, backend access.API, link models.LinkGenerator) (*models.Block, error) { +func getBlock(option blockProviderOption, req *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (*models.Block, error) { // lookup block - blkProvider := NewBlockRequestProvider(backend, option) - blk, blockStatus, err := blkProvider.GetBlock(context) + blkProvider := NewBlockProvider(backend, option) + blk, blockStatus, err := blkProvider.getBlock(req.Context()) if err != nil { return nil, err } @@ -54,12 +124,12 @@ func GetBlock(option BlockRequestOption, context context.Context, expandFields m // lookup execution result // (even if not specified as expandable, since we need the execution result ID to generate its expandable link) var block models.Block - executionResult, err := backend.GetExecutionResultForBlockID(context, blk.ID()) + executionResult, err := backend.GetExecutionResultForBlockID(req.Context(), blk.ID()) if err != nil { // handle case where execution result is not yet available if se, ok := status.FromError(err); ok { if se.Code() == codes.NotFound { - err := block.Build(blk, nil, link, blockStatus, expandFields) + err := block.Build(blk, nil, link, blockStatus, req.ExpandFields) if err != nil { return nil, err } @@ -69,64 +139,60 @@ func GetBlock(option BlockRequestOption, context context.Context, expandFields m return nil, err } - err = block.Build(blk, executionResult, link, blockStatus, expandFields) + err = block.Build(blk, executionResult, link, blockStatus, req.ExpandFields) if err != nil { return nil, err } return &block, nil } -type blockRequest struct { - id *flow.Identifier - height uint64 - latest bool - sealed bool +// blockProvider is a layer of abstraction on top of the backend api.RestBackendApi and provides a uniform way to +// look up a block or a block header either by ID or by height +type blockProvider struct { + id *flow.Identifier + height uint64 + latest bool + sealed bool + backend api.RestBackendApi } -type BlockRequestOption func(blkRequest *blockRequest) +type blockProviderOption func(blkProvider *blockProvider) -func ForID(id *flow.Identifier) BlockRequestOption { - return func(blockRequest *blockRequest) { - blockRequest.id = id +func forID(id *flow.Identifier) blockProviderOption { + return func(blkProvider *blockProvider) { + blkProvider.id = id } } -func ForHeight(height uint64) BlockRequestOption { - return func(blockRequest *blockRequest) { - blockRequest.height = height +func forHeight(height uint64) blockProviderOption { + return func(blkProvider *blockProvider) { + blkProvider.height = height } } -func ForFinalized(queryParam uint64) BlockRequestOption { - return func(blockRequest *blockRequest) { +func forFinalized(queryParam uint64) blockProviderOption { + return func(blkProvider *blockProvider) { switch queryParam { case request.SealedHeight: - blockRequest.sealed = true + blkProvider.sealed = true fallthrough case request.FinalHeight: - blockRequest.latest = true + blkProvider.latest = true } } } -// blockRequestProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to -// look up a block or a block header either by ID or by height -type blockRequestProvider struct { - blockRequest - backend access.API -} - -func NewBlockRequestProvider(backend access.API, options ...BlockRequestOption) *blockRequestProvider { - blockRequestProvider := &blockRequestProvider{ +func NewBlockProvider(backend api.RestBackendApi, options ...blockProviderOption) *blockProvider { + blkProvider := &blockProvider{ backend: backend, } for _, o := range options { - o(&blockRequestProvider.blockRequest) + o(blkProvider) } - return blockRequestProvider + return blkProvider } -func (blkProvider *blockRequestProvider) GetBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { +func (blkProvider *blockProvider) getBlock(ctx context.Context) (*flow.Block, flow.BlockStatus, error) { if blkProvider.id != nil { blk, _, err := blkProvider.backend.GetBlockByID(ctx, *blkProvider.id) if err != nil { // unfortunately backend returns internal error status if not found @@ -154,60 +220,3 @@ func (blkProvider *blockRequestProvider) GetBlock(ctx context.Context) (*flow.Bl } return blk, status, nil } - -// BlockFromGrpcProvider is a layer of abstraction on top of the accessproto.AccessAPIClient and provides a uniform way to -// look up a block or a block header either by ID or by height -type BlockFromGrpcProvider struct { - blockRequest - upstream accessproto.AccessAPIClient -} - -func NewBlockFromGrpcProvider(upstream accessproto.AccessAPIClient, options ...BlockRequestOption) *BlockFromGrpcProvider { - blockFromGrpcProvider := &BlockFromGrpcProvider{ - upstream: upstream, - } - - for _, o := range options { - o(&blockFromGrpcProvider.blockRequest) - } - return blockFromGrpcProvider -} - -func (blkProvider *BlockFromGrpcProvider) GetBlock(ctx context.Context) (*entities.Block, entities.BlockStatus, error) { - if blkProvider.id != nil { - getBlockByIdRequest := &accessproto.GetBlockByIDRequest{ - Id: []byte(blkProvider.id.String()), - } - blockResponse, err := blkProvider.upstream.GetBlockByID(ctx, getBlockByIdRequest) - if err != nil { // unfortunately grpc returns internal error status if not found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewNotFoundError( - fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err, - ) - } - return blockResponse.Block, entities.BlockStatus_BLOCK_UNKNOWN, nil - } - - if blkProvider.latest { - getLatestBlockRequest := &accessproto.GetLatestBlockRequest{ - IsSealed: blkProvider.sealed, - } - blockResponse, err := blkProvider.upstream.GetLatestBlock(ctx, getLatestBlockRequest) - if err != nil { - // cannot be a 'not found' error since final and sealed block should always be found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewRestError(http.StatusInternalServerError, "block lookup failed", err) - } - return blockResponse.Block, blockResponse.BlockStatus, nil - } - - getBlockByHeight := &accessproto.GetBlockByHeightRequest{ - Height: blkProvider.height, - FullBlockResponse: true, - } - blockResponse, err := blkProvider.upstream.GetBlockByHeight(ctx, getBlockByHeight) - if err != nil { // unfortunately grpc returns internal error status if not found - return nil, entities.BlockStatus_BLOCK_UNKNOWN, models.NewNotFoundError( - fmt.Sprintf("error looking up block at height %d", blkProvider.height), err, - ) - } - return blockResponse.Block, blockResponse.BlockStatus, nil -} diff --git a/engine/access/rest/routes/collections.go b/engine/access/rest/routes/collections.go index 97a38005961..182581f90f0 100644 --- a/engine/access/rest/routes/collections.go +++ b/engine/access/rest/routes/collections.go @@ -4,14 +4,39 @@ import ( "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/model/flow" ) // GetCollectionByID retrieves a collection by ID and builds a response -func GetCollectionByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetCollectionByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetCollectionRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetCollectionByID(req, r.Context(), r.ExpandFields, link, r.Chain) + collection, err := backend.GetCollectionByID(r.Context(), req.ID) + if err != nil { + return nil, err + } + + // if we expand transactions in the query retrieve each transaction data + transactions := make([]*flow.TransactionBody, 0) + if req.ExpandsTransactions { + for _, tid := range collection.Transactions { + tx, err := backend.GetTransaction(r.Context(), tid) + if err != nil { + return nil, err + } + + transactions = append(transactions, tx) + } + } + + var response models.Collection + err = response.Build(collection, transactions, link, r.ExpandFields) + if err != nil { + return nil, err + } + + return response, nil } diff --git a/engine/access/rest/routes/events.go b/engine/access/rest/routes/events.go index fd728bc1188..36eee728386 100644 --- a/engine/access/rest/routes/events.go +++ b/engine/access/rest/routes/events.go @@ -1,6 +1,8 @@ package routes import ( + "fmt" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" @@ -10,11 +12,44 @@ const BlockQueryParam = "block_ids" const EventTypeQuery = "type" // GetEvents for the provided block range or list of block IDs filtered by type. -func GetEvents(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { +func GetEvents(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetEventsRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetEvents(req, r.Context()) + // if the request has block IDs provided then return events for block IDs + var blocksEvents models.BlocksEvents + if len(req.BlockIDs) > 0 { + events, err := backend.GetEventsForBlockIDs(r.Context(), req.Type, req.BlockIDs) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil + } + + // if end height is provided with special values then load the height + if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { + latest, _, err := backend.GetLatestBlockHeader(r.Context(), req.EndHeight == request.SealedHeight) + if err != nil { + return nil, err + } + + req.EndHeight = latest.Height + // special check after we resolve special height value + if req.StartHeight > req.EndHeight { + return nil, models.NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) + } + } + + // if request provided block height range then return events for that range + events, err := backend.GetEventsForHeightRange(r.Context(), req.Type, req.StartHeight, req.EndHeight) + if err != nil { + return nil, err + } + + blocksEvents.Build(events) + return blocksEvents, nil } diff --git a/engine/access/rest/routes/execution_result.go b/engine/access/rest/routes/execution_result.go index 4c1ba8aab7e..f923ce7905c 100644 --- a/engine/access/rest/routes/execution_result.go +++ b/engine/access/rest/routes/execution_result.go @@ -1,27 +1,61 @@ package routes import ( + "fmt" + "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func GetExecutionResultsByBlockIDs(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultsByBlockIDs(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultByBlockIDsRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetExecutionResultsByBlockIDs(req, r.Context(), link) + // for each block ID we retrieve execution result + results := make([]models.ExecutionResult, len(req.BlockIDs)) + for i, id := range req.BlockIDs { + res, err := backend.GetExecutionResultForBlockID(r.Context(), id) + if err != nil { + return nil, err + } + + var response models.ExecutionResult + err = response.Build(res, link) + if err != nil { + return nil, err + } + results[i] = response + } + + return results, nil } // GetExecutionResultByID gets execution result by the ID. -func GetExecutionResultByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetExecutionResultByID(req, r.Context(), link) + res, err := backend.GetExecutionResultByID(r.Context(), req.ID) + if err != nil { + return nil, err + } + + if res == nil { + err := fmt.Errorf("execution result with ID: %s not found", req.ID.String()) + return nil, models.NewNotFoundError(err.Error(), err) + } + + var response models.ExecutionResult + err = response.Build(res, link) + if err != nil { + return nil, err + } + + return response, nil } diff --git a/engine/access/rest/routes/network.go b/engine/access/rest/routes/network.go index a409fd4b893..88aa787108e 100644 --- a/engine/access/rest/routes/network.go +++ b/engine/access/rest/routes/network.go @@ -7,6 +7,10 @@ import ( ) // GetNetworkParameters returns network-wide parameters of the blockchain -func GetNetworkParameters(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { - return srv.GetNetworkParameters(r) +func GetNetworkParameters(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { + params := backend.GetNetworkParameters(r.Context()) + + var response models.NetworkParameters + response.Build(¶ms) + return response, nil } diff --git a/engine/access/rest/routes/node_version_info.go b/engine/access/rest/routes/node_version_info.go index 14fbd8bbf26..daf658e8869 100644 --- a/engine/access/rest/routes/node_version_info.go +++ b/engine/access/rest/routes/node_version_info.go @@ -7,6 +7,13 @@ import ( ) // GetNodeVersionInfo returns node version information -func GetNodeVersionInfo(r *request.Request, srv api.RestServerApi, _ models.LinkGenerator) (interface{}, error) { - return srv.GetNodeVersionInfo(r) +func GetNodeVersionInfo(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { + params, err := backend.GetNodeVersionInfo(r.Context()) + if err != nil { + return nil, err + } + + var response models.NodeVersionInfo + response.Build(params) + return response, nil } diff --git a/engine/access/rest/routes/scripts.go b/engine/access/rest/routes/scripts.go index 1debbb07934..e991dd3a89e 100644 --- a/engine/access/rest/routes/scripts.go +++ b/engine/access/rest/routes/scripts.go @@ -4,14 +4,32 @@ import ( "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" + "github.com/onflow/flow-go/model/flow" ) // ExecuteScript handler sends the script from the request to be executed. -func ExecuteScript(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func ExecuteScript(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetScriptRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.ExecuteScript(req, r.Context(), link) + if req.BlockID != flow.ZeroID { + return backend.ExecuteScriptAtBlockID(r.Context(), req.BlockID, req.Script.Source, req.Script.Args) + } + + // default to sealed height + if req.BlockHeight == request.SealedHeight || req.BlockHeight == request.EmptyHeight { + return backend.ExecuteScriptAtLatestBlock(r.Context(), req.Script.Source, req.Script.Args) + } + + if req.BlockHeight == request.FinalHeight { + finalBlock, _, err := backend.GetLatestBlockHeader(r.Context(), false) + if err != nil { + return nil, err + } + req.BlockHeight = finalBlock.Height + } + + return backend.ExecuteScriptAtBlockHeight(r.Context(), req.BlockHeight, req.Script.Source, req.Script.Args) } diff --git a/engine/access/rest/routes/transactions.go b/engine/access/rest/routes/transactions.go index 9f9f49d8cf1..763e9098299 100644 --- a/engine/access/rest/routes/transactions.go +++ b/engine/access/rest/routes/transactions.go @@ -1,37 +1,68 @@ package routes import ( + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetTransactionByID gets a transaction by requested ID. -func GetTransactionByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetTransactionByID(req, r.Context(), link, r.Chain) + tx, err := backend.GetTransaction(r.Context(), req.ID) + if err != nil { + return nil, err + } + + var txr *access.TransactionResult + // only lookup result if transaction result is to be expanded + if req.ExpandsResult { + txr, err = backend.GetTransactionResult(r.Context(), req.ID, req.BlockID, req.CollectionID) + if err != nil { + return nil, err + } + } + + var response models.Transaction + response.Build(tx, txr, link) + return response, nil } // GetTransactionResultByID retrieves transaction result by the transaction ID. -func GetTransactionResultByID(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionResultByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionResultRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.GetTransactionResultByID(req, r.Context(), link) + txr, err := backend.GetTransactionResult(r.Context(), req.ID, req.BlockID, req.CollectionID) + if err != nil { + return nil, err + } + + var response models.TransactionResult + response.Build(txr, req.ID, link) + return response, nil } // CreateTransaction creates a new transaction from provided payload. -func CreateTransaction(r *request.Request, srv api.RestServerApi, link models.LinkGenerator) (interface{}, error) { +func CreateTransaction(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { req, err := r.CreateTransactionRequest() if err != nil { return nil, models.NewBadRequestError(err) } - return srv.CreateTransaction(req, r.Context(), link) + err = backend.SendTransaction(r.Context(), &req.Transaction) + if err != nil { + return nil, err + } + + var response models.Transaction + response.Build(&req.Transaction, nil, link) + return response, nil } diff --git a/engine/access/rest/server.go b/engine/access/rest/server.go index 9dc7112e6b6..d07983abb64 100644 --- a/engine/access/rest/server.go +++ b/engine/access/rest/server.go @@ -13,7 +13,7 @@ import ( ) // NewServer returns an HTTP server initialized with the REST API handler -func NewServer(serverAPI api.RestServerApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { +func NewServer(serverAPI api.RestBackendApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { router, err := NewRouter(serverAPI, logger, chain, restCollector) if err != nil { return nil, err diff --git a/engine/access/rest/server_request_handler.go b/engine/access/rest/server_request_handler.go deleted file mode 100644 index 257c9f82deb..00000000000 --- a/engine/access/rest/server_request_handler.go +++ /dev/null @@ -1,346 +0,0 @@ -package rest - -import ( - "context" - "fmt" - - "github.com/rs/zerolog" - - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/rest/api" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/engine/access/rest/routes" - "github.com/onflow/flow-go/model/flow" -) - -// ServerRequestHandler is a structure that represents handling local requests. -type ServerRequestHandler struct { - log zerolog.Logger - backend access.API -} - -var _ api.RestServerApi = (*ServerRequestHandler)(nil) - -// NewServerRequestHandler returns new ServerRequestHandler. -func NewServerRequestHandler(log zerolog.Logger, backend access.API) *ServerRequestHandler { - return &ServerRequestHandler{ - log: log, - backend: backend, - } -} - -// GetTransactionByID gets a transaction by requested ID. -func (h *ServerRequestHandler) GetTransactionByID(r request.GetTransaction, context context.Context, link models.LinkGenerator, _ flow.Chain) (models.Transaction, error) { - var response models.Transaction - - tx, err := h.backend.GetTransaction(context, r.ID) - if err != nil { - return response, err - } - - var txr *access.TransactionResult - // only lookup result if transaction result is to be expanded - if r.ExpandsResult { - txr, err = h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) - if err != nil { - return response, err - } - } - - response.Build(tx, txr, link) - return response, nil -} - -// CreateTransaction creates a new transaction from provided payload. -func (h *ServerRequestHandler) CreateTransaction(r request.CreateTransaction, context context.Context, link models.LinkGenerator) (models.Transaction, error) { - var response models.Transaction - - err := h.backend.SendTransaction(context, &r.Transaction) - if err != nil { - return response, err - } - - response.Build(&r.Transaction, nil, link) - return response, nil -} - -// GetTransactionResultByID retrieves transaction result by the transaction ID. -func (h *ServerRequestHandler) GetTransactionResultByID(r request.GetTransactionResult, context context.Context, link models.LinkGenerator) (models.TransactionResult, error) { - var response models.TransactionResult - - txr, err := h.backend.GetTransactionResult(context, r.ID, r.BlockID, r.CollectionID) - if err != nil { - return response, err - } - - response.Build(txr, r.ID, link) - return response, nil -} - -// GetBlocksByIDs gets blocks by provided ID or list of IDs. -func (h *ServerRequestHandler) GetBlocksByIDs(r request.GetBlockByIDs, context context.Context, expandFields map[string]bool, link models.LinkGenerator) ([]*models.Block, error) { - blocks := make([]*models.Block, len(r.IDs)) - - for i, id := range r.IDs { - block, err := routes.GetBlock(routes.ForID(&id), context, expandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil -} - -// GetBlocksByHeight gets blocks by provided height. -func (h *ServerRequestHandler) GetBlocksByHeight(r *request.Request, link models.LinkGenerator) ([]*models.Block, error) { - req, err := r.GetBlockRequest() - if err != nil { - return nil, models.NewBadRequestError(err) - } - - if req.FinalHeight || req.SealedHeight { - block, err := routes.GetBlock(routes.ForFinalized(req.Heights[0]), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - - return []*models.Block{block}, nil - } - - // if the query is /blocks/height=1000,1008,1049... - if req.HasHeights() { - blocks := make([]*models.Block, len(req.Heights)) - for i, height := range req.Heights { - block, err := routes.GetBlock(routes.ForHeight(height), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks[i] = block - } - - return blocks, nil - } - - // support providing end height as "sealed" or "final" - if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight { - latest, _, err := h.backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - req.EndHeight = latest.Header.Height // overwrite special value height with fetched - - if req.StartHeight > req.EndHeight { - return nil, models.NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height")) - } - } - - blocks := make([]*models.Block, 0) - // start and end height inclusive - for i := req.StartHeight; i <= req.EndHeight; i++ { - block, err := routes.GetBlock(routes.ForHeight(i), r.Context(), r.ExpandFields, h.backend, link) - if err != nil { - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -// GetBlockPayloadByID gets block payload by ID -func (h *ServerRequestHandler) GetBlockPayloadByID(r request.GetBlockPayload, context context.Context, _ models.LinkGenerator) (models.BlockPayload, error) { - var payload models.BlockPayload - - blkProvider := routes.NewBlockRequestProvider(h.backend, routes.ForID(&r.ID)) - blk, _, statusErr := blkProvider.GetBlock(context) - if statusErr != nil { - return payload, statusErr - } - - err := payload.Build(blk.Payload) - if err != nil { - return payload, err - } - - return payload, nil -} - -// GetExecutionResultByID gets execution result by the ID. -func (h *ServerRequestHandler) GetExecutionResultByID(r request.GetExecutionResult, context context.Context, link models.LinkGenerator) (models.ExecutionResult, error) { - var response models.ExecutionResult - - res, err := h.backend.GetExecutionResultByID(context, r.ID) - if err != nil { - return response, err - } - - if res == nil { - err := fmt.Errorf("execution result with ID: %s not found", r.ID.String()) - return response, models.NewNotFoundError(err.Error(), err) - } - - err = response.Build(res, link) - if err != nil { - return response, err - } - - return response, nil -} - -// GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func (h *ServerRequestHandler) GetExecutionResultsByBlockIDs(r request.GetExecutionResultByBlockIDs, context context.Context, link models.LinkGenerator) ([]models.ExecutionResult, error) { - // for each block ID we retrieve execution result - results := make([]models.ExecutionResult, len(r.BlockIDs)) - for i, id := range r.BlockIDs { - res, err := h.backend.GetExecutionResultForBlockID(context, id) - if err != nil { - return nil, err - } - - var response models.ExecutionResult - err = response.Build(res, link) - if err != nil { - return nil, err - } - results[i] = response - } - - return results, nil -} - -// GetCollectionByID retrieves a collection by ID and builds a response -func (h *ServerRequestHandler) GetCollectionByID(r request.GetCollection, context context.Context, expandFields map[string]bool, link models.LinkGenerator, _ flow.Chain) (models.Collection, error) { - var response models.Collection - - collection, err := h.backend.GetCollectionByID(context, r.ID) - if err != nil { - return response, err - } - - // if we expand transactions in the query retrieve each transaction data - transactions := make([]*flow.TransactionBody, 0) - if r.ExpandsTransactions { - for _, tid := range collection.Transactions { - tx, err := h.backend.GetTransaction(context, tid) - if err != nil { - return response, err - } - - transactions = append(transactions, tx) - } - } - - err = response.Build(collection, transactions, link, expandFields) - if err != nil { - return response, err - } - - return response, nil -} - -// ExecuteScript handler sends the script from the request to be executed. -func (h *ServerRequestHandler) ExecuteScript(r request.GetScript, context context.Context, _ models.LinkGenerator) ([]byte, error) { - if r.BlockID != flow.ZeroID { - return h.backend.ExecuteScriptAtBlockID(context, r.BlockID, r.Script.Source, r.Script.Args) - } - - // default to sealed height - if r.BlockHeight == request.SealedHeight || r.BlockHeight == request.EmptyHeight { - return h.backend.ExecuteScriptAtLatestBlock(context, r.Script.Source, r.Script.Args) - } - - if r.BlockHeight == request.FinalHeight { - finalBlock, _, err := h.backend.GetLatestBlockHeader(context, false) - if err != nil { - return nil, err - } - r.BlockHeight = finalBlock.Height - } - - return h.backend.ExecuteScriptAtBlockHeight(context, r.BlockHeight, r.Script.Source, r.Script.Args) -} - -// GetAccount handler retrieves account by address and returns the response. -func (h *ServerRequestHandler) GetAccount(r request.GetAccount, context context.Context, expandFields map[string]bool, link models.LinkGenerator) (models.Account, error) { - var response models.Account - - // in case we receive special height values 'final' and 'sealed', fetch that height and overwrite request with it - if r.Height == request.FinalHeight || r.Height == request.SealedHeight { - header, _, err := h.backend.GetLatestBlockHeader(context, r.Height == request.SealedHeight) - if err != nil { - return response, err - } - r.Height = header.Height - } - - account, err := h.backend.GetAccountAtBlockHeight(context, r.Address, r.Height) - if err != nil { - return response, models.NewNotFoundError("not found account at block height", err) - } - - err = response.Build(account, link, expandFields) - return response, err -} - -// GetEvents for the provided block range or list of block IDs filtered by type. -func (h *ServerRequestHandler) GetEvents(r request.GetEvents, context context.Context) (models.BlocksEvents, error) { - // if the request has block IDs provided then return events for block IDs - var blocksEvents models.BlocksEvents - if len(r.BlockIDs) > 0 { - events, err := h.backend.GetEventsForBlockIDs(context, r.Type, r.BlockIDs) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil - } - - // if end height is provided with special values then load the height - if r.EndHeight == request.FinalHeight || r.EndHeight == request.SealedHeight { - latest, _, err := h.backend.GetLatestBlockHeader(context, r.EndHeight == request.SealedHeight) - if err != nil { - return nil, err - } - - r.EndHeight = latest.Height - // special check after we resolve special height value - if r.StartHeight > r.EndHeight { - return nil, models.NewBadRequestError(fmt.Errorf("current retrieved end height value is lower than start height")) - } - } - - // if request provided block height range then return events for that range - events, err := h.backend.GetEventsForHeightRange(context, r.Type, r.StartHeight, r.EndHeight) - if err != nil { - return nil, err - } - - blocksEvents.Build(events) - return blocksEvents, nil -} - -// GetNetworkParameters returns network-wide parameters of the blockchain -func (h *ServerRequestHandler) GetNetworkParameters(r *request.Request) (models.NetworkParameters, error) { - params := h.backend.GetNetworkParameters(r.Context()) - - var response models.NetworkParameters - response.Build(¶ms) - return response, nil -} - -// GetNodeVersionInfo returns node version information -func (h *ServerRequestHandler) GetNodeVersionInfo(r *request.Request) (models.NodeVersionInfo, error) { - var response models.NodeVersionInfo - - params, err := h.backend.GetNodeVersionInfo(r.Context()) - if err != nil { - return response, err - } - - response.Build(params) - return response, nil -} diff --git a/engine/access/rest/tests/accounts_test.go b/engine/access/rest/tests/accounts_test.go index c9a247f8c15..0ff84f79075 100644 --- a/engine/access/rest/tests/accounts_test.go +++ b/engine/access/rest/tests/accounts_test.go @@ -14,9 +14,8 @@ import ( "github.com/onflow/flow-go/access/mock" "github.com/onflow/flow-go/engine/access/rest/middleware" restmock "github.com/onflow/flow-go/engine/access/rest/mock" - "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" ) @@ -46,7 +45,6 @@ func accountURL(t *testing.T, address string, height string) string { // 5. Get invalid account. func TestAccessGetAccount(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("get by address at latest sealed block", func(t *testing.T) { account := accountFixture(t) @@ -65,7 +63,7 @@ func TestAccessGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -85,7 +83,7 @@ func TestAccessGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -100,7 +98,7 @@ func TestAccessGetAccount(t *testing.T) { expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -115,7 +113,7 @@ func TestAccessGetAccount(t *testing.T) { expected := expectedCondensedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -130,7 +128,7 @@ func TestAccessGetAccount(t *testing.T) { for i, test := range tests { req, _ := http.NewRequest("GET", test.url, nil) - rr, err := executeRequest(req, restHandler) + rr, err := executeRequest(req, backend) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, rr.Code) @@ -148,186 +146,76 @@ func TestAccessGetAccount(t *testing.T) { // 4. Get account by address at height condensed. // 5. Get invalid account. func TestObserverGetAccount(t *testing.T) { - backend := &mock.API{} - restForwarder := &restmock.RestServerApi{} - - restHandler, err := newObserverRestHandler(backend, restForwarder) - assert.NoError(t, err) + backend := &restmock.RestBackendApi{} t.Run("get by address at latest sealed block", func(t *testing.T) { account := accountFixture(t) + var height uint64 = 100 + block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) req := getAccountRequest(t, account, sealedHeightQueryParam, expandableFieldKeys, expandableFieldContracts) - accountKeys := make([]models.AccountPublicKey, 1) - - sigAlgo := models.SigningAlgorithm("ECDSA_P256") - hashAlgo := models.HashingAlgorithm("SHA3_256") - - accountKeys[0] = models.AccountPublicKey{ - Index: "0", - PublicKey: account.Keys[0].PublicKey.String(), - SigningAlgorithm: &sigAlgo, - HashingAlgorithm: &hashAlgo, - SequenceNumber: "0", - Weight: "1000", - Revoked: false, - } + backend.Mock. + On("GetLatestBlockHeader", mocktestify.Anything, true). + Return(block, flow.BlockStatusSealed, nil) - restForwarder.Mock.On("GetAccount", - request.GetAccount{ - Address: account.Address, - Height: request.SealedHeight, - }, - mocktestify.Anything, - mocktestify.Anything, - mocktestify.Anything). - Return(models.Account{ - Address: account.Address.String(), - Balance: fmt.Sprintf("%d", account.Balance), - Keys: accountKeys, - Contracts: map[string]string{ - "contract1": "Y29udHJhY3Qx", - "contract2": "Y29udHJhY3Qy", - }, - Expandable: &models.AccountExpandable{}, - Links: &models.Links{ - Self: fmt.Sprintf("/v1/accounts/%s", account.Address), - }, - }, nil) + backend.Mock. + On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account, nil) expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) t.Run("get by address at latest finalized block", func(t *testing.T) { + var height uint64 = 100 + block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) account := accountFixture(t) req := getAccountRequest(t, account, finalHeightQueryParam, expandableFieldKeys, expandableFieldContracts) - accountKeys := make([]models.AccountPublicKey, 1) - - sigAlgo := models.SigningAlgorithm("ECDSA_P256") - hashAlgo := models.HashingAlgorithm("SHA3_256") - - accountKeys[0] = models.AccountPublicKey{ - Index: "0", - PublicKey: account.Keys[0].PublicKey.String(), - SigningAlgorithm: &sigAlgo, - HashingAlgorithm: &hashAlgo, - SequenceNumber: "0", - Weight: "1000", - Revoked: false, - } - - restForwarder.Mock.On("GetAccount", - request.GetAccount{ - Address: account.Address, - Height: request.FinalHeight, - }, - mocktestify.Anything, - mocktestify.Anything, - mocktestify.Anything). - Return(models.Account{ - Address: account.Address.String(), - Balance: fmt.Sprintf("%d", account.Balance), - Keys: accountKeys, - Contracts: map[string]string{ - "contract1": "Y29udHJhY3Qx", - "contract2": "Y29udHJhY3Qy", - }, - Expandable: &models.AccountExpandable{}, - Links: &models.Links{ - Self: fmt.Sprintf("/v1/accounts/%s", account.Address), - }, - }, nil) + backend.Mock. + On("GetLatestBlockHeader", mocktestify.Anything, false). + Return(block, flow.BlockStatusFinalized, nil) + backend.Mock. + On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account, nil) expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) t.Run("get by address at height", func(t *testing.T) { var height uint64 = 1337 account := accountFixture(t) - req := getAccountRequest(t, account, fmt.Sprintf("%d", height), expandableFieldKeys, expandableFieldContracts) - accountKeys := make([]models.AccountPublicKey, 1) - - sigAlgo := models.SigningAlgorithm("ECDSA_P256") - hashAlgo := models.HashingAlgorithm("SHA3_256") - - accountKeys[0] = models.AccountPublicKey{ - Index: "0", - PublicKey: account.Keys[0].PublicKey.String(), - SigningAlgorithm: &sigAlgo, - HashingAlgorithm: &hashAlgo, - SequenceNumber: "0", - Weight: "1000", - Revoked: false, - } - restForwarder.Mock.On("GetAccount", - request.GetAccount{ - Address: account.Address, - Height: height, - }, - mocktestify.Anything, - mocktestify.Anything, - mocktestify.Anything). - Return(models.Account{ - Address: account.Address.String(), - Balance: fmt.Sprintf("%d", account.Balance), - Keys: accountKeys, - Contracts: map[string]string{ - "contract1": "Y29udHJhY3Qx", - "contract2": "Y29udHJhY3Qy", - }, - Expandable: &models.AccountExpandable{}, - Links: &models.Links{ - Self: fmt.Sprintf("/v1/accounts/%s", account.Address), - }, - }, nil) + backend.Mock. + On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account, nil) expected := expectedExpandedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) t.Run("get by address at height condensed", func(t *testing.T) { var height uint64 = 1337 account := accountFixture(t) - req := getAccountRequest(t, account, fmt.Sprintf("%d", height)) - restForwarder.Mock.On("GetAccount", - request.GetAccount{ - Address: account.Address, - Height: height, - }, - mocktestify.Anything, - mocktestify.Anything, - mocktestify.Anything). - Return(models.Account{ - Address: account.Address.String(), - Balance: fmt.Sprintf("%d", account.Balance), - Contracts: map[string]string{}, - Expandable: &models.AccountExpandable{ - Keys: expandableFieldKeys, - Contracts: expandableFieldContracts, - }, - Links: &models.Links{ - Self: fmt.Sprintf("/v1/accounts/%s", account.Address), - }, - }, nil) + backend.Mock. + On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). + Return(account, nil) expected := expectedCondensedResponse(account) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) @@ -342,7 +230,7 @@ func TestObserverGetAccount(t *testing.T) { for i, test := range tests { req, _ := http.NewRequest("GET", test.url, nil) - rr, err := executeRequest(req, restHandler) + rr, err := executeRequest(req, backend) assert.NoError(t, err) assert.Equal(t, http.StatusBadRequest, rr.Code) diff --git a/engine/access/rest/tests/blocks_test.go b/engine/access/rest/tests/blocks_test.go index 0b8fd2c66c5..7b8b18cdc5f 100644 --- a/engine/access/rest/tests/blocks_test.go +++ b/engine/access/rest/tests/blocks_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" - restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/util" @@ -148,48 +147,9 @@ func TestAccessGetBlocks(t *testing.T) { blkCnt := 10 blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) - restHandler := newAccessRestHandler(backend) for _, tv := range testVectors { - responseRec, err := executeRequest(tv.request, restHandler) - assert.NoError(t, err) - require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) - actualResp := responseRec.Body.String() - require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description) - } -} - -// TestObserverGetBlocks tests requests forwarding for get blocks by ID and get blocks by heights. -// -// Check the following cases: -// 1. Get single expanded block by ID. -// 2. Get multiple expanded blocks by IDs -// 3. Get single condensed block by ID. -// 4. Get multiple condensed blocks by IDs. -// 5. Get single expanded block by height. -// 6. Get multiple expanded blocks by heights. -// 7. Get multiple expanded blocks by start and end height. -// 8. Get block by ID not found. -// 9. Get block by height not found. -// 10. Get block by end height less than start height. -// 11. Get block by both heights and start and end height. -// 12. Get block with missing height param. -// 13. Get block with missing height values. -// 14. Get block by more than maximum permissible number of IDs. -func TestObserverGetBlocks(t *testing.T) { - backend := &mock.API{} - restForwarder := &restmock.RestServerApi{} - - blkCnt := 10 - blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt) - - testVectors := prepareTestVectors(t, blockIDs, heights, blocks, executionResults, blkCnt) - - restHandler, err := newObserverRestHandler(backend, restForwarder) - assert.NoError(t, err) - - for _, tv := range testVectors { - responseRec, err := executeRequest(tv.request, restHandler) + responseRec, err := executeRequest(tv.request, backend) assert.NoError(t, err) require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description) actualResp := responseRec.Body.String() diff --git a/engine/access/rest/tests/collections_test.go b/engine/access/rest/tests/collections_test.go index 77a97957ae8..db57c4101e5 100644 --- a/engine/access/rest/tests/collections_test.go +++ b/engine/access/rest/tests/collections_test.go @@ -31,7 +31,6 @@ func getCollectionReq(id string, expandTransactions bool) *http.Request { func TestGetCollections(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("get by ID", func(t *testing.T) { inputs := []flow.LightCollection{ @@ -63,7 +62,7 @@ func TestGetCollections(t *testing.T) { }`, col.ID(), col.ID(), transactionsStr) req := getCollectionReq(col.ID().String(), false) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocks.AssertExpectationsForObjects(t, backend) } }) @@ -88,7 +87,7 @@ func TestGetCollections(t *testing.T) { Once() req := getCollectionReq(col.ID().String(), true) - rr, err := executeRequest(req, restHandler) + rr, err := executeRequest(req, backend) assert.NoError(t, err) assert.Equal(t, http.StatusOK, rr.Code) @@ -147,7 +146,7 @@ func TestGetCollections(t *testing.T) { Return(test.mockValue, test.mockErr) } req := getCollectionReq(test.id, false) - assertResponse(t, req, test.status, test.response, restHandler) + assertResponse(t, req, test.status, test.response, backend) } }) } diff --git a/engine/access/rest/tests/events_test.go b/engine/access/rest/tests/events_test.go index 806b7860b96..2de8a4b0834 100644 --- a/engine/access/rest/tests/events_test.go +++ b/engine/access/rest/tests/events_test.go @@ -25,7 +25,6 @@ import ( func TestGetEvents(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) events := generateEventsMocks(backend, 5) allBlockIDs := make([]string, len(events)) @@ -128,7 +127,7 @@ func TestGetEvents(t *testing.T) { for _, test := range testVectors { t.Run(test.description, func(t *testing.T) { - assertResponse(t, test.request, test.expectedStatus, test.expectedResponse, restHandler) + assertResponse(t, test.request, test.expectedStatus, test.expectedResponse, backend) }) } diff --git a/engine/access/rest/tests/execution_result_test.go b/engine/access/rest/tests/execution_result_test.go index f978f9bd299..23038b1abdb 100644 --- a/engine/access/rest/tests/execution_result_test.go +++ b/engine/access/rest/tests/execution_result_test.go @@ -37,10 +37,8 @@ func getResultByIDReq(id string, blockIDs []string) *http.Request { } func TestGetResultByID(t *testing.T) { - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) - t.Run("get by ID", func(t *testing.T) { + backend := &mock.API{} result := unittest.ExecutionResultFixture() id := unittest.IdentifierFixture() backend.Mock. @@ -50,11 +48,12 @@ func TestGetResultByID(t *testing.T) { req := getResultByIDReq(id.String(), nil) expected := executionResultExpectedStr(result) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocks.AssertExpectationsForObjects(t, backend) }) t.Run("get by ID not found", func(t *testing.T) { + backend := &mock.API{} id := unittest.IdentifierFixture() backend.Mock. On("GetExecutionResultByID", mocks.Anything, id). @@ -62,17 +61,15 @@ func TestGetResultByID(t *testing.T) { Once() req := getResultByIDReq(id.String(), nil) - assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, restHandler) + assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, backend) mocks.AssertExpectationsForObjects(t, backend) }) } func TestGetResultBlockID(t *testing.T) { - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("get by block ID", func(t *testing.T) { - + backend := &mock.API{} blockID := unittest.IdentifierFixture() result := unittest.ExecutionResultFixture(unittest.WithExecutionResultBlockID(blockID)) @@ -84,11 +81,12 @@ func TestGetResultBlockID(t *testing.T) { req := getResultByIDReq("", []string{blockID.String()}) expected := fmt.Sprintf(`[%s]`, executionResultExpectedStr(result)) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocks.AssertExpectationsForObjects(t, backend) }) t.Run("get by block ID not found", func(t *testing.T) { + backend := &mock.API{} blockID := unittest.IdentifierFixture() backend.Mock. On("GetExecutionResultForBlockID", mocks.Anything, blockID). @@ -96,7 +94,7 @@ func TestGetResultBlockID(t *testing.T) { Once() req := getResultByIDReq("", []string{blockID.String()}) - assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, restHandler) + assertResponse(t, req, http.StatusNotFound, `{"code":404,"message":"Flow resource not found: block not found"}`, backend) mocks.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/tests/network_test.go b/engine/access/rest/tests/network_test.go index 4af5b501c06..a841f076f60 100644 --- a/engine/access/rest/tests/network_test.go +++ b/engine/access/rest/tests/network_test.go @@ -23,7 +23,6 @@ func networkURL(t *testing.T) string { func TestGetNetworkParameters(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("get network parameters on mainnet", func(t *testing.T) { @@ -39,7 +38,7 @@ func TestGetNetworkParameters(t *testing.T) { expected := networkParametersExpectedStr(flow.Mainnet) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/tests/node_version_info_test.go b/engine/access/rest/tests/node_version_info_test.go index a4935a29438..25213102934 100644 --- a/engine/access/rest/tests/node_version_info_test.go +++ b/engine/access/rest/tests/node_version_info_test.go @@ -24,7 +24,6 @@ func nodeVersionInfoURL(t *testing.T) string { func TestGetNodeVersionInfo(t *testing.T) { backend := mock.NewAPI(t) - restHandler := newAccessRestHandler(backend) t.Run("get node version info", func(t *testing.T) { req := getNodeVersionInfoRequest(t) @@ -42,7 +41,7 @@ func TestGetNodeVersionInfo(t *testing.T) { expected := nodeVersionInfoExpectedStr(params) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) mocktestify.AssertExpectationsForObjects(t, backend) }) } diff --git a/engine/access/rest/tests/scripts_test.go b/engine/access/rest/tests/scripts_test.go index 0c93106b38a..da498e59dc2 100644 --- a/engine/access/rest/tests/scripts_test.go +++ b/engine/access/rest/tests/scripts_test.go @@ -48,7 +48,6 @@ func TestScripts(t *testing.T) { t.Run("get by Latest height", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) backend.Mock. On("ExecuteScriptAtLatestBlock", mocks.Anything, validCode, [][]byte{validArgs}). @@ -58,14 +57,12 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), restHandler) + ), backend) }) t.Run("get by height", func(t *testing.T) { - height := uint64(1337) - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) + height := uint64(1337) backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, height, validCode, [][]byte{validArgs}). @@ -75,14 +72,12 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), restHandler) + ), backend) }) t.Run("get by ID", func(t *testing.T) { - id, _ := flow.HexStringToIdentifier("222dc5dd51b9e4910f687e475f892f495f3352362ba318b53e318b4d78131312") - backend := &mock.API{} - restHandler := newAccessRestHandler(backend) + id, _ := flow.HexStringToIdentifier("222dc5dd51b9e4910f687e475f892f495f3352362ba318b53e318b4d78131312") backend.Mock. On("ExecuteScriptAtBlockID", mocks.Anything, id, validCode, [][]byte{validArgs}). @@ -92,12 +87,11 @@ func TestScripts(t *testing.T) { assertOKResponse(t, req, fmt.Sprintf( "\"%s\"", base64.StdEncoding.EncodeToString([]byte(`hello world`)), - ), restHandler) + ), backend) }) t.Run("get error", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, uint64(1337), validCode, [][]byte{validArgs}). @@ -109,13 +103,12 @@ func TestScripts(t *testing.T) { req, http.StatusBadRequest, `{"code":400, "message":"Invalid Flow request: internal server error"}`, - restHandler, + backend, ) }) t.Run("get invalid", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, mocks.Anything, mocks.Anything, mocks.Anything). @@ -136,7 +129,7 @@ func TestScripts(t *testing.T) { for _, test := range tests { req := scriptReq(test.id, test.height, test.body) - assertResponse(t, req, http.StatusBadRequest, test.out, restHandler) + assertResponse(t, req, http.StatusBadRequest, test.out, backend) } }) } diff --git a/engine/access/rest/tests/test_helpers.go b/engine/access/rest/tests/test_helpers.go index 4ce4fbc2f50..388b5ca999d 100644 --- a/engine/access/rest/tests/test_helpers.go +++ b/engine/access/rest/tests/test_helpers.go @@ -11,11 +11,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/access/mock" "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rest/api" - restproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" - restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" ) @@ -30,11 +27,11 @@ const ( heightQueryParam = "height" ) -func executeRequest(req *http.Request, restHandler api.RestServerApi) (*httptest.ResponseRecorder, error) { +func executeRequest(req *http.Request, backend api.RestBackendApi) (*httptest.ResponseRecorder, error) { var b bytes.Buffer logger := zerolog.New(&b) - router, err := rest.NewRouter(restHandler, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) + router, err := rest.NewRouter(backend, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) if err != nil { return nil, err } @@ -44,32 +41,12 @@ func executeRequest(req *http.Request, restHandler api.RestServerApi) (*httptest return rr, nil } -func newAccessRestHandler(backend *mock.API) api.RestServerApi { - var b bytes.Buffer - logger := zerolog.New(&b) - - return rest.NewServerRequestHandler(logger, backend) -} - -func newObserverRestHandler(backend *mock.API, restForwarder *restmock.RestServerApi) (api.RestServerApi, error) { - var b bytes.Buffer - logger := zerolog.New(&b) - observerCollector := metrics.NewNoopCollector() - - return &restproxy.RestRouter{ - Logger: logger, - Metrics: observerCollector, - Upstream: restForwarder, - Observer: rest.NewServerRequestHandler(logger, backend), - }, nil -} - -func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, restHandler api.RestServerApi) { - assertResponse(t, req, http.StatusOK, expectedRespBody, restHandler) +func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend api.RestBackendApi) { + assertResponse(t, req, http.StatusOK, expectedRespBody, backend) } -func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, restHandler api.RestServerApi) { - rr, err := executeRequest(req, restHandler) +func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend api.RestBackendApi) { + rr, err := executeRequest(req, backend) assert.NoError(t, err) actualResponseBody := rr.Body.String() require.JSONEq(t, diff --git a/engine/access/rest/tests/transactions_test.go b/engine/access/rest/tests/transactions_test.go index f4570d3a191..3d81a0b5eff 100644 --- a/engine/access/rest/tests/transactions_test.go +++ b/engine/access/rest/tests/transactions_test.go @@ -104,7 +104,6 @@ func validCreateBody(tx flow.TransactionBody) map[string]interface{} { func TestGetTransactions(t *testing.T) { t.Run("get by ID without results", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -146,12 +145,11 @@ func TestGetTransactions(t *testing.T) { }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("Get by ID with results", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) tx := unittest.TransactionFixture() txr := transactionResultFixture(tx) @@ -217,21 +215,19 @@ func TestGetTransactions(t *testing.T) { } }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ReferenceBlockID, txr.CollectionID, tx.ID(), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("get by ID Invalid", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) req := getTransactionReq("invalid", false, "", "") expected := `{"code":400, "message":"invalid ID format"}` - assertResponse(t, req, http.StatusBadRequest, expected, restHandler) + assertResponse(t, req, http.StatusBadRequest, expected, backend) }) t.Run("get by ID non-existing", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -241,7 +237,7 @@ func TestGetTransactions(t *testing.T) { Return(nil, status.Error(codes.NotFound, "transaction not found")) expected := `{"code":404, "message":"Flow resource not found: transaction not found"}` - assertResponse(t, req, http.StatusNotFound, expected, restHandler) + assertResponse(t, req, http.StatusNotFound, expected, backend) }) } @@ -284,7 +280,6 @@ func TestGetTransactionResult(t *testing.T) { t.Run("get by transaction ID", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) req := getTransactionResultReq(id.String(), "", "") @@ -292,12 +287,11 @@ func TestGetTransactionResult(t *testing.T) { On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, flow.ZeroID). Return(txr, nil) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("get by block ID", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) req := getTransactionResultReq(id.String(), bid.String(), "") @@ -305,12 +299,11 @@ func TestGetTransactionResult(t *testing.T) { On("GetTransactionResult", mocks.Anything, id, bid, flow.ZeroID). Return(txr, nil) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("get by collection ID", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) req := getTransactionResultReq(id.String(), "", cid.String()) @@ -318,12 +311,11 @@ func TestGetTransactionResult(t *testing.T) { On("GetTransactionResult", mocks.Anything, id, flow.ZeroID, cid). Return(txr, nil) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("get execution statuses", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) testVectors := map[*access.TransactionResult]string{{ Status: flow.TransactionStatusExpired, @@ -367,24 +359,22 @@ func TestGetTransactionResult(t *testing.T) { "_self": "/v1/transaction_results/%s" } }`, bid.String(), cid.String(), err, cases.Title(language.English).String(strings.ToLower(txResult.Status.String())), txResult.ErrorMessage, id.String()) - assertOKResponse(t, req, expectedResp, restHandler) + assertOKResponse(t, req, expectedResp, backend) } }) t.Run("get by ID Invalid", func(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) req := getTransactionResultReq("invalid", "", "") expected := `{"code":400, "message":"invalid ID format"}` - assertResponse(t, req, http.StatusBadRequest, expected, restHandler) + assertResponse(t, req, http.StatusBadRequest, expected, backend) }) } func TestCreateTransaction(t *testing.T) { backend := &mock.API{} - restHandler := newAccessRestHandler(backend) t.Run("create", func(t *testing.T) { tx := unittest.TransactionBodyFixture() @@ -434,7 +424,7 @@ func TestCreateTransaction(t *testing.T) { } }`, tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.PayloadSignatures[0].Signature), util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID()) - assertOKResponse(t, req, expected, restHandler) + assertOKResponse(t, req, expected, backend) }) t.Run("post invalid transaction", func(t *testing.T) { @@ -461,7 +451,7 @@ func TestCreateTransaction(t *testing.T) { testTx[test.inputField] = test.inputValue req := createTransactionReq(testTx) - assertResponse(t, req, http.StatusBadRequest, test.output, restHandler) + assertResponse(t, req, http.StatusBadRequest, test.output, backend) } }) } diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index 97d23f9a5ab..3e5a08a3570 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -201,6 +201,7 @@ func New( return b } +// NewCache constructs cache and its size. func NewCache( log zerolog.Logger, accessMetrics module.AccessMetrics, diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index 42b4aec9021..7ef17d032c3 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -69,7 +69,7 @@ type Engine struct { secureGrpcAddress net.Addr restAPIAddress net.Addr - restHandler api.RestServerApi + restHandler api.RestBackendApi } type Option func(*RPCEngineBuilder) diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index 79db9853c29..7308a18597d 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -8,7 +8,6 @@ import ( "github.com/onflow/flow-go/access" legacyaccess "github.com/onflow/flow-go/access/legacy" "github.com/onflow/flow-go/consensus/hotstuff" - "github.com/onflow/flow-go/engine/access/rest" restapi "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/module" @@ -40,7 +39,7 @@ func (builder *RPCEngineBuilder) RpcHandler() accessproto.AccessAPIServer { return builder.rpcHandler } -func (builder *RPCEngineBuilder) RestHandler() restapi.RestServerApi { +func (builder *RPCEngineBuilder) RestHandler() restapi.RestBackendApi { return builder.restHandler } @@ -69,8 +68,8 @@ func (builder *RPCEngineBuilder) WithRpcHandler(handler accessproto.AccessAPISer return builder } -// WithRestHandler specifies that the given `RestServerApi` should be used for REST. -func (builder *RPCEngineBuilder) WithRestHandler(handler restapi.RestServerApi) *RPCEngineBuilder { +// WithRestHandler specifies that the given `RestBackendApi` should be used for REST. +func (builder *RPCEngineBuilder) WithRestHandler(handler restapi.RestBackendApi) *RPCEngineBuilder { builder.restHandler = handler return builder } @@ -117,7 +116,7 @@ func (builder *RPCEngineBuilder) Build() (*Engine, error) { restHandler := builder.Engine.restHandler if restHandler == nil { - restHandler = rest.NewServerRequestHandler(builder.log, builder.backend) + restHandler = builder.backend } builder.Engine.restHandler = restHandler diff --git a/engine/common/rpc/convert/convert.go b/engine/common/rpc/convert/convert.go index d2f817df8c7..f2f7a0eee9c 100644 --- a/engine/common/rpc/convert/convert.go +++ b/engine/common/rpc/convert/convert.go @@ -7,8 +7,10 @@ import ( "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" execproto "github.com/onflow/flow/protobuf/go/flow/execution" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" @@ -651,6 +653,26 @@ func AccountKeyToMessage(a flow.AccountPublicKey) (*entities.AccountKey, error) }, nil } +func MessagesToBlockEvents(blocksEvents []*accessproto.EventsResponse_Result) []flow.BlockEvents { + evs := make([]flow.BlockEvents, len(blocksEvents)) + for _, ev := range blocksEvents { + var blockEvent flow.BlockEvents + MessageToBlockEvent(ev) + evs = append(evs, blockEvent) + } + + return evs +} + +func MessageToBlockEvent(blockEvents *accessproto.EventsResponse_Result) flow.BlockEvents { + return flow.BlockEvents{ + BlockHeight: blockEvents.BlockHeight, + BlockID: MessageToIdentifier(blockEvents.BlockId), + BlockTimestamp: blockEvents.BlockTimestamp.AsTime(), + Events: MessagesToEvents(blockEvents.Events), + } +} + func MessageToEvent(m *entities.Event) flow.Event { return flow.Event{ Type: flow.EventType(m.GetType()), From 9deccb87b03c6aa09dca4026a4d0e88d7c34c1cf Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 29 Jun 2023 14:20:43 +0300 Subject: [PATCH 16/30] Added constructor to rest proxy handler, updated test --- cmd/observer/node_builder/observer_builder.go | 22 +++++------- .../rest/apiproxy/rest_proxy_handler.go | 34 ++++++++++++++++++- integration/tests/access/observer_test.go | 4 +-- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 5c8e4922c7e..56512e02609 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -32,7 +32,6 @@ import ( "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/follower" - "github.com/onflow/flow-go/engine/common/grpc/forwarder" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/engine/protocol" "github.com/onflow/flow-go/model/encodable" @@ -913,7 +912,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { } // upstream access node forwarder - rpcForwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) + forwarder, err := apiproxy.NewFlowAccessAPIForwarder(builder.upstreamIdentities, builder.apiTimeout, config.MaxMsgSize) if err != nil { return nil, err } @@ -923,7 +922,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { rpcHandler := &apiproxy.FlowAccessAPIRouter{ Logger: builder.Logger, Metrics: observerCollector, - Upstream: rpcForwarder, + Upstream: forwarder, Observer: protocol.NewHandler(protocol.New( node.State, node.Storage.Blocks, @@ -931,22 +930,19 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { backend.NewNetworkAPI(node.State, node.RootChainID, backend.DefaultSnapshotHistoryLimit), )), } - frw, err := forwarder.NewForwarder( + + restHandler, err := restapiproxy.NewRestProxyHandler( + accessBackend, builder.upstreamIdentities, builder.apiTimeout, - config.MaxMsgSize) + config.MaxMsgSize, + builder.Logger, + observerCollector, + node.RootChainID.Chain()) if err != nil { return nil, err } - restHandler := &restapiproxy.RestProxyHandler{ - Logger: builder.Logger, - Metrics: observerCollector, - Chain: node.RootChainID.Chain(), - } - restHandler.API = accessBackend - restHandler.Forwarder = frw - // build the rpc engine builder.RpcEng, err = engineBuilder. WithRpcHandler(rpcHandler). diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index d4bc6546a40..d2a4c6adbd5 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -2,6 +2,7 @@ package apiproxy import ( "context" + "time" "google.golang.org/grpc/status" @@ -27,6 +28,37 @@ type RestProxyHandler struct { Chain flow.Chain } +// NewRestProxyHandler returns a new rest proxy handler for observer node. +func NewRestProxyHandler( + api access.API, + identities flow.IdentityList, + timeout time.Duration, + maxMsgSize uint, + log zerolog.Logger, + metrics metrics.ObserverMetrics, + chain flow.Chain, +) (*RestProxyHandler, error) { + + forwarder, err := forwarder.NewForwarder( + identities, + timeout, + maxMsgSize) + if err != nil { + return nil, err + } + + restProxyHandler := &RestProxyHandler{ + Logger: log, + Metrics: metrics, + Chain: chain, + } + + restProxyHandler.API = api + restProxyHandler.Forwarder = forwarder + + return restProxyHandler, nil +} + func (r *RestProxyHandler) log(handler, rpc string, err error) { code := status.Code(err) r.Metrics.RecordRPC(handler, rpc, code) @@ -46,7 +78,7 @@ func (r *RestProxyHandler) log(handler, rpc string, err error) { } // GetLatestBlockHeader returns the latest block header and block status, if isSealed = true - returns the latest seal header. -func (r *RestProxyHandler) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { //Uliana getAccount та GetEvents були з Upstream +func (r *RestProxyHandler) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { upstream, err := r.FaultTolerantClient() if err != nil { return nil, flow.BlockStatusUnknown, err diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index e36642eb662..a70bf31c3d6 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -418,7 +418,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { { name: "getBlocksByHeight", method: http.MethodGet, - path: "/blocks?height=0", + path: "/blocks?height=1", }, { name: "getBlockPayloadByID", @@ -448,7 +448,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { { name: "getAccount", method: http.MethodGet, - path: "/accounts/" + account.Address.HexWithPrefix(), + path: "/accounts/" + account.Address.HexWithPrefix() + "?block_height=1", }, { name: "getEvents", From bbe70d0190d7de4c384f8e0598afb09c6db0746d Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 29 Jun 2023 15:49:33 +0300 Subject: [PATCH 17/30] Changed flow/protobuf/go/flow module to latest, removed lines with replacing mod --- go.mod | 5 +---- go.sum | 4 ++-- insecure/go.mod | 2 +- insecure/go.sum | 4 ++-- integration/go.mod | 5 +---- integration/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 46031eb3d96..9e6a8f81059 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 github.com/onflow/flow-go-sdk v0.41.6 github.com/onflow/flow-go/crypto v0.24.7 - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pierrec/lz4 v2.6.1+incompatible @@ -282,6 +282,3 @@ require ( lukechampine.com/blake3 v1.1.7 // indirect nhooyr.io/websocket v1.8.6 // indirect ) - -//TODO: Remove when branch UlyanaAndrukhiv/3138-rest-api-on-observers on onflow/flow will be merged -replace github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 => github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 diff --git a/go.sum b/go.sum index fa433a6d696..613e1f90fd5 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,6 @@ github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdII github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 h1:8l0uZ9ut9TowB1qNKbPFt/ar/5mqxhqcp0r+HWv1zps= -github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -1246,6 +1244,8 @@ github.com/onflow/flow-go-sdk v0.41.6 h1:x5HhmRDvbCWXRCzHITJxOp0Komq5JJ9zphoR2u6 github.com/onflow/flow-go-sdk v0.41.6/go.mod h1:AYypQvn6ecMONhF3M1vBOUX9b4oHKFWkkrw8bO4VEik= github.com/onflow/flow-go/crypto v0.24.7 h1:RCLuB83At4z5wkAyUCF7MYEnPoIIOHghJaODuJyEoW0= github.com/onflow/flow-go/crypto v0.24.7/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 h1:XcSR/n2aSVO7lOEsKScYALcpHlfowLwicZ9yVbL6bnA= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8/go.mod h1:73C8FlT4L/Qe4Cf5iXUNL8b2pvu4zs5dJMMJ5V2TjUI= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/insecure/go.mod b/insecure/go.mod index e3748bc6c3d..e91291c8fff 100644 --- a/insecure/go.mod +++ b/insecure/go.mod @@ -190,7 +190,7 @@ require ( github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 // indirect github.com/onflow/flow-ft/lib/go/contracts v0.7.0 // indirect github.com/onflow/flow-go-sdk v0.41.6 // indirect - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 // indirect + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce // indirect github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 // indirect github.com/onflow/sdks v0.5.0 // indirect github.com/onflow/wal v0.0.0-20230529184820-bc9f8244608d // indirect diff --git a/insecure/go.sum b/insecure/go.sum index 614d1513e79..5a16af690b0 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -1192,8 +1192,8 @@ github.com/onflow/flow-go-sdk v0.41.6 h1:x5HhmRDvbCWXRCzHITJxOp0Komq5JJ9zphoR2u6 github.com/onflow/flow-go-sdk v0.41.6/go.mod h1:AYypQvn6ecMONhF3M1vBOUX9b4oHKFWkkrw8bO4VEik= github.com/onflow/flow-go/crypto v0.24.7 h1:RCLuB83At4z5wkAyUCF7MYEnPoIIOHghJaODuJyEoW0= github.com/onflow/flow-go/crypto v0.24.7/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 h1:6uKg0gpLKpTZKMihrsFR0Gkq++1hykzfR1tQCKuOfw4= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 h1:XcSR/n2aSVO7lOEsKScYALcpHlfowLwicZ9yVbL6bnA= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8/go.mod h1:73C8FlT4L/Qe4Cf5iXUNL8b2pvu4zs5dJMMJ5V2TjUI= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/integration/go.mod b/integration/go.mod index be1de65103b..549e9dffb09 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -25,7 +25,7 @@ require ( github.com/onflow/flow-go-sdk v0.41.6 github.com/onflow/flow-go/crypto v0.24.7 github.com/onflow/flow-go/insecure v0.0.0-00010101000000-000000000000 - github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 + github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce github.com/plus3it/gorecurcopy v0.0.1 github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_model v0.3.0 @@ -330,6 +330,3 @@ require ( replace github.com/onflow/flow-go => ../ replace github.com/onflow/flow-go/insecure => ../insecure - -//TODO: Remove when branch UlyanaAndrukhiv/3138-rest-api-on-observers on onflow/flow will be merged -replace github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230602212908-08fc6536d391 => github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 diff --git a/integration/go.sum b/integration/go.sum index 91c55c2d7ae..15ed5a63d18 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -112,8 +112,6 @@ github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBY github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702 h1:8l0uZ9ut9TowB1qNKbPFt/ar/5mqxhqcp0r+HWv1zps= -github.com/UlyanaAndrukhiv/flow/protobuf/go/flow v0.0.0-20230612132933-04dabe947702/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/VictoriaMetrics/fastcache v1.5.3/go.mod h1:+jv9Ckb+za/P1ZRg/sulP5Ni1v49daAVERr0H3CuscE= github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -1382,6 +1380,8 @@ github.com/onflow/flow-go/crypto v0.24.7/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7 github.com/onflow/flow-nft/lib/go/contracts v0.0.0-20220727161549-d59b1e547ac4 h1:5AnM9jIwkyHaY6+C3cWnt07oTOYctmwxvpiL25HRJws= github.com/onflow/flow-nft/lib/go/contracts v0.0.0-20220727161549-d59b1e547ac4/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/fusd/lib/go/contracts v0.0.0-20211021081023-ae9de8fb2c7e h1:RHaXPHvWCy3VM62+HTyu6DYq5T8rrK1gxxqogKuJ4S4= github.com/onflow/fusd/lib/go/contracts v0.0.0-20211021081023-ae9de8fb2c7e/go.mod h1:CRX9eXtc9zHaRVTW1Xh4Cf5pZgKkQuu1NuSEVyHXr/0= github.com/onflow/go-bitswap v0.0.0-20221017184039-808c5791a8a8 h1:XcSR/n2aSVO7lOEsKScYALcpHlfowLwicZ9yVbL6bnA= From 276a101b04eebfd8bbb5975b86cf1c65e1b271d5 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 30 Jun 2023 12:26:34 +0300 Subject: [PATCH 18/30] Refactored according to commits --- .../node_builder/access_node_builder.go | 1 + cmd/observer/node_builder/observer_builder.go | 29 ++++++----- .../rest/apiproxy/rest_proxy_handler.go | 48 +++---------------- engine/access/rest/handler.go | 2 +- engine/access/rpc/engine.go | 2 + engine/access/rpc/engine_builder.go | 12 ----- engine/common/rpc/convert/convert.go | 21 ++++---- 7 files changed, 33 insertions(+), 82 deletions(-) diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index c13a6db6f40..e5fe875d113 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -1045,6 +1045,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { builder.apiBurstlimits, builder.Me, backend, + backend, ) if err != nil { return nil, err diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 56512e02609..4070c277749 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -895,6 +895,19 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { backend.DefaultSnapshotHistoryLimit, backendConfig.ArchiveAddressList) + observerCollector := metrics.NewObserverCollector() + restHandler, err := restapiproxy.NewRestProxyHandler( + accessBackend, + builder.upstreamIdentities, + builder.apiTimeout, + config.MaxMsgSize, + builder.Logger, + observerCollector, + node.RootChainID.Chain()) + if err != nil { + return nil, err + } + engineBuilder, err := rpc.NewBuilder( node.Logger, node.State, @@ -906,6 +919,7 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { builder.apiBurstlimits, builder.Me, accessBackend, + restHandler, ) if err != nil { return nil, err @@ -917,8 +931,6 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { return nil, err } - observerCollector := metrics.NewObserverCollector() - rpcHandler := &apiproxy.FlowAccessAPIRouter{ Logger: builder.Logger, Metrics: observerCollector, @@ -931,22 +943,9 @@ func (builder *ObserverServiceBuilder) enqueueRPCServer() { )), } - restHandler, err := restapiproxy.NewRestProxyHandler( - accessBackend, - builder.upstreamIdentities, - builder.apiTimeout, - config.MaxMsgSize, - builder.Logger, - observerCollector, - node.RootChainID.Chain()) - if err != nil { - return nil, err - } - // build the rpc engine builder.RpcEng, err = engineBuilder. WithRpcHandler(rpcHandler). - WithRestHandler(restHandler). WithLegacy(). Build() if err != nil { diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index d2a4c6adbd5..18e7cfe3d9e 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -77,33 +77,6 @@ func (r *RestProxyHandler) log(handler, rpc string, err error) { logger.Info().Msg("request succeeded") } -// GetLatestBlockHeader returns the latest block header and block status, if isSealed = true - returns the latest seal header. -func (r *RestProxyHandler) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { - upstream, err := r.FaultTolerantClient() - if err != nil { - return nil, flow.BlockStatusUnknown, err - } - - getLatestBlockHeaderRequest := &accessproto.GetLatestBlockHeaderRequest{ - IsSealed: isSealed, - } - latestBlockHeaderResponse, err := upstream.GetLatestBlockHeader(ctx, getLatestBlockHeaderRequest) - if err != nil { - return nil, flow.BlockStatusUnknown, err - } - blockHeader, err := convert.MessageToBlockHeader(latestBlockHeaderResponse.Block) - if err != nil { - return nil, flow.BlockStatusUnknown, err - } - blockStatus, err := convert.MessagesToBlockStatus(latestBlockHeaderResponse.BlockStatus) - if err != nil { - return nil, flow.BlockStatusUnknown, err - } - - r.log("upstream", "GetLatestBlockHeader", err) - return blockHeader, blockStatus, nil -} - // GetCollectionByID returns a collection by ID. func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) { upstream, err := r.FaultTolerantClient() @@ -120,15 +93,13 @@ func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identi return nil, err } - transactions := make([]flow.Identifier, len(collectionResponse.Collection.TransactionIds)) - for _, txId := range collectionResponse.Collection.TransactionIds { - transactions = append(transactions, convert.MessageToIdentifier(txId)) + transactions, err := convert.MessageToLightCollection(collectionResponse.Collection) + if err != nil { + return nil, err } r.log("upstream", "GetCollectionByID", err) - return &flow.LightCollection{ - Transactions: transactions, - }, nil + return transactions, nil } // SendTransaction sends already created transaction. @@ -144,13 +115,9 @@ func (r *RestProxyHandler) SendTransaction(ctx context.Context, tx *flow.Transac } _, err = upstream.SendTransaction(ctx, sendTransactionRequest) - if err != nil { - return err - } r.log("upstream", "SendTransaction", err) - return nil - + return err } // GetTransaction returns transaction by ID. @@ -310,10 +277,7 @@ func (r *RestProxyHandler) GetEventsForBlockIDs(ctx context.Context, eventType s return nil, err } - var blockIds [][]byte - for _, id := range blockIDs { - blockIds = append(blockIds, id[:]) - } + blockIds := convert.IdentifiersToMessages(blockIDs) getEventsForBlockIDsRequest := &accessproto.GetEventsForBlockIDsRequest{ Type: eventType, diff --git a/engine/access/rest/handler.go b/engine/access/rest/handler.go index c025a0c4c24..f2f7cc4a640 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/handler.go @@ -125,7 +125,7 @@ func (h *Handler) errorHandler(w http.ResponseWriter, err error, errorLogger zer return } if se.Code() == codes.Unavailable { - msg := fmt.Sprintf("Invalid Upstream request: %s", se.Message()) + msg := fmt.Sprintf("Failed to process request: %s", se.Message()) h.errorResponse(w, http.StatusServiceUnavailable, msg, errorLogger) return } diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index 7ef17d032c3..17f26cf0435 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -84,6 +84,7 @@ func NewBuilder(log zerolog.Logger, apiBurstLimits map[string]int, // the api burst limit (max calls at the same time) for each of the Access API e.g. Ping->50, GetTransaction->10 me module.Local, backend *backend.Backend, + restHandler api.RestBackendApi, ) (*RPCEngineBuilder, error) { log = log.With().Str("engine", "rpc").Logger() @@ -139,6 +140,7 @@ func NewBuilder(log zerolog.Logger, config: config, chain: chainID.Chain(), restCollector: accessMetrics, + restHandler: restHandler, } backendNotifierActor, backendNotifierWorker := events.NewFinalizationActor(eng.notifyBackendOnBlockFinalized) eng.backendNotifierActor = backendNotifierActor diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index 7308a18597d..41c8bbdc192 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -68,12 +68,6 @@ func (builder *RPCEngineBuilder) WithRpcHandler(handler accessproto.AccessAPISer return builder } -// WithRestHandler specifies that the given `RestBackendApi` should be used for REST. -func (builder *RPCEngineBuilder) WithRestHandler(handler restapi.RestBackendApi) *RPCEngineBuilder { - builder.restHandler = handler - return builder -} - // WithLegacy specifies that a legacy access API should be instantiated // Returns self-reference for chaining. func (builder *RPCEngineBuilder) WithLegacy() *RPCEngineBuilder { @@ -114,11 +108,5 @@ func (builder *RPCEngineBuilder) Build() (*Engine, error) { accessproto.RegisterAccessAPIServer(builder.unsecureGrpcServer, rpcHandler) accessproto.RegisterAccessAPIServer(builder.secureGrpcServer, rpcHandler) - restHandler := builder.Engine.restHandler - if restHandler == nil { - restHandler = builder.backend - } - builder.Engine.restHandler = restHandler - return builder.Engine, nil } diff --git a/engine/common/rpc/convert/convert.go b/engine/common/rpc/convert/convert.go index f2f7a0eee9c..cfcae8ade60 100644 --- a/engine/common/rpc/convert/convert.go +++ b/engine/common/rpc/convert/convert.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" accessproto "github.com/onflow/flow/protobuf/go/flow/access" @@ -379,17 +378,15 @@ func MessageToBlock(m *entities.Block) (*flow.Block, error) { }, nil } -func MessagesToBlockStatus(s entities.BlockStatus) (flow.BlockStatus, error) { - switch s { - case entities.BlockStatus_BLOCK_UNKNOWN: - return flow.BlockStatusUnknown, nil - case entities.BlockStatus_BLOCK_FINALIZED: - return flow.BlockStatusFinalized, nil - case entities.BlockStatus_BLOCK_SEALED: - return flow.BlockStatusSealed, nil +func MessageToLightCollection(m *entities.Collection) (*flow.LightCollection, error) { + transactions := make([]flow.Identifier, 0, len(m.TransactionIds)) + for _, txId := range m.TransactionIds { + transactions = append(transactions, MessageToIdentifier(txId)) } - return flow.BlockStatusUnknown, fmt.Errorf("failed to convert block status") + return &flow.LightCollection{ + Transactions: transactions, + }, nil } func MessagesToExecutionResultMetaList(m []*entities.ExecutionReceiptMeta) flow.ExecutionReceiptMetaList { @@ -657,14 +654,14 @@ func MessagesToBlockEvents(blocksEvents []*accessproto.EventsResponse_Result) [] evs := make([]flow.BlockEvents, len(blocksEvents)) for _, ev := range blocksEvents { var blockEvent flow.BlockEvents - MessageToBlockEvent(ev) + MessageToBlockEvents(ev) evs = append(evs, blockEvent) } return evs } -func MessageToBlockEvent(blockEvents *accessproto.EventsResponse_Result) flow.BlockEvents { +func MessageToBlockEvents(blockEvents *accessproto.EventsResponse_Result) flow.BlockEvents { return flow.BlockEvents{ BlockHeight: blockEvents.BlockHeight, BlockID: MessageToIdentifier(blockEvents.BlockId), From 74c47d597ab1da505b9a8b11d9bae6005f7a8b57 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 30 Jun 2023 13:38:03 +0300 Subject: [PATCH 19/30] Removed RestBackendApi and refactored rest using standard Access API's backend, refactored tests place --- engine/access/rest/api/api.go | 38 -- engine/access/rest/mock/rest_backend_api.go | 505 ------------------ engine/access/rest/routes/accounts.go | 4 +- .../rest/{tests => routes}/accounts_test.go | 105 +--- engine/access/rest/routes/blocks.go | 14 +- .../rest/{tests => routes}/blocks_test.go | 2 +- engine/access/rest/routes/collections.go | 4 +- .../{tests => routes}/collections_test.go | 2 +- engine/access/rest/routes/events.go | 4 +- .../rest/{tests => routes}/events_test.go | 7 +- engine/access/rest/routes/execution_result.go | 6 +- .../execution_result_test.go | 2 +- engine/access/rest/{ => routes}/handler.go | 10 +- engine/access/rest/routes/network.go | 4 +- .../rest/{tests => routes}/network_test.go | 2 +- .../access/rest/routes/node_version_info.go | 4 +- .../node_version_info_test.go | 2 +- engine/access/rest/{ => routes}/router.go | 35 +- engine/access/rest/routes/scripts.go | 4 +- .../rest/{tests => routes}/scripts_test.go | 2 +- .../rest/{tests => routes}/test_helpers.go | 13 +- engine/access/rest/routes/transactions.go | 7 +- .../{tests => routes}/transactions_test.go | 2 +- engine/access/rest/server.go | 7 +- engine/access/rest_api_test.go | 14 +- engine/access/rpc/engine.go | 6 +- engine/access/rpc/engine_builder.go | 5 - engine/access/rpc/rate_limit_test.go | 2 + engine/access/secure_grpcr_test.go | 1 + engine/common/rpc/convert/convert.go | 1 + 30 files changed, 82 insertions(+), 732 deletions(-) delete mode 100644 engine/access/rest/api/api.go delete mode 100644 engine/access/rest/mock/rest_backend_api.go rename engine/access/rest/{tests => routes}/accounts_test.go (61%) rename engine/access/rest/{tests => routes}/blocks_test.go (99%) rename engine/access/rest/{tests => routes}/collections_test.go (99%) rename engine/access/rest/{tests => routes}/events_test.go (98%) rename engine/access/rest/{tests => routes}/execution_result_test.go (99%) rename engine/access/rest/{ => routes}/handler.go (97%) rename engine/access/rest/{tests => routes}/network_test.go (98%) rename engine/access/rest/{tests => routes}/node_version_info_test.go (99%) rename engine/access/rest/{ => routes}/router.go (74%) rename engine/access/rest/{tests => routes}/scripts_test.go (99%) rename engine/access/rest/{tests => routes}/test_helpers.go (74%) rename engine/access/rest/{tests => routes}/transactions_test.go (99%) diff --git a/engine/access/rest/api/api.go b/engine/access/rest/api/api.go deleted file mode 100644 index 81d45cccb6e..00000000000 --- a/engine/access/rest/api/api.go +++ /dev/null @@ -1,38 +0,0 @@ -package api - -import ( - "context" - - "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/model/flow" -) - -// RestBackendApi is the backend API for REST service. -type RestBackendApi interface { - GetNetworkParameters(ctx context.Context) access.NetworkParameters - GetNodeVersionInfo(ctx context.Context) (*access.NodeVersionInfo, error) - - GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) - - GetLatestBlock(ctx context.Context, isSealed bool) (*flow.Block, flow.BlockStatus, error) - GetBlockByHeight(ctx context.Context, height uint64) (*flow.Block, flow.BlockStatus, error) - GetBlockByID(ctx context.Context, id flow.Identifier) (*flow.Block, flow.BlockStatus, error) - - GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) - - SendTransaction(ctx context.Context, tx *flow.TransactionBody) error - GetTransaction(ctx context.Context, id flow.Identifier) (*flow.TransactionBody, error) - GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) - - GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) - - ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments [][]byte) ([]byte, error) - ExecuteScriptAtBlockHeight(ctx context.Context, blockHeight uint64, script []byte, arguments [][]byte) ([]byte, error) - ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments [][]byte) ([]byte, error) - - GetEventsForHeightRange(ctx context.Context, eventType string, startHeight, endHeight uint64) ([]flow.BlockEvents, error) - GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) - - GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) - GetExecutionResultByID(ctx context.Context, id flow.Identifier) (*flow.ExecutionResult, error) -} diff --git a/engine/access/rest/mock/rest_backend_api.go b/engine/access/rest/mock/rest_backend_api.go deleted file mode 100644 index 61c3a938efa..00000000000 --- a/engine/access/rest/mock/rest_backend_api.go +++ /dev/null @@ -1,505 +0,0 @@ -// Code generated by mockery v2.21.4. DO NOT EDIT. - -package mock - -import ( - access "github.com/onflow/flow-go/access" - - context "context" - - flow "github.com/onflow/flow-go/model/flow" - - mock "github.com/stretchr/testify/mock" -) - -// RestBackendApi is an autogenerated mock type for the RestBackendApi type -type RestBackendApi struct { - mock.Mock -} - -// ExecuteScriptAtBlockHeight provides a mock function with given fields: ctx, blockHeight, script, arguments -func (_m *RestBackendApi) ExecuteScriptAtBlockHeight(ctx context.Context, blockHeight uint64, script []byte, arguments [][]byte) ([]byte, error) { - ret := _m.Called(ctx, blockHeight, script, arguments) - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, [][]byte) ([]byte, error)); ok { - return rf(ctx, blockHeight, script, arguments) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64, []byte, [][]byte) []byte); ok { - r0 = rf(ctx, blockHeight, script, arguments) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64, []byte, [][]byte) error); ok { - r1 = rf(ctx, blockHeight, script, arguments) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ExecuteScriptAtBlockID provides a mock function with given fields: ctx, blockID, script, arguments -func (_m *RestBackendApi) ExecuteScriptAtBlockID(ctx context.Context, blockID flow.Identifier, script []byte, arguments [][]byte) ([]byte, error) { - ret := _m.Called(ctx, blockID, script, arguments) - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, [][]byte) ([]byte, error)); ok { - return rf(ctx, blockID, script, arguments) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, []byte, [][]byte) []byte); ok { - r0 = rf(ctx, blockID, script, arguments) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, []byte, [][]byte) error); ok { - r1 = rf(ctx, blockID, script, arguments) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ExecuteScriptAtLatestBlock provides a mock function with given fields: ctx, script, arguments -func (_m *RestBackendApi) ExecuteScriptAtLatestBlock(ctx context.Context, script []byte, arguments [][]byte) ([]byte, error) { - ret := _m.Called(ctx, script, arguments) - - var r0 []byte - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []byte, [][]byte) ([]byte, error)); ok { - return rf(ctx, script, arguments) - } - if rf, ok := ret.Get(0).(func(context.Context, []byte, [][]byte) []byte); ok { - r0 = rf(ctx, script, arguments) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]byte) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []byte, [][]byte) error); ok { - r1 = rf(ctx, script, arguments) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetAccountAtBlockHeight provides a mock function with given fields: ctx, address, height -func (_m *RestBackendApi) GetAccountAtBlockHeight(ctx context.Context, address flow.Address, height uint64) (*flow.Account, error) { - ret := _m.Called(ctx, address, height) - - var r0 *flow.Account - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) (*flow.Account, error)); ok { - return rf(ctx, address, height) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Address, uint64) *flow.Account); ok { - r0 = rf(ctx, address, height) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.Account) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Address, uint64) error); ok { - r1 = rf(ctx, address, height) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetBlockByHeight provides a mock function with given fields: ctx, height -func (_m *RestBackendApi) GetBlockByHeight(ctx context.Context, height uint64) (*flow.Block, flow.BlockStatus, error) { - ret := _m.Called(ctx, height) - - var r0 *flow.Block - var r1 flow.BlockStatus - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, uint64) (*flow.Block, flow.BlockStatus, error)); ok { - return rf(ctx, height) - } - if rf, ok := ret.Get(0).(func(context.Context, uint64) *flow.Block); ok { - r0 = rf(ctx, height) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.Block) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, uint64) flow.BlockStatus); ok { - r1 = rf(ctx, height) - } else { - r1 = ret.Get(1).(flow.BlockStatus) - } - - if rf, ok := ret.Get(2).(func(context.Context, uint64) error); ok { - r2 = rf(ctx, height) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetBlockByID provides a mock function with given fields: ctx, id -func (_m *RestBackendApi) GetBlockByID(ctx context.Context, id flow.Identifier) (*flow.Block, flow.BlockStatus, error) { - ret := _m.Called(ctx, id) - - var r0 *flow.Block - var r1 flow.BlockStatus - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.Block, flow.BlockStatus, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.Block); ok { - r0 = rf(ctx, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.Block) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) flow.BlockStatus); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Get(1).(flow.BlockStatus) - } - - if rf, ok := ret.Get(2).(func(context.Context, flow.Identifier) error); ok { - r2 = rf(ctx, id) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetCollectionByID provides a mock function with given fields: ctx, id -func (_m *RestBackendApi) GetCollectionByID(ctx context.Context, id flow.Identifier) (*flow.LightCollection, error) { - ret := _m.Called(ctx, id) - - var r0 *flow.LightCollection - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.LightCollection, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.LightCollection); ok { - r0 = rf(ctx, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.LightCollection) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetEventsForBlockIDs provides a mock function with given fields: ctx, eventType, blockIDs -func (_m *RestBackendApi) GetEventsForBlockIDs(ctx context.Context, eventType string, blockIDs []flow.Identifier) ([]flow.BlockEvents, error) { - ret := _m.Called(ctx, eventType, blockIDs) - - var r0 []flow.BlockEvents - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) ([]flow.BlockEvents, error)); ok { - return rf(ctx, eventType, blockIDs) - } - if rf, ok := ret.Get(0).(func(context.Context, string, []flow.Identifier) []flow.BlockEvents); ok { - r0 = rf(ctx, eventType, blockIDs) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]flow.BlockEvents) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, []flow.Identifier) error); ok { - r1 = rf(ctx, eventType, blockIDs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetEventsForHeightRange provides a mock function with given fields: ctx, eventType, startHeight, endHeight -func (_m *RestBackendApi) GetEventsForHeightRange(ctx context.Context, eventType string, startHeight uint64, endHeight uint64) ([]flow.BlockEvents, error) { - ret := _m.Called(ctx, eventType, startHeight, endHeight) - - var r0 []flow.BlockEvents - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) ([]flow.BlockEvents, error)); ok { - return rf(ctx, eventType, startHeight, endHeight) - } - if rf, ok := ret.Get(0).(func(context.Context, string, uint64, uint64) []flow.BlockEvents); ok { - r0 = rf(ctx, eventType, startHeight, endHeight) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]flow.BlockEvents) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string, uint64, uint64) error); ok { - r1 = rf(ctx, eventType, startHeight, endHeight) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetExecutionResultByID provides a mock function with given fields: ctx, id -func (_m *RestBackendApi) GetExecutionResultByID(ctx context.Context, id flow.Identifier) (*flow.ExecutionResult, error) { - ret := _m.Called(ctx, id) - - var r0 *flow.ExecutionResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionResult, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionResult); ok { - r0 = rf(ctx, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.ExecutionResult) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetExecutionResultForBlockID provides a mock function with given fields: ctx, blockID -func (_m *RestBackendApi) GetExecutionResultForBlockID(ctx context.Context, blockID flow.Identifier) (*flow.ExecutionResult, error) { - ret := _m.Called(ctx, blockID) - - var r0 *flow.ExecutionResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.ExecutionResult, error)); ok { - return rf(ctx, blockID) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.ExecutionResult); ok { - r0 = rf(ctx, blockID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.ExecutionResult) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { - r1 = rf(ctx, blockID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetLatestBlock provides a mock function with given fields: ctx, isSealed -func (_m *RestBackendApi) GetLatestBlock(ctx context.Context, isSealed bool) (*flow.Block, flow.BlockStatus, error) { - ret := _m.Called(ctx, isSealed) - - var r0 *flow.Block - var r1 flow.BlockStatus - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.Block, flow.BlockStatus, error)); ok { - return rf(ctx, isSealed) - } - if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.Block); ok { - r0 = rf(ctx, isSealed) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.Block) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, bool) flow.BlockStatus); ok { - r1 = rf(ctx, isSealed) - } else { - r1 = ret.Get(1).(flow.BlockStatus) - } - - if rf, ok := ret.Get(2).(func(context.Context, bool) error); ok { - r2 = rf(ctx, isSealed) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetLatestBlockHeader provides a mock function with given fields: ctx, isSealed -func (_m *RestBackendApi) GetLatestBlockHeader(ctx context.Context, isSealed bool) (*flow.Header, flow.BlockStatus, error) { - ret := _m.Called(ctx, isSealed) - - var r0 *flow.Header - var r1 flow.BlockStatus - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, bool) (*flow.Header, flow.BlockStatus, error)); ok { - return rf(ctx, isSealed) - } - if rf, ok := ret.Get(0).(func(context.Context, bool) *flow.Header); ok { - r0 = rf(ctx, isSealed) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.Header) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, bool) flow.BlockStatus); ok { - r1 = rf(ctx, isSealed) - } else { - r1 = ret.Get(1).(flow.BlockStatus) - } - - if rf, ok := ret.Get(2).(func(context.Context, bool) error); ok { - r2 = rf(ctx, isSealed) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// GetNetworkParameters provides a mock function with given fields: ctx -func (_m *RestBackendApi) GetNetworkParameters(ctx context.Context) access.NetworkParameters { - ret := _m.Called(ctx) - - var r0 access.NetworkParameters - if rf, ok := ret.Get(0).(func(context.Context) access.NetworkParameters); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(access.NetworkParameters) - } - - return r0 -} - -// GetNodeVersionInfo provides a mock function with given fields: ctx -func (_m *RestBackendApi) GetNodeVersionInfo(ctx context.Context) (*access.NodeVersionInfo, error) { - ret := _m.Called(ctx) - - var r0 *access.NodeVersionInfo - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*access.NodeVersionInfo, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *access.NodeVersionInfo); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*access.NodeVersionInfo) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTransaction provides a mock function with given fields: ctx, id -func (_m *RestBackendApi) GetTransaction(ctx context.Context, id flow.Identifier) (*flow.TransactionBody, error) { - ret := _m.Called(ctx, id) - - var r0 *flow.TransactionBody - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) (*flow.TransactionBody, error)); ok { - return rf(ctx, id) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier) *flow.TransactionBody); ok { - r0 = rf(ctx, id) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*flow.TransactionBody) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier) error); ok { - r1 = rf(ctx, id) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetTransactionResult provides a mock function with given fields: ctx, id, blockID, collectionID -func (_m *RestBackendApi) GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) { - ret := _m.Called(ctx, id, blockID, collectionID) - - var r0 *access.TransactionResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) (*access.TransactionResult, error)); ok { - return rf(ctx, id, blockID, collectionID) - } - if rf, ok := ret.Get(0).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) *access.TransactionResult); ok { - r0 = rf(ctx, id, blockID, collectionID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*access.TransactionResult) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, flow.Identifier, flow.Identifier, flow.Identifier) error); ok { - r1 = rf(ctx, id, blockID, collectionID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SendTransaction provides a mock function with given fields: ctx, tx -func (_m *RestBackendApi) SendTransaction(ctx context.Context, tx *flow.TransactionBody) error { - ret := _m.Called(ctx, tx) - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *flow.TransactionBody) error); ok { - r0 = rf(ctx, tx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -type mockConstructorTestingTNewRestBackendApi interface { - mock.TestingT - Cleanup(func()) -} - -// NewRestBackendApi creates a new instance of RestBackendApi. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewRestBackendApi(t mockConstructorTestingTNewRestBackendApi) *RestBackendApi { - mock := &RestBackendApi{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/engine/access/rest/routes/accounts.go b/engine/access/rest/routes/accounts.go index d9f16562f1d..972c2ba68ac 100644 --- a/engine/access/rest/routes/accounts.go +++ b/engine/access/rest/routes/accounts.go @@ -1,13 +1,13 @@ package routes import ( - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetAccount handler retrieves account by address and returns the response -func GetAccount(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetAccount(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetAccountRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/accounts_test.go b/engine/access/rest/routes/accounts_test.go similarity index 61% rename from engine/access/rest/tests/accounts_test.go rename to engine/access/rest/routes/accounts_test.go index 0ff84f79075..b8bebea8e85 100644 --- a/engine/access/rest/tests/accounts_test.go +++ b/engine/access/rest/routes/accounts_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "fmt" @@ -13,7 +13,6 @@ import ( "github.com/onflow/flow-go/access/mock" "github.com/onflow/flow-go/engine/access/rest/middleware" - restmock "github.com/onflow/flow-go/engine/access/rest/mock" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -137,108 +136,6 @@ func TestAccessGetAccount(t *testing.T) { }) } -// TestObserverGetAccount tests the get account request forwarding to an upstream. -// -// Runs the following tests: -// 1. Get account by address at latest sealed block. -// 2. Get account by address at latest finalized block. -// 3. Get account by address at height. -// 4. Get account by address at height condensed. -// 5. Get invalid account. -func TestObserverGetAccount(t *testing.T) { - backend := &restmock.RestBackendApi{} - - t.Run("get by address at latest sealed block", func(t *testing.T) { - account := accountFixture(t) - var height uint64 = 100 - block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) - - req := getAccountRequest(t, account, sealedHeightQueryParam, expandableFieldKeys, expandableFieldContracts) - - backend.Mock. - On("GetLatestBlockHeader", mocktestify.Anything, true). - Return(block, flow.BlockStatusSealed, nil) - - backend.Mock. - On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). - Return(account, nil) - - expected := expectedExpandedResponse(account) - - assertOKResponse(t, req, expected, backend) - mocktestify.AssertExpectationsForObjects(t, backend) - }) - - t.Run("get by address at latest finalized block", func(t *testing.T) { - var height uint64 = 100 - block := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(height)) - account := accountFixture(t) - - req := getAccountRequest(t, account, finalHeightQueryParam, expandableFieldKeys, expandableFieldContracts) - - backend.Mock. - On("GetLatestBlockHeader", mocktestify.Anything, false). - Return(block, flow.BlockStatusFinalized, nil) - backend.Mock. - On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). - Return(account, nil) - - expected := expectedExpandedResponse(account) - - assertOKResponse(t, req, expected, backend) - mocktestify.AssertExpectationsForObjects(t, backend) - }) - - t.Run("get by address at height", func(t *testing.T) { - var height uint64 = 1337 - account := accountFixture(t) - req := getAccountRequest(t, account, fmt.Sprintf("%d", height), expandableFieldKeys, expandableFieldContracts) - - backend.Mock. - On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). - Return(account, nil) - - expected := expectedExpandedResponse(account) - - assertOKResponse(t, req, expected, backend) - mocktestify.AssertExpectationsForObjects(t, backend) - }) - - t.Run("get by address at height condensed", func(t *testing.T) { - var height uint64 = 1337 - account := accountFixture(t) - req := getAccountRequest(t, account, fmt.Sprintf("%d", height)) - - backend.Mock. - On("GetAccountAtBlockHeight", mocktestify.Anything, account.Address, height). - Return(account, nil) - - expected := expectedCondensedResponse(account) - - assertOKResponse(t, req, expected, backend) - mocktestify.AssertExpectationsForObjects(t, backend) - }) - - t.Run("get invalid", func(t *testing.T) { - tests := []struct { - url string - out string - }{ - {accountURL(t, "123", ""), `{"code":400, "message":"invalid address"}`}, - {accountURL(t, unittest.AddressFixture().String(), "foo"), `{"code":400, "message":"invalid height format"}`}, - } - - for i, test := range tests { - req, _ := http.NewRequest("GET", test.url, nil) - rr, err := executeRequest(req, backend) - assert.NoError(t, err) - - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.JSONEq(t, test.out, rr.Body.String(), fmt.Sprintf("test #%d failed: %v", i, test)) - } - }) -} - func expectedExpandedResponse(account *flow.Account) string { return fmt.Sprintf(`{ "address":"%s", diff --git a/engine/access/rest/routes/blocks.go b/engine/access/rest/routes/blocks.go index 7f0537a0c07..cd8547bca93 100644 --- a/engine/access/rest/routes/blocks.go +++ b/engine/access/rest/routes/blocks.go @@ -8,14 +8,14 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" ) // GetBlocksByIDs gets blocks by provided ID or list of IDs. -func GetBlocksByIDs(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByIDs(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockByIDsRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -35,7 +35,7 @@ func GetBlocksByIDs(r *request.Request, backend api.RestBackendApi, link models. } // GetBlocksByHeight gets blocks by height. -func GetBlocksByHeight(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetBlocksByHeight(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -92,7 +92,7 @@ func GetBlocksByHeight(r *request.Request, backend api.RestBackendApi, link mode } // GetBlockPayloadByID gets block payload by ID -func GetBlockPayloadByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetBlockPayloadByID(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetBlockPayloadRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -113,7 +113,7 @@ func GetBlockPayloadByID(r *request.Request, backend api.RestBackendApi, link mo return payload, nil } -func getBlock(option blockProviderOption, req *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (*models.Block, error) { +func getBlock(option blockProviderOption, req *request.Request, backend access.API, link models.LinkGenerator) (*models.Block, error) { // lookup block blkProvider := NewBlockProvider(backend, option) blk, blockStatus, err := blkProvider.getBlock(req.Context()) @@ -153,7 +153,7 @@ type blockProvider struct { height uint64 latest bool sealed bool - backend api.RestBackendApi + backend access.API } type blockProviderOption func(blkProvider *blockProvider) @@ -181,7 +181,7 @@ func forFinalized(queryParam uint64) blockProviderOption { } } -func NewBlockProvider(backend api.RestBackendApi, options ...blockProviderOption) *blockProvider { +func NewBlockProvider(backend access.API, options ...blockProviderOption) *blockProvider { blkProvider := &blockProvider{ backend: backend, } diff --git a/engine/access/rest/tests/blocks_test.go b/engine/access/rest/routes/blocks_test.go similarity index 99% rename from engine/access/rest/tests/blocks_test.go rename to engine/access/rest/routes/blocks_test.go index 7b8b18cdc5f..3abccc9c78a 100644 --- a/engine/access/rest/tests/blocks_test.go +++ b/engine/access/rest/routes/blocks_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "fmt" diff --git a/engine/access/rest/routes/collections.go b/engine/access/rest/routes/collections.go index 182581f90f0..47b6150f480 100644 --- a/engine/access/rest/routes/collections.go +++ b/engine/access/rest/routes/collections.go @@ -1,14 +1,14 @@ package routes import ( - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" ) // GetCollectionByID retrieves a collection by ID and builds a response -func GetCollectionByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetCollectionByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetCollectionRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/collections_test.go b/engine/access/rest/routes/collections_test.go similarity index 99% rename from engine/access/rest/tests/collections_test.go rename to engine/access/rest/routes/collections_test.go index db57c4101e5..de05152b6d5 100644 --- a/engine/access/rest/tests/collections_test.go +++ b/engine/access/rest/routes/collections_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "encoding/json" diff --git a/engine/access/rest/routes/events.go b/engine/access/rest/routes/events.go index 36eee728386..4f03624c768 100644 --- a/engine/access/rest/routes/events.go +++ b/engine/access/rest/routes/events.go @@ -3,7 +3,7 @@ package routes import ( "fmt" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) @@ -12,7 +12,7 @@ const BlockQueryParam = "block_ids" const EventTypeQuery = "type" // GetEvents for the provided block range or list of block IDs filtered by type. -func GetEvents(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { +func GetEvents(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetEventsRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/events_test.go b/engine/access/rest/routes/events_test.go similarity index 98% rename from engine/access/rest/tests/events_test.go rename to engine/access/rest/routes/events_test.go index 2de8a4b0834..47d4d89fd52 100644 --- a/engine/access/rest/tests/events_test.go +++ b/engine/access/rest/routes/events_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "encoding/json" @@ -17,7 +17,6 @@ import ( "google.golang.org/grpc/status" "github.com/onflow/flow-go/access/mock" - "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -138,7 +137,7 @@ func getEventReq(t *testing.T, eventType string, start string, end string, block q := u.Query() if len(blockIDs) > 0 { - q.Add(routes.BlockQueryParam, strings.Join(blockIDs, ",")) + q.Add(BlockQueryParam, strings.Join(blockIDs, ",")) } if start != "" && end != "" { @@ -146,7 +145,7 @@ func getEventReq(t *testing.T, eventType string, start string, end string, block q.Add(endHeightQueryParam, end) } - q.Add(routes.EventTypeQuery, eventType) + q.Add(EventTypeQuery, eventType) u.RawQuery = q.Encode() diff --git a/engine/access/rest/routes/execution_result.go b/engine/access/rest/routes/execution_result.go index f923ce7905c..b999665b26b 100644 --- a/engine/access/rest/routes/execution_result.go +++ b/engine/access/rest/routes/execution_result.go @@ -3,13 +3,13 @@ package routes import ( "fmt" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetExecutionResultsByBlockIDs gets Execution Result payload by block IDs. -func GetExecutionResultsByBlockIDs(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultsByBlockIDs(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultByBlockIDsRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -35,7 +35,7 @@ func GetExecutionResultsByBlockIDs(r *request.Request, backend api.RestBackendAp } // GetExecutionResultByID gets execution result by the ID. -func GetExecutionResultByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetExecutionResultByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetExecutionResultRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/execution_result_test.go b/engine/access/rest/routes/execution_result_test.go similarity index 99% rename from engine/access/rest/tests/execution_result_test.go rename to engine/access/rest/routes/execution_result_test.go index 23038b1abdb..ba74974af1a 100644 --- a/engine/access/rest/tests/execution_result_test.go +++ b/engine/access/rest/routes/execution_result_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "fmt" diff --git a/engine/access/rest/handler.go b/engine/access/rest/routes/handler.go similarity index 97% rename from engine/access/rest/handler.go rename to engine/access/rest/routes/handler.go index f2f7cc4a640..e323843e50e 100644 --- a/engine/access/rest/handler.go +++ b/engine/access/rest/routes/handler.go @@ -1,4 +1,4 @@ -package rest +package routes import ( "encoding/json" @@ -11,7 +11,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/engine/access/rest/util" @@ -25,7 +25,7 @@ const MaxRequestSize = 2 << 20 // 2MB // it fetches necessary resources and returns an error or response model. type ApiHandlerFunc func( r *request.Request, - backend api.RestBackendApi, + backend access.API, generator models.LinkGenerator, ) (interface{}, error) @@ -34,7 +34,7 @@ type ApiHandlerFunc func( // wraps functionality for handling error and responses outside of endpoint handling. type Handler struct { logger zerolog.Logger - backend api.RestBackendApi + backend access.API linkGenerator models.LinkGenerator apiHandlerFunc ApiHandlerFunc chain flow.Chain @@ -42,7 +42,7 @@ type Handler struct { func NewHandler( logger zerolog.Logger, - backend api.RestBackendApi, + backend access.API, handlerFunc ApiHandlerFunc, generator models.LinkGenerator, chain flow.Chain, diff --git a/engine/access/rest/routes/network.go b/engine/access/rest/routes/network.go index 88aa787108e..82abcbb6d49 100644 --- a/engine/access/rest/routes/network.go +++ b/engine/access/rest/routes/network.go @@ -1,13 +1,13 @@ package routes import ( - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNetworkParameters returns network-wide parameters of the blockchain -func GetNetworkParameters(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { +func GetNetworkParameters(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { params := backend.GetNetworkParameters(r.Context()) var response models.NetworkParameters diff --git a/engine/access/rest/tests/network_test.go b/engine/access/rest/routes/network_test.go similarity index 98% rename from engine/access/rest/tests/network_test.go rename to engine/access/rest/routes/network_test.go index a841f076f60..00d0ca03944 100644 --- a/engine/access/rest/tests/network_test.go +++ b/engine/access/rest/routes/network_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "fmt" diff --git a/engine/access/rest/routes/node_version_info.go b/engine/access/rest/routes/node_version_info.go index daf658e8869..31e172bba9f 100644 --- a/engine/access/rest/routes/node_version_info.go +++ b/engine/access/rest/routes/node_version_info.go @@ -1,13 +1,13 @@ package routes import ( - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetNodeVersionInfo returns node version information -func GetNodeVersionInfo(r *request.Request, backend api.RestBackendApi, _ models.LinkGenerator) (interface{}, error) { +func GetNodeVersionInfo(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { params, err := backend.GetNodeVersionInfo(r.Context()) if err != nil { return nil, err diff --git a/engine/access/rest/tests/node_version_info_test.go b/engine/access/rest/routes/node_version_info_test.go similarity index 99% rename from engine/access/rest/tests/node_version_info_test.go rename to engine/access/rest/routes/node_version_info_test.go index 25213102934..25f19ae1f3c 100644 --- a/engine/access/rest/tests/node_version_info_test.go +++ b/engine/access/rest/routes/node_version_info_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "fmt" diff --git a/engine/access/rest/router.go b/engine/access/rest/routes/router.go similarity index 74% rename from engine/access/rest/router.go rename to engine/access/rest/routes/router.go index ef1f9665a91..b9b0b650183 100644 --- a/engine/access/rest/router.go +++ b/engine/access/rest/routes/router.go @@ -1,4 +1,4 @@ -package rest +package routes import ( "net/http" @@ -6,15 +6,14 @@ import ( "github.com/gorilla/mux" "github.com/rs/zerolog" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/middleware" "github.com/onflow/flow-go/engine/access/rest/models" - "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) -func NewRouter(backend api.RestBackendApi, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { +func NewRouter(backend access.API, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*mux.Router, error) { router := mux.NewRouter().StrictSlash(true) v1SubRouter := router.PathPrefix("/v1").Subrouter() @@ -48,70 +47,70 @@ var Routes = []route{{ Method: http.MethodGet, Pattern: "/transactions/{id}", Name: "getTransactionByID", - Handler: routes.GetTransactionByID, + Handler: GetTransactionByID, }, { Method: http.MethodPost, Pattern: "/transactions", Name: "createTransaction", - Handler: routes.CreateTransaction, + Handler: CreateTransaction, }, { Method: http.MethodGet, Pattern: "/transaction_results/{id}", Name: "getTransactionResultByID", - Handler: routes.GetTransactionResultByID, + Handler: GetTransactionResultByID, }, { Method: http.MethodGet, Pattern: "/blocks/{id}", Name: "getBlocksByIDs", - Handler: routes.GetBlocksByIDs, + Handler: GetBlocksByIDs, }, { Method: http.MethodGet, Pattern: "/blocks", Name: "getBlocksByHeight", - Handler: routes.GetBlocksByHeight, + Handler: GetBlocksByHeight, }, { Method: http.MethodGet, Pattern: "/blocks/{id}/payload", Name: "getBlockPayloadByID", - Handler: routes.GetBlockPayloadByID, + Handler: GetBlockPayloadByID, }, { Method: http.MethodGet, Pattern: "/execution_results/{id}", Name: "getExecutionResultByID", - Handler: routes.GetExecutionResultByID, + Handler: GetExecutionResultByID, }, { Method: http.MethodGet, Pattern: "/execution_results", Name: "getExecutionResultByBlockID", - Handler: routes.GetExecutionResultsByBlockIDs, + Handler: GetExecutionResultsByBlockIDs, }, { Method: http.MethodGet, Pattern: "/collections/{id}", Name: "getCollectionByID", - Handler: routes.GetCollectionByID, + Handler: GetCollectionByID, }, { Method: http.MethodPost, Pattern: "/scripts", Name: "executeScript", - Handler: routes.ExecuteScript, + Handler: ExecuteScript, }, { Method: http.MethodGet, Pattern: "/accounts/{address}", Name: "getAccount", - Handler: routes.GetAccount, + Handler: GetAccount, }, { Method: http.MethodGet, Pattern: "/events", Name: "getEvents", - Handler: routes.GetEvents, + Handler: GetEvents, }, { Method: http.MethodGet, Pattern: "/network/parameters", Name: "getNetworkParameters", - Handler: routes.GetNetworkParameters, + Handler: GetNetworkParameters, }, { Method: http.MethodGet, Pattern: "/node_version_info", Name: "getNodeVersionInfo", - Handler: routes.GetNodeVersionInfo, + Handler: GetNodeVersionInfo, }} diff --git a/engine/access/rest/routes/scripts.go b/engine/access/rest/routes/scripts.go index e991dd3a89e..8627470ab88 100644 --- a/engine/access/rest/routes/scripts.go +++ b/engine/access/rest/routes/scripts.go @@ -1,14 +1,14 @@ package routes import ( - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" "github.com/onflow/flow-go/model/flow" ) // ExecuteScript handler sends the script from the request to be executed. -func ExecuteScript(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func ExecuteScript(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) { req, err := r.GetScriptRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/scripts_test.go b/engine/access/rest/routes/scripts_test.go similarity index 99% rename from engine/access/rest/tests/scripts_test.go rename to engine/access/rest/routes/scripts_test.go index da498e59dc2..a3f2a64663c 100644 --- a/engine/access/rest/tests/scripts_test.go +++ b/engine/access/rest/routes/scripts_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "bytes" diff --git a/engine/access/rest/tests/test_helpers.go b/engine/access/rest/routes/test_helpers.go similarity index 74% rename from engine/access/rest/tests/test_helpers.go rename to engine/access/rest/routes/test_helpers.go index 388b5ca999d..e512cc94434 100644 --- a/engine/access/rest/tests/test_helpers.go +++ b/engine/access/rest/routes/test_helpers.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "bytes" @@ -11,8 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/engine/access/rest" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/metrics" ) @@ -27,11 +26,11 @@ const ( heightQueryParam = "height" ) -func executeRequest(req *http.Request, backend api.RestBackendApi) (*httptest.ResponseRecorder, error) { +func executeRequest(req *http.Request, backend access.API) (*httptest.ResponseRecorder, error) { var b bytes.Buffer logger := zerolog.New(&b) - router, err := rest.NewRouter(backend, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) + router, err := NewRouter(backend, logger, flow.Testnet.Chain(), metrics.NewNoopCollector()) if err != nil { return nil, err } @@ -41,11 +40,11 @@ func executeRequest(req *http.Request, backend api.RestBackendApi) (*httptest.Re return rr, nil } -func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend api.RestBackendApi) { +func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend access.API) { assertResponse(t, req, http.StatusOK, expectedRespBody, backend) } -func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend api.RestBackendApi) { +func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend access.API) { rr, err := executeRequest(req, backend) assert.NoError(t, err) actualResponseBody := rr.Body.String() diff --git a/engine/access/rest/routes/transactions.go b/engine/access/rest/routes/transactions.go index 763e9098299..b77aead82b4 100644 --- a/engine/access/rest/routes/transactions.go +++ b/engine/access/rest/routes/transactions.go @@ -2,13 +2,12 @@ package routes import ( "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/access/rest/request" ) // GetTransactionByID gets a transaction by requested ID. -func GetTransactionByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -34,7 +33,7 @@ func GetTransactionByID(r *request.Request, backend api.RestBackendApi, link mod } // GetTransactionResultByID retrieves transaction result by the transaction ID. -func GetTransactionResultByID(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func GetTransactionResultByID(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.GetTransactionResultRequest() if err != nil { return nil, models.NewBadRequestError(err) @@ -51,7 +50,7 @@ func GetTransactionResultByID(r *request.Request, backend api.RestBackendApi, li } // CreateTransaction creates a new transaction from provided payload. -func CreateTransaction(r *request.Request, backend api.RestBackendApi, link models.LinkGenerator) (interface{}, error) { +func CreateTransaction(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) { req, err := r.CreateTransactionRequest() if err != nil { return nil, models.NewBadRequestError(err) diff --git a/engine/access/rest/tests/transactions_test.go b/engine/access/rest/routes/transactions_test.go similarity index 99% rename from engine/access/rest/tests/transactions_test.go rename to engine/access/rest/routes/transactions_test.go index 3d81a0b5eff..6adde49b245 100644 --- a/engine/access/rest/tests/transactions_test.go +++ b/engine/access/rest/routes/transactions_test.go @@ -1,4 +1,4 @@ -package tests +package routes import ( "bytes" diff --git a/engine/access/rest/server.go b/engine/access/rest/server.go index d07983abb64..4a4b1be6f0e 100644 --- a/engine/access/rest/server.go +++ b/engine/access/rest/server.go @@ -7,14 +7,15 @@ import ( "github.com/rs/cors" "github.com/rs/zerolog" - "github.com/onflow/flow-go/engine/access/rest/api" + "github.com/onflow/flow-go/access" + "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" ) // NewServer returns an HTTP server initialized with the REST API handler -func NewServer(serverAPI api.RestBackendApi, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { - router, err := NewRouter(serverAPI, logger, chain, restCollector) +func NewServer(serverAPI access.API, listenAddress string, logger zerolog.Logger, chain flow.Chain, restCollector module.RestMetrics) (*http.Server, error) { + router, err := routes.NewRouter(serverAPI, logger, chain, restCollector) if err != nil { return nil, err } diff --git a/engine/access/rest_api_test.go b/engine/access/rest_api_test.go index 61c4f75de0b..1c09b77bac4 100644 --- a/engine/access/rest_api_test.go +++ b/engine/access/rest_api_test.go @@ -19,9 +19,8 @@ import ( "github.com/stretchr/testify/suite" accessmock "github.com/onflow/flow-go/engine/access/mock" - "github.com/onflow/flow-go/engine/access/rest" "github.com/onflow/flow-go/engine/access/rest/request" - "github.com/onflow/flow-go/engine/access/rest/tests" + "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/model/flow" @@ -152,6 +151,7 @@ func (suite *RestAPITestSuite) SetupTest() { nil, suite.me, backend, + backend, ) assert.NoError(suite.T(), err) suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() @@ -369,7 +369,7 @@ func (suite *RestAPITestSuite) TestRequestSizeRestriction() { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() // make a request of size larger than the max permitted size - requestBytes := make([]byte, rest.MaxRequestSize+1) + requestBytes := make([]byte, routes.MaxRequestSize+1) script := restclient.ScriptsBody{ Script: string(requestBytes), } @@ -396,13 +396,13 @@ func assertError(t *testing.T, resp *http.Response, err error, expectedCode int, func optionsForBlockByID() *restclient.BlocksApiBlocksIdGetOpts { return &restclient.BlocksApiBlocksIdGetOpts{ - Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{routes.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id"}), } } func optionsForBlockByStartEndHeight(startHeight, endHeight uint64) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{routes.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), StartHeight: optional.NewInterface(startHeight), EndHeight: optional.NewInterface(endHeight), @@ -411,7 +411,7 @@ func optionsForBlockByStartEndHeight(startHeight, endHeight uint64) *restclient. func optionsForBlockByHeights(heights []uint64) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{routes.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), Height: optional.NewInterface(heights), } @@ -419,7 +419,7 @@ func optionsForBlockByHeights(heights []uint64) *restclient.BlocksApiBlocksGetOp func optionsForFinalizedBlock(finalOrSealed string) *restclient.BlocksApiBlocksGetOpts { return &restclient.BlocksApiBlocksGetOpts{ - Expand: optional.NewInterface([]string{tests.ExpandableFieldPayload}), + Expand: optional.NewInterface([]string{routes.ExpandableFieldPayload}), Select_: optional.NewInterface([]string{"header.id", "header.height"}), Height: optional.NewInterface(finalOrSealed), } diff --git a/engine/access/rpc/engine.go b/engine/access/rpc/engine.go index 17f26cf0435..8577a1ba676 100644 --- a/engine/access/rpc/engine.go +++ b/engine/access/rpc/engine.go @@ -13,9 +13,9 @@ import ( "github.com/rs/zerolog" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/consensus/hotstuff/model" "github.com/onflow/flow-go/engine/access/rest" - "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/rpc" "github.com/onflow/flow-go/model/flow" @@ -69,7 +69,7 @@ type Engine struct { secureGrpcAddress net.Addr restAPIAddress net.Addr - restHandler api.RestBackendApi + restHandler access.API } type Option func(*RPCEngineBuilder) @@ -84,7 +84,7 @@ func NewBuilder(log zerolog.Logger, apiBurstLimits map[string]int, // the api burst limit (max calls at the same time) for each of the Access API e.g. Ping->50, GetTransaction->10 me module.Local, backend *backend.Backend, - restHandler api.RestBackendApi, + restHandler access.API, ) (*RPCEngineBuilder, error) { log = log.With().Str("engine", "rpc").Logger() diff --git a/engine/access/rpc/engine_builder.go b/engine/access/rpc/engine_builder.go index 41c8bbdc192..f63d30289b9 100644 --- a/engine/access/rpc/engine_builder.go +++ b/engine/access/rpc/engine_builder.go @@ -8,7 +8,6 @@ import ( "github.com/onflow/flow-go/access" legacyaccess "github.com/onflow/flow-go/access/legacy" "github.com/onflow/flow-go/consensus/hotstuff" - restapi "github.com/onflow/flow-go/engine/access/rest/api" "github.com/onflow/flow-go/module" accessproto "github.com/onflow/flow/protobuf/go/flow/access" @@ -39,10 +38,6 @@ func (builder *RPCEngineBuilder) RpcHandler() accessproto.AccessAPIServer { return builder.rpcHandler } -func (builder *RPCEngineBuilder) RestHandler() restapi.RestBackendApi { - return builder.restHandler -} - // WithBlockSignerDecoder specifies that signer indices in block headers should be translated // to full node IDs with the given decoder. // Caution: diff --git a/engine/access/rpc/rate_limit_test.go b/engine/access/rpc/rate_limit_test.go index 87551c96d5d..ea25c9370db 100644 --- a/engine/access/rpc/rate_limit_test.go +++ b/engine/access/rpc/rate_limit_test.go @@ -150,7 +150,9 @@ func (suite *RateLimitTestSuite) SetupTest() { apiRateLimt, apiBurstLimt, suite.me, + backend, backend) + require.NoError(suite.T(), err) suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() require.NoError(suite.T(), err) diff --git a/engine/access/secure_grpcr_test.go b/engine/access/secure_grpcr_test.go index a5975a7e92c..d8ed0169ce2 100644 --- a/engine/access/secure_grpcr_test.go +++ b/engine/access/secure_grpcr_test.go @@ -141,6 +141,7 @@ func (suite *SecureGRPCTestSuite) SetupTest() { nil, suite.me, backend, + backend, ) assert.NoError(suite.T(), err) suite.rpcEng, err = rpcEngBuilder.WithLegacy().Build() diff --git a/engine/common/rpc/convert/convert.go b/engine/common/rpc/convert/convert.go index cfcae8ade60..ed1c1c10b63 100644 --- a/engine/common/rpc/convert/convert.go +++ b/engine/common/rpc/convert/convert.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" accessproto "github.com/onflow/flow/protobuf/go/flow/access" From f9a15e89deb2bdbef5dba72cf5dd2b39a640477a Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 30 Jun 2023 19:13:51 +0300 Subject: [PATCH 20/30] Updated logs, added CLI flag and added access metrics initialization --- cmd/observer/node_builder/observer_builder.go | 3 ++- .../rest/apiproxy/rest_proxy_handler.go | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 4070c277749..53671c3098d 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -452,6 +452,7 @@ func (builder *ObserverServiceBuilder) extraFlags() { flags.StringVarP(&builder.rpcConf.HTTPListenAddr, "http-addr", "h", defaultConfig.rpcConf.HTTPListenAddr, "the address the http proxy server listens on") flags.StringVar(&builder.rpcConf.RESTListenAddr, "rest-addr", defaultConfig.rpcConf.RESTListenAddr, "the address the REST server listens on (if empty the REST server will not be started)") flags.UintVar(&builder.rpcConf.MaxMsgSize, "rpc-max-message-size", defaultConfig.rpcConf.MaxMsgSize, "the maximum message size in bytes for messages sent or received over grpc") + flags.UintVar(&builder.rpcConf.BackendConfig.ConnectionPoolSize, "connection-pool-size", defaultConfig.rpcConf.BackendConfig.ConnectionPoolSize, "maximum number of connections allowed in the connection pool, size of 0 disables the connection pooling, and anything less than the default size will be overridden to use the default size") flags.UintVar(&builder.rpcConf.BackendConfig.MaxHeightRange, "rpc-max-height-range", defaultConfig.rpcConf.BackendConfig.MaxHeightRange, "maximum size for height range requests") flags.StringToIntVar(&builder.apiRatelimits, "api-rate-limits", defaultConfig.apiRatelimits, "per second rate limits for Access API methods e.g. Ping=300,GetTransaction=500 etc.") flags.StringToIntVar(&builder.apiBurstlimits, "api-burst-limits", defaultConfig.apiBurstlimits, "burst limits for Access API methods e.g. Ping=100,GetTransaction=100 etc.") @@ -851,7 +852,7 @@ func (builder *ObserverServiceBuilder) enqueueConnectWithStakedAN() { func (builder *ObserverServiceBuilder) enqueueRPCServer() { builder.Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { - accessMetrics := metrics.NewNoopCollector() + accessMetrics := metrics.NewAccessCollector() config := builder.rpcConf backendConfig := config.BackendConfig diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index 18e7cfe3d9e..9fb8cc64ada 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -90,6 +90,7 @@ func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identi collectionResponse, err := upstream.GetCollectionByID(ctx, getCollectionByIDRequest) if err != nil { + r.log("upstream", "GetCollectionByID", err) return nil, err } @@ -98,7 +99,6 @@ func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identi return nil, err } - r.log("upstream", "GetCollectionByID", err) return transactions, nil } @@ -115,8 +115,8 @@ func (r *RestProxyHandler) SendTransaction(ctx context.Context, tx *flow.Transac } _, err = upstream.SendTransaction(ctx, sendTransactionRequest) - r.log("upstream", "SendTransaction", err) + return err } @@ -132,6 +132,7 @@ func (r *RestProxyHandler) GetTransaction(ctx context.Context, id flow.Identifie } transactionResponse, err := upstream.GetTransaction(ctx, getTransactionRequest) if err != nil { + r.log("upstream", "GetTransaction", err) return nil, err } @@ -140,7 +141,6 @@ func (r *RestProxyHandler) GetTransaction(ctx context.Context, id flow.Identifie return nil, err } - r.log("upstream", "GetTransaction", err) return &transactionBody, nil } @@ -148,6 +148,7 @@ func (r *RestProxyHandler) GetTransaction(ctx context.Context, id flow.Identifie func (r *RestProxyHandler) GetTransactionResult(ctx context.Context, id flow.Identifier, blockID flow.Identifier, collectionID flow.Identifier) (*access.TransactionResult, error) { upstream, err := r.FaultTolerantClient() if err != nil { + return nil, err } @@ -159,10 +160,10 @@ func (r *RestProxyHandler) GetTransactionResult(ctx context.Context, id flow.Ide transactionResultResponse, err := upstream.GetTransactionResult(ctx, getTransactionResultRequest) if err != nil { + r.log("upstream", "GetTransactionResult", err) return nil, err } - r.log("upstream", "GetTransactionResult", err) return access.MessageToTransactionResult(transactionResultResponse), nil } @@ -180,10 +181,10 @@ func (r *RestProxyHandler) GetAccountAtBlockHeight(ctx context.Context, address accountResponse, err := upstream.GetAccountAtBlockHeight(ctx, getAccountAtBlockHeightRequest) if err != nil { + r.log("upstream", "GetAccountAtBlockHeight", err) return nil, models.NewNotFoundError("not found account at block height", err) } - r.log("upstream", "GetAccountAtBlockHeight", err) return convert.MessageToAccount(accountResponse.Account) } @@ -200,10 +201,10 @@ func (r *RestProxyHandler) ExecuteScriptAtLatestBlock(ctx context.Context, scrip } executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(ctx, executeScriptAtLatestBlockRequest) if err != nil { + r.log("upstream", "ExecuteScriptAtLatestBlock", err) return nil, err } - r.log("upstream", "ExecuteScriptAtLatestBlock", err) return executeScriptAtLatestBlockResponse.Value, nil } @@ -221,10 +222,10 @@ func (r *RestProxyHandler) ExecuteScriptAtBlockHeight(ctx context.Context, block } executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(ctx, executeScriptAtBlockHeightRequest) if err != nil { + r.log("upstream", "ExecuteScriptAtBlockHeight", err) return nil, err } - r.log("upstream", "ExecuteScriptAtBlockHeight", err) return executeScriptAtBlockHeightResponse.Value, nil } @@ -242,10 +243,10 @@ func (r *RestProxyHandler) ExecuteScriptAtBlockID(ctx context.Context, blockID f } executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(ctx, executeScriptAtBlockIDRequest) if err != nil { + r.log("upstream", "ExecuteScriptAtBlockID", err) return nil, err } - r.log("upstream", "ExecuteScriptAtBlockID", err) return executeScriptAtBlockIDResponse.Value, nil } @@ -263,10 +264,10 @@ func (r *RestProxyHandler) GetEventsForHeightRange(ctx context.Context, eventTyp } eventsResponse, err := upstream.GetEventsForHeightRange(ctx, getEventsForHeightRangeRequest) if err != nil { + r.log("upstream", "GetEventsForHeightRange", err) return nil, err } - r.log("upstream", "GetEventsForHeightRange", err) return convert.MessagesToBlockEvents(eventsResponse.Results), nil } @@ -285,10 +286,10 @@ func (r *RestProxyHandler) GetEventsForBlockIDs(ctx context.Context, eventType s } eventsResponse, err := upstream.GetEventsForBlockIDs(ctx, getEventsForBlockIDsRequest) if err != nil { + r.log("upstream", "GetEventsForBlockIDs", err) return nil, err } - r.log("upstream", "GetEventsForBlockIDs", err) return convert.MessagesToBlockEvents(eventsResponse.Results), nil } @@ -304,10 +305,10 @@ func (r *RestProxyHandler) GetExecutionResultForBlockID(ctx context.Context, blo } executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(ctx, getExecutionResultForBlockID) if err != nil { + r.log("upstream", "GetExecutionResultForBlockID", err) return nil, err } - r.log("upsteram", "GetExecutionResultForBlockID", err) return convert.MessageToExecutionResult(executionResultForBlockIDResponse.ExecutionResult) } @@ -324,9 +325,9 @@ func (r *RestProxyHandler) GetExecutionResultByID(ctx context.Context, id flow.I executionResultByIDResponse, err := upstream.GetExecutionResultByID(ctx, executionResultByIDRequest) if err != nil { + r.log("upstream", "GetExecutionResultByID", err) return nil, err } - r.log("upstream", "GetExecutionResultByID", err) return convert.MessageToExecutionResult(executionResultByIDResponse.ExecutionResult) } From aee37a9b32425b8d0ba6ca0d31942188a57eae4c Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 30 Jun 2023 19:25:09 +0300 Subject: [PATCH 21/30] Updated README file, updated comment --- engine/access/rest/README.md | 14 ++++++-------- engine/access/rest/routes/blocks.go | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/engine/access/rest/README.md b/engine/access/rest/README.md index 633acf65707..68378fa19b1 100644 --- a/engine/access/rest/README.md +++ b/engine/access/rest/README.md @@ -10,11 +10,9 @@ available on our [docs site](https://docs.onflow.org/http-api/). - `middleware`: The common [middlewares](https://github.com/gorilla/mux#middleware) that all request pass through. - `models`: The generated models using openapi generators and implementation of model builders. - `request`: Implementation of API requests that provide validation for input data and build request models. -- `routes`: The common HTTP handlers for all the requests. -- `api`: The server API interface for REST service. +- `routes`: The common HTTP handlers for all the requests, tests for each request. - `apiproxy`: Implementation of proxy backend handler which includes the local backend and forwards the methods which can't be handled locally to an upstream using gRPC API. -- `tests`: Test for each request. ## Request lifecycle @@ -48,12 +46,12 @@ package that complies with function interfaced defined as: ```go type ApiHandlerFunc func ( r *request.Request, -backend api.RestBackendApi, +backend access.API, generator models.LinkGenerator, ) (interface{}, error) ``` -That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Then needs -to be added new request to `RestBackendApi` interface and overrides the method if it should be proxied to the backend -handler `RestProxyHandler` for request forwarding. Adding a new API endpoint also requires for a new request builder to -be implemented and added in request package. Make sure to not forget about adding tests for each of the API handler. +That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Also needs +to override the method if it should be proxied to the backend handler `RestProxyHandler` for request forwarding. Adding +a new API endpoint also requires for a new request builder to be implemented and added in request package. Make sure to +not forget about adding tests for each of the API handler. diff --git a/engine/access/rest/routes/blocks.go b/engine/access/rest/routes/blocks.go index cd8547bca93..c26f14dd8bf 100644 --- a/engine/access/rest/routes/blocks.go +++ b/engine/access/rest/routes/blocks.go @@ -146,7 +146,7 @@ func getBlock(option blockProviderOption, req *request.Request, backend access.A return &block, nil } -// blockProvider is a layer of abstraction on top of the backend api.RestBackendApi and provides a uniform way to +// blockProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to // look up a block or a block header either by ID or by height type blockProvider struct { id *flow.Identifier From 92674a70750f1b5801eff2064c31a790e024da19 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Mon, 10 Jul 2023 12:48:29 +0300 Subject: [PATCH 22/30] Updated README file, updated logging accoring to comments --- engine/access/rest/README.md | 11 ++++--- .../rest/apiproxy/rest_proxy_handler.go | 33 ++++++++++++------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/engine/access/rest/README.md b/engine/access/rest/README.md index 68378fa19b1..d94af68c238 100644 --- a/engine/access/rest/README.md +++ b/engine/access/rest/README.md @@ -12,7 +12,8 @@ available on our [docs site](https://docs.onflow.org/http-api/). - `request`: Implementation of API requests that provide validation for input data and build request models. - `routes`: The common HTTP handlers for all the requests, tests for each request. - `apiproxy`: Implementation of proxy backend handler which includes the local backend and forwards the methods which -can't be handled locally to an upstream using gRPC API. +can't be handled locally to an upstream using gRPC API. This is used by observers that don't have all data in their +local db. ## Request lifecycle @@ -51,7 +52,7 @@ generator models.LinkGenerator, ) (interface{}, error) ``` -That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. Also needs -to override the method if it should be proxied to the backend handler `RestProxyHandler` for request forwarding. Adding -a new API endpoint also requires for a new request builder to be implemented and added in request package. Make sure to -not forget about adding tests for each of the API handler. +That handler implementation needs to be added to the `router.go` with corresponding API endpoint and method. If the data +is not available on observers, an override the method is needed in the backend handler `RestProxyHandler` for request +forwarding. Adding a new API endpoint also requires for a new request builder to be implemented and added in request +package. Make sure to not forget about adding tests for each of the API handler. diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index 9fb8cc64ada..9dd1ea58e82 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -89,8 +89,9 @@ func (r *RestProxyHandler) GetCollectionByID(ctx context.Context, id flow.Identi } collectionResponse, err := upstream.GetCollectionByID(ctx, getCollectionByIDRequest) + r.log("upstream", "GetCollectionByID", err) + if err != nil { - r.log("upstream", "GetCollectionByID", err) return nil, err } @@ -131,8 +132,9 @@ func (r *RestProxyHandler) GetTransaction(ctx context.Context, id flow.Identifie Id: id[:], } transactionResponse, err := upstream.GetTransaction(ctx, getTransactionRequest) + r.log("upstream", "GetTransaction", err) + if err != nil { - r.log("upstream", "GetTransaction", err) return nil, err } @@ -159,8 +161,9 @@ func (r *RestProxyHandler) GetTransactionResult(ctx context.Context, id flow.Ide } transactionResultResponse, err := upstream.GetTransactionResult(ctx, getTransactionResultRequest) + r.log("upstream", "GetTransactionResult", err) + if err != nil { - r.log("upstream", "GetTransactionResult", err) return nil, err } @@ -180,8 +183,9 @@ func (r *RestProxyHandler) GetAccountAtBlockHeight(ctx context.Context, address } accountResponse, err := upstream.GetAccountAtBlockHeight(ctx, getAccountAtBlockHeightRequest) + r.log("upstream", "GetAccountAtBlockHeight", err) + if err != nil { - r.log("upstream", "GetAccountAtBlockHeight", err) return nil, models.NewNotFoundError("not found account at block height", err) } @@ -200,8 +204,9 @@ func (r *RestProxyHandler) ExecuteScriptAtLatestBlock(ctx context.Context, scrip Arguments: arguments, } executeScriptAtLatestBlockResponse, err := upstream.ExecuteScriptAtLatestBlock(ctx, executeScriptAtLatestBlockRequest) + r.log("upstream", "ExecuteScriptAtLatestBlock", err) + if err != nil { - r.log("upstream", "ExecuteScriptAtLatestBlock", err) return nil, err } @@ -221,8 +226,9 @@ func (r *RestProxyHandler) ExecuteScriptAtBlockHeight(ctx context.Context, block Arguments: arguments, } executeScriptAtBlockHeightResponse, err := upstream.ExecuteScriptAtBlockHeight(ctx, executeScriptAtBlockHeightRequest) + r.log("upstream", "ExecuteScriptAtBlockHeight", err) + if err != nil { - r.log("upstream", "ExecuteScriptAtBlockHeight", err) return nil, err } @@ -242,8 +248,9 @@ func (r *RestProxyHandler) ExecuteScriptAtBlockID(ctx context.Context, blockID f Arguments: arguments, } executeScriptAtBlockIDResponse, err := upstream.ExecuteScriptAtBlockID(ctx, executeScriptAtBlockIDRequest) + r.log("upstream", "ExecuteScriptAtBlockID", err) + if err != nil { - r.log("upstream", "ExecuteScriptAtBlockID", err) return nil, err } @@ -263,8 +270,9 @@ func (r *RestProxyHandler) GetEventsForHeightRange(ctx context.Context, eventTyp EndHeight: endHeight, } eventsResponse, err := upstream.GetEventsForHeightRange(ctx, getEventsForHeightRangeRequest) + r.log("upstream", "GetEventsForHeightRange", err) + if err != nil { - r.log("upstream", "GetEventsForHeightRange", err) return nil, err } @@ -285,8 +293,9 @@ func (r *RestProxyHandler) GetEventsForBlockIDs(ctx context.Context, eventType s BlockIds: blockIds, } eventsResponse, err := upstream.GetEventsForBlockIDs(ctx, getEventsForBlockIDsRequest) + r.log("upstream", "GetEventsForBlockIDs", err) + if err != nil { - r.log("upstream", "GetEventsForBlockIDs", err) return nil, err } @@ -304,8 +313,9 @@ func (r *RestProxyHandler) GetExecutionResultForBlockID(ctx context.Context, blo BlockId: blockID[:], } executionResultForBlockIDResponse, err := upstream.GetExecutionResultForBlockID(ctx, getExecutionResultForBlockID) + r.log("upstream", "GetExecutionResultForBlockID", err) + if err != nil { - r.log("upstream", "GetExecutionResultForBlockID", err) return nil, err } @@ -324,8 +334,9 @@ func (r *RestProxyHandler) GetExecutionResultByID(ctx context.Context, id flow.I } executionResultByIDResponse, err := upstream.GetExecutionResultByID(ctx, executionResultByIDRequest) + r.log("upstream", "GetExecutionResultByID", err) + if err != nil { - r.log("upstream", "GetExecutionResultByID", err) return nil, err } From 9256de44d33a6c2edcd048867e369e9e9fbe5d72 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Mon, 10 Jul 2023 15:20:00 +0300 Subject: [PATCH 23/30] Added tests for convert functions, moved 2 to right file --- access/handler.go | 6 ++--- engine/common/rpc/convert/blocks.go | 23 ------------------- engine/common/rpc/convert/collections_test.go | 20 +++++++++++++--- engine/common/rpc/convert/events.go | 21 +++++++++++++++++ engine/common/rpc/convert/events_test.go | 22 ++++++++++++++++++ 5 files changed, 63 insertions(+), 29 deletions(-) diff --git a/access/handler.go b/access/handler.go index 2417d4037ac..e5b4a18374c 100644 --- a/access/handler.go +++ b/access/handler.go @@ -516,7 +516,7 @@ func (h *Handler) GetEventsForHeightRange( return nil, err } - resultEvents, err := blockEventsToMessages(results) + resultEvents, err := BlockEventsToMessages(results) if err != nil { return nil, err } @@ -548,7 +548,7 @@ func (h *Handler) GetEventsForBlockIDs( return nil, err } - resultEvents, err := blockEventsToMessages(results) + resultEvents, err := BlockEventsToMessages(results) if err != nil { return nil, err } @@ -680,7 +680,7 @@ func executionResultToMessages(er *flow.ExecutionResult, metadata *entities.Meta }, nil } -func blockEventsToMessages(blocks []flow.BlockEvents) ([]*access.EventsResponse_Result, error) { +func BlockEventsToMessages(blocks []flow.BlockEvents) ([]*access.EventsResponse_Result, error) { results := make([]*access.EventsResponse_Result, len(blocks)) for i, block := range blocks { diff --git a/engine/common/rpc/convert/blocks.go b/engine/common/rpc/convert/blocks.go index a10cd6a7854..2e7f5689515 100644 --- a/engine/common/rpc/convert/blocks.go +++ b/engine/common/rpc/convert/blocks.go @@ -7,7 +7,6 @@ import ( "github.com/onflow/flow-go/model/flow" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" ) @@ -154,25 +153,3 @@ func PayloadFromMessage(m *entities.Block) (*flow.Payload, error) { Results: results, }, nil } - -// MessagesToBlockEvents converts a protobuf EventsResponse_Result messages to a flow.BlockEvents. -func MessagesToBlockEvents(blocksEvents []*accessproto.EventsResponse_Result) []flow.BlockEvents { - evs := make([]flow.BlockEvents, len(blocksEvents)) - for _, ev := range blocksEvents { - var blockEvent flow.BlockEvents - MessageToBlockEvents(ev) - evs = append(evs, blockEvent) - } - - return evs -} - -// MessageToBlockEvents converts a protobuf EventsResponse_Result message to a slice of flow.BlockEvents. -func MessageToBlockEvents(blockEvents *accessproto.EventsResponse_Result) flow.BlockEvents { - return flow.BlockEvents{ - BlockHeight: blockEvents.BlockHeight, - BlockID: MessageToIdentifier(blockEvents.BlockId), - BlockTimestamp: blockEvents.BlockTimestamp.AsTime(), - Events: MessagesToEvents(blockEvents.Events), - } -} diff --git a/engine/common/rpc/convert/collections_test.go b/engine/common/rpc/convert/collections_test.go index 75ab6f25adc..2e14a6dc225 100644 --- a/engine/common/rpc/convert/collections_test.go +++ b/engine/common/rpc/convert/collections_test.go @@ -9,6 +9,8 @@ import ( "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" + + "github.com/onflow/flow/protobuf/go/flow/entities" ) // TestConvertCollection tests that converting a collection to a protobuf message results in the correct @@ -32,10 +34,12 @@ func TestConvertCollection(t *testing.T) { } }) - t.Run("convert light collection to message", func(t *testing.T) { - lightCollection := flow.LightCollection{Transactions: txIDs} + var msg *entities.Collection + lightCollection := flow.LightCollection{Transactions: txIDs} - msg, err := convert.LightCollectionToMessage(&lightCollection) + t.Run("convert light collection to message", func(t *testing.T) { + var err error + msg, err = convert.LightCollectionToMessage(&lightCollection) require.NoError(t, err) assert.Len(t, msg.TransactionIds, len(txIDs)) @@ -43,6 +47,16 @@ func TestConvertCollection(t *testing.T) { assert.Equal(t, txID[:], msg.TransactionIds[i]) } }) + + t.Run("convert message to light collection", func(t *testing.T) { + lightColl, err := convert.MessageToLightCollection(msg) + require.NoError(t, err) + + assert.Equal(t, len(txIDs), len(lightColl.Transactions)) + for _, txID := range lightColl.Transactions { + assert.Equal(t, txID[:], txID[:]) + } + }) } // TestConvertCollectionGuarantee tests that converting a collection guarantee to and from a protobuf diff --git a/engine/common/rpc/convert/events.go b/engine/common/rpc/convert/events.go index d3bd469cd48..e80631ae4a3 100644 --- a/engine/common/rpc/convert/events.go +++ b/engine/common/rpc/convert/events.go @@ -6,6 +6,7 @@ import ( "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" execproto "github.com/onflow/flow/protobuf/go/flow/execution" @@ -172,3 +173,23 @@ func CcfEventToJsonEvent(e flow.Event) (*flow.Event, error) { Payload: convertedPayload, }, nil } + +// MessagesToBlockEvents converts a protobuf EventsResponse_Result messages to a slice of flow.BlockEvents. +func MessagesToBlockEvents(blocksEvents []*accessproto.EventsResponse_Result) []flow.BlockEvents { + evs := make([]flow.BlockEvents, len(blocksEvents)) + for i, ev := range blocksEvents { + evs[i] = MessageToBlockEvents(ev) + } + + return evs +} + +// MessageToBlockEvents converts a protobuf EventsResponse_Result message to a flow.BlockEvents. +func MessageToBlockEvents(blockEvents *accessproto.EventsResponse_Result) flow.BlockEvents { + return flow.BlockEvents{ + BlockHeight: blockEvents.BlockHeight, + BlockID: MessageToIdentifier(blockEvents.BlockId), + BlockTimestamp: blockEvents.BlockTimestamp.AsTime(), + Events: MessagesToEvents(blockEvents.Events), + } +} diff --git a/engine/common/rpc/convert/events_test.go b/engine/common/rpc/convert/events_test.go index 2cf010fa011..6483dac72ee 100644 --- a/engine/common/rpc/convert/events_test.go +++ b/engine/common/rpc/convert/events_test.go @@ -11,6 +11,7 @@ import ( jsoncdc "github.com/onflow/cadence/encoding/json" execproto "github.com/onflow/flow/protobuf/go/flow/execution" + "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -193,3 +194,24 @@ func TestConvertServiceEventList(t *testing.T) { assert.Equal(t, serviceEvents, converted) } + +// TestConvertMessagesToBlockEvents tests that converting a protobuf EventsResponse_Result message to and from block events in the same +// block +func TestConvertMessagesToBlockEvents(t *testing.T) { + t.Parallel() + + count := 2 + blockEvents := make([]flow.BlockEvents, count) + for i := 0; i < count; i++ { + header := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(uint64(i))) + blockEvents[i] = unittest.BlockEventsFixture(header, 2) + } + + msg, err := access.BlockEventsToMessages(blockEvents) + require.NoError(t, err) + + converted := convert.MessagesToBlockEvents(msg) + require.NoError(t, err) + + assert.Equal(t, blockEvents, converted) +} From c467235817e73b05aef2974535424dd477c13873 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Tue, 11 Jul 2023 15:34:40 +0300 Subject: [PATCH 24/30] Updated unittest according to comment --- .../access/rest/routes/transactions_test.go | 36 +--------------- integration/tests/access/observer_test.go | 43 ++++++++++++++++--- utils/unittest/fixtures.go | 33 ++++++++++++++ 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/engine/access/rest/routes/transactions_test.go b/engine/access/rest/routes/transactions_test.go index 6adde49b245..a23e2053cb7 100644 --- a/engine/access/rest/routes/transactions_test.go +++ b/engine/access/rest/routes/transactions_test.go @@ -69,38 +69,6 @@ func createTransactionReq(body interface{}) *http.Request { return req } -func validCreateBody(tx flow.TransactionBody) map[string]interface{} { - tx.Arguments = [][]uint8{} // fix how fixture creates nil values - auth := make([]string, len(tx.Authorizers)) - for i, a := range tx.Authorizers { - auth[i] = a.String() - } - - return map[string]interface{}{ - "script": util.ToBase64(tx.Script), - "arguments": tx.Arguments, - "reference_block_id": tx.ReferenceBlockID.String(), - "gas_limit": fmt.Sprintf("%d", tx.GasLimit), - "payer": tx.Payer.String(), - "proposal_key": map[string]interface{}{ - "address": tx.ProposalKey.Address.String(), - "key_index": fmt.Sprintf("%d", tx.ProposalKey.KeyIndex), - "sequence_number": fmt.Sprintf("%d", tx.ProposalKey.SequenceNumber), - }, - "authorizers": auth, - "payload_signatures": []map[string]interface{}{{ - "address": tx.PayloadSignatures[0].Address.String(), - "key_index": fmt.Sprintf("%d", tx.PayloadSignatures[0].KeyIndex), - "signature": util.ToBase64(tx.PayloadSignatures[0].Signature), - }}, - "envelope_signatures": []map[string]interface{}{{ - "address": tx.EnvelopeSignatures[0].Address.String(), - "key_index": fmt.Sprintf("%d", tx.EnvelopeSignatures[0].KeyIndex), - "signature": util.ToBase64(tx.EnvelopeSignatures[0].Signature), - }}, - } -} - func TestGetTransactions(t *testing.T) { t.Run("get by ID without results", func(t *testing.T) { backend := &mock.API{} @@ -380,7 +348,7 @@ func TestCreateTransaction(t *testing.T) { tx := unittest.TransactionBodyFixture() tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()} tx.Arguments = [][]uint8{} - req := createTransactionReq(validCreateBody(tx)) + req := createTransactionReq(unittest.ValidCreateBody(tx)) backend.Mock. On("SendTransaction", mocks.Anything, &tx). @@ -447,7 +415,7 @@ func TestCreateTransaction(t *testing.T) { for _, test := range tests { tx := unittest.TransactionBodyFixture() tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()} - testTx := validCreateBody(tx) + testTx := unittest.ValidCreateBody(tx) testTx[test.inputField] = test.inputValue req := createTransactionReq(testTx) diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 164b131be38..57a1553fc3b 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -1,8 +1,11 @@ package access import ( + "bytes" "context" + "encoding/json" "fmt" + "io" "net/http" "strings" "testing" @@ -173,7 +176,7 @@ func (s *ObserverSuite) TestObserverRPC() { // TestObserverRest runs the following tests: // 1. CompareRPCs: verifies that the observer client returns the same errors as the access client for rests proxied to the upstream AN // 2. HandledByUpstream: stops the upstream AN and verifies that the observer client returns errors for all rests handled by the upstream -// 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all other queries +// 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all others queries func (s *ObserverSuite) TestObserverRest() { t := s.T() @@ -186,7 +189,14 @@ func (s *ObserverSuite) TestObserverRest() { case http.MethodGet: return httpClient.Get(url) case http.MethodPost: - return httpClient.Post(url, "application/json", strings.NewReader("{}")) + var body io.Reader + if strings.Contains(url, "/transactions") { + body = createTx(s.net) + } else { + body = strings.NewReader("{}") + } + + return httpClient.Post(url, "application/json", body) } panic("not supported") } @@ -210,6 +220,7 @@ func (s *ObserverSuite) TestObserverRest() { assert.NoError(t, accessErr) assert.NoError(t, observerErr) assert.Equal(t, accessResp.Status, observerResp.Status) + assert.Equal(t, accessResp.StatusCode, observerResp.StatusCode) }) } }) @@ -219,7 +230,7 @@ func (s *ObserverSuite) TestObserverRest() { require.NoError(t, err) t.Run("HandledByUpstream", func(t *testing.T) { - // verify that we receive StatusInternalServerError, StatusServiceUnavailable, StatusBadRequest errors from all rests handled upstream + // verify that we receive StatusInternalServerError, StatusServiceUnavailable errors from all rests handled upstream for _, endpoint := range s.getRestEndpoints() { if _, local := s.localRest[endpoint.name]; local { continue @@ -229,8 +240,7 @@ func (s *ObserverSuite) TestObserverRest() { require.NoError(t, observerErr) assert.Contains(t, [...]int{ http.StatusInternalServerError, - http.StatusServiceUnavailable, - http.StatusBadRequest}, observerResp.StatusCode) + http.StatusServiceUnavailable}, observerResp.StatusCode) }) } }) @@ -393,7 +403,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { block := unittest.BlockFixture() executionResult := unittest.ExecutionResultFixture() collection := unittest.CollectionFixture(2) - blockEvents := unittest.BlockEventsFixture(unittest.BlockHeaderFixture(unittest.WithHeaderHeight(uint64(2))), 2) + eventType := "A.0123456789abcdef.flow.event" return []RestEndpointTest{ { @@ -454,7 +464,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { { name: "getEvents", method: http.MethodGet, - path: fmt.Sprintf("/events?type=%s&start_height=%d&end_height=%d", blockEvents.Events[0].Type, 1, 3), + path: fmt.Sprintf("/events?type=%s&start_height=%d&end_height=%d", eventType, 1, 3), }, { name: "getNetworkParameters", @@ -468,3 +478,22 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { }, } } + +func createTx(net *testnet.FlowNetwork) *bytes.Buffer { + flowAddr := flow.Localnet.Chain().ServiceAddress() + signature := unittest.TransactionSignatureFixture() + signature.Address = flowAddr + + tx := flow.NewTransactionBody(). + AddAuthorizer(flowAddr). + SetPayer(flowAddr). + SetScript(unittest.NoopTxScript()). + SetReferenceBlockID(net.Root().ID()). + SetProposalKey(flowAddr, 1, 0) + tx.PayloadSignatures = []flow.TransactionSignature{signature} + tx.EnvelopeSignatures = []flow.TransactionSignature{signature} + + jsonBody, _ := json.Marshal(unittest.ValidCreateBody(*tx)) + + return bytes.NewBuffer(jsonBody) +} diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index f5d454d01b0..6746875b3b7 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -22,6 +22,7 @@ import ( "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/crypto/hash" "github.com/onflow/flow-go/engine" + "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/bitutils" @@ -2461,3 +2462,35 @@ func ChunkExecutionDataFixture(t *testing.T, minSize int, opts ...func(*executio size *= 2 } } + +func ValidCreateBody(tx flow.TransactionBody) map[string]interface{} { + tx.Arguments = [][]uint8{} // fix how fixture creates nil values + auth := make([]string, len(tx.Authorizers)) + for i, a := range tx.Authorizers { + auth[i] = a.String() + } + + return map[string]interface{}{ + "script": util.ToBase64(tx.Script), + "arguments": tx.Arguments, + "reference_block_id": tx.ReferenceBlockID.String(), + "gas_limit": fmt.Sprintf("%d", tx.GasLimit), + "payer": tx.Payer.String(), + "proposal_key": map[string]interface{}{ + "address": tx.ProposalKey.Address.String(), + "key_index": fmt.Sprintf("%d", tx.ProposalKey.KeyIndex), + "sequence_number": fmt.Sprintf("%d", tx.ProposalKey.SequenceNumber), + }, + "authorizers": auth, + "payload_signatures": []map[string]interface{}{{ + "address": tx.PayloadSignatures[0].Address.String(), + "key_index": fmt.Sprintf("%d", tx.PayloadSignatures[0].KeyIndex), + "signature": util.ToBase64(tx.PayloadSignatures[0].Signature), + }}, + "envelope_signatures": []map[string]interface{}{{ + "address": tx.EnvelopeSignatures[0].Address.String(), + "key_index": fmt.Sprintf("%d", tx.EnvelopeSignatures[0].KeyIndex), + "signature": util.ToBase64(tx.EnvelopeSignatures[0].Signature), + }}, + } +} From 145de0517a1eff4a1d32d87a70531d73e9f56059 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Tue, 11 Jul 2023 17:15:39 +0300 Subject: [PATCH 25/30] Updated rest integration test --- integration/tests/access/observer_test.go | 30 ++++++++++------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 57a1553fc3b..9806685217a 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -174,7 +174,7 @@ func (s *ObserverSuite) TestObserverRPC() { } // TestObserverRest runs the following tests: -// 1. CompareRPCs: verifies that the observer client returns the same errors as the access client for rests proxied to the upstream AN +// 1. CompareEndpoints: verifies that the observer client returns the same errors as the access client for rests proxied to the upstream AN // 2. HandledByUpstream: stops the upstream AN and verifies that the observer client returns errors for all rests handled by the upstream // 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all others queries func (s *ObserverSuite) TestObserverRest() { @@ -184,27 +184,20 @@ func (s *ObserverSuite) TestObserverRest() { observerAddr := s.net.ContainerByName("observer_1").Addr(testnet.RESTPort) httpClient := http.DefaultClient - makeHttpCall := func(method string, url string) (*http.Response, error) { + makeHttpCall := func(method string, url string, body io.Reader) (*http.Response, error) { switch method { case http.MethodGet: return httpClient.Get(url) case http.MethodPost: - var body io.Reader - if strings.Contains(url, "/transactions") { - body = createTx(s.net) - } else { - body = strings.NewReader("{}") - } - return httpClient.Post(url, "application/json", body) } panic("not supported") } - makeObserverCall := func(method string, path string) (*http.Response, error) { - return makeHttpCall(method, "http://"+observerAddr+"/v1"+path) + makeObserverCall := func(method string, path string, body io.Reader) (*http.Response, error) { + return makeHttpCall(method, "http://"+observerAddr+"/v1"+path, body) } - makeAccessCall := func(method string, path string) (*http.Response, error) { - return makeHttpCall(method, "http://"+accessAddr+"/v1"+path) + makeAccessCall := func(method string, path string, body io.Reader) (*http.Response, error) { + return makeHttpCall(method, "http://"+accessAddr+"/v1"+path, body) } t.Run("CompareEndpoints", func(t *testing.T) { @@ -215,8 +208,8 @@ func (s *ObserverSuite) TestObserverRest() { continue } t.Run(endpoint.name, func(t *testing.T) { - accessResp, accessErr := makeAccessCall(endpoint.method, endpoint.path) - observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + accessResp, accessErr := makeAccessCall(endpoint.method, endpoint.path, endpoint.body) + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path, endpoint.body) assert.NoError(t, accessErr) assert.NoError(t, observerErr) assert.Equal(t, accessResp.Status, observerResp.Status) @@ -236,7 +229,7 @@ func (s *ObserverSuite) TestObserverRest() { continue } t.Run(endpoint.name, func(t *testing.T) { - observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path, endpoint.body) require.NoError(t, observerErr) assert.Contains(t, [...]int{ http.StatusInternalServerError, @@ -252,7 +245,7 @@ func (s *ObserverSuite) TestObserverRest() { continue } t.Run(endpoint.name, func(t *testing.T) { - observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path) + observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path, endpoint.body) require.NoError(t, observerErr) assert.Contains(t, [...]int{http.StatusNotFound, http.StatusOK}, observerResp.StatusCode) }) @@ -395,6 +388,7 @@ type RestEndpointTest struct { name string method string path string + body io.Reader } func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { @@ -415,6 +409,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { name: "createTransaction", method: http.MethodPost, path: "/transactions", + body: createTx(s.net), }, { name: "getTransactionResultByID", @@ -455,6 +450,7 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { name: "executeScript", method: http.MethodPost, path: "/scripts", + body: strings.NewReader("{}"), }, { name: "getAccount", From 000581eab6c47e710a6b80405bb469cdafa66dfe Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Tue, 11 Jul 2023 18:33:24 +0300 Subject: [PATCH 26/30] Fixed typo, updated last commit --- integration/tests/access/observer_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 9806685217a..1cc222cc990 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -176,7 +176,7 @@ func (s *ObserverSuite) TestObserverRPC() { // TestObserverRest runs the following tests: // 1. CompareEndpoints: verifies that the observer client returns the same errors as the access client for rests proxied to the upstream AN // 2. HandledByUpstream: stops the upstream AN and verifies that the observer client returns errors for all rests handled by the upstream -// 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all others queries +// 3. HandledByObserver: stops the upstream AN and verifies that the observer client handles all other queries func (s *ObserverSuite) TestObserverRest() { t := s.T() @@ -189,7 +189,11 @@ func (s *ObserverSuite) TestObserverRest() { case http.MethodGet: return httpClient.Get(url) case http.MethodPost: - return httpClient.Post(url, "application/json", body) + if body == nil { + return httpClient.Post(url, "application/json", strings.NewReader("{}")) + } else { + return httpClient.Post(url, "application/json", body) + } } panic("not supported") } @@ -450,7 +454,6 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { name: "executeScript", method: http.MethodPost, path: "/scripts", - body: strings.NewReader("{}"), }, { name: "getAccount", From ea7748f94d81618ce9037f7ec324accd1ac1bfee Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 20 Jul 2023 16:13:18 +0300 Subject: [PATCH 27/30] Added part of updates according to comments --- access/handler.go | 38 +++---------------- .../rest/apiproxy/rest_proxy_handler.go | 3 +- engine/access/rest/routes/scripts_test.go | 3 -- .../access/rest/routes/transactions_test.go | 5 +-- engine/access/rpc/backend/backend.go | 3 +- engine/common/rpc/convert/events.go | 34 ++++++++++++++++- engine/common/rpc/convert/events_test.go | 3 +- integration/tests/access/observer_test.go | 2 +- utils/unittest/fixtures.go | 2 +- 9 files changed, 46 insertions(+), 47 deletions(-) diff --git a/access/handler.go b/access/handler.go index e5b4a18374c..11e47dd3521 100644 --- a/access/handler.go +++ b/access/handler.go @@ -3,17 +3,17 @@ package access import ( "context" - "github.com/onflow/flow/protobuf/go/flow/access" - "github.com/onflow/flow/protobuf/go/flow/entities" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/timestamppb" "github.com/onflow/flow-go/consensus/hotstuff" "github.com/onflow/flow-go/consensus/hotstuff/signature" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + + "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" ) type Handler struct { @@ -516,7 +516,7 @@ func (h *Handler) GetEventsForHeightRange( return nil, err } - resultEvents, err := BlockEventsToMessages(results) + resultEvents, err := convert.BlockEventsToMessages(results) if err != nil { return nil, err } @@ -548,7 +548,7 @@ func (h *Handler) GetEventsForBlockIDs( return nil, err } - resultEvents, err := BlockEventsToMessages(results) + resultEvents, err := convert.BlockEventsToMessages(results) if err != nil { return nil, err } @@ -680,34 +680,6 @@ func executionResultToMessages(er *flow.ExecutionResult, metadata *entities.Meta }, nil } -func BlockEventsToMessages(blocks []flow.BlockEvents) ([]*access.EventsResponse_Result, error) { - results := make([]*access.EventsResponse_Result, len(blocks)) - - for i, block := range blocks { - event, err := blockEventsToMessage(block) - if err != nil { - return nil, err - } - results[i] = event - } - - return results, nil -} - -func blockEventsToMessage(block flow.BlockEvents) (*access.EventsResponse_Result, error) { - eventMessages := make([]*entities.Event, len(block.Events)) - for i, event := range block.Events { - eventMessages[i] = convert.EventToMessage(event) - } - timestamp := timestamppb.New(block.BlockTimestamp) - return &access.EventsResponse_Result{ - BlockId: block.BlockID[:], - BlockHeight: block.BlockHeight, - BlockTimestamp: timestamp, - Events: eventMessages, - }, nil -} - // WithBlockSignerDecoder configures the Handler to decode signer indices // via the provided hotstuff.BlockSignerDecoder func WithBlockSignerDecoder(signerIndicesDecoder hotstuff.BlockSignerDecoder) func(*Handler) { diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index 8275348959d..b89dc46b771 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -2,6 +2,7 @@ package apiproxy import ( "context" + "fmt" "time" "google.golang.org/grpc/status" @@ -44,7 +45,7 @@ func NewRestProxyHandler( timeout, maxMsgSize) if err != nil { - return nil, err + return nil, fmt.Errorf("could not create REST forwarder: %w", err) } restProxyHandler := &RestProxyHandler{ diff --git a/engine/access/rest/routes/scripts_test.go b/engine/access/rest/routes/scripts_test.go index a3f2a64663c..8a6a63cc819 100644 --- a/engine/access/rest/routes/scripts_test.go +++ b/engine/access/rest/routes/scripts_test.go @@ -48,7 +48,6 @@ func TestScripts(t *testing.T) { t.Run("get by Latest height", func(t *testing.T) { backend := &mock.API{} - backend.Mock. On("ExecuteScriptAtLatestBlock", mocks.Anything, validCode, [][]byte{validArgs}). Return([]byte("hello world"), nil) @@ -92,7 +91,6 @@ func TestScripts(t *testing.T) { t.Run("get error", func(t *testing.T) { backend := &mock.API{} - backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, uint64(1337), validCode, [][]byte{validArgs}). Return(nil, status.Error(codes.Internal, "internal server error")) @@ -109,7 +107,6 @@ func TestScripts(t *testing.T) { t.Run("get invalid", func(t *testing.T) { backend := &mock.API{} - backend.Mock. On("ExecuteScriptAtBlockHeight", mocks.Anything, mocks.Anything, mocks.Anything, mocks.Anything). Return(nil, nil) diff --git a/engine/access/rest/routes/transactions_test.go b/engine/access/rest/routes/transactions_test.go index a23e2053cb7..3b02c4d5de5 100644 --- a/engine/access/rest/routes/transactions_test.go +++ b/engine/access/rest/routes/transactions_test.go @@ -72,7 +72,6 @@ func createTransactionReq(body interface{}) *http.Request { func TestGetTransactions(t *testing.T) { t.Run("get by ID without results", func(t *testing.T) { backend := &mock.API{} - tx := unittest.TransactionFixture() req := getTransactionReq(tx.ID().String(), false, "", "") @@ -348,7 +347,7 @@ func TestCreateTransaction(t *testing.T) { tx := unittest.TransactionBodyFixture() tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()} tx.Arguments = [][]uint8{} - req := createTransactionReq(unittest.ValidCreateBody(tx)) + req := createTransactionReq(unittest.CreateSendTxHttpPayload(tx)) backend.Mock. On("SendTransaction", mocks.Anything, &tx). @@ -415,7 +414,7 @@ func TestCreateTransaction(t *testing.T) { for _, test := range tests { tx := unittest.TransactionBodyFixture() tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()} - testTx := unittest.ValidCreateBody(tx) + testTx := unittest.CreateSendTxHttpPayload(tx) testTx[test.inputField] = test.inputValue req := createTransactionReq(testTx) diff --git a/engine/access/rpc/backend/backend.go b/engine/access/rpc/backend/backend.go index 590dff099a8..fbc98f5319a 100644 --- a/engine/access/rpc/backend/backend.go +++ b/engine/access/rpc/backend/backend.go @@ -213,7 +213,8 @@ func New( return b } -// NewCache constructs cache and its size. +// NewCache constructs cache for storing connections to other nodes. +// No errors are expected during normal operations. func NewCache( log zerolog.Logger, accessMetrics module.AccessMetrics, diff --git a/engine/common/rpc/convert/events.go b/engine/common/rpc/convert/events.go index e80631ae4a3..c25f405355b 100644 --- a/engine/common/rpc/convert/events.go +++ b/engine/common/rpc/convert/events.go @@ -4,13 +4,15 @@ import ( "encoding/json" "fmt" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/flow-go/model/flow" accessproto "github.com/onflow/flow/protobuf/go/flow/access" + "github.com/onflow/flow/protobuf/go/flow/entities" execproto "github.com/onflow/flow/protobuf/go/flow/execution" - - "github.com/onflow/flow-go/model/flow" ) // EventToMessage converts a flow.Event to a protobuf message @@ -193,3 +195,31 @@ func MessageToBlockEvents(blockEvents *accessproto.EventsResponse_Result) flow.B Events: MessagesToEvents(blockEvents.Events), } } + +func BlockEventsToMessages(blocks []flow.BlockEvents) ([]*accessproto.EventsResponse_Result, error) { + results := make([]*accessproto.EventsResponse_Result, len(blocks)) + + for i, block := range blocks { + event, err := BlockEventsToMessage(block) + if err != nil { + return nil, err + } + results[i] = event + } + + return results, nil +} + +func BlockEventsToMessage(block flow.BlockEvents) (*accessproto.EventsResponse_Result, error) { + eventMessages := make([]*entities.Event, len(block.Events)) + for i, event := range block.Events { + eventMessages[i] = EventToMessage(event) + } + timestamp := timestamppb.New(block.BlockTimestamp) + return &accessproto.EventsResponse_Result{ + BlockId: block.BlockID[:], + BlockHeight: block.BlockHeight, + BlockTimestamp: timestamp, + Events: eventMessages, + }, nil +} diff --git a/engine/common/rpc/convert/events_test.go b/engine/common/rpc/convert/events_test.go index 6483dac72ee..879db710f8b 100644 --- a/engine/common/rpc/convert/events_test.go +++ b/engine/common/rpc/convert/events_test.go @@ -11,7 +11,6 @@ import ( jsoncdc "github.com/onflow/cadence/encoding/json" execproto "github.com/onflow/flow/protobuf/go/flow/execution" - "github.com/onflow/flow-go/access" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -207,7 +206,7 @@ func TestConvertMessagesToBlockEvents(t *testing.T) { blockEvents[i] = unittest.BlockEventsFixture(header, 2) } - msg, err := access.BlockEventsToMessages(blockEvents) + msg, err := convert.BlockEventsToMessages(blockEvents) require.NoError(t, err) converted := convert.MessagesToBlockEvents(msg) diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 1cc222cc990..55733820d92 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -492,7 +492,7 @@ func createTx(net *testnet.FlowNetwork) *bytes.Buffer { tx.PayloadSignatures = []flow.TransactionSignature{signature} tx.EnvelopeSignatures = []flow.TransactionSignature{signature} - jsonBody, _ := json.Marshal(unittest.ValidCreateBody(*tx)) + jsonBody, _ := json.Marshal(unittest.CreateSendTxHttpPayload(*tx)) return bytes.NewBuffer(jsonBody) } diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index 1da31deb093..e2b30bcefad 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -2455,7 +2455,7 @@ func ChunkExecutionDataFixture(t *testing.T, minSize int, opts ...func(*executio } } -func ValidCreateBody(tx flow.TransactionBody) map[string]interface{} { +func CreateSendTxHttpPayload(tx flow.TransactionBody) map[string]interface{} { tx.Arguments = [][]uint8{} // fix how fixture creates nil values auth := make([]string, len(tx.Authorizers)) for i, a := range tx.Authorizers { From c1347be245eb6960db19ad46782441db61fb9fc3 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Thu, 20 Jul 2023 22:21:40 +0300 Subject: [PATCH 28/30] Linted --- engine/common/rpc/convert/events.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/common/rpc/convert/events.go b/engine/common/rpc/convert/events.go index c25f405355b..58ccb0ed9a1 100644 --- a/engine/common/rpc/convert/events.go +++ b/engine/common/rpc/convert/events.go @@ -8,9 +8,10 @@ import ( "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" + "github.com/onflow/flow-go/model/flow" - accessproto "github.com/onflow/flow/protobuf/go/flow/access" + accessproto "github.com/onflow/flow/protobuf/go/flow/access" "github.com/onflow/flow/protobuf/go/flow/entities" execproto "github.com/onflow/flow/protobuf/go/flow/execution" ) From 7faa9e73a655a3caafdecc379007b20ce9afa806 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 21 Jul 2023 00:52:56 +0300 Subject: [PATCH 29/30] Fixed last commit --- cmd/access/node_builder/access_node_builder.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index bb609cd5815..2a576945ac2 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -37,7 +37,7 @@ import ( "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/ingestion" pingeng "github.com/onflow/flow-go/engine/access/ping" - "github.com/onflow/flow-go/engine/access/rest" + "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/access/state_stream" @@ -969,7 +969,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) { return nil }). Module("rest metrics", func(node *cmd.NodeConfig) error { - m, err := metrics.NewRestCollector(rest.URLToRoute, node.MetricsRegisterer) + m, err := metrics.NewRestCollector(routes.URLToRoute, node.MetricsRegisterer) if err != nil { return err } From 26b435e151c76984cab675624f4717433f176a59 Mon Sep 17 00:00:00 2001 From: UlyanaAndrukhiv Date: Fri, 21 Jul 2023 14:42:35 +0300 Subject: [PATCH 30/30] Updated according to comments, added rest metrics to observer node --- cmd/observer/node_builder/observer_builder.go | 20 +++++- .../rest/apiproxy/rest_proxy_handler.go | 3 +- go.sum | 4 +- integration/tests/access/observer_test.go | 65 +++++++++++-------- 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index 8309f12dd28..b2d179aa942 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -29,6 +29,7 @@ import ( "github.com/onflow/flow-go/crypto" "github.com/onflow/flow-go/engine/access/apiproxy" restapiproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy" + "github.com/onflow/flow-go/engine/access/rest/routes" "github.com/onflow/flow-go/engine/access/rpc" "github.com/onflow/flow-go/engine/access/rpc/backend" "github.com/onflow/flow-go/engine/common/follower" @@ -166,6 +167,9 @@ type ObserverServiceBuilder struct { // Public network peerID peer.ID + + RestMetrics *metrics.RestCollector + AccessMetrics module.AccessMetrics } // deriveBootstrapPeerIdentities derives the Flow Identity of the bootstrap peers from the parameters. @@ -849,8 +853,22 @@ func (builder *ObserverServiceBuilder) enqueueConnectWithStakedAN() { } func (builder *ObserverServiceBuilder) enqueueRPCServer() { + builder.Module("rest metrics", func(node *cmd.NodeConfig) error { + m, err := metrics.NewRestCollector(routes.URLToRoute, node.MetricsRegisterer) + if err != nil { + return err + } + builder.RestMetrics = m + return nil + }) + builder.Module("access metrics", func(node *cmd.NodeConfig) error { + builder.AccessMetrics = metrics.NewAccessCollector( + metrics.WithRestMetrics(builder.RestMetrics), + ) + return nil + }) builder.Component("RPC engine", func(node *cmd.NodeConfig) (module.ReadyDoneAware, error) { - accessMetrics := metrics.NewAccessCollector() + accessMetrics := builder.AccessMetrics config := builder.rpcConf backendConfig := config.BackendConfig diff --git a/engine/access/rest/apiproxy/rest_proxy_handler.go b/engine/access/rest/apiproxy/rest_proxy_handler.go index b89dc46b771..01e7b56724d 100644 --- a/engine/access/rest/apiproxy/rest_proxy_handler.go +++ b/engine/access/rest/apiproxy/rest_proxy_handler.go @@ -10,7 +10,6 @@ import ( "github.com/rs/zerolog" "github.com/onflow/flow-go/access" - "github.com/onflow/flow-go/engine/access/rest/models" "github.com/onflow/flow-go/engine/common/grpc/forwarder" "github.com/onflow/flow-go/engine/common/rpc/convert" "github.com/onflow/flow-go/model/flow" @@ -187,7 +186,7 @@ func (r *RestProxyHandler) GetAccountAtBlockHeight(ctx context.Context, address r.log("upstream", "GetAccountAtBlockHeight", err) if err != nil { - return nil, models.NewNotFoundError("not found account at block height", err) + return nil, err } return convert.MessageToAccount(accountResponse.Account) diff --git a/go.sum b/go.sum index 14df972d74f..97ca856b74d 100644 --- a/go.sum +++ b/go.sum @@ -1254,13 +1254,13 @@ github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJ github.com/onflow/flow-go-sdk v0.41.9 h1:cyplhhhc0RnfOAan2t7I/7C9g1hVGDDLUhWj6ZHAkk4= github.com/onflow/flow-go-sdk v0.41.9/go.mod h1:e9Q5TITCy7g08lkdQJxP8fAKBnBoC5FjALvUKr36j4I= github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/flow-go/crypto v0.24.9 h1:0EQp+kSZYJepMIiSypfJVe7tzsPcb6UXOdOtsTCDhBs= github.com/onflow/flow-go/crypto v0.24.9/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d h1:QcOAeEyF3iAUHv21LQ12sdcsr0yFrJGoGLyCAzYYtvI= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d/go.mod h1:GCPpiyRoHncdqPj++zPr9ZOYBX4hpJ0pYZRYqSE8VKk= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/integration/tests/access/observer_test.go b/integration/tests/access/observer_test.go index 55733820d92..25bfeab2f3a 100644 --- a/integration/tests/access/observer_test.go +++ b/integration/tests/access/observer_test.go @@ -5,9 +5,7 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" - "strings" "testing" "google.golang.org/grpc" @@ -20,6 +18,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/onflow/flow-go/engine/access/rest/util" "github.com/onflow/flow-go/integration/testnet" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/utils/unittest" @@ -77,14 +76,14 @@ func (s *ObserverSuite) SetupTest() { // access node with unstaked nodes supported testnet.NewNodeConfig(flow.RoleAccess, testnet.WithLogLevel(zerolog.InfoLevel), testnet.WithAdditionalFlag("--supports-observer=true")), - // need one dummy execution node (unused ghost) - testnet.NewNodeConfig(flow.RoleExecution, testnet.WithLogLevel(zerolog.FatalLevel), testnet.AsGhost()), + // need one dummy execution node + testnet.NewNodeConfig(flow.RoleExecution, testnet.WithLogLevel(zerolog.FatalLevel)), // need one dummy verification node (unused ghost) testnet.NewNodeConfig(flow.RoleVerification, testnet.WithLogLevel(zerolog.FatalLevel), testnet.AsGhost()), - // need one controllable collection node (unused ghost) - testnet.NewNodeConfig(flow.RoleCollection, testnet.WithLogLevel(zerolog.FatalLevel), testnet.AsGhost()), + // need one controllable collection node + testnet.NewNodeConfig(flow.RoleCollection, testnet.WithLogLevel(zerolog.FatalLevel)), // need three consensus nodes (unused ghost) testnet.NewNodeConfig(flow.RoleConsensus, testnet.WithLogLevel(zerolog.FatalLevel), testnet.AsGhost()), @@ -184,23 +183,20 @@ func (s *ObserverSuite) TestObserverRest() { observerAddr := s.net.ContainerByName("observer_1").Addr(testnet.RESTPort) httpClient := http.DefaultClient - makeHttpCall := func(method string, url string, body io.Reader) (*http.Response, error) { + makeHttpCall := func(method string, url string, body interface{}) (*http.Response, error) { switch method { case http.MethodGet: return httpClient.Get(url) case http.MethodPost: - if body == nil { - return httpClient.Post(url, "application/json", strings.NewReader("{}")) - } else { - return httpClient.Post(url, "application/json", body) - } + jsonBody, _ := json.Marshal(body) + return httpClient.Post(url, "application/json", bytes.NewBuffer(jsonBody)) } panic("not supported") } - makeObserverCall := func(method string, path string, body io.Reader) (*http.Response, error) { + makeObserverCall := func(method string, path string, body interface{}) (*http.Response, error) { return makeHttpCall(method, "http://"+observerAddr+"/v1"+path, body) } - makeAccessCall := func(method string, path string, body io.Reader) (*http.Response, error) { + makeAccessCall := func(method string, path string, body interface{}) (*http.Response, error) { return makeHttpCall(method, "http://"+accessAddr+"/v1"+path, body) } @@ -218,6 +214,10 @@ func (s *ObserverSuite) TestObserverRest() { assert.NoError(t, observerErr) assert.Equal(t, accessResp.Status, observerResp.Status) assert.Equal(t, accessResp.StatusCode, observerResp.StatusCode) + assert.Contains(t, [...]int{ + http.StatusNotFound, + http.StatusOK, + }, observerResp.StatusCode) }) } }) @@ -236,7 +236,6 @@ func (s *ObserverSuite) TestObserverRest() { observerResp, observerErr := makeObserverCall(endpoint.method, endpoint.path, endpoint.body) require.NoError(t, observerErr) assert.Contains(t, [...]int{ - http.StatusInternalServerError, http.StatusServiceUnavailable}, observerResp.StatusCode) }) } @@ -392,12 +391,12 @@ type RestEndpointTest struct { name string method string path string - body io.Reader + body interface{} } func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { transactionId := unittest.IdentifierFixture().String() - account, _ := unittest.AccountFixture() + account := flow.Localnet.Chain().ServiceAddress().String() block := unittest.BlockFixture() executionResult := unittest.ExecutionResultFixture() collection := unittest.CollectionFixture(2) @@ -454,16 +453,17 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { name: "executeScript", method: http.MethodPost, path: "/scripts", + body: createScript(), }, { name: "getAccount", method: http.MethodGet, - path: "/accounts/" + account.Address.HexWithPrefix() + "?block_height=1", + path: "/accounts/" + account + "?block_height=1", }, { name: "getEvents", method: http.MethodGet, - path: fmt.Sprintf("/events?type=%s&start_height=%d&end_height=%d", eventType, 1, 3), + path: fmt.Sprintf("/events?type=%s&start_height=%d&end_height=%d", eventType, 0, 3), }, { name: "getNetworkParameters", @@ -478,10 +478,15 @@ func (s *ObserverSuite) getRestEndpoints() []RestEndpointTest { } } -func createTx(net *testnet.FlowNetwork) *bytes.Buffer { +func createTx(net *testnet.FlowNetwork) interface{} { flowAddr := flow.Localnet.Chain().ServiceAddress() - signature := unittest.TransactionSignatureFixture() - signature.Address = flowAddr + payloadSignature := unittest.TransactionSignatureFixture() + envelopeSignature := unittest.TransactionSignatureFixture() + + payloadSignature.Address = flowAddr + + envelopeSignature.Address = flowAddr + envelopeSignature.KeyIndex = 2 tx := flow.NewTransactionBody(). AddAuthorizer(flowAddr). @@ -489,10 +494,18 @@ func createTx(net *testnet.FlowNetwork) *bytes.Buffer { SetScript(unittest.NoopTxScript()). SetReferenceBlockID(net.Root().ID()). SetProposalKey(flowAddr, 1, 0) - tx.PayloadSignatures = []flow.TransactionSignature{signature} - tx.EnvelopeSignatures = []flow.TransactionSignature{signature} + tx.PayloadSignatures = []flow.TransactionSignature{payloadSignature} + tx.EnvelopeSignatures = []flow.TransactionSignature{envelopeSignature} - jsonBody, _ := json.Marshal(unittest.CreateSendTxHttpPayload(*tx)) + return unittest.CreateSendTxHttpPayload(*tx) +} - return bytes.NewBuffer(jsonBody) +func createScript() interface{} { + validCode := []byte(`pub fun main(foo: String): String { return foo }`) + validArgs := []byte(`{ "type": "String", "value": "hello world" }`) + body := map[string]interface{}{ + "script": util.ToBase64(validCode), + "arguments": []string{util.ToBase64(validArgs)}, + } + return body }