decred.org/dcrwallet/v3@v3.1.0/wallet/sidechains.go (about) 1 // Copyright (c) 2018 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package wallet 6 7 import ( 8 "context" 9 "math/big" 10 "sort" 11 12 "decred.org/dcrwallet/v3/errors" 13 "decred.org/dcrwallet/v3/wallet/walletdb" 14 blockchain "github.com/decred/dcrd/blockchain/standalone/v2" 15 "github.com/decred/dcrd/chaincfg/chainhash" 16 "github.com/decred/dcrd/chaincfg/v3" 17 "github.com/decred/dcrd/gcs/v4" 18 "github.com/decred/dcrd/wire" 19 ) 20 21 // SidechainForest provides in-memory management of sidechain and orphan blocks. 22 // It implements a forest of disjoint rooted trees, each tree containing 23 // sidechains stemming from a different fork point in the main chain, or 24 // orphans. 25 // 26 // SidechainForest is not safe for concurrent access. 27 type SidechainForest struct { 28 trees []*sidechainRootedTree 29 } 30 31 // BlockNode represents a block node for a SidechainForest. BlockNodes are not 32 // safe for concurrent access, and all exported fields must be treated as 33 // immutable. 34 type BlockNode struct { 35 Header *wire.BlockHeader 36 Hash *chainhash.Hash 37 FilterV2 *gcs.FilterV2 38 parent *BlockNode 39 workSum *big.Int 40 invalid bool 41 } 42 43 // sidechainRootedTree represents a rooted tree of blocks not currently in the 44 // wallet's main chain. If the parent of the root is not in the wallet's main 45 // chain, the root and all child blocks are orphans. 46 type sidechainRootedTree struct { 47 root *BlockNode 48 children map[chainhash.Hash]*BlockNode 49 tips map[chainhash.Hash]*BlockNode 50 bestChain []*BlockNode // memoized 51 } 52 53 // newSideChainRootedTree creates a new rooted tree for a SidechainForest. The 54 // root must either be the first block in a fork off the main chain, or an 55 // orphan block. 56 func newSideChainRootedTree(root *BlockNode) *sidechainRootedTree { 57 root.workSum = blockchain.CalcWork(root.Header.Bits) 58 return &sidechainRootedTree{ 59 root: root, 60 children: make(map[chainhash.Hash]*BlockNode), 61 tips: make(map[chainhash.Hash]*BlockNode), 62 } 63 } 64 65 // NewBlockNode creates a block node for usage with a SidechainForest. 66 func NewBlockNode(header *wire.BlockHeader, hash *chainhash.Hash, filter *gcs.FilterV2) *BlockNode { 67 return &BlockNode{ 68 Header: header, 69 Hash: hash, 70 FilterV2: filter, 71 } 72 } 73 74 // BadCheckpoint marks a block node invalid due to not matching a required block 75 // checkpoint. Invalid blocks are still recorded in the sidechain forest but 76 // are not returned as a possible best chain. Nodes must be marked invalid 77 // prior to adding them to the forest to propagate the invalid status to child 78 // blocks. 79 func (n *BlockNode) BadCheckpoint() { 80 n.invalid = true 81 } 82 83 // duplicateNode checks if n, or another node which represents the same block, 84 // is already contained in the tree. 85 func (t *sidechainRootedTree) duplicateNode(n *BlockNode) bool { 86 old, ok := t.root, *t.root.Hash == *n.Hash 87 if !ok { 88 old, ok = t.children[*n.Hash] 89 } 90 91 // Copy the cfilter to the older node if it doesn't exist there yet. 92 // This happens when we received sidechains that are about to become a 93 // mainchain. 94 if ok && n.FilterV2 != nil && old.FilterV2 == nil { 95 old.FilterV2 = n.FilterV2 96 } 97 98 return ok 99 } 100 101 // maybeAttachNode checks whether the node is a child of any node in the rooted 102 // tree. If so, the child is added to the tree and true is returned. This 103 // function does not check for duplicate nodes and must only be called on nodes 104 // known to not already exist in the tree. 105 func (t *sidechainRootedTree) maybeAttachNode(n *BlockNode) bool { 106 if *t.root.Hash == n.Header.PrevBlock && n.Header.Height == t.root.Header.Height+1 { 107 n.parent = t.root 108 n.invalid = n.invalid || n.parent.invalid 109 t.children[*n.Hash] = n 110 t.tips[*n.Hash] = n 111 n.workSum = new(big.Int).Add(n.parent.workSum, blockchain.CalcWork(n.Header.Bits)) 112 t.bestChain = nil 113 return true 114 } 115 if parent, ok := t.children[n.Header.PrevBlock]; ok && n.Header.Height == parent.Header.Height+1 { 116 n.parent = parent 117 n.invalid = n.invalid || n.parent.invalid 118 t.children[*n.Hash] = n 119 t.tips[*n.Hash] = n 120 delete(t.tips, *parent.Hash) 121 n.workSum = new(big.Int).Add(n.parent.workSum, blockchain.CalcWork(n.Header.Bits)) 122 t.bestChain = nil 123 return true 124 } 125 return false 126 } 127 128 // best returns one of the best sidechains in the tree, starting with the root 129 // and sorted in increasing order of block heights, along with the summed work 130 // of blocks in the sidechain including the root. If there are multiple best 131 // chain candidates, the chosen chain is indeterminate. 132 // 133 // This method will always return at least one valid block node, or panic if 134 // called on an invalid tree. 135 func (t *sidechainRootedTree) best() ([]*BlockNode, *big.Int) { 136 if t.root.invalid { 137 panic("no best chain for invalid sidechain tree") 138 } 139 140 // Return memoized best chain if unchanged. 141 if len(t.bestChain) != 0 { 142 return t.bestChain, t.bestChain[len(t.bestChain)-1].workSum 143 } 144 145 // Find a tip block, if any, with the largest total work sum (relative to 146 // this tree). 147 var best *BlockNode 148 for _, n := range t.tips { 149 if n.invalid { 150 continue 151 } 152 if best == nil || best.workSum.Cmp(n.workSum) == -1 { 153 best = n 154 } 155 } 156 157 // If only the root exists in this tree, the entire sidechain is only one 158 // block long. 159 if best == nil { 160 t.bestChain = []*BlockNode{t.root} 161 return t.bestChain, t.root.workSum 162 } 163 164 // Create the sidechain by iterating the chain in reverse starting with the 165 // tip. 166 chain := make([]*BlockNode, best.Header.Height-t.root.Header.Height+1) 167 n := best 168 for i, j := 0, len(chain)-1; i < len(chain); i, j = i+1, j-1 { 169 chain[j] = n 170 n = n.parent 171 } 172 173 // Memoize the best chain for future calls. This value remains cached until 174 // a new node is added to the tree. 175 t.bestChain = chain 176 177 return chain, best.workSum 178 } 179 180 // AddBlockNode adds a sidechain block node to the forest. The node may either 181 // begin a new sidechain, extend an existing sidechain, or start or extend a 182 // tree of orphan blocks. Adding the parent node of a previously-saved orphan 183 // block will restructure the forest by re-rooting the previous orphan tree onto 184 // the tree containing the added node. Returns true iff the node if the node 185 // was not a duplicate. 186 func (f *SidechainForest) AddBlockNode(n *BlockNode) bool { 187 // Add the node to an existing tree if it is a direct child of any recorded 188 // blocks, or create a new tree containing only the node as the root. 189 var nodeTree *sidechainRootedTree 190 for _, t := range f.trees { 191 // Avoid adding the node if it represents the same block already in the 192 // tree. This keeps previous-parent consistency in the case that this 193 // node has a different memory address than the existing node, and 194 // prevents adding a duplicate block as a new root in the forest. 195 if t.duplicateNode(n) { 196 return false 197 } 198 199 if t.maybeAttachNode(n) { 200 nodeTree = t 201 break 202 } 203 } 204 if nodeTree == nil { 205 nodeTree = newSideChainRootedTree(n) 206 f.trees = append(f.trees, nodeTree) 207 } 208 209 // Search for any trees whose root references the added node as a parent. 210 // These trees, which were previously orphans, are now children of nodeTree. 211 // The forest is kept disjoint by attaching all nodes of the previous orphan 212 // tree to nodeTree and removing the old tree. 213 for i := 0; i < len(f.trees); { 214 orphanTree := f.trees[i] 215 if orphanTree.root.Header.PrevBlock != *n.Hash { 216 i++ 217 continue 218 } 219 220 // The previous orphan tree must be combined with the extended side 221 // chain tree and removed from the forest. All nodes from the old 222 // orphan tree are dumped to a single slice, sorted by block height, and 223 // then reattached to the extended tree. A failure to add any of these 224 // side chain nodes indicates an internal consistency error and the 225 // algorithm will panic. 226 var nodes []*BlockNode 227 nodes = append(nodes, orphanTree.root) 228 for _, node := range orphanTree.children { 229 nodes = append(nodes, node) 230 } 231 sort.Slice(nodes, func(i, j int) bool { 232 return nodes[i].Header.Height < nodes[j].Header.Height 233 }) 234 for _, n := range nodes { 235 if nodeTree.duplicateNode(n) || !nodeTree.maybeAttachNode(n) { 236 panic("sidechain forest internal consistency error") 237 } 238 } 239 f.trees[i] = f.trees[len(f.trees)-1] 240 f.trees[len(f.trees)-1] = nil 241 f.trees = f.trees[:len(f.trees)-1] 242 } 243 244 return true 245 } 246 247 // Prune removes any sidechain trees which contain a root that is significantly 248 // behind the current main chain tip block. 249 func (f *SidechainForest) Prune(mainChainHeight int32, params *chaincfg.Params) { 250 pruneDepth := int32(params.CoinbaseMaturity) 251 for i := 0; i < len(f.trees); { 252 if int32(f.trees[i].root.Header.Height)+pruneDepth < mainChainHeight { 253 f.trees[i] = f.trees[len(f.trees)-1] 254 f.trees[len(f.trees)-1] = nil 255 f.trees = f.trees[:len(f.trees)-1] 256 } else { 257 i++ 258 } 259 } 260 } 261 262 // PruneTree removes the tree beginning with root from the forest. 263 func (f *SidechainForest) PruneTree(root *chainhash.Hash) { 264 for i, tree := range f.trees { 265 if *root == *tree.root.Hash { 266 f.trees[i] = f.trees[len(f.trees)-1] 267 f.trees[len(f.trees)-1] = nil 268 f.trees = f.trees[:len(f.trees)-1] 269 return 270 } 271 } 272 } 273 274 // HasSideChainBlock returns true if the given block hash is contained in the 275 // sidechain forest at any level. 276 func (f *SidechainForest) HasSideChainBlock(blockHash *chainhash.Hash) bool { 277 for _, tree := range f.trees { 278 if *blockHash == *tree.root.Hash { 279 return true 280 } 281 if _, ok := tree.children[*blockHash]; ok { 282 return true 283 } 284 } 285 return false 286 } 287 288 // FullSideChain returns the sidechain which starts at one of the existing 289 // roots and ends with the set of passed new blocks. 290 func (f *SidechainForest) FullSideChain(newBlocks []*BlockNode) ([]*BlockNode, error) { 291 const op errors.Op = "wallet.EvaluateBestChain" 292 293 if len(newBlocks) == 0 { 294 return newBlocks, nil 295 } 296 297 wantParent := newBlocks[0].Header.PrevBlock 298 299 for _, tree := range f.trees { 300 if wantParent == *tree.root.Hash { 301 // Connects to this root directly. 302 return append([]*BlockNode{tree.root}, newBlocks...), nil 303 } 304 305 parent, ok := tree.children[wantParent] 306 if !ok { 307 // Parent is not in this tree. 308 continue 309 } 310 311 // Shouldn't happen due to the invariants maintained by 312 // SidechainForest, but be cautious to avoid a large loop below 313 // due to wrapping uint32. 314 if parent.Header.Height <= tree.root.Header.Height { 315 err := errors.E(op, "broken assumption of height > parent.Height") 316 return nil, err 317 } 318 319 // Iterate backwards, from parent down to the root to 320 // accumulate the prefix chain. 321 prefixLen := parent.Header.Height - tree.root.Header.Height + 1 322 res := make([]*BlockNode, prefixLen, prefixLen+uint32(len(newBlocks))) 323 for i := int(prefixLen) - 1; i >= 0; i-- { 324 if parent == nil { 325 err := errors.E(op, "broken assumption about "+ 326 "connectedness of sidechain nodes") 327 return nil, err 328 } 329 res[i] = parent 330 parent = parent.parent 331 } 332 333 // Add the newBlocks suffix chain. 334 res = append(res, newBlocks...) 335 return res, nil 336 } 337 338 // New sidechain. 339 return newBlocks, nil 340 } 341 342 // EvaluateBestChain returns block nodes to create the best main chain. These 343 // may extend the main chain or require a reorg. An empty slice indicates there 344 // is no better chain. 345 func (w *Wallet) EvaluateBestChain(ctx context.Context, f *SidechainForest) ([]*BlockNode, error) { 346 const op errors.Op = "wallet.EvaluateBestChain" 347 var newBestChain []*BlockNode 348 err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error { 349 tipHash, _ := w.txStore.MainChainTip(dbtx) 350 tipHeader, err := w.txStore.GetBlockHeader(dbtx, &tipHash) 351 if err != nil { 352 return err 353 } 354 workDiff := new(big.Int) 355 356 // Find chain with most work 357 for _, t := range f.trees { 358 // Ignore invalid and orphan trees 359 if t.root.invalid { 360 continue 361 } 362 fork := &t.root.Header.PrevBlock 363 inMainChain, _ := w.txStore.BlockInMainChain(dbtx, fork) 364 if !inMainChain { 365 continue 366 } 367 368 chain, chainWork := t.best() 369 work := new(big.Int) 370 // Subtract removed work 371 for hash, header := &tipHash, tipHeader; *hash != *fork; { 372 work.Sub(work, blockchain.CalcWork(header.Bits)) 373 prev := &header.PrevBlock 374 header, err = w.txStore.GetBlockHeader(dbtx, prev) 375 if err != nil { 376 return err 377 } 378 hash = prev 379 } 380 // Add sidechain work 381 work.Add(work, chainWork) 382 if work.Cmp(workDiff) == 1 { // work > workDiff 383 newBestChain = chain 384 workDiff = work 385 } 386 } 387 return nil 388 }) 389 if err != nil { 390 return nil, errors.E(op, err) 391 } 392 return newBestChain, nil 393 }