github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/trie/committer.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package trie 18 19 import ( 20 "errors" 21 "fmt" 22 "sync" 23 24 "github.com/electroneum/electroneum-sc/common" 25 ) 26 27 // leafChanSize is the size of the leafCh. It's a pretty arbitrary number, to allow 28 // some parallelism but not incur too much memory overhead. 29 const leafChanSize = 200 30 31 // leaf represents a trie leaf value 32 type leaf struct { 33 size int // size of the rlp data (estimate) 34 hash common.Hash // hash of rlp data 35 node node // the node to commit 36 } 37 38 // committer is a type used for the trie Commit operation. A committer has some 39 // internal preallocated temp space, and also a callback that is invoked when 40 // leaves are committed. The leafs are passed through the `leafCh`, to allow 41 // some level of parallelism. 42 // By 'some level' of parallelism, it's still the case that all leaves will be 43 // processed sequentially - onleaf will never be called in parallel or out of order. 44 type committer struct { 45 onleaf LeafCallback 46 leafCh chan *leaf 47 } 48 49 // committers live in a global sync.Pool 50 var committerPool = sync.Pool{ 51 New: func() interface{} { 52 return &committer{} 53 }, 54 } 55 56 // newCommitter creates a new committer or picks one from the pool. 57 func newCommitter() *committer { 58 return committerPool.Get().(*committer) 59 } 60 61 func returnCommitterToPool(h *committer) { 62 h.onleaf = nil 63 h.leafCh = nil 64 committerPool.Put(h) 65 } 66 67 // Commit collapses a node down into a hash node and inserts it into the database 68 func (c *committer) Commit(n node, db *Database) (hashNode, int, error) { 69 if db == nil { 70 return nil, 0, errors.New("no db provided") 71 } 72 h, committed, err := c.commit(n, db) 73 if err != nil { 74 return nil, 0, err 75 } 76 return h.(hashNode), committed, nil 77 } 78 79 // commit collapses a node down into a hash node and inserts it into the database 80 func (c *committer) commit(n node, db *Database) (node, int, error) { 81 // if this path is clean, use available cached data 82 hash, dirty := n.cache() 83 if hash != nil && !dirty { 84 return hash, 0, nil 85 } 86 // Commit children, then parent, and remove the dirty flag. 87 switch cn := n.(type) { 88 case *shortNode: 89 // Commit child 90 collapsed := cn.copy() 91 92 // If the child is fullNode, recursively commit, 93 // otherwise it can only be hashNode or valueNode. 94 var childCommitted int 95 if _, ok := cn.Val.(*fullNode); ok { 96 childV, committed, err := c.commit(cn.Val, db) 97 if err != nil { 98 return nil, 0, err 99 } 100 collapsed.Val, childCommitted = childV, committed 101 } 102 // The key needs to be copied, since we're delivering it to database 103 collapsed.Key = hexToCompact(cn.Key) 104 hashedNode := c.store(collapsed, db) 105 if hn, ok := hashedNode.(hashNode); ok { 106 return hn, childCommitted + 1, nil 107 } 108 return collapsed, childCommitted, nil 109 case *fullNode: 110 hashedKids, childCommitted, err := c.commitChildren(cn, db) 111 if err != nil { 112 return nil, 0, err 113 } 114 collapsed := cn.copy() 115 collapsed.Children = hashedKids 116 117 hashedNode := c.store(collapsed, db) 118 if hn, ok := hashedNode.(hashNode); ok { 119 return hn, childCommitted + 1, nil 120 } 121 return collapsed, childCommitted, nil 122 case hashNode: 123 return cn, 0, nil 124 default: 125 // nil, valuenode shouldn't be committed 126 panic(fmt.Sprintf("%T: invalid node: %v", n, n)) 127 } 128 } 129 130 // commitChildren commits the children of the given fullnode 131 func (c *committer) commitChildren(n *fullNode, db *Database) ([17]node, int, error) { 132 var ( 133 committed int 134 children [17]node 135 ) 136 for i := 0; i < 16; i++ { 137 child := n.Children[i] 138 if child == nil { 139 continue 140 } 141 // If it's the hashed child, save the hash value directly. 142 // Note: it's impossible that the child in range [0, 15] 143 // is a valueNode. 144 if hn, ok := child.(hashNode); ok { 145 children[i] = hn 146 continue 147 } 148 // Commit the child recursively and store the "hashed" value. 149 // Note the returned node can be some embedded nodes, so it's 150 // possible the type is not hashNode. 151 hashed, childCommitted, err := c.commit(child, db) 152 if err != nil { 153 return children, 0, err 154 } 155 children[i] = hashed 156 committed += childCommitted 157 } 158 // For the 17th child, it's possible the type is valuenode. 159 if n.Children[16] != nil { 160 children[16] = n.Children[16] 161 } 162 return children, committed, nil 163 } 164 165 // store hashes the node n and if we have a storage layer specified, it writes 166 // the key/value pair to it and tracks any node->child references as well as any 167 // node->external trie references. 168 func (c *committer) store(n node, db *Database) node { 169 // Larger nodes are replaced by their hash and stored in the database. 170 var ( 171 hash, _ = n.cache() 172 size int 173 ) 174 if hash == nil { 175 // This was not generated - must be a small node stored in the parent. 176 // In theory, we should apply the leafCall here if it's not nil(embedded 177 // node usually contains value). But small value(less than 32bytes) is 178 // not our target. 179 return n 180 } else { 181 // We have the hash already, estimate the RLP encoding-size of the node. 182 // The size is used for mem tracking, does not need to be exact 183 size = estimateSize(n) 184 } 185 // If we're using channel-based leaf-reporting, send to channel. 186 // The leaf channel will be active only when there an active leaf-callback 187 if c.leafCh != nil { 188 c.leafCh <- &leaf{ 189 size: size, 190 hash: common.BytesToHash(hash), 191 node: n, 192 } 193 } else if db != nil { 194 // No leaf-callback used, but there's still a database. Do serial 195 // insertion 196 db.lock.Lock() 197 db.insert(common.BytesToHash(hash), size, n) 198 db.lock.Unlock() 199 } 200 return hash 201 } 202 203 // commitLoop does the actual insert + leaf callback for nodes. 204 func (c *committer) commitLoop(db *Database) { 205 for item := range c.leafCh { 206 var ( 207 hash = item.hash 208 size = item.size 209 n = item.node 210 ) 211 // We are pooling the trie nodes into an intermediate memory cache 212 db.lock.Lock() 213 db.insert(hash, size, n) 214 db.lock.Unlock() 215 216 if c.onleaf != nil { 217 switch n := n.(type) { 218 case *shortNode: 219 if child, ok := n.Val.(valueNode); ok { 220 c.onleaf(nil, nil, child, hash) 221 } 222 case *fullNode: 223 // For children in range [0, 15], it's impossible 224 // to contain valueNode. Only check the 17th child. 225 if n.Children[16] != nil { 226 c.onleaf(nil, nil, n.Children[16].(valueNode), hash) 227 } 228 } 229 } 230 } 231 } 232 233 // estimateSize estimates the size of an rlp-encoded node, without actually 234 // rlp-encoding it (zero allocs). This method has been experimentally tried, and with a trie 235 // with 1000 leafs, the only errors above 1% are on small shortnodes, where this 236 // method overestimates by 2 or 3 bytes (e.g. 37 instead of 35) 237 func estimateSize(n node) int { 238 switch n := n.(type) { 239 case *shortNode: 240 // A short node contains a compacted key, and a value. 241 return 3 + len(n.Key) + estimateSize(n.Val) 242 case *fullNode: 243 // A full node contains up to 16 hashes (some nils), and a key 244 s := 3 245 for i := 0; i < 16; i++ { 246 if child := n.Children[i]; child != nil { 247 s += estimateSize(child) 248 } else { 249 s++ 250 } 251 } 252 return s 253 case valueNode: 254 return 1 + len(n) 255 case hashNode: 256 return 1 + len(n) 257 default: 258 panic(fmt.Sprintf("node type %T", n)) 259 } 260 }