gitlab.com/jokerrs1/Sia@v1.3.2/modules/transactionpool/update.go (about) 1 package transactionpool 2 3 import ( 4 "bytes" 5 "sort" 6 7 "github.com/NebulousLabs/Sia/crypto" 8 "github.com/NebulousLabs/Sia/modules" 9 "github.com/NebulousLabs/Sia/types" 10 ) 11 12 // findSets takes a bunch of transactions (presumably from a block) and finds 13 // all of the separate transaction sets within it. Set does not check for 14 // conflicts. 15 // 16 // The algorithm goes through one transaction at a time. All of the outputs of 17 // that transaction are added to the objMap, pointing to the transaction to 18 // indicate that the transaction contains those outputs. The transaction is 19 // assigned an integer id (each transaction will have a unique id) and added to 20 // the txMap. 21 // 22 // The transaction's inputs are then checked against the objMap to see if there 23 // are any parents of the transaction in the graph. If there are, the 24 // transaction is added to the parent set instead of its own set. If not, the 25 // transaction is added as its own set. 26 // 27 // The forwards map contains a list of ints indicating when a transaction has 28 // been merged with a set. When a transaction gets merged with a parent set, its 29 // integer id gets added to the forwards map, indicating that the transaction is 30 // no longer in its own set, but instead has been merged with other sets. 31 // 32 // Some transactions will have parents from multiple distinct sets. If a 33 // transaction has parents in multiple distinct sets, those sets get merged 34 // together and the transaction gets added to the result. One of the sets is 35 // nominated (arbitrarily) as the official set, and the integer id of the other 36 // set and the new transaction get forwarded to the official set. 37 // 38 // TODO: Set merging currently occurs any time that there is a child. But 39 // really, it should only occur if the child increases the average fee value of 40 // the set that it is merging with (which it will if and only if it has a higher 41 // average fee than that set). If the child has multiple parent sets, it should 42 // be compared with the parent set that has the lowest fee value. Then, after it 43 // is merged with that parent, the result should be merged with the next 44 // lowest-fee parent set if and only if the new set has a higher average fee 45 // than the parent set. And this continues until either all of the sets have 46 // been merged, or until the remaining parent sets have higher values. 47 func findSets(ts []types.Transaction) [][]types.Transaction { 48 // txMap marks what set each transaction is in. If two sets get combined, 49 // this number will not be updated. The 'forwards' map defined further on 50 // will help to discover which sets have been combined. 51 txMap := make(map[types.TransactionID]int) 52 setMap := make(map[int][]types.Transaction) 53 objMap := make(map[ObjectID]types.TransactionID) 54 forwards := make(map[int]int) 55 56 // Define a function to follow and collapse any update chain. 57 forward := func(prev int) (ret int) { 58 ret = prev 59 next, exists := forwards[prev] 60 for exists { 61 ret = next 62 forwards[prev] = next // collapse the forwards function to prevent quadratic runtime of findSets. 63 next, exists = forwards[next] 64 } 65 return ret 66 } 67 68 // Add the transactions to the setup one-by-one, merging them as they belong 69 // to a set. 70 for i, t := range ts { 71 // Check if the inputs depend on any previous transaction outputs. 72 tid := t.ID() 73 parentSets := make(map[int]struct{}) 74 for _, obj := range t.SiacoinInputs { 75 txid, exists := objMap[ObjectID(obj.ParentID)] 76 if exists { 77 parentSet := forward(txMap[txid]) 78 parentSets[parentSet] = struct{}{} 79 } 80 } 81 for _, obj := range t.FileContractRevisions { 82 txid, exists := objMap[ObjectID(obj.ParentID)] 83 if exists { 84 parentSet := forward(txMap[txid]) 85 parentSets[parentSet] = struct{}{} 86 } 87 } 88 for _, obj := range t.StorageProofs { 89 txid, exists := objMap[ObjectID(obj.ParentID)] 90 if exists { 91 parentSet := forward(txMap[txid]) 92 parentSets[parentSet] = struct{}{} 93 } 94 } 95 for _, obj := range t.SiafundInputs { 96 txid, exists := objMap[ObjectID(obj.ParentID)] 97 if exists { 98 parentSet := forward(txMap[txid]) 99 parentSets[parentSet] = struct{}{} 100 } 101 } 102 103 // Determine the new counter for this transaction. 104 if len(parentSets) == 0 { 105 // No parent sets. Make a new set for this transaction. 106 txMap[tid] = i 107 setMap[i] = []types.Transaction{t} 108 // Don't need to add anything for the file contract outputs, storage 109 // proof outputs, siafund claim outputs; these outputs are not 110 // allowed to be spent until 50 confirmations. 111 } else { 112 // There are parent sets, pick one as the base and then merge the 113 // rest into it. 114 parentsSlice := make([]int, 0, len(parentSets)) 115 for j := range parentSets { 116 parentsSlice = append(parentsSlice, j) 117 } 118 base := parentsSlice[0] 119 txMap[tid] = base 120 for _, j := range parentsSlice[1:] { 121 // Forward any future transactions pointing at this set to the 122 // base set. 123 forwards[j] = base 124 // Combine the transactions in this set with the transactions in 125 // the base set. 126 setMap[base] = append(setMap[base], setMap[j]...) 127 // Delete this set map, it has been merged with the base set. 128 delete(setMap, j) 129 } 130 // Add this transaction to the base set. 131 setMap[base] = append(setMap[base], t) 132 } 133 134 // Mark this transaction's outputs as potential inputs to future 135 // transactions. 136 for j := range t.SiacoinOutputs { 137 scoid := t.SiacoinOutputID(uint64(j)) 138 objMap[ObjectID(scoid)] = tid 139 } 140 for j := range t.FileContracts { 141 fcid := t.FileContractID(uint64(j)) 142 objMap[ObjectID(fcid)] = tid 143 } 144 for j := range t.FileContractRevisions { 145 fcid := t.FileContractRevisions[j].ParentID 146 objMap[ObjectID(fcid)] = tid 147 } 148 for j := range t.SiafundOutputs { 149 sfoid := t.SiafundOutputID(uint64(j)) 150 objMap[ObjectID(sfoid)] = tid 151 } 152 } 153 154 // Compile the final group of sets. 155 ret := make([][]types.Transaction, 0, len(setMap)) 156 for _, set := range setMap { 157 ret = append(ret, set) 158 } 159 return ret 160 } 161 162 // purge removes all transactions from the transaction pool. 163 func (tp *TransactionPool) purge() { 164 tp.knownObjects = make(map[ObjectID]TransactionSetID) 165 tp.transactionSets = make(map[TransactionSetID][]types.Transaction) 166 tp.transactionSetDiffs = make(map[TransactionSetID]*modules.ConsensusChange) 167 tp.transactionListSize = 0 168 } 169 170 // ProcessConsensusChange gets called to inform the transaction pool of changes 171 // to the consensus set. 172 func (tp *TransactionPool) ProcessConsensusChange(cc modules.ConsensusChange) { 173 tp.mu.Lock() 174 175 tp.log.Printf("CCID %v (height %v): %v applied blocks, %v reverted blocks", crypto.Hash(cc.ID).String()[:8], tp.blockHeight, len(cc.AppliedBlocks), len(cc.RevertedBlocks)) 176 177 // Get the recent block ID for a sanity check that the consensus change is 178 // being provided to us correctly. 179 resetSanityCheck := false 180 recentID, err := tp.getRecentBlockID(tp.dbTx) 181 if err == errNilRecentBlock { 182 // This almost certainly means that the database hasn't been initialized 183 // yet with a recent block, meaning the user was previously running 184 // v1.3.1 or earlier. 185 tp.log.Println("NOTE: Upgrading tpool database to support consensus change verification.") 186 resetSanityCheck = true 187 } else if err != nil { 188 tp.log.Println("ERROR: Could not access recentID from tpool.") 189 } 190 191 // Update the database of confirmed transactions. 192 for _, block := range cc.RevertedBlocks { 193 // Sanity check - the id of each reverted block should match the recent 194 // parent id. 195 if block.ID() != recentID && !resetSanityCheck { 196 panic("Consensus change series appears to be inconsistent - we are reverting the wrong block.") 197 } 198 recentID = block.ParentID 199 200 if tp.blockHeight > 0 || block.ID() != types.GenesisID { 201 tp.blockHeight-- 202 } 203 for _, txn := range block.Transactions { 204 err := tp.deleteTransaction(tp.dbTx, txn.ID()) 205 if err != nil { 206 tp.log.Println("ERROR: could not delete a transaction:", err) 207 } 208 } 209 210 // Pull the transactions out of the fee summary. For estimating only 211 // over 10 blocks, it is extremely likely that there will be more 212 // applied blocks than reverted blocks, and if there aren't (a height 213 // decreasing reorg), there will be more than 10 applied blocks. 214 if len(tp.recentMedians) > 0 { 215 // Strip out all of the transactions in this block. 216 tp.recentMedians = tp.recentMedians[:len(tp.recentMedians)-1] 217 } 218 } 219 for _, block := range cc.AppliedBlocks { 220 // Sanity check - the parent id of each block should match the current 221 // block id. 222 if block.ParentID != recentID && !resetSanityCheck { 223 panic("Consensus change series appears to be inconsistent - we are applying the wrong block.") 224 } 225 recentID = block.ID() 226 227 if tp.blockHeight > 0 || block.ID() != types.GenesisID { 228 tp.blockHeight++ 229 } 230 for _, txn := range block.Transactions { 231 err := tp.putTransaction(tp.dbTx, txn.ID()) 232 if err != nil { 233 tp.log.Println("ERROR: could not add a transaction:", err) 234 } 235 } 236 237 // Find the median transaction fee for this block. 238 type feeSummary struct { 239 fee types.Currency 240 size int 241 } 242 var fees []feeSummary 243 var totalSize int 244 txnSets := findSets(block.Transactions) 245 for _, set := range txnSets { 246 // Compile the fees for this set. 247 var feeSum types.Currency 248 var sizeSum int 249 b := new(bytes.Buffer) 250 for _, txn := range set { 251 txn.MarshalSia(b) 252 sizeSum += b.Len() 253 b.Reset() 254 for _, fee := range txn.MinerFees { 255 feeSum = feeSum.Add(fee) 256 } 257 } 258 feeAvg := feeSum.Div64(uint64(sizeSum)) 259 fees = append(fees, feeSummary{ 260 fee: feeAvg, 261 size: sizeSum, 262 }) 263 totalSize += sizeSum 264 } 265 // Add an extra zero-fee tranasction for any unused block space. 266 remaining := int(types.BlockSizeLimit) - totalSize 267 fees = append(fees, feeSummary{ 268 fee: types.ZeroCurrency, 269 size: remaining, // fine if remaining is zero. 270 }) 271 // Sort the fees by value and then scroll until the median. 272 sort.Slice(fees, func(i, j int) bool { 273 return fees[i].fee.Cmp(fees[j].fee) < 0 274 }) 275 var progress int 276 for i := range fees { 277 progress += fees[i].size 278 // Instead of grabbing the full median, look at the 75%-ile. It's 279 // going to be cheaper than the 50%-ile, but it still got into a 280 // block. 281 if uint64(progress) > types.BlockSizeLimit/4 { 282 tp.recentMedians = append(tp.recentMedians, fees[i].fee) 283 break 284 } 285 } 286 287 // If there are more than 10 blocks recorded in the txnsPerBlock, strip 288 // off the oldest blocks. 289 for len(tp.recentMedians) > blockFeeEstimationDepth { 290 tp.recentMedians = tp.recentMedians[1:] 291 } 292 } 293 // Grab the median of the recent medians. Copy to a new slice so the sorting 294 // doesn't screw up the slice. 295 safeMedians := make([]types.Currency, len(tp.recentMedians)) 296 copy(safeMedians, tp.recentMedians) 297 sort.Slice(safeMedians, func(i, j int) bool { 298 return safeMedians[i].Cmp(safeMedians[j]) < 0 299 }) 300 tp.recentMedianFee = safeMedians[len(safeMedians)/2] 301 302 // Update all the on-disk structures. 303 err = tp.putRecentConsensusChange(tp.dbTx, cc.ID) 304 if err != nil { 305 tp.log.Println("ERROR: could not update the recent consensus change:", err) 306 } 307 err = tp.putRecentBlockID(tp.dbTx, recentID) 308 if err != nil { 309 tp.log.Println("ERROR: could not store recent block id:", err) 310 } 311 err = tp.putBlockHeight(tp.dbTx, tp.blockHeight) 312 if err != nil { 313 tp.log.Println("ERROR: could not update the block height:", err) 314 } 315 err = tp.putFeeMedian(tp.dbTx, medianPersist{ 316 RecentMedians: tp.recentMedians, 317 RecentMedianFee: tp.recentMedianFee, 318 }) 319 if err != nil { 320 tp.log.Println("ERROR: could not update the transaction pool median fee information:", err) 321 } 322 323 // Scan the applied blocks for transactions that got accepted. This will 324 // help to determine which transactions to remove from the transaction 325 // pool. Having this list enables both efficiency improvements and helps to 326 // clean out transactions with no dependencies, such as arbitrary data 327 // transactions from the host. 328 txids := make(map[types.TransactionID]struct{}) 329 for _, block := range cc.AppliedBlocks { 330 for _, txn := range block.Transactions { 331 txids[txn.ID()] = struct{}{} 332 } 333 } 334 335 // Save all of the current unconfirmed transaction sets into a list. 336 var unconfirmedSets [][]types.Transaction 337 for _, tSet := range tp.transactionSets { 338 // Compile a new transaction set the removes all transactions duplicated 339 // in the block. Though mostly handled by the dependency manager in the 340 // transaction pool, this should both improve efficiency and will strip 341 // out duplicate transactions with no dependencies (arbitrary data only 342 // transactions) 343 var newTSet []types.Transaction 344 for _, txn := range tSet { 345 _, exists := txids[txn.ID()] 346 if !exists { 347 newTSet = append(newTSet, txn) 348 } 349 } 350 unconfirmedSets = append(unconfirmedSets, newTSet) 351 } 352 353 // Purge the transaction pool. Some of the transactions sets may be invalid 354 // after the consensus change. 355 tp.purge() 356 357 // prune transactions older than maxTxnAge. 358 for i, tSet := range unconfirmedSets { 359 var validTxns []types.Transaction 360 for _, txn := range tSet { 361 seenHeight, seen := tp.transactionHeights[txn.ID()] 362 if tp.blockHeight-seenHeight <= maxTxnAge || !seen { 363 validTxns = append(validTxns, txn) 364 } else { 365 delete(tp.transactionHeights, txn.ID()) 366 } 367 } 368 unconfirmedSets[i] = validTxns 369 } 370 371 // Scan through the reverted blocks and re-add any transactions that got 372 // reverted to the tpool. 373 for i := len(cc.RevertedBlocks) - 1; i >= 0; i-- { 374 block := cc.RevertedBlocks[i] 375 for _, txn := range block.Transactions { 376 // Check whether this transaction has already be re-added to the 377 // consensus set by the applied blocks. 378 _, exists := txids[txn.ID()] 379 if exists { 380 continue 381 } 382 383 // Try adding the transaction back into the transaction pool. 384 tp.acceptTransactionSet([]types.Transaction{txn}, cc.TryTransactionSet) // Error is ignored. 385 } 386 } 387 388 // Add all of the unconfirmed transaction sets back to the transaction 389 // pool. The ones that are invalid will throw an error and will not be 390 // re-added. 391 // 392 // Accepting a transaction set requires locking the consensus set (to check 393 // validity). But, ProcessConsensusChange is only called when the consensus 394 // set is already locked, causing a deadlock problem. Therefore, 395 // transactions are readded to the pool in a goroutine, so that this 396 // function can finish and consensus can unlock. The tpool lock is held 397 // however until the goroutine completes. 398 // 399 // Which means that no other modules can require a tpool lock when 400 // processing consensus changes. Overall, the locking is pretty fragile and 401 // more rules need to be put in place. 402 for _, set := range unconfirmedSets { 403 for _, txn := range set { 404 err := tp.acceptTransactionSet([]types.Transaction{txn}, cc.TryTransactionSet) 405 if err != nil { 406 // The transaction is no longer valid, delete it from the 407 // heights map to prevent a memory leak. 408 delete(tp.transactionHeights, txn.ID()) 409 } 410 } 411 } 412 413 // Inform subscribers that an update has executed. 414 tp.mu.Demote() 415 tp.updateSubscribersTransactions() 416 tp.mu.DemotedUnlock() 417 } 418 419 // PurgeTransactionPool deletes all transactions from the transaction pool. 420 func (tp *TransactionPool) PurgeTransactionPool() { 421 tp.mu.Lock() 422 tp.purge() 423 tp.mu.Unlock() 424 }