github.com/decred/dcrd/blockchain@v1.2.1/stakeext.go (about) 1 // Copyright (c) 2013-2014 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/chaincfg/chainhash" 12 "github.com/decred/dcrd/database" 13 "github.com/decred/dcrd/dcrutil" 14 "github.com/decred/dcrd/txscript" 15 ) 16 17 // NextLotteryData returns the next tickets eligible for spending as SSGen 18 // on the top block. It also returns the ticket pool size and the PRNG 19 // state checksum. 20 // 21 // This function is safe for concurrent access. 22 func (b *BlockChain) NextLotteryData() ([]chainhash.Hash, int, [6]byte, error) { 23 b.chainLock.RLock() 24 defer b.chainLock.RUnlock() 25 26 tipStakeNode := b.bestChain.Tip().stakeNode 27 return tipStakeNode.Winners(), tipStakeNode.PoolSize(), 28 tipStakeNode.FinalState(), nil 29 } 30 31 // lotteryDataForNode is a helper function that returns winning tickets 32 // along with the ticket pool size and PRNG checksum for a given node. 33 // 34 // This function is NOT safe for concurrent access and MUST be called 35 // with the chainLock held for writes. 36 func (b *BlockChain) lotteryDataForNode(node *blockNode) ([]chainhash.Hash, int, [6]byte, error) { 37 if node.height < b.chainParams.StakeEnabledHeight { 38 return nil, 0, [6]byte{}, nil 39 } 40 stakeNode, err := b.fetchStakeNode(node) 41 if err != nil { 42 return nil, 0, [6]byte{}, err 43 } 44 45 return stakeNode.Winners(), stakeNode.PoolSize(), stakeNode.FinalState(), nil 46 } 47 48 // lotteryDataForBlock takes a node block hash and returns the next tickets 49 // eligible for voting, the number of tickets in the ticket pool, and the 50 // final state of the PRNG. 51 // 52 // This function is NOT safe for concurrent access and must have the chainLock 53 // held for write access. 54 func (b *BlockChain) lotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) { 55 node := b.index.LookupNode(hash) 56 if node == nil { 57 return nil, 0, [6]byte{}, fmt.Errorf("block %s is not known", hash) 58 } 59 60 winningTickets, poolSize, finalState, err := b.lotteryDataForNode(node) 61 if err != nil { 62 return nil, 0, [6]byte{}, err 63 } 64 65 return winningTickets, poolSize, finalState, nil 66 } 67 68 // LotteryDataForBlock returns lottery data for a given block in the block 69 // chain, including side chain blocks. 70 // 71 // It is safe for concurrent access. 72 func (b *BlockChain) LotteryDataForBlock(hash *chainhash.Hash) ([]chainhash.Hash, int, [6]byte, error) { 73 // TODO: An optimization can be added that only calls the read lock if the 74 // block is not minMemoryStakeNodes blocks before the current best node. 75 // This is because all the data for these nodes can be assumed to be 76 // in memory. 77 b.chainLock.Lock() 78 winningTickets, poolSize, finalState, err := b.lotteryDataForBlock(hash) 79 b.chainLock.Unlock() 80 return winningTickets, poolSize, finalState, err 81 } 82 83 // LiveTickets returns all currently live tickets from the stake database. 84 // 85 // This function is NOT safe for concurrent access. 86 func (b *BlockChain) LiveTickets() ([]chainhash.Hash, error) { 87 b.chainLock.RLock() 88 sn := b.bestChain.Tip().stakeNode 89 b.chainLock.RUnlock() 90 91 return sn.LiveTickets(), nil 92 } 93 94 // MissedTickets returns all currently missed tickets from the stake database. 95 // 96 // This function is NOT safe for concurrent access. 97 func (b *BlockChain) MissedTickets() ([]chainhash.Hash, error) { 98 b.chainLock.RLock() 99 sn := b.bestChain.Tip().stakeNode 100 b.chainLock.RUnlock() 101 102 return sn.MissedTickets(), nil 103 } 104 105 // TicketsWithAddress returns a slice of ticket hashes that are currently live 106 // corresponding to the given address. 107 // 108 // This function is safe for concurrent access. 109 func (b *BlockChain) TicketsWithAddress(address dcrutil.Address) ([]chainhash.Hash, error) { 110 b.chainLock.RLock() 111 sn := b.bestChain.Tip().stakeNode 112 b.chainLock.RUnlock() 113 114 tickets := sn.LiveTickets() 115 116 var ticketsWithAddr []chainhash.Hash 117 err := b.db.View(func(dbTx database.Tx) error { 118 for _, hash := range tickets { 119 utxo, err := dbFetchUtxoEntry(dbTx, &hash) 120 if err != nil { 121 return err 122 } 123 124 _, addrs, _, err := 125 txscript.ExtractPkScriptAddrs(txscript.DefaultScriptVersion, 126 utxo.PkScriptByIndex(0), b.chainParams) 127 if err != nil { 128 return err 129 } 130 if addrs[0].EncodeAddress() == address.EncodeAddress() { 131 ticketsWithAddr = append(ticketsWithAddr, hash) 132 } 133 } 134 return nil 135 }) 136 if err != nil { 137 return nil, err 138 } 139 140 return ticketsWithAddr, nil 141 } 142 143 // CheckLiveTicket returns whether or not a ticket exists in the live ticket 144 // treap of the best node. 145 // 146 // This function is safe for concurrent access. 147 func (b *BlockChain) CheckLiveTicket(hash chainhash.Hash) bool { 148 b.chainLock.RLock() 149 sn := b.bestChain.Tip().stakeNode 150 b.chainLock.RUnlock() 151 152 return sn.ExistsLiveTicket(hash) 153 } 154 155 // CheckLiveTickets returns whether or not a slice of tickets exist in the live 156 // ticket treap of the best node. 157 // 158 // This function is safe for concurrent access. 159 func (b *BlockChain) CheckLiveTickets(hashes []chainhash.Hash) []bool { 160 b.chainLock.RLock() 161 sn := b.bestChain.Tip().stakeNode 162 b.chainLock.RUnlock() 163 164 existsSlice := make([]bool, len(hashes)) 165 for i := range hashes { 166 existsSlice[i] = sn.ExistsLiveTicket(hashes[i]) 167 } 168 169 return existsSlice 170 } 171 172 // CheckMissedTickets returns a slice of bools representing whether each ticket 173 // hash has been missed in the live ticket treap of the best node. 174 // 175 // This function is safe for concurrent access. 176 func (b *BlockChain) CheckMissedTickets(hashes []chainhash.Hash) []bool { 177 b.chainLock.RLock() 178 sn := b.bestChain.Tip().stakeNode 179 b.chainLock.RUnlock() 180 181 existsSlice := make([]bool, len(hashes)) 182 for i := range hashes { 183 existsSlice[i] = sn.ExistsMissedTicket(hashes[i]) 184 } 185 186 return existsSlice 187 } 188 189 // CheckExpiredTicket returns whether or not a ticket was ever expired. 190 // 191 // This function is safe for concurrent access. 192 func (b *BlockChain) CheckExpiredTicket(hash chainhash.Hash) bool { 193 b.chainLock.RLock() 194 sn := b.bestChain.Tip().stakeNode 195 b.chainLock.RUnlock() 196 197 return sn.ExistsExpiredTicket(hash) 198 } 199 200 // CheckExpiredTickets returns whether or not a ticket in a slice of 201 // tickets was ever expired. 202 // 203 // This function is safe for concurrent access. 204 func (b *BlockChain) CheckExpiredTickets(hashes []chainhash.Hash) []bool { 205 b.chainLock.RLock() 206 sn := b.bestChain.Tip().stakeNode 207 b.chainLock.RUnlock() 208 209 existsSlice := make([]bool, len(hashes)) 210 for i := range hashes { 211 existsSlice[i] = sn.ExistsExpiredTicket(hashes[i]) 212 } 213 214 return existsSlice 215 } 216 217 // TicketPoolValue returns the current value of all the locked funds in the 218 // ticket pool. 219 // 220 // This function is safe for concurrent access. All live tickets are at least 221 // 256 blocks deep on mainnet, so the UTXO set should generally always have 222 // the asked for transactions. 223 func (b *BlockChain) TicketPoolValue() (dcrutil.Amount, error) { 224 b.chainLock.RLock() 225 sn := b.bestChain.Tip().stakeNode 226 b.chainLock.RUnlock() 227 228 var amt int64 229 err := b.db.View(func(dbTx database.Tx) error { 230 for _, hash := range sn.LiveTickets() { 231 utxo, err := dbFetchUtxoEntry(dbTx, &hash) 232 if err != nil { 233 return err 234 } 235 236 amt += utxo.sparseOutputs[0].amount 237 } 238 return nil 239 }) 240 if err != nil { 241 return 0, err 242 } 243 return dcrutil.Amount(amt), nil 244 }