github.com/nebulouslabs/sia@v1.3.7/modules/miner/update.go (about) 1 package miner 2 3 import ( 4 "sort" 5 6 "github.com/NebulousLabs/Sia/modules" 7 "github.com/NebulousLabs/Sia/types" 8 ) 9 10 // addMapElementTxns places the splitSet from a mapElement into the correct 11 // mapHeap. 12 func (m *Miner) addMapElementTxns(elem *mapElement) { 13 candidateSet := elem.set 14 15 // Check if heap for highest fee transactions has space. 16 if m.blockMapHeap.size+candidateSet.size < types.BlockSizeLimit-5e3 { 17 m.pushToBlock(elem) 18 return 19 } 20 21 // While the heap cannot fit this set s, and while the (weighted) average 22 // fee for the lowest sets from the block is less than the fee for the set 23 // s, continue removing from the heap. The block heap doesn't have enough 24 // space for this transaction. Check if removing sets from the blockMapHeap 25 // will be worth it. bottomSets will hold the lowest fee sets from the 26 // blockMapHeap 27 bottomSets := make([]*mapElement, 0) 28 var sizeOfBottomSets uint64 29 var averageFeeOfBottomSets types.Currency 30 for { 31 // Check if the candidateSet can fit in the block. 32 if m.blockMapHeap.size-sizeOfBottomSets+candidateSet.size < types.BlockSizeLimit-5e3 { 33 // Place candidate into block, 34 m.pushToBlock(elem) 35 // Place transactions removed from block heap into 36 // the overflow heap. 37 for _, v := range bottomSets { 38 m.pushToOverflow(v) 39 } 40 break 41 } 42 43 // If the blockMapHeap is empty, push all elements removed from it back 44 // in, and place the candidate set into the overflow. This should never 45 // happen since transaction sets are much smaller than the max block 46 // size. 47 _, exists := m.blockMapHeap.peek() 48 if !exists { 49 m.pushToOverflow(elem) 50 // Put back in transactions removed. 51 for _, v := range bottomSets { 52 m.pushToBlock(v) 53 } 54 // Finished with this candidate set. 55 break 56 } 57 // Add the set to the bottomSets slice. Note that we don't increase 58 // sizeOfBottomSets until after calculating the average. 59 nextSet := m.popFromBlock() 60 bottomSets = append(bottomSets, nextSet) 61 62 // Calculating fees to compare total fee from those sets removed and the current set s. 63 totalFeeFromNextSet := nextSet.set.averageFee.Mul64(nextSet.set.size) 64 totalBottomFees := averageFeeOfBottomSets.Mul64(sizeOfBottomSets).Add(totalFeeFromNextSet) 65 sizeOfBottomSets += nextSet.set.size 66 averageFeeOfBottomSets := totalBottomFees.Div64(sizeOfBottomSets) 67 68 // If the average fee of the bottom sets from the block is higher than 69 // the fee from this candidate set, put the candidate into the overflow 70 // MapHeap. 71 if averageFeeOfBottomSets.Cmp(candidateSet.averageFee) == 1 { 72 // CandidateSet goes into the overflow. 73 m.pushToOverflow(elem) 74 // Put transaction sets from bottom back into the blockMapHeap. 75 for _, v := range bottomSets { 76 m.pushToBlock(v) 77 } 78 // Finished with this candidate set. 79 break 80 } 81 } 82 } 83 84 // addNewTxns adds new unconfirmed transactions to the miner's transaction 85 // selection and updates the splitSet and mapElement state of the miner. 86 func (m *Miner) addNewTxns(diff *modules.TransactionPoolDiff) { 87 // Get new splitSets (in form of mapElement) 88 newElements := m.getNewSplitSets(diff) 89 90 // Place each elem in one of the MapHeaps. 91 for i := 0; i < len(newElements); i++ { 92 // Add splitSet to miner's global state using pointer and ID stored in 93 // the mapElement and then add the mapElement to the miner's global 94 // state. 95 m.splitSets[newElements[i].id] = newElements[i].set 96 for _, tx := range newElements[i].set.transactions { 97 m.splitSetIDFromTxID[tx.ID()] = newElements[i].id 98 } 99 m.addMapElementTxns(newElements[i]) 100 } 101 } 102 103 // deleteMapElementTxns removes a splitSet (by id) from the miner's mapheaps and 104 // readjusts the mapheap for the block if needed. 105 func (m *Miner) deleteMapElementTxns(id splitSetID) { 106 _, inBlockMapHeap := m.blockMapHeap.selectID[id] 107 _, inOverflowMapHeap := m.overflowMapHeap.selectID[id] 108 109 // If the transaction set is in the overflow, we can just delete it. 110 if inOverflowMapHeap { 111 m.overflowMapHeap.removeSetByID(id) 112 } else if inBlockMapHeap { 113 // Remove from blockMapHeap. 114 m.blockMapHeap.removeSetByID(id) 115 m.removeSplitSetFromUnsolvedBlock(id) 116 117 // Promote sets from overflow heap to block if possible. 118 for overflowElem, canPromote := m.peekAtOverflow(); canPromote && m.blockMapHeap.size+overflowElem.set.size < types.BlockSizeLimit-5e3; { 119 promotedElem := m.popFromOverflow() 120 m.pushToBlock(promotedElem) 121 } 122 } 123 } 124 125 // deleteReverts deletes transactions from the miner's transaction selection 126 // which are no longer in the transaction pool. 127 func (m *Miner) deleteReverts(diff *modules.TransactionPoolDiff) { 128 // Delete the sets that are no longer useful. That means recognizing which 129 // of your splits belong to the missing sets. 130 for _, id := range diff.RevertedTransactions { 131 // Look up all of the split sets associated with the set being reverted, 132 // and delete them. Then delete the lookups from the list of full sets 133 // as well. 134 splitSetIndexes := m.fullSets[id] 135 for _, ss := range splitSetIndexes { 136 m.deleteMapElementTxns(splitSetID(ss)) 137 delete(m.splitSets, splitSetID(ss)) 138 } 139 delete(m.fullSets, id) 140 } 141 } 142 143 // fixSplitSetOrdering maintains the relative ordering of transactions from a 144 // split set within the block. 145 func (m *Miner) fixSplitSetOrdering(id splitSetID) { 146 set, _ := m.splitSets[id] 147 setTxs := set.transactions 148 var setTxIDs []types.TransactionID 149 var setTxIndices []int // These are the indices within the unsolved block. 150 151 // No swapping necessary if there are less than 2 transactions in the set. 152 if len(setTxs) < 2 { 153 return 154 } 155 156 // Iterate over all transactions in the set and store their txIDs and their 157 // indices within the unsoved block. 158 for i := 0; i < len(setTxs); i++ { 159 txID := setTxs[i].ID() 160 setTxIDs = append(setTxIDs, txID) 161 setTxIndices = append(setTxIndices, m.unsolvedBlockIndex[txID]) 162 } 163 164 // Sort the indices and maintain the sets relative ordering in the block by 165 // changing their positions if necessary. The ordering within the set should 166 // be exactly the order in which the sets appear in the block. 167 sort.Ints(setTxIndices) 168 for i := 0; i < len(setTxIDs); i++ { 169 index := m.unsolvedBlockIndex[setTxIDs[i]] 170 expectedIndex := setTxIndices[i] 171 // Put the transaction in the correct position in the block. 172 if index != expectedIndex { 173 m.persist.UnsolvedBlock.Transactions[expectedIndex] = setTxs[i] 174 m.unsolvedBlockIndex[setTxIDs[i]] = expectedIndex 175 } 176 } 177 } 178 179 // getNewSplitSets creates split sets from a transaction pool diff, returns them 180 // in a slice of map elements. Does not update the miner's global state. 181 func (m *Miner) getNewSplitSets(diff *modules.TransactionPoolDiff) []*mapElement { 182 // Split the new sets and add the splits to the list of transactions we pull 183 // form. 184 newElements := make([]*mapElement, 0) 185 for _, newSet := range diff.AppliedTransactions { 186 // Split the sets into smaller sets, and add them to the list of 187 // transactions the miner can draw from. 188 // TODO: Split the one set into a bunch of smaller sets using the cp4p 189 // splitter. 190 m.setCounter++ 191 m.fullSets[newSet.ID] = []int{m.setCounter} 192 var size uint64 193 var totalFees types.Currency 194 for i := range newSet.IDs { 195 size += newSet.Sizes[i] 196 for _, fee := range newSet.Transactions[i].MinerFees { 197 totalFees = totalFees.Add(fee) 198 } 199 } 200 // We will check to see if this splitSet belongs in the block. 201 s := &splitSet{ 202 size: size, 203 averageFee: totalFees.Div64(size), 204 transactions: newSet.Transactions, 205 } 206 207 elem := &mapElement{ 208 set: s, 209 id: splitSetID(m.setCounter), 210 index: 0, 211 } 212 newElements = append(newElements, elem) 213 } 214 return newElements 215 } 216 217 // peekAtBlock checks top of the blockMapHeap, and returns the top element (but 218 // does not remove it from the heap). Returns false if the heap is empty. 219 func (m *Miner) peekAtBlock() (*mapElement, bool) { 220 return m.blockMapHeap.peek() 221 } 222 223 // peekAtOverflow checks top of the overflowMapHeap, and returns the top element 224 // (but does not remove it from the heap). Returns false if the heap is empty. 225 func (m *Miner) peekAtOverflow() (*mapElement, bool) { 226 return m.overflowMapHeap.peek() 227 } 228 229 // popFromBlock pops an element from the blockMapHeap, removes it from the 230 // miner's unsolved block, and maintains proper set ordering within the block. 231 func (m *Miner) popFromBlock() *mapElement { 232 elem := m.blockMapHeap.pop() 233 m.removeSplitSetFromUnsolvedBlock(elem.id) 234 return elem 235 } 236 237 // popFromBlock pops an element from the overflowMapHeap. 238 func (m *Miner) popFromOverflow() *mapElement { 239 return m.overflowMapHeap.pop() 240 } 241 242 // pushToBlock pushes a mapElement onto the blockMapHeap and appends it to the 243 // unsolved block in the miner's global state. 244 func (m *Miner) pushToBlock(elem *mapElement) { 245 m.blockMapHeap.push(elem) 246 transactions := elem.set.transactions 247 248 // Place the transactions from this set into the block and store their indices. 249 for i := 0; i < len(transactions); i++ { 250 m.unsolvedBlockIndex[transactions[i].ID()] = len(m.persist.UnsolvedBlock.Transactions) 251 m.persist.UnsolvedBlock.Transactions = append(m.persist.UnsolvedBlock.Transactions, transactions[i]) 252 } 253 } 254 255 // pushToOverflow pushes a mapElement onto the overflowMapHeap. 256 func (m *Miner) pushToOverflow(elem *mapElement) { 257 m.overflowMapHeap.push(elem) 258 } 259 260 // ProcessConsensusChange will update the miner's most recent block. 261 func (m *Miner) ProcessConsensusChange(cc modules.ConsensusChange) { 262 m.mu.Lock() 263 defer m.mu.Unlock() 264 265 // Update the miner's understanding of the block height. 266 for _, block := range cc.RevertedBlocks { 267 // Only doing the block check if the height is above zero saves hashing 268 // and saves a nontrivial amount of time during IBD. 269 if m.persist.Height > 0 || block.ID() != types.GenesisID { 270 m.persist.Height-- 271 } else if m.persist.Height != 0 { 272 // Sanity check - if the current block is the genesis block, the 273 // miner height should be set to zero. 274 m.log.Critical("Miner has detected a genesis block, but the height of the miner is set to ", m.persist.Height) 275 m.persist.Height = 0 276 } 277 } 278 for _, block := range cc.AppliedBlocks { 279 // Only doing the block check if the height is above zero saves hashing 280 // and saves a nontrivial amount of time during IBD. 281 if m.persist.Height > 0 || block.ID() != types.GenesisID { 282 m.persist.Height++ 283 } else if m.persist.Height != 0 { 284 // Sanity check - if the current block is the genesis block, the 285 // miner height should be set to zero. 286 m.log.Critical("Miner has detected a genesis block, but the height of the miner is set to ", m.persist.Height) 287 m.persist.Height = 0 288 } 289 } 290 291 // Update the unsolved block. 292 m.persist.UnsolvedBlock.ParentID = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID() 293 m.persist.Target = cc.ChildTarget 294 m.persist.UnsolvedBlock.Timestamp = cc.MinimumValidChildTimestamp 295 296 // There is a new parent block, the source block should be updated to keep 297 // the stale rate as low as possible. 298 if cc.Synced { 299 m.newSourceBlock() 300 } 301 m.persist.RecentChange = cc.ID 302 } 303 304 // ReceiveUpdatedUnconfirmedTransactions will replace the current unconfirmed 305 // set of transactions with the input transactions. 306 func (m *Miner) ReceiveUpdatedUnconfirmedTransactions(diff *modules.TransactionPoolDiff) { 307 m.mu.Lock() 308 defer m.mu.Unlock() 309 310 m.deleteReverts(diff) 311 m.addNewTxns(diff) 312 } 313 314 // removeSplitSetFromUnsolvedBlock removes a split set from the miner's unsolved 315 // block. 316 func (m *Miner) removeSplitSetFromUnsolvedBlock(id splitSetID) { 317 transactions := m.splitSets[id].transactions 318 // swappedTxs stores transaction IDs for all transactions that are swapped 319 // during the process of removing this splitSet. 320 swappedTxs := make(map[types.TransactionID]struct{}) 321 322 // Remove each transaction from this set from the block and track the 323 // transactions that were moved during that action. 324 for i := 0; i < len(transactions); i++ { 325 txID := m.removeTxFromUnsolvedBlock(transactions[i].ID()) 326 swappedTxs[txID] = struct{}{} 327 } 328 329 // setsFixed keeps track of the splitSets which contain swapped transactions 330 // and have been checked for having the correct set ordering. 331 setsFixed := make(map[splitSetID]struct{}) 332 // Iterate over all swapped transactions and fix the ordering of their set 333 // if necessary. 334 for txID := range swappedTxs { 335 setID, _ := m.splitSetIDFromTxID[txID] 336 _, thisSetFixed := setsFixed[setID] 337 338 // If this set was already fixed, or if the transaction is from the set 339 // being removed we can move on to the next transaction. 340 if thisSetFixed || setID == id { 341 continue 342 } 343 344 // Fix the set ordering and add the splitSet to the set of fixed sets. 345 m.fixSplitSetOrdering(setID) 346 setsFixed[setID] = struct{}{} 347 } 348 } 349 350 // removeTxFromUnsolvedBlock removes the given transaction by either swapping it 351 // with the transaction at the end of the slice or, if the transaction to be 352 // removed is the last transaction in the block, just shrinking the slice. It 353 // returns the transaction ID of the last element in the block prior to the 354 // swap/removal taking place. 355 func (m *Miner) removeTxFromUnsolvedBlock(id types.TransactionID) types.TransactionID { 356 index, _ := m.unsolvedBlockIndex[id] 357 length := len(m.persist.UnsolvedBlock.Transactions) 358 // Remove this transactionID from the map of indices. 359 delete(m.unsolvedBlockIndex, id) 360 361 // If the transaction is already the last transaction in the block, we can 362 // remove it by just shrinking the block. 363 if index == length-1 { 364 m.persist.UnsolvedBlock.Transactions = m.persist.UnsolvedBlock.Transactions[:length-1] 365 return id 366 } 367 368 lastTx := m.persist.UnsolvedBlock.Transactions[length-1] 369 lastTxID := lastTx.ID() 370 // Swap with the last transaction in the slice, change the miner state to 371 // match the new index, and shrink the slice by 1 space. 372 m.persist.UnsolvedBlock.Transactions[index] = lastTx 373 m.unsolvedBlockIndex[lastTxID] = index 374 m.persist.UnsolvedBlock.Transactions = m.persist.UnsolvedBlock.Transactions[:length-1] 375 return lastTxID 376 }