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 }