github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/miningpool/update.go (about) 1 package pool 2 3 import ( 4 "time" 5 6 "SiaPrime/crypto" 7 8 "SiaPrime/modules" 9 "SiaPrime/types" 10 ) 11 12 // addMapElementTxns places the splitSet from a mapElement into the correct 13 // mapHeap. 14 func (p *Pool) addMapElementTxns(elem *mapElement) { 15 candidateSet := elem.set 16 17 // Check if heap for highest fee transactions has space. 18 if p.blockMapHeap.size+candidateSet.size < types.BlockSizeLimit-5e3 { 19 p.pushToTxnList(elem) 20 return 21 } 22 23 // While the heap cannot fit this set s, and while the (weighted) average 24 // fee for the lowest sets from the block is less than the fee for the set 25 // s, continue removing from the heap. The block heap doesn't have enough 26 // space for this transaction. Check if removing sets from the blockMapHeap 27 // will be worth it. bottomSets will hold the lowest fee sets from the 28 // blockMapHeap 29 bottomSets := make([]*mapElement, 0) 30 var sizeOfBottomSets uint64 31 var averageFeeOfBottomSets types.Currency 32 for { 33 // Check if the candidateSet can fit in the block. 34 if p.blockMapHeap.size-sizeOfBottomSets+candidateSet.size < types.BlockSizeLimit-5e3 { 35 // Place candidate into block, 36 p.pushToTxnList(elem) 37 38 // Place transactions removed from block heap into 39 // the overflow heap. 40 for _, v := range bottomSets { 41 p.pushToOverflow(v) 42 } 43 break 44 } 45 46 // If the blockMapHeap is empty, push all elements removed from it back 47 // in, and place the candidate set into the overflow. This should never 48 // happen since transaction sets are much smaller than the max block 49 // size. 50 _, exists := p.blockMapHeap.peek() 51 if !exists { 52 p.pushToOverflow(elem) 53 // Put back in transactions removed. 54 for _, v := range bottomSets { 55 p.pushToTxnList(v) 56 } 57 // Finished with this candidate set. 58 break 59 } 60 // Add the set to the bottomSets slice. Note that we don't increase 61 // sizeOfBottomSets until after calculating the average. 62 nextSet := p.popFromBlock() 63 bottomSets = append(bottomSets, nextSet) 64 65 // Calculating fees to compare total fee from those sets removed and the current set s. 66 totalFeeFromNextSet := nextSet.set.averageFee.Mul64(nextSet.set.size) 67 totalBottomFees := averageFeeOfBottomSets.Mul64(sizeOfBottomSets).Add(totalFeeFromNextSet) 68 sizeOfBottomSets += nextSet.set.size 69 averageFeeOfBottomSets := totalBottomFees.Div64(sizeOfBottomSets) 70 71 // If the average fee of the bottom sets from the block is higher than 72 // the fee from this candidate set, put the candidate into the overflow 73 // MapHeap. 74 if averageFeeOfBottomSets.Cmp(candidateSet.averageFee) == 1 { 75 // CandidateSet goes into the overflow. 76 p.pushToOverflow(elem) 77 // Put transaction sets from bottom back into the blockMapHeap. 78 for _, v := range bottomSets { 79 p.pushToTxnList(v) 80 } 81 // Finished with this candidate set. 82 break 83 } 84 } 85 } 86 87 // addNewTxns adds new unconfirmed transactions to the pool's transaction 88 // selection and updates the splitSet and mapElement state of the pool. 89 func (p *Pool) addNewTxns(diff *modules.TransactionPoolDiff) { 90 // Get new splitSets (in form of mapElement) 91 newElements := p.getNewSplitSets(diff) 92 93 // Place each elem in one of the MapHeaps. 94 for i := 0; i < len(newElements); i++ { 95 // Add splitSet to pool's global state using pointer and ID stored in 96 // the mapElement and then add the mapElement to the pool's global 97 // state. 98 p.splitSets[newElements[i].id] = newElements[i].set 99 p.addMapElementTxns(newElements[i]) 100 } 101 } 102 103 // deleteMapElementTxns removes a splitSet (by id) from the pool's mapheaps and 104 // readjusts the mapheap for the block if needed. 105 func (p *Pool) deleteMapElementTxns(id splitSetID) { 106 _, inBlockMapHeap := p.blockMapHeap.selectID[id] 107 _, inOverflowMapHeap := p.overflowMapHeap.selectID[id] 108 109 // If the transaction set is in the overflow, we can just delete it. 110 if inOverflowMapHeap { 111 p.overflowMapHeap.removeSetByID(id) 112 } else if inBlockMapHeap { 113 // Remove from blockMapHeap. 114 p.blockMapHeap.removeSetByID(id) 115 p.removeSplitSetFromTxnList(id) 116 117 // Promote sets from overflow heap to block if possible. 118 for overflowElem, canPromote := p.overflowMapHeap.peek(); canPromote && p.blockMapHeap.size+overflowElem.set.size < types.BlockSizeLimit-5e3; { 119 promotedElem := p.popFromOverflow() 120 p.pushToTxnList(promotedElem) 121 } 122 } 123 delete(p.splitSets, id) 124 } 125 126 // deleteReverts deletes transactions from the mining pool's transaction selection 127 // which are no longer in the transaction pool. 128 func (p *Pool) deleteReverts(diff *modules.TransactionPoolDiff) { 129 // Delete the sets that are no longer useful. That means recognizing which 130 // of your splits belong to the missing sets. 131 for _, id := range diff.RevertedTransactions { 132 // Look up all of the split sets associated with the set being reverted, 133 // and delete them. Then delete the lookups from the list of full sets 134 // as well. 135 splitSetIndexes := p.fullSets[id] 136 for _, ss := range splitSetIndexes { 137 p.deleteMapElementTxns(splitSetID(ss)) 138 delete(p.splitSets, splitSetID(ss)) 139 } 140 delete(p.fullSets, id) 141 } 142 } 143 144 // getNewSplitSets creates split sets from a transaction pool diff, returns them 145 // in a slice of map elements. Does not update the pool's global state. 146 func (p *Pool) getNewSplitSets(diff *modules.TransactionPoolDiff) []*mapElement { 147 // Split the new sets and add the splits to the list of transactions we pull 148 // form. 149 newElements := make([]*mapElement, 0) 150 for _, newSet := range diff.AppliedTransactions { 151 // Split the sets into smaller sets, and add them to the list of 152 // transactions the pool can draw from. 153 // TODO: Split the one set into a bunch of smaller sets using the cp4p 154 // splitter. 155 p.setCounter++ 156 p.fullSets[newSet.ID] = []int{p.setCounter} 157 var size uint64 158 var totalFees types.Currency 159 for i := range newSet.IDs { 160 size += newSet.Sizes[i] 161 for _, fee := range newSet.Transactions[i].MinerFees { 162 totalFees = totalFees.Add(fee) 163 } 164 } 165 // We will check to see if this splitSet belongs in the block. 166 s := &splitSet{ 167 size: size, 168 averageFee: totalFees.Div64(size), 169 transactions: newSet.Transactions, 170 } 171 172 elem := &mapElement{ 173 set: s, 174 id: splitSetID(p.setCounter), 175 index: 0, 176 } 177 newElements = append(newElements, elem) 178 } 179 return newElements 180 } 181 182 // peekAtOverflow checks top of the overflowMapHeap, and returns the top element 183 // (but does not remove it from the heap). Returns false if the heap is empty. 184 func (p *Pool) peekAtOverflow() (*mapElement, bool) { 185 return p.overflowMapHeap.peek() 186 } 187 188 // popFromBlock pops an element from the blockMapHeap, removes it from the 189 // miner's unsolved block, and maintains proper set ordering within the block. 190 func (p *Pool) popFromBlock() *mapElement { 191 elem := p.blockMapHeap.pop() 192 p.removeSplitSetFromTxnList(elem.id) 193 return elem 194 } 195 196 // popFromBlock pops an element from the overflowMapHeap. 197 func (p *Pool) popFromOverflow() *mapElement { 198 return p.overflowMapHeap.pop() 199 } 200 201 // pushToTxnList inserts a blockElement into the list of transactions that 202 // populates the unsolved block. 203 func (p *Pool) pushToTxnList(elem *mapElement) { 204 p.blockMapHeap.push(elem) 205 transactions := elem.set.transactions 206 207 // Place the transactions from this set into the block and store their indices. 208 for i := 0; i < len(transactions); i++ { 209 p.blockTxns.appendTxn(&transactions[i]) 210 } 211 } 212 213 // pushToOverflow pushes a mapElement onto the overflowMapHeap. 214 func (p *Pool) pushToOverflow(elem *mapElement) { 215 p.overflowMapHeap.push(elem) 216 } 217 218 // ProcessConsensusChange will update the pool's most recent block. 219 func (p *Pool) ProcessConsensusChange(cc modules.ConsensusChange) { 220 p.mu.Lock() 221 defer p.mu.Unlock() 222 223 p.log.Printf("CCID %v (height %v): %v applied blocks, %v reverted blocks", crypto.Hash(cc.ID).String()[:8], p.persist.GetBlockHeight(), len(cc.AppliedBlocks), len(cc.RevertedBlocks)) 224 // Update the pool's understanding of the block height. 225 for _, block := range cc.RevertedBlocks { 226 // Only doing the block check if the height is above zero saves hashing 227 // and saves a nontrivial amount of time during IBD. 228 if p.persist.GetBlockHeight() > 0 || block.ID() != types.GenesisID { 229 p.persist.SetBlockHeight(p.persist.GetBlockHeight() - 1) 230 } else if p.persist.GetBlockHeight() != 0 { 231 // Sanity check - if the current block is the genesis block, the 232 // pool height should be set to zero. 233 p.log.Critical("Pool has detected a genesis block, but the height of the pool is set to ", p.persist.GetBlockHeight()) 234 p.persist.SetBlockHeight(0) 235 } 236 } 237 for _, block := range cc.AppliedBlocks { 238 // Only doing the block check if the height is above zero saves hashing 239 // and saves a nontrivial amount of time during IBD. 240 if p.persist.GetBlockHeight() > 0 || block.ID() != types.GenesisID { 241 p.persist.SetBlockHeight(p.persist.GetBlockHeight() + 1) 242 } else if p.persist.GetBlockHeight() != 0 { 243 // Sanity check - if the current block is the genesis block, the 244 // pool height should be set to zero. 245 p.log.Critical("Pool has detected a genesis block, but the height of the pool is set to ", p.persist.GetBlockHeight()) 246 p.persist.SetBlockHeight(0) 247 } 248 } 249 250 // Update the unsolved block. 251 // TODO do we really need to do this if we're not synced? 252 p.persist.mu.Lock() 253 p.sourceBlock.ParentID = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() 254 p.sourceBlock.Timestamp = cc.MinimumValidChildTimestamp 255 p.persist.Target = cc.ChildTarget 256 p.persist.RecentChange = cc.ID 257 p.persist.mu.Unlock() 258 // There is a new parent block, the source block should be updated to keep 259 // the stale rate as low as possible. 260 if cc.Synced { 261 p.log.Printf("Consensus change detected\n") 262 // we do this because the new block could have come from us 263 264 p.newSourceBlock() 265 if p.dispatcher != nil { 266 //p.log.Printf("Notifying clients\n") 267 p.dispatcher.ClearJobAndNotifyClients() 268 } 269 } 270 } 271 272 // ReceiveUpdatedUnconfirmedTransactions will replace the current unconfirmed 273 // set of transactions with the input transactions. 274 func (p *Pool) ReceiveUpdatedUnconfirmedTransactions(diff *modules.TransactionPoolDiff) { 275 p.mu.Lock() 276 defer p.mu.Unlock() 277 278 p.deleteReverts(diff) 279 p.addNewTxns(diff) 280 since := time.Now().Sub(p.sourceBlockTime).Seconds() 281 if since > 30 { 282 p.log.Printf("Block update detected\n") 283 p.newSourceBlock() 284 if p.dispatcher != nil { 285 //p.log.Printf("Notifying clients\n") 286 p.dispatcher.NotifyClients() 287 } 288 } 289 } 290 291 // removeSplitSetFromTxnList removes a split set from the miner's unsolved 292 // block. 293 func (p *Pool) removeSplitSetFromTxnList(id splitSetID) { 294 transactions := p.splitSets[id].transactions 295 296 // Remove each transaction from this set from the block and track the 297 // transactions that were moved during that action. 298 for i := 0; i < len(transactions); i++ { 299 p.blockTxns.removeTxn(transactions[i].ID()) 300 } 301 }