-
Notifications
You must be signed in to change notification settings - Fork 188
/
Copy pathchunk.go
430 lines (385 loc) · 15 KB
/
chunk.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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
package flow
import (
"fmt"
"io"
"log"
"github.com/ipfs/go-cid"
"github.com/onflow/go-ethereum/rlp"
"github.com/vmihailenco/msgpack/v4"
)
var EmptyEventCollectionID Identifier
func init() {
// Convert hexadecimal string to a byte slice.
var err error
emptyEventCollectionHex := "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"
EmptyEventCollectionID, err = HexStringToIdentifier(emptyEventCollectionHex)
if err != nil {
log.Fatalf("Failed to decode hex: %v", err)
}
}
// ChunkBodyV0 is the prior version of ChunkBody, used for computing backward-compatible IDs and tests.
// Compared to ChunkBody, ChunkBodyV0 does not have the ServiceEventCount field.
// Deprecated: to be removed in Mainnet27
// TODO(mainnet27, #6773): Remove this data structure https://github.com/onflow/flow-go/issues/6773
type ChunkBodyV0 struct {
CollectionIndex uint
StartState StateCommitment
EventCollection Identifier
BlockID Identifier
TotalComputationUsed uint64
NumberOfTransactions uint64
}
type ChunkBody struct {
CollectionIndex uint
// execution info
StartState StateCommitment // start state when starting executing this chunk
EventCollection Identifier // Events generated by executing results
// ServiceEventCount defines how many service events were emitted in this chunk.
// By reading these fields from the prior chunks in the same ExecutionResult, we can
// compute exactly what service events were emitted in this chunk.
//
// Let C be this chunk, K be the set of chunks in the ExecutionResult containing C.
// Then the service event indices for C are given by:
// StartIndex = ∑Ci.ServiceEventCount : Ci ∈ K, Ci.Index < C.Index
// EndIndex = StartIndex + C.ServiceEventCount
// The service events for C are given by:
// ExecutionResult.ServiceEvents[StartIndex:EndIndex]
//
// BACKWARD COMPATIBILITY:
// (1) If ServiceEventCount is nil, this indicates that this chunk was created by an older software version
// which did not support specifying a mapping between chunks and service events.
// In this case, all service events are assumed to have been emitted in the system chunk (last chunk).
// This was the implicit behaviour prior to the introduction of this field.
// (2) Otherwise, ServiceEventCount must be non-nil.
// Within an ExecutionResult, all chunks must use either representation (1) or (2), not both.
// TODO(mainnet27, #6773): make this field non-pointer https://github.com/onflow/flow-go/issues/6773
ServiceEventCount *uint16 `cbor:",omitempty"`
BlockID Identifier // Block id of the execution result this chunk belongs to
// Computation consumption info
TotalComputationUsed uint64 // total amount of computation used by running all txs in this chunk
NumberOfTransactions uint64 // number of transactions inside the collection
}
// We TEMPORARILY implement the [rlp.Encoder] interface to implement backwards-compatible ID computation.
// TODO(mainnet27, #6773): remove EncodeRLP methods on Chunk and ChunkBody https://github.com/onflow/flow-go/issues/6773
var _ rlp.Encoder = &ChunkBody{}
// EncodeRLP defines custom encoding logic for the ChunkBody type.
// NOTE: For correct operation when encoding a larger structure containing ChunkBody,
// this method depends on Chunk also overriding EncodeRLP. Otherwise, since ChunkBody
// is an embedded field, the RLP encoder will skip Chunk fields besides those in ChunkBody.
//
// The encoding is defined for backward compatibility with prior data model version (ChunkBodyV0):
// - All new ChunkBody instances must have non-nil ServiceEventCount field
// - A nil ServiceEventCount field indicates a v0 version of ChunkBody
// - when computing the ID of such a ChunkBody, the ServiceEventCount field is omitted from the fingerprint
//
// No errors expected during normal operations.
// TODO(mainnet27, #6773): remove this method https://github.com/onflow/flow-go/issues/6773
func (ch ChunkBody) EncodeRLP(w io.Writer) error {
var err error
if ch.ServiceEventCount == nil {
err = rlp.Encode(w, struct {
CollectionIndex uint
StartState StateCommitment
EventCollection Identifier
BlockID Identifier
TotalComputationUsed uint64
NumberOfTransactions uint64
}{
CollectionIndex: ch.CollectionIndex,
StartState: ch.StartState,
EventCollection: ch.EventCollection,
BlockID: ch.BlockID,
TotalComputationUsed: ch.TotalComputationUsed,
NumberOfTransactions: ch.NumberOfTransactions,
})
} else {
err = rlp.Encode(w, struct {
CollectionIndex uint
StartState StateCommitment
EventCollection Identifier
ServiceEventCount *uint16
BlockID Identifier
TotalComputationUsed uint64
NumberOfTransactions uint64
}{
CollectionIndex: ch.CollectionIndex,
StartState: ch.StartState,
EventCollection: ch.EventCollection,
ServiceEventCount: ch.ServiceEventCount,
BlockID: ch.BlockID,
TotalComputationUsed: ch.TotalComputationUsed,
NumberOfTransactions: ch.NumberOfTransactions,
})
}
if err != nil {
return fmt.Errorf("failed to rlp encode ChunkBody: %w", err)
}
return nil
}
type Chunk struct {
ChunkBody
Index uint64 // chunk index inside the ER (starts from zero)
// EndState inferred from next chunk or from the ER
EndState StateCommitment
}
// We TEMPORARILY implement the [rlp.Encoder] interface to implement backwards-compatible ID computation.
// TODO(mainnet27, #6773): remove EncodeRLP methods on Chunk and ChunkBody https://github.com/onflow/flow-go/issues/6773
var _ rlp.Encoder = &Chunk{}
// EncodeRLP defines custom encoding logic for the Chunk type.
// This method exists only so that the embedded ChunkBody's EncodeRLP method is
// not interpreted as the RLP encoding for the entire Chunk.
// No errors expected during normal operation.
// TODO(mainnet27, #6773): remove this method https://github.com/onflow/flow-go/issues/6773
func (ch Chunk) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, struct {
ChunkBody ChunkBody
Index uint64
EndState StateCommitment
}{
ChunkBody: ch.ChunkBody,
Index: ch.Index,
EndState: ch.EndState,
})
}
// Deprecated: this constructor is a TEMPORARY abstraction layer, that allows us to construct chunks according
// to the old or the new protocol version model (without or with field [Chunk.ServiceEventCount] respectively),
// depending on the block's view that this chunk belongs to.
// TODO(mainnet27, #6773): remove this type https://github.com/onflow/flow-go/issues/6773
type ChunkConstructor func(
blockID Identifier,
collectionIndex int,
startState StateCommitment,
numberOfTransactions int,
eventCollection Identifier,
serviceEventCount uint16,
endState StateCommitment,
totalComputationUsed uint64) *Chunk
// NewChunk returns a Chunk compliant with Protocol Version 2 and later.
func NewChunk(
blockID Identifier,
collectionIndex int,
startState StateCommitment,
numberOfTransactions int,
eventCollection Identifier,
serviceEventCount uint16,
endState StateCommitment,
totalComputationUsed uint64,
) *Chunk {
return &Chunk{
ChunkBody: ChunkBody{
BlockID: blockID,
CollectionIndex: uint(collectionIndex),
StartState: startState,
NumberOfTransactions: uint64(numberOfTransactions),
EventCollection: eventCollection,
ServiceEventCount: &serviceEventCount,
TotalComputationUsed: totalComputationUsed,
},
Index: uint64(collectionIndex),
EndState: endState,
}
}
// NewChunk_ProtocolVersion1 returns a Chunk compliant with Protocol Version 1,
// omitting the value of the field [Chunk.ServiceEventCount] respectively).
// TODO(mainnet27, #6773): remove this function https://github.com/onflow/flow-go/issues/6773
// Deprecated: for backward compatibility only until upgrade to Protocol Version 2.
func NewChunk_ProtocolVersion1(
blockID Identifier,
collectionIndex int,
startState StateCommitment,
numberOfTransactions int,
eventCollection Identifier,
serviceEventCount uint16, // ignored
endState StateCommitment,
totalComputationUsed uint64,
) *Chunk {
return &Chunk{
ChunkBody: ChunkBody{
BlockID: blockID,
CollectionIndex: uint(collectionIndex),
StartState: startState,
NumberOfTransactions: uint64(numberOfTransactions),
EventCollection: eventCollection,
ServiceEventCount: nil,
TotalComputationUsed: totalComputationUsed,
},
Index: uint64(collectionIndex),
EndState: endState,
}
}
// ID returns a unique id for this entity
func (ch *Chunk) ID() Identifier {
return MakeID(ch.ChunkBody)
}
// Checksum provides a cryptographic commitment for a chunk content
func (ch *Chunk) Checksum() Identifier {
return MakeID(ch)
}
// ChunkDataPack holds all register touches (any read, or write).
//
// Note that we have to include merkle paths as storage proof for all registers touched (read or written) for
// the _starting_ state of the chunk (i.e. before the chunk computation updates the registers).
// For instance, if an execution state contains three registers: { A: 1, B: 2, C: 3}, and a certain
// chunk has a tx that assigns A = A + B, then its chunk data pack should include the merkle
// paths for { A: 1, B: 2 } as storage proof.
// C is not included because it's neither read or written by the chunk.
// B is included because it's read by the chunk.
// A is included because it's updated by the chunk, and its value 1 is included because it's
// the value before the chunk computation.
// This is necessary for Verification Nodes to (i) check that the read register values are
// consistent with the starting state's root hash and (ii) verify the correctness of the resulting
// state after the chunk computation. `Proof` includes merkle proofs for all touched registers
// during the execution of the chunk.
// Register proofs order must not be correlated to the order of register reads during
// the chunk execution in order to enforce the SPoCK secret high entropy.
type ChunkDataPack struct {
ChunkID Identifier // ID of the chunk this data pack is for
StartState StateCommitment // commitment for starting state
Proof StorageProof // proof for all registers touched (read or written) during the chunk execution
Collection *Collection // collection executed in this chunk
// ExecutionDataRoot is the root data structure of an execution_data.BlockExecutionData.
// It contains the necessary information for a verification node to validate that the
// BlockExecutionData produced is valid.
ExecutionDataRoot BlockExecutionDataRoot
}
// NewChunkDataPack returns an initialized chunk data pack.
func NewChunkDataPack(
chunkID Identifier,
startState StateCommitment,
proof StorageProof,
collection *Collection,
execDataRoot BlockExecutionDataRoot,
) *ChunkDataPack {
return &ChunkDataPack{
ChunkID: chunkID,
StartState: startState,
Proof: proof,
Collection: collection,
ExecutionDataRoot: execDataRoot,
}
}
// ID returns the unique identifier for the concrete view, which is the ID of
// the chunk the view is for.
func (c *ChunkDataPack) ID() Identifier {
return c.ChunkID
}
// Checksum returns the checksum of the chunk data pack.
func (c *ChunkDataPack) Checksum() Identifier {
return MakeID(c)
}
// TODO: This is the basic version of the list, we need to substitute it with something like Merkle tree at some point
type ChunkList []*Chunk
func (cl ChunkList) Fingerprint() Identifier {
return MerkleRoot(GetIDs(cl)...)
}
func (cl *ChunkList) Insert(ch *Chunk) {
*cl = append(*cl, ch)
}
func (cl ChunkList) Items() []*Chunk {
return cl
}
// Empty returns true if the chunk list is empty. Otherwise it returns false.
func (cl ChunkList) Empty() bool {
return len(cl) == 0
}
func (cl ChunkList) Indices() []uint64 {
indices := make([]uint64, len(cl))
for i, chunk := range cl {
indices[i] = chunk.Index
}
return indices
}
// ByChecksum returns an entity from the list by entity fingerprint
func (cl ChunkList) ByChecksum(cs Identifier) (*Chunk, bool) {
for _, ch := range cl {
if ch.Checksum() == cs {
return ch, true
}
}
return nil, false
}
// ByIndex returns an entity from the list by index
// if requested chunk is within range of list, it returns chunk and true
// if requested chunk is out of the range, it returns nil and false
// boolean return value indicates whether requested chunk is within range
func (cl ChunkList) ByIndex(i uint64) (*Chunk, bool) {
if i >= uint64(len(cl)) {
// index out of range
return nil, false
}
return cl[i], true
}
// Len returns the number of Chunks in the list. It is also part of the sort
// interface that makes ChunkList sortable
func (cl ChunkList) Len() int {
return len(cl)
}
// BlockExecutionDataRoot represents the root of a serialized execution_data.BlockExecutionData.
// The hash of the serialized BlockExecutionDataRoot is the ExecutionDataID used within an
// flow.ExecutionResult.
// Context:
// - The trie updates in BlockExecutionDataRoot contain the _mutated_ registers only, which is
// helpful for clients to truslessly replicate the state.
// - In comparison, the chunk data packs contains all the register values at the chunk's starting
// state that were _touched_ (written and/or read). This is necessary for Verification Nodes to
// re-run the chunk the computation.
type BlockExecutionDataRoot struct {
// BlockID is the ID of the block, whose result this execution data is for.
BlockID Identifier
// ChunkExecutionDataIDs is a list of the root CIDs for each serialized execution_data.ChunkExecutionData
// associated with this block.
ChunkExecutionDataIDs []cid.Cid
}
// MarshalMsgpack implements the msgpack.Marshaler interface
func (b BlockExecutionDataRoot) MarshalMsgpack() ([]byte, error) {
return msgpack.Marshal(struct {
BlockID Identifier
ChunkExecutionDataIDs []string
}{
BlockID: b.BlockID,
ChunkExecutionDataIDs: cidsToStrings(b.ChunkExecutionDataIDs),
})
}
// UnmarshalMsgpack implements the msgpack.Unmarshaler interface
func (b *BlockExecutionDataRoot) UnmarshalMsgpack(data []byte) error {
var temp struct {
BlockID Identifier
ChunkExecutionDataIDs []string
}
if err := msgpack.Unmarshal(data, &temp); err != nil {
return err
}
b.BlockID = temp.BlockID
cids, err := stringsToCids(temp.ChunkExecutionDataIDs)
if err != nil {
return fmt.Errorf("failed to decode chunk execution data ids: %w", err)
}
b.ChunkExecutionDataIDs = cids
return nil
}
// Helper function to convert a slice of cid.Cid to a slice of strings
func cidsToStrings(cids []cid.Cid) []string {
if cids == nil {
return nil
}
strs := make([]string, len(cids))
for i, c := range cids {
strs[i] = c.String()
}
return strs
}
// Helper function to convert a slice of strings to a slice of cid.Cid
func stringsToCids(strs []string) ([]cid.Cid, error) {
if strs == nil {
return nil, nil
}
cids := make([]cid.Cid, len(strs))
for i, s := range strs {
c, err := cid.Decode(s)
if err != nil {
return nil, fmt.Errorf("failed to decode cid %v: %w", s, err)
}
cids[i] = c
}
return cids, nil
}