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 }