github.com/decred/dcrd/blockchain@v1.2.1/stakenode.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2018 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain
     7  
     8  import (
     9  	"fmt"
    10  
    11  	"github.com/decred/dcrd/blockchain/stake"
    12  	"github.com/decred/dcrd/chaincfg/chainhash"
    13  	"github.com/decred/dcrd/database"
    14  )
    15  
    16  // maybeFetchNewTickets loads the list of newly maturing tickets for a given
    17  // node by traversing backwards through its parents until it finds the block
    18  // that contains the original tickets to mature if needed.
    19  //
    20  // This function MUST be called with the chain state lock held (for writes).
    21  func (b *BlockChain) maybeFetchNewTickets(node *blockNode) error {
    22  	// Nothing to do if the tickets are already loaded.  It's important to make
    23  	// the distinction here that nil means the value was never looked up, while
    24  	// an empty slice means that there are no new tickets at this height.
    25  	if node.newTickets != nil {
    26  		return nil
    27  	}
    28  
    29  	// No tickets in the live ticket pool are possible before stake enabled
    30  	// height.
    31  	if node.height < b.chainParams.StakeEnabledHeight {
    32  		node.newTickets = []chainhash.Hash{}
    33  		return nil
    34  	}
    35  
    36  	// Calculate block number for where new tickets matured from and retrieve
    37  	// its block from DB.
    38  	matureNode := node.RelativeAncestor(int64(b.chainParams.TicketMaturity))
    39  	if matureNode == nil {
    40  		return fmt.Errorf("unable to obtain ancestor %d blocks prior to %s "+
    41  			"(height %d)", b.chainParams.TicketMaturity, node.hash, node.height)
    42  	}
    43  	matureBlock, err := b.fetchBlockByNode(matureNode)
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	// Extract any ticket purchases from the block and cache them.
    49  	tickets := []chainhash.Hash{}
    50  	for _, stx := range matureBlock.MsgBlock().STransactions {
    51  		if stake.IsSStx(stx) {
    52  			tickets = append(tickets, stx.TxHash())
    53  		}
    54  	}
    55  	node.newTickets = tickets
    56  	return nil
    57  }
    58  
    59  // maybeFetchTicketInfo loads and populates prunable ticket information in the
    60  // provided block node if needed.
    61  //
    62  // This function MUST be called with the chain state lock held (for writes).
    63  func (b *BlockChain) maybeFetchTicketInfo(node *blockNode) error {
    64  	// Load and populate the tickets maturing in this block when they are not
    65  	// already loaded.
    66  	if err := b.maybeFetchNewTickets(node); err != nil {
    67  		return err
    68  	}
    69  
    70  	// Load and populate the vote and revocation information as needed.
    71  	if node.ticketsVoted == nil || node.ticketsRevoked == nil ||
    72  		node.votes == nil {
    73  
    74  		block, err := b.fetchBlockByNode(node)
    75  		if err != nil {
    76  			return err
    77  		}
    78  
    79  		node.populateTicketInfo(stake.FindSpentTicketsInBlock(block.MsgBlock()))
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  // fetchStakeNode returns the stake node associated with the requested node
    86  // while handling the logic to create the stake node if needed.  In the majority
    87  // of cases, the stake node either already exists and is simply returned, or it
    88  // can be quickly created when the parent stake node is already available.
    89  // However, it should be noted that, since old stake nodes are pruned, this
    90  // function can be quite expensive if a node deep in history or on a long side
    91  // chain is requested since that requires reconstructing all of the intermediate
    92  // nodes along the path from the existing tip to the requested node that have
    93  // not already been pruned.
    94  //
    95  // This function MUST be called with the chain state lock held (for writes).
    96  func (b *BlockChain) fetchStakeNode(node *blockNode) (*stake.Node, error) {
    97  	// Return the cached immutable stake node when it is already loaded.
    98  	if node.stakeNode != nil {
    99  		return node.stakeNode, nil
   100  	}
   101  
   102  	// Create the requested stake node from the parent stake node if it is
   103  	// already loaded as an optimization.
   104  	if node.parent.stakeNode != nil {
   105  		// Populate the prunable ticket information as needed.
   106  		if err := b.maybeFetchTicketInfo(node); err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		stakeNode, err := node.parent.stakeNode.ConnectNode(node.lotteryIV(),
   111  			node.ticketsVoted, node.ticketsRevoked, node.newTickets)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  		node.stakeNode = stakeNode
   116  
   117  		return stakeNode, nil
   118  	}
   119  
   120  	// -------------------------------------------------------------------------
   121  	// In order to create the stake node, it is necessary to generate a path to
   122  	// the stake node from the current tip, which always has the stake node
   123  	// loaded, and undo the effects of each block back to, and including, the
   124  	// fork point (which might be the requested node itself), and then, in the
   125  	// case the target node is on a side chain, replay the effects of each on
   126  	// the side chain.  In most cases, many of the stake nodes along the path
   127  	// will already be loaded, so, they are only regenerated and populated if
   128  	// they aren't.
   129  	//
   130  	// For example, consider the following scenario:
   131  	//   A -> B  -> C  -> D
   132  	//    \-> B' -> C'
   133  	//
   134  	// Further assume the requested stake node is for C'.  The code that follows
   135  	// will regenerate and populate (only for those not already loaded) the
   136  	// stake nodes for C, B, A, B', and finally, C'.
   137  	// -------------------------------------------------------------------------
   138  
   139  	// Start by undoing the effects from the current tip back to, and including
   140  	// the fork point per the above description.
   141  	tip := b.bestChain.Tip()
   142  	fork := b.bestChain.FindFork(node)
   143  	err := b.db.View(func(dbTx database.Tx) error {
   144  		for n := tip; n != nil && n != fork; n = n.parent {
   145  			// No need to load nodes that are already loaded.
   146  			prev := n.parent
   147  			if prev == nil || prev.stakeNode != nil {
   148  				continue
   149  			}
   150  
   151  			// Generate the previous stake node by starting with the child stake
   152  			// node and undoing the modifications caused by the stake details in
   153  			// the previous block.
   154  			stakeNode, err := n.stakeNode.DisconnectNode(prev.lotteryIV(), nil,
   155  				nil, dbTx)
   156  			if err != nil {
   157  				return err
   158  			}
   159  			prev.stakeNode = stakeNode
   160  		}
   161  
   162  		return nil
   163  	})
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	// Nothing more to do if the requested node is the fork point itself.
   169  	if node == fork {
   170  		return node.stakeNode, nil
   171  	}
   172  
   173  	// The requested node is on a side chain, so replay the effects of the
   174  	// blocks up to the requested node per the above description.
   175  	//
   176  	// Note that the blocks between the fork point and the requested node are
   177  	// added to the slice from back to front so that they are attached in the
   178  	// appropriate order when iterating the slice.
   179  	attachNodes := make([]*blockNode, node.height-fork.height)
   180  	for n := node; n != nil && n != fork; n = n.parent {
   181  		attachNodes[n.height-fork.height-1] = n
   182  	}
   183  	for _, n := range attachNodes {
   184  		// No need to load nodes that are already loaded.
   185  		if n.stakeNode != nil {
   186  			continue
   187  		}
   188  
   189  		// Populate the prunable ticket information as needed.
   190  		if err := b.maybeFetchTicketInfo(n); err != nil {
   191  			return nil, err
   192  		}
   193  
   194  		// Generate the stake node by applying the stake details in the current
   195  		// block to the previous stake node.
   196  		stakeNode, err := n.parent.stakeNode.ConnectNode(n.lotteryIV(),
   197  			n.ticketsVoted, n.ticketsRevoked, n.newTickets)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		n.stakeNode = stakeNode
   202  	}
   203  
   204  	return node.stakeNode, nil
   205  }