github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/mtrie/flattener/encoding.go (about) 1 package flattener 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 8 "github.com/onflow/flow-go/ledger" 9 "github.com/onflow/flow-go/ledger/common/hash" 10 "github.com/onflow/flow-go/ledger/complete/mtrie/node" 11 "github.com/onflow/flow-go/ledger/complete/mtrie/trie" 12 ) 13 14 type nodeType byte 15 16 const ( 17 leafNodeType nodeType = iota 18 interimNodeType 19 ) 20 21 const ( 22 encNodeTypeSize = 1 23 encHeightSize = 2 24 encRegCountSize = 8 25 encRegSizeSize = 8 26 encHashSize = hash.HashLen 27 encPathSize = ledger.PathLen 28 encNodeIndexSize = 8 29 encPayloadLengthSize = 4 30 31 encodedTrieSize = encNodeIndexSize + encRegCountSize + encRegSizeSize + encHashSize 32 EncodedTrieSize = encodedTrieSize 33 ) 34 35 const payloadEncodingVersion = 1 36 37 // encodeLeafNode encodes leaf node in the following format: 38 // - node type (1 byte) 39 // - height (2 bytes) 40 // - hash (32 bytes) 41 // - path (32 bytes) 42 // - payload (4 bytes + n bytes) 43 // Encoded leaf node size is 81 bytes (assuming length of hash/path is 32 bytes) + 44 // length of encoded payload size. 45 // Scratch buffer is used to avoid allocs. It should be used directly instead 46 // of using append. This function uses len(scratch) and ignores cap(scratch), 47 // so any extra capacity will not be utilized. 48 // WARNING: The returned buffer is likely to share the same underlying array as 49 // the scratch buffer. Caller is responsible for copying or using returned buffer 50 // before scratch buffer is used again. 51 func encodeLeafNode(n *node.Node, scratch []byte) []byte { 52 53 encPayloadSize := ledger.EncodedPayloadLengthWithoutPrefix(n.Payload(), payloadEncodingVersion) 54 55 encodedNodeSize := encNodeTypeSize + 56 encHeightSize + 57 encHashSize + 58 encPathSize + 59 encPayloadLengthSize + 60 encPayloadSize 61 62 // buf uses received scratch buffer if it's large enough. 63 // Otherwise, a new buffer is allocated. 64 // buf is used directly so len(buf) must not be 0. 65 // buf will be resliced to proper size before being returned from this function. 66 buf := scratch 67 if len(scratch) < encodedNodeSize { 68 buf = make([]byte, encodedNodeSize) 69 } 70 71 pos := 0 72 73 // Encode node type (1 byte) 74 buf[pos] = byte(leafNodeType) 75 pos += encNodeTypeSize 76 77 // Encode height (2 bytes Big Endian) 78 binary.BigEndian.PutUint16(buf[pos:], uint16(n.Height())) 79 pos += encHeightSize 80 81 // Encode hash (32 bytes hashValue) 82 hash := n.Hash() 83 copy(buf[pos:], hash[:]) 84 pos += encHashSize 85 86 // Encode path (32 bytes path) 87 path := n.Path() 88 copy(buf[pos:], path[:]) 89 pos += encPathSize 90 91 // Encode payload (4 bytes Big Endian for encoded payload length and n bytes encoded payload) 92 binary.BigEndian.PutUint32(buf[pos:], uint32(encPayloadSize)) 93 pos += encPayloadLengthSize 94 95 // EncodeAndAppendPayloadWithoutPrefix appends encoded payload to the resliced buf. 96 // Returned buf is resliced to include appended payload. 97 buf = ledger.EncodeAndAppendPayloadWithoutPrefix(buf[:pos], n.Payload(), payloadEncodingVersion) 98 99 return buf 100 } 101 102 // encodeInterimNode encodes interim node in the following format: 103 // - node type (1 byte) 104 // - height (2 bytes) 105 // - hash (32 bytes) 106 // - lchild index (8 bytes) 107 // - rchild index (8 bytes) 108 // Encoded interim node size is 61 bytes (assuming length of hash is 32 bytes). 109 // Scratch buffer is used to avoid allocs. It should be used directly instead 110 // of using append. This function uses len(scratch) and ignores cap(scratch), 111 // so any extra capacity will not be utilized. 112 // WARNING: The returned buffer is likely to share the same underlying array as 113 // the scratch buffer. Caller is responsible for copying or using returned buffer 114 // before scratch buffer is used again. 115 func encodeInterimNode(n *node.Node, lchildIndex uint64, rchildIndex uint64, scratch []byte) []byte { 116 117 const encodedNodeSize = encNodeTypeSize + 118 encHeightSize + 119 encHashSize + 120 encNodeIndexSize + 121 encNodeIndexSize 122 123 // buf uses received scratch buffer if it's large enough. 124 // Otherwise, a new buffer is allocated. 125 // buf is used directly so len(buf) must not be 0. 126 // buf will be resliced to proper size before being returned from this function. 127 buf := scratch 128 if len(scratch) < encodedNodeSize { 129 buf = make([]byte, encodedNodeSize) 130 } 131 132 pos := 0 133 134 // Encode node type (1 byte) 135 buf[pos] = byte(interimNodeType) 136 pos += encNodeTypeSize 137 138 // Encode height (2 bytes Big Endian) 139 binary.BigEndian.PutUint16(buf[pos:], uint16(n.Height())) 140 pos += encHeightSize 141 142 // Encode hash (32 bytes hashValue) 143 h := n.Hash() 144 copy(buf[pos:], h[:]) 145 pos += encHashSize 146 147 // Encode left child index (8 bytes Big Endian) 148 binary.BigEndian.PutUint64(buf[pos:], lchildIndex) 149 pos += encNodeIndexSize 150 151 // Encode right child index (8 bytes Big Endian) 152 binary.BigEndian.PutUint64(buf[pos:], rchildIndex) 153 pos += encNodeIndexSize 154 155 return buf[:pos] 156 } 157 158 // EncodeNode encodes node. 159 // Scratch buffer is used to avoid allocs. 160 // WARNING: The returned buffer is likely to share the same underlying array as 161 // the scratch buffer. Caller is responsible for copying or using returned buffer 162 // before scratch buffer is used again. 163 func EncodeNode(n *node.Node, lchildIndex uint64, rchildIndex uint64, scratch []byte) []byte { 164 if n.IsLeaf() { 165 return encodeLeafNode(n, scratch) 166 } 167 return encodeInterimNode(n, lchildIndex, rchildIndex, scratch) 168 } 169 170 // ReadNode reconstructs a node from data read from reader. 171 // Scratch buffer is used to avoid allocs. It should be used directly instead 172 // of using append. This function uses len(scratch) and ignores cap(scratch), 173 // so any extra capacity will not be utilized. 174 // If len(scratch) < 1024, then a new buffer will be allocated and used. 175 func ReadNode(reader io.Reader, scratch []byte, getNode func(nodeIndex uint64) (*node.Node, error)) (*node.Node, error) { 176 177 // minBufSize should be large enough for interim node and leaf node with small payload. 178 // minBufSize is a failsafe and is only used when len(scratch) is much smaller 179 // than expected. len(scratch) is 4096 by default, so minBufSize isn't likely to be used. 180 const minBufSize = 1024 181 182 if len(scratch) < minBufSize { 183 scratch = make([]byte, minBufSize) 184 } 185 186 // fixLengthSize is the size of shared data of leaf node and interim node 187 const fixLengthSize = encNodeTypeSize + encHeightSize + encHashSize 188 189 _, err := io.ReadFull(reader, scratch[:fixLengthSize]) 190 if err != nil { 191 return nil, fmt.Errorf("failed to read fixed-length part of serialized node: %w", err) 192 } 193 194 pos := 0 195 196 // Decode node type (1 byte) 197 nType := scratch[pos] 198 pos += encNodeTypeSize 199 200 if nType != byte(leafNodeType) && nType != byte(interimNodeType) { 201 return nil, fmt.Errorf("failed to decode node type %d", nType) 202 } 203 204 // Decode height (2 bytes) 205 height := binary.BigEndian.Uint16(scratch[pos:]) 206 pos += encHeightSize 207 208 // Decode and create hash.Hash (32 bytes) 209 nodeHash, err := hash.ToHash(scratch[pos : pos+encHashSize]) 210 if err != nil { 211 return nil, fmt.Errorf("failed to decode hash of serialized node: %w", err) 212 } 213 214 if nType == byte(leafNodeType) { 215 216 // Read path (32 bytes) 217 encPath := scratch[:encPathSize] 218 _, err := io.ReadFull(reader, encPath) 219 if err != nil { 220 return nil, fmt.Errorf("failed to read path of serialized node: %w", err) 221 } 222 223 // Decode and create ledger.Path. 224 path, err := ledger.ToPath(encPath) 225 if err != nil { 226 return nil, fmt.Errorf("failed to decode path of serialized node: %w", err) 227 } 228 229 // Read encoded payload data and create ledger.Payload. 230 payload, err := readPayloadFromReader(reader, scratch) 231 if err != nil { 232 return nil, fmt.Errorf("failed to read and decode payload of serialized node: %w", err) 233 } 234 235 node := node.NewNode(int(height), nil, nil, path, payload, nodeHash) 236 return node, nil 237 } 238 239 // Read interim node 240 241 // Read left and right child index (16 bytes) 242 _, err = io.ReadFull(reader, scratch[:encNodeIndexSize*2]) 243 if err != nil { 244 return nil, fmt.Errorf("failed to read child index of serialized node: %w", err) 245 } 246 247 pos = 0 248 249 // Decode left child index (8 bytes) 250 lchildIndex := binary.BigEndian.Uint64(scratch[pos:]) 251 pos += encNodeIndexSize 252 253 // Decode right child index (8 bytes) 254 rchildIndex := binary.BigEndian.Uint64(scratch[pos:]) 255 256 // Get left child node by node index 257 lchild, err := getNode(lchildIndex) 258 if err != nil { 259 return nil, fmt.Errorf("failed to find left child node of serialized node: %w", err) 260 } 261 262 // Get right child node by node index 263 rchild, err := getNode(rchildIndex) 264 if err != nil { 265 return nil, fmt.Errorf("failed to find right child node of serialized node: %w", err) 266 } 267 268 n := node.NewNode(int(height), lchild, rchild, ledger.DummyPath, nil, nodeHash) 269 return n, nil 270 } 271 272 type EncodedTrie struct { 273 RootIndex uint64 274 RegCount uint64 275 RegSize uint64 276 RootHash hash.Hash 277 } 278 279 // EncodeTrie encodes trie in the following format: 280 // - root node index (8 byte) 281 // - allocated reg count (8 byte) 282 // - allocated reg size (8 byte) 283 // - root node hash (32 bytes) 284 // Scratch buffer is used to avoid allocs. 285 // WARNING: The returned buffer is likely to share the same underlying array as 286 // the scratch buffer. Caller is responsible for copying or using returned buffer 287 // before scratch buffer is used again. 288 func EncodeTrie(trie *trie.MTrie, rootIndex uint64, scratch []byte) []byte { 289 buf := scratch 290 if len(scratch) < encodedTrieSize { 291 buf = make([]byte, encodedTrieSize) 292 } 293 294 pos := 0 295 296 // Encode root node index (8 bytes Big Endian) 297 binary.BigEndian.PutUint64(buf, rootIndex) 298 pos += encNodeIndexSize 299 300 // Encode trie reg count (8 bytes Big Endian) 301 binary.BigEndian.PutUint64(buf[pos:], trie.AllocatedRegCount()) 302 pos += encRegCountSize 303 304 // Encode trie reg size (8 bytes Big Endian) 305 binary.BigEndian.PutUint64(buf[pos:], trie.AllocatedRegSize()) 306 pos += encRegSizeSize 307 308 // Encode hash (32-bytes hashValue) 309 rootHash := trie.RootHash() 310 copy(buf[pos:], rootHash[:]) 311 pos += encHashSize 312 313 return buf[:pos] 314 } 315 316 func ReadEncodedTrie(reader io.Reader, scratch []byte) (EncodedTrie, error) { 317 if len(scratch) < encodedTrieSize { 318 scratch = make([]byte, encodedTrieSize) 319 } 320 321 // Read encoded trie 322 _, err := io.ReadFull(reader, scratch[:encodedTrieSize]) 323 if err != nil { 324 return EncodedTrie{}, fmt.Errorf("failed to read serialized trie: %w", err) 325 } 326 327 pos := 0 328 329 // Decode root node index 330 rootIndex := binary.BigEndian.Uint64(scratch) 331 pos += encNodeIndexSize 332 333 // Decode trie reg count (8 bytes) 334 regCount := binary.BigEndian.Uint64(scratch[pos:]) 335 pos += encRegCountSize 336 337 // Decode trie reg size (8 bytes) 338 regSize := binary.BigEndian.Uint64(scratch[pos:]) 339 pos += encRegSizeSize 340 341 // Decode root node hash 342 readRootHash, err := hash.ToHash(scratch[pos : pos+encHashSize]) 343 if err != nil { 344 return EncodedTrie{}, fmt.Errorf("failed to decode hash of serialized trie: %w", err) 345 } 346 347 return EncodedTrie{ 348 RootIndex: rootIndex, 349 RegCount: regCount, 350 RegSize: regSize, 351 RootHash: readRootHash, 352 }, nil 353 } 354 355 // ReadTrie reconstructs a trie from data read from reader. 356 func ReadTrie(reader io.Reader, scratch []byte, getNode func(nodeIndex uint64) (*node.Node, error)) (*trie.MTrie, error) { 357 encodedTrie, err := ReadEncodedTrie(reader, scratch) 358 if err != nil { 359 return nil, err 360 } 361 362 rootNode, err := getNode(encodedTrie.RootIndex) 363 if err != nil { 364 return nil, fmt.Errorf("failed to find root node of serialized trie: %w", err) 365 } 366 367 mtrie, err := trie.NewMTrie(rootNode, encodedTrie.RegCount, encodedTrie.RegSize) 368 if err != nil { 369 return nil, fmt.Errorf("failed to restore serialized trie: %w", err) 370 } 371 372 rootHash := mtrie.RootHash() 373 if !rootHash.Equals(ledger.RootHash(encodedTrie.RootHash)) { 374 return nil, fmt.Errorf("failed to restore serialized trie: roothash doesn't match") 375 } 376 377 return mtrie, nil 378 } 379 380 // readPayloadFromReader reads and decodes payload from reader. 381 // Returned payload is a copy. 382 func readPayloadFromReader(reader io.Reader, scratch []byte) (*ledger.Payload, error) { 383 384 if len(scratch) < encPayloadLengthSize { 385 scratch = make([]byte, encPayloadLengthSize) 386 } 387 388 // Read payload size 389 _, err := io.ReadFull(reader, scratch[:encPayloadLengthSize]) 390 if err != nil { 391 return nil, fmt.Errorf("cannot read payload length: %w", err) 392 } 393 394 // Decode payload size 395 size := binary.BigEndian.Uint32(scratch) 396 397 if len(scratch) < int(size) { 398 scratch = make([]byte, size) 399 } else { 400 scratch = scratch[:size] 401 } 402 403 _, err = io.ReadFull(reader, scratch) 404 if err != nil { 405 return nil, fmt.Errorf("cannot read payload: %w", err) 406 } 407 408 // Decode and copy payload 409 payload, err := ledger.DecodePayloadWithoutPrefix(scratch, false, payloadEncodingVersion) 410 if err != nil { 411 return nil, fmt.Errorf("failed to decode payload: %w", err) 412 } 413 414 return payload, nil 415 }