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  }