-
Notifications
You must be signed in to change notification settings - Fork 179
/
chunk.go
269 lines (231 loc) · 8.26 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
package flow
import (
"fmt"
"log"
"github.com/ipfs/go-cid"
"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)
}
}
type ChunkBody struct {
CollectionIndex uint
// execution info
StartState StateCommitment // start state when starting executing this chunk
EventCollection Identifier // Events generated by executing results
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
}
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
}
func NewChunk(
blockID Identifier,
collectionIndex int,
startState StateCommitment,
numberOfTransactions int,
eventCollection Identifier,
endState StateCommitment,
totalComputationUsed uint64,
) *Chunk {
return &Chunk{
ChunkBody: ChunkBody{
BlockID: blockID,
CollectionIndex: uint(collectionIndex),
StartState: startState,
NumberOfTransactions: uint64(numberOfTransactions),
EventCollection: eventCollection,
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
}