/
blocks.go
222 lines (189 loc) · 6.11 KB
/
blocks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package routes
import (
"context"
"fmt"
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"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 access.API, link models.LinkGenerator) (interface{}, error) {
req, err := r.GetBlockByIDsRequest()
if err != nil {
return nil, models.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 blocks, nil
}
// GetBlocksByHeight gets blocks by height.
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)
}
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, backend access.API, _ models.LinkGenerator) (interface{}, error) {
req, err := r.GetBlockPayloadRequest()
if err != nil {
return nil, models.NewBadRequestError(err)
}
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 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())
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)
if err != nil {
return nil, err
}
return &block, nil
}
}
return nil, err
}
err = block.Build(blk, executionResult, link, blockStatus, req.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 blockProviderOption func(blkProvider *blockProvider)
func forID(id *flow.Identifier) blockProviderOption {
return func(blkProvider *blockProvider) {
blkProvider.id = id
}
}
func forHeight(height uint64) blockProviderOption {
return func(blkProvider *blockProvider) {
blkProvider.height = height
}
}
func forFinalized(queryParam uint64) blockProviderOption {
return func(blkProvider *blockProvider) {
switch queryParam {
case request.SealedHeight:
blkProvider.sealed = true
fallthrough
case request.FinalHeight:
blkProvider.latest = true
}
}
}
func NewBlockProvider(backend access.API, options ...blockProviderOption) *blockProvider {
blkProvider := &blockProvider{
backend: backend,
}
for _, o := range options {
o(blkProvider)
}
return blkProvider
}
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
return nil, flow.BlockStatusUnknown, models.NewNotFoundError(
fmt.Sprintf("error looking up block with ID %s", blkProvider.id.String()), err,
)
}
return blk, flow.BlockStatusUnknown, nil
}
if blkProvider.latest {
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, 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, models.NewNotFoundError(
fmt.Sprintf("error looking up block at height %d", blkProvider.height), err,
)
}
return blk, status, nil
}