-
Notifications
You must be signed in to change notification settings - Fork 175
/
backend_network.go
186 lines (159 loc) · 7.05 KB
/
backend_network.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
package backend
import (
"context"
"errors"
"github.com/onflow/flow-go/state"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/onflow/flow-go/access"
"github.com/onflow/flow-go/cmd/build"
"github.com/onflow/flow-go/engine/common/rpc/convert"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/state/protocol"
"github.com/onflow/flow-go/state/protocol/snapshots"
"github.com/onflow/flow-go/storage"
)
type backendNetwork struct {
state protocol.State
chainID flow.ChainID
headers storage.Headers
snapshotHistoryLimit int
}
/*
NetworkAPI func
The observer and access nodes need to be able to handle GetNetworkParameters
and GetLatestProtocolStateSnapshot RPCs so this logic was split into
the backendNetwork so that we can ignore the rest of the backend logic
*/
func NewNetworkAPI(
state protocol.State,
chainID flow.ChainID,
headers storage.Headers,
snapshotHistoryLimit int,
) *backendNetwork {
return &backendNetwork{
state: state,
chainID: chainID,
headers: headers,
snapshotHistoryLimit: snapshotHistoryLimit,
}
}
func (b *backendNetwork) GetNetworkParameters(_ context.Context) access.NetworkParameters {
return access.NetworkParameters{
ChainID: b.chainID,
}
}
func (b *backendNetwork) GetNodeVersionInfo(ctx context.Context) (*access.NodeVersionInfo, error) {
stateParams := b.state.Params()
sporkId, err := stateParams.SporkID()
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to read spork ID: %v", err)
}
protocolVersion, err := stateParams.ProtocolVersion()
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to read protocol version: %v", err)
}
return &access.NodeVersionInfo{
Semver: build.Version(),
Commit: build.Commit(),
SporkId: sporkId,
ProtocolVersion: uint64(protocolVersion),
}, nil
}
// GetLatestProtocolStateSnapshot returns the latest finalized snapshot
func (b *backendNetwork) GetLatestProtocolStateSnapshot(_ context.Context) ([]byte, error) {
snapshot := b.state.Final()
validSnapshot, err := snapshots.GetClosestDynamicBootstrapSnapshot(b.state, snapshot, b.snapshotHistoryLimit)
if err != nil {
return nil, err
}
data, err := convert.SnapshotToBytes(validSnapshot)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert snapshot to bytes: %v", err)
}
return data, nil
}
// GetProtocolStateSnapshotByBlockID returns serializable Snapshot for a block, by blockID.
// The requested block must be finalized, otherwise an error is returned.
// Expected errors during normal operation:
// - status.Error[codes.NotFound] - No block with the given ID was found
// - status.Error[codes.InvalidArgument] - Block ID will never have a valid snapshot:
// 1. A block was found, but it is not finalized and is below the finalized height, so it will never be finalized.
// 2. A block was found, however its sealing segment spans an epoch phase transition, yielding an invalid snapshot.
// - status.Error[codes.FailedPrecondition] - A block was found, but it is not finalized and is above the finalized height.
// The block may or may not be finalized in the future; the client can retry later.
func (b *backendNetwork) GetProtocolStateSnapshotByBlockID(_ context.Context, blockID flow.Identifier) ([]byte, error) {
snapshot := b.state.AtBlockID(blockID)
snapshotHeadByBlockId, err := snapshot.Head()
if err != nil {
if errors.Is(err, state.ErrUnknownSnapshotReference) {
return nil, status.Errorf(codes.NotFound, "failed to get a valid snapshot: block not found")
}
return nil, status.Errorf(codes.Internal, "could not get header by blockID: %v", err)
}
// Because there is no index from block ID to finalized height, we separately look up the finalized
// block ID by the height of the queried block, then compare the queried ID to the finalized ID.
// If they match, then the queried block must be finalized.
blockIDFinalizedAtHeight, err := b.headers.BlockIDByHeight(snapshotHeadByBlockId.Height)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
// The block exists, but no block has been finalized at its height. Therefore, this block
// may be finalized in the future, and the client can retry.
return nil, status.Errorf(codes.FailedPrecondition,
"failed to retrieve snapshot for block with height %d: block not finalized and is above finalized height",
snapshotHeadByBlockId.Height)
}
return nil, status.Errorf(codes.Internal, "failed to lookup block id by height %d", snapshotHeadByBlockId.Height)
}
if blockIDFinalizedAtHeight != blockID {
// A different block than what was queried has been finalized at this height.
// Therefore, the queried block will never be finalized.
return nil, status.Errorf(codes.InvalidArgument,
"failed to retrieve snapshot for block: block not finalized and is below finalized height")
}
validSnapshot, err := snapshots.GetDynamicBootstrapSnapshot(b.state, snapshot)
if err != nil {
if errors.Is(err, snapshots.ErrSnapshotPhaseMismatch) {
return nil, status.Errorf(codes.InvalidArgument,
"failed to retrieve snapshot for block, try again with different block: "+
"%v", err)
}
return nil, status.Errorf(codes.Internal, "failed to get a valid snapshot: %v", err)
}
data, err := convert.SnapshotToBytes(validSnapshot)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert snapshot to bytes: %v", err)
}
return data, nil
}
// GetProtocolStateSnapshotByHeight returns serializable Snapshot by block height.
// The block must be finalized (otherwise the by-height query is ambiguous).
// Expected errors during normal operation:
// - status.Error[codes.NotFound] - No block with the given height was found.
// The block height may or may not be finalized in the future; the client can retry later.
// - status.Error[codes.InvalidArgument] - A block was found, however its sealing segment spans an epoch phase transition,
// yielding an invalid snapshot. Therefore we will never return a snapshot for this block height.
func (b *backendNetwork) GetProtocolStateSnapshotByHeight(_ context.Context, blockHeight uint64) ([]byte, error) {
snapshot := b.state.AtHeight(blockHeight)
_, err := snapshot.Head()
if err != nil {
if errors.Is(err, state.ErrUnknownSnapshotReference) {
return nil, status.Errorf(codes.NotFound, "failed to find snapshot: %v", err)
}
return nil, status.Errorf(codes.Internal, "failed to get a valid snapshot: %v", err)
}
validSnapshot, err := snapshots.GetDynamicBootstrapSnapshot(b.state, snapshot)
if err != nil {
if errors.Is(err, snapshots.ErrSnapshotPhaseMismatch) {
return nil, status.Errorf(codes.InvalidArgument,
"failed to retrieve snapshot for block, try again with different block: "+
"%v", err)
}
return nil, status.Errorf(codes.Internal, "failed to get a valid snapshot: %v", err)
}
data, err := convert.SnapshotToBytes(validSnapshot)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert snapshot to bytes: %v", err)
}
return data, nil
}