github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/mtrie/node/node.go (about) 1 package node 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 7 "github.com/onflow/flow-go/ledger" 8 "github.com/onflow/flow-go/ledger/common/hash" 9 ) 10 11 // Node defines an Mtrie node 12 // 13 // DEFINITIONS: 14 // - HEIGHT of a node v in a tree is the number of edges on the longest 15 // downward path between v and a tree leaf. 16 // 17 // Conceptually, an MTrie is a sparse Merkle Trie, which has two node types: 18 // - INTERIM node: has at least one child (i.e. lChild or rChild is not 19 // nil). Interim nodes do not store a path and have no payload. 20 // - LEAF node: has _no_ children. 21 // 22 // Per convention, we also consider nil as a leaf. Formally, nil is the generic 23 // representative for any empty (sub)-trie (i.e. a trie without allocated 24 // registers). 25 // 26 // Nodes are supposed to be treated as _immutable_ data structures. 27 // TODO: optimized data structures might be able to reduce memory consumption 28 type Node struct { 29 // Implementation Comments: 30 // Formally, a tree can hold up to 2^maxDepth number of registers. However, 31 // the current implementation is designed to operate on a sparsely populated 32 // tree, holding much less than 2^64 registers. 33 34 lChild *Node // Left Child 35 rChild *Node // Right Child 36 height int // height where the Node is at 37 path ledger.Path // the storage path (dummy value for interim nodes) 38 payload *ledger.Payload // the payload this node is storing (leaf nodes only) 39 hashValue hash.Hash // hash value of node (cached) 40 } 41 42 // NewNode creates a new Node. 43 // UNCHECKED requirement: combination of values must conform to 44 // a valid node type (see documentation of `Node` for details) 45 func NewNode(height int, 46 lchild, 47 rchild *Node, 48 path ledger.Path, 49 payload *ledger.Payload, 50 hashValue hash.Hash, 51 ) *Node { 52 n := &Node{ 53 lChild: lchild, 54 rChild: rchild, 55 height: height, 56 path: path, 57 hashValue: hashValue, 58 payload: payload, 59 } 60 return n 61 } 62 63 // NewLeaf creates a compact leaf Node. 64 // UNCHECKED requirement: height must be non-negative 65 // UNCHECKED requirement: payload is non nil 66 // UNCHECKED requirement: payload should be deep copied if received from external sources 67 func NewLeaf(path ledger.Path, 68 payload *ledger.Payload, 69 height int, 70 ) *Node { 71 n := &Node{ 72 lChild: nil, 73 rChild: nil, 74 height: height, 75 path: path, 76 payload: payload, 77 } 78 n.hashValue = n.computeHash() 79 return n 80 } 81 82 // NewInterimNode creates a new interim Node. 83 // UNCHECKED requirement: 84 // - for any child `c` that is non-nil, its height must satisfy: height = c.height + 1 85 func NewInterimNode(height int, lchild, rchild *Node) *Node { 86 n := &Node{ 87 lChild: lchild, 88 rChild: rchild, 89 height: height, 90 payload: nil, 91 } 92 n.hashValue = n.computeHash() 93 return n 94 } 95 96 // NewInterimCompactifiedNode creates a new compactified interim Node. For compactification, 97 // we only consider the immediate children. When starting with a maximally pruned trie and 98 // creating only InterimCompactifiedNodes during an update, the resulting trie remains maximally 99 // pruned. Details on compactification: 100 // - If _both_ immediate children represent completely unallocated sub-tries, then the sub-trie 101 // with the new interim node is also completely empty. We return nil. 102 // - If either child is a leaf (i.e. representing a single allocated register) _and_ the other 103 // child represents a completely unallocated sub-trie, the new interim node also only holds 104 // a single allocated register. In this case, we return a compactified leaf. 105 // 106 // UNCHECKED requirement: 107 // - for any child `c` that is non-nil, its height must satisfy: height = c.height + 1 108 func NewInterimCompactifiedNode(height int, lChild, rChild *Node) *Node { 109 if lChild.IsDefaultNode() { 110 lChild = nil 111 } 112 if rChild.IsDefaultNode() { 113 rChild = nil 114 } 115 116 // CASE (a): _both_ children do _not_ contain any allocated registers: 117 if lChild == nil && rChild == nil { 118 return nil // return nil representing as completely empty sub-trie 119 } 120 121 // CASE (b): one child is a compactified leaf (single allocated register) _and_ the other child represents 122 // an empty subtrie => in total we have one allocated register, which we represent as single leaf node 123 if rChild == nil && lChild.IsLeaf() { 124 h := hash.HashInterNode(lChild.hashValue, ledger.GetDefaultHashForHeight(lChild.height)) 125 return &Node{height: height, path: lChild.path, payload: lChild.payload, hashValue: h} 126 } 127 if lChild == nil && rChild.IsLeaf() { 128 h := hash.HashInterNode(ledger.GetDefaultHashForHeight(rChild.height), rChild.hashValue) 129 return &Node{height: height, path: rChild.path, payload: rChild.payload, hashValue: h} 130 } 131 132 // CASE (b): both children contain some allocated registers => we can't compactify; return a full interim leaf 133 return NewInterimNode(height, lChild, rChild) 134 } 135 136 // IsDefaultNode returns true iff the sub-trie represented by this root node contains 137 // only unallocated registers. This is the case, if the node is nil or the node's hash 138 // is equal to the default hash value at the respective height. 139 func (n *Node) IsDefaultNode() bool { 140 if n == nil { 141 return true 142 } 143 return n.hashValue == ledger.GetDefaultHashForHeight(n.height) 144 } 145 146 // computeHash returns the hashValue of the node 147 func (n *Node) computeHash() hash.Hash { 148 // check for leaf node 149 if n.lChild == nil && n.rChild == nil { 150 // if payload is non-nil, compute the hash based on the payload content 151 if n.payload != nil { 152 return ledger.ComputeCompactValue(hash.Hash(n.path), n.payload.Value(), n.height) 153 } 154 // if payload is nil, return the default hash 155 return ledger.GetDefaultHashForHeight(n.height) 156 } 157 158 // this is an interim node at least one of lChild or rChild is not nil. 159 var h1, h2 hash.Hash 160 if n.lChild != nil { 161 h1 = n.lChild.Hash() 162 } else { 163 h1 = ledger.GetDefaultHashForHeight(n.height - 1) 164 } 165 166 if n.rChild != nil { 167 h2 = n.rChild.Hash() 168 } else { 169 h2 = ledger.GetDefaultHashForHeight(n.height - 1) 170 } 171 return hash.HashInterNode(h1, h2) 172 } 173 174 // VerifyCachedHash verifies the hash of a node is valid 175 func verifyCachedHashRecursive(n *Node) bool { 176 if n == nil { 177 return true 178 } 179 if !verifyCachedHashRecursive(n.lChild) || !verifyCachedHashRecursive(n.rChild) { 180 return false 181 } 182 183 computedHash := n.computeHash() 184 return n.hashValue == computedHash 185 } 186 187 // VerifyCachedHash verifies the hash of a node is valid 188 func (n *Node) VerifyCachedHash() bool { 189 return verifyCachedHashRecursive(n) 190 } 191 192 // Hash returns the Node's hash value. 193 // Do NOT MODIFY returned slice! 194 func (n *Node) Hash() hash.Hash { 195 return n.hashValue 196 } 197 198 // Height returns the Node's height. 199 // Per definition, the height of a node v in a tree is the number 200 // of edges on the longest downward path between v and a tree leaf. 201 func (n *Node) Height() int { 202 return n.height 203 } 204 205 // Path returns a pointer to the Node's register storage path. 206 // If the node is not a leaf, the function returns `nil`. 207 func (n *Node) Path() *ledger.Path { 208 if n.IsLeaf() { 209 return &n.path 210 } 211 return nil 212 } 213 214 // Payload returns the the Node's payload. 215 // Do NOT MODIFY returned slices! 216 func (n *Node) Payload() *ledger.Payload { 217 return n.payload 218 } 219 220 // LeftChild returns the the Node's left child. 221 // Only INTERIM nodes have children. 222 // Do NOT MODIFY returned Node! 223 func (n *Node) LeftChild() *Node { return n.lChild } 224 225 // RightChild returns the the Node's right child. 226 // Only INTERIM nodes have children. 227 // Do NOT MODIFY returned Node! 228 func (n *Node) RightChild() *Node { return n.rChild } 229 230 // IsLeaf returns true if and only if Node is a LEAF. 231 func (n *Node) IsLeaf() bool { 232 // Per definition, a node is a leaf if and only it has no children 233 return n == nil || (n.lChild == nil && n.rChild == nil) 234 } 235 236 // FmtStr provides formatted string representation of the Node and sub tree 237 func (n *Node) FmtStr(prefix string, subpath string) string { 238 right := "" 239 if n.rChild != nil { 240 right = fmt.Sprintf("\n%v", n.rChild.FmtStr(prefix+"\t", subpath+"1")) 241 } 242 left := "" 243 if n.lChild != nil { 244 left = fmt.Sprintf("\n%v", n.lChild.FmtStr(prefix+"\t", subpath+"0")) 245 } 246 payloadSize := 0 247 if n.payload != nil { 248 payloadSize = n.payload.Size() 249 } 250 hashStr := hex.EncodeToString(n.hashValue[:]) 251 hashStr = hashStr[:3] + "..." + hashStr[len(hashStr)-3:] 252 return fmt.Sprintf("%v%v: (path:%v, payloadSize:%d hash:%v)[%s] (obj %p) %v %v ", prefix, n.height, n.path, payloadSize, hashStr, subpath, n, left, right) 253 } 254 255 // AllPayloads returns the payload of this node and all payloads of the subtrie 256 func (n *Node) AllPayloads() []*ledger.Payload { 257 return n.appendSubtreePayloads([]*ledger.Payload{}) 258 } 259 260 // appendSubtreePayloads appends the payloads of the subtree with this node as root 261 // to the provided Payload slice. Follows same pattern as Go's native append method. 262 func (n *Node) appendSubtreePayloads(result []*ledger.Payload) []*ledger.Payload { 263 if n == nil { 264 return result 265 } 266 if n.IsLeaf() { 267 return append(result, n.Payload()) 268 } 269 result = n.lChild.appendSubtreePayloads(result) 270 result = n.rChild.appendSubtreePayloads(result) 271 return result 272 }