github.com/koko1123/flow-go-1@v0.29.6/engine/access/rest/blocks.go (about)

     1  package rest
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/koko1123/flow-go-1/engine/access/rest/models"
     9  	"github.com/koko1123/flow-go-1/engine/access/rest/request"
    10  
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  
    14  	"github.com/koko1123/flow-go-1/model/flow"
    15  
    16  	"github.com/koko1123/flow-go-1/access"
    17  )
    18  
    19  // GetBlocksByIDs gets blocks by provided ID or list of IDs.
    20  func GetBlocksByIDs(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) {
    21  	req, err := r.GetBlockByIDsRequest()
    22  	if err != nil {
    23  		return nil, NewBadRequestError(err)
    24  	}
    25  
    26  	blocks := make([]*models.Block, len(req.IDs))
    27  	for i, id := range req.IDs {
    28  		block, err := getBlock(forID(&id), r, backend, link)
    29  		if err != nil {
    30  			return nil, err
    31  		}
    32  		blocks[i] = block
    33  	}
    34  
    35  	return blocks, nil
    36  }
    37  
    38  func GetBlocksByHeight(r *request.Request, backend access.API, link models.LinkGenerator) (interface{}, error) {
    39  	req, err := r.GetBlockRequest()
    40  	if err != nil {
    41  		return nil, NewBadRequestError(err)
    42  	}
    43  
    44  	if req.FinalHeight || req.SealedHeight {
    45  		block, err := getBlock(forFinalized(req.Heights[0]), r, backend, link)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  
    50  		return []*models.Block{block}, nil
    51  	}
    52  
    53  	// if the query is /blocks/height=1000,1008,1049...
    54  	if req.HasHeights() {
    55  		blocks := make([]*models.Block, len(req.Heights))
    56  		for i, h := range req.Heights {
    57  			block, err := getBlock(forHeight(h), r, backend, link)
    58  			if err != nil {
    59  				return nil, err
    60  			}
    61  			blocks[i] = block
    62  		}
    63  
    64  		return blocks, nil
    65  	}
    66  
    67  	// support providing end height as "sealed" or "final"
    68  	if req.EndHeight == request.FinalHeight || req.EndHeight == request.SealedHeight {
    69  		latest, _, err := backend.GetLatestBlock(r.Context(), req.EndHeight == request.SealedHeight)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  
    74  		req.EndHeight = latest.Header.Height // overwrite special value height with fetched
    75  
    76  		if req.StartHeight > req.EndHeight {
    77  			return nil, NewBadRequestError(fmt.Errorf("start height must be less than or equal to end height"))
    78  		}
    79  	}
    80  
    81  	blocks := make([]*models.Block, 0)
    82  	// start and end height inclusive
    83  	for i := req.StartHeight; i <= req.EndHeight; i++ {
    84  		block, err := getBlock(forHeight(i), r, backend, link)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		blocks = append(blocks, block)
    89  	}
    90  
    91  	return blocks, nil
    92  }
    93  
    94  // GetBlockPayloadByID gets block payload by ID
    95  func GetBlockPayloadByID(r *request.Request, backend access.API, _ models.LinkGenerator) (interface{}, error) {
    96  	req, err := r.GetBlockPayloadRequest()
    97  	if err != nil {
    98  		return nil, NewBadRequestError(err)
    99  	}
   100  
   101  	blkProvider := NewBlockProvider(backend, forID(&req.ID))
   102  	blk, statusErr := blkProvider.getBlock(r.Context())
   103  	if statusErr != nil {
   104  		return nil, statusErr
   105  	}
   106  
   107  	var payload models.BlockPayload
   108  	err = payload.Build(blk.Payload)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	return payload, nil
   114  }
   115  
   116  func getBlock(option blockProviderOption, req *request.Request, backend access.API, link models.LinkGenerator) (*models.Block, error) {
   117  	// lookup block
   118  	blkProvider := NewBlockProvider(backend, option)
   119  	blk, err := blkProvider.getBlock(req.Context())
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	// lookup execution result
   125  	// (even if not specified as expandable, since we need the execution result ID to generate its expandable link)
   126  	var block models.Block
   127  	executionResult, err := backend.GetExecutionResultForBlockID(req.Context(), blk.ID())
   128  	if err != nil {
   129  		// handle case where execution result is not yet available
   130  		if se, ok := status.FromError(err); ok {
   131  			if se.Code() == codes.NotFound {
   132  				err := block.Build(blk, nil, link, req.ExpandFields)
   133  				if err != nil {
   134  					return nil, err
   135  				}
   136  				return &block, nil
   137  			}
   138  		}
   139  		return nil, err
   140  	}
   141  
   142  	err = block.Build(blk, executionResult, link, req.ExpandFields)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	return &block, nil
   147  }
   148  
   149  // blockProvider is a layer of abstraction on top of the backend access.API and provides a uniform way to
   150  // look up a block or a block header either by ID or by height
   151  type blockProvider struct {
   152  	id      *flow.Identifier
   153  	height  uint64
   154  	latest  bool
   155  	sealed  bool
   156  	backend access.API
   157  }
   158  
   159  type blockProviderOption func(blkProvider *blockProvider)
   160  
   161  func forID(id *flow.Identifier) blockProviderOption {
   162  	return func(blkProvider *blockProvider) {
   163  		blkProvider.id = id
   164  	}
   165  }
   166  func forHeight(height uint64) blockProviderOption {
   167  	return func(blkProvider *blockProvider) {
   168  		blkProvider.height = height
   169  	}
   170  }
   171  
   172  func forFinalized(queryParam uint64) blockProviderOption {
   173  	return func(blkProvider *blockProvider) {
   174  		switch queryParam {
   175  		case request.SealedHeight:
   176  			blkProvider.sealed = true
   177  			fallthrough
   178  		case request.FinalHeight:
   179  			blkProvider.latest = true
   180  		}
   181  	}
   182  }
   183  
   184  func NewBlockProvider(backend access.API, options ...blockProviderOption) *blockProvider {
   185  	blkProvider := &blockProvider{
   186  		backend: backend,
   187  	}
   188  
   189  	for _, o := range options {
   190  		o(blkProvider)
   191  	}
   192  	return blkProvider
   193  }
   194  
   195  func (blkProvider *blockProvider) getBlock(ctx context.Context) (*flow.Block, error) {
   196  	if blkProvider.id != nil {
   197  		blk, _, err := blkProvider.backend.GetBlockByID(ctx, *blkProvider.id)
   198  		if err != nil { // unfortunately backend returns internal error status if not found
   199  			return nil, NewNotFoundError(
   200  				fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err,
   201  			)
   202  		}
   203  		return blk, nil
   204  	}
   205  
   206  	if blkProvider.latest {
   207  		blk, _, err := blkProvider.backend.GetLatestBlock(ctx, blkProvider.sealed)
   208  		if err != nil {
   209  			// cannot be a 'not found' error since final and sealed block should always be found
   210  			return nil, NewRestError(http.StatusInternalServerError, "block lookup failed", err)
   211  		}
   212  		return blk, nil
   213  	}
   214  
   215  	blk, _, err := blkProvider.backend.GetBlockByHeight(ctx, blkProvider.height)
   216  	if err != nil { // unfortunately backend returns internal error status if not found
   217  		return nil, NewNotFoundError(
   218  			fmt.Sprintf("error looking up block at height %d", blkProvider.height), err,
   219  		)
   220  	}
   221  	return blk, nil
   222  }