github.com/deso-protocol/core@v1.2.9/lib/mempool.go (about) 1 package lib 2 3 import ( 4 "container/heap" 5 "container/list" 6 "encoding/hex" 7 "encoding/json" 8 "fmt" 9 "github.com/btcsuite/btcutil" 10 "github.com/gernest/mention" 11 "log" 12 "math" 13 "os" 14 "path/filepath" 15 "regexp" 16 "sort" 17 "strings" 18 "sync/atomic" 19 "time" 20 21 "github.com/dgraph-io/badger/v3" 22 23 "github.com/btcsuite/btcd/btcec" 24 "github.com/deso-protocol/go-deadlock" 25 "github.com/golang/glog" 26 "github.com/pkg/errors" 27 ) 28 29 // mempool.go contains all of the mempool logic for the DeSo node. 30 31 const ( 32 // MaxTotalTransactionSizeBytes is the maximum number of bytes the pool can store 33 // across all of its transactions. Once this limit is reached, transactions must 34 // be evicted from the pool based on their feerate before new transactions can be 35 // added. 36 MaxTotalTransactionSizeBytes = 250000000 // 250MB 37 38 // UnconnectedTxnExpirationInterval is how long we wait before automatically removing an 39 // unconnected transaction. 40 UnconnectedTxnExpirationInterval = time.Minute * 5 41 42 // The maximum number of unconnected transactions the pool will store. 43 MaxUnconnectedTransactions = 10000 44 45 // The maximum number of bytes a single unconnected transaction can take up 46 MaxUnconnectedTxSizeBytes = 100000 47 ) 48 49 var ( 50 // The readOnlyUtxoView will update after the number of seconds specified here OR 51 // the number of transactions specified here, whichever comes first. An update 52 // resets both counters. 53 // 54 // We make these vars rather than const for testing 55 ReadOnlyUtxoViewRegenerationIntervalSeconds = float64(1.0) 56 ReadOnlyUtxoViewRegenerationIntervalTxns = int64(1000) 57 58 // LowFeeTxLimitBytesPerTenMinutes defines the number of bytes per 10 minutes of "low fee" 59 // transactions the mempool will tolerate before it starts rejecting transactions 60 // that fail to meet the MinTxFeePerKBNanos threshold. 61 LowFeeTxLimitBytesPerTenMinutes = 150000 // Allow 150KB per minute in low-fee txns. 62 ) 63 64 // MempoolTx contains a transaction along with additional metadata like the 65 // fee and time added. 66 type MempoolTx struct { 67 Tx *MsgDeSoTxn 68 69 // TxMeta is the transaction metadata 70 TxMeta *TransactionMetadata 71 72 // Hash is a hash of the transaction so we don't have to recompute 73 // it all the time. 74 Hash *BlockHash 75 76 // TxSizeBytes is the cached size of the transaction. 77 TxSizeBytes uint64 78 79 // The time when the txn was added to the pool 80 Added time.Time 81 82 // The block height when the txn was added to the pool. It's generally set 83 // to tip+1. 84 Height uint32 85 86 // The total fee the txn pays. Cached for efficiency reasons. 87 Fee uint64 88 89 // The fee rate of the transaction in nanos per KB. 90 FeePerKB uint64 91 92 // index is used by the heap logic to allow for modification in-place. 93 index int 94 } 95 96 // Summary stats for a set of transactions of a specific type in the mempool. 97 type SummaryStats struct { 98 // Number of transactions of this type in the mempool. 99 Count uint32 100 101 // Number of bytes for transactions of this type in the mempool. 102 TotalBytes uint64 103 } 104 105 func (mempoolTx *MempoolTx) String() string { 106 return fmt.Sprintf("< Added: %v, index: %d, Fee: %d, Type: %v, Hash: %v", mempoolTx.Added, mempoolTx.index, mempoolTx.Fee, mempoolTx.Tx.TxnMeta.GetTxnType(), mempoolTx.Hash) 107 } 108 109 // MempoolTxFeeMinHeap is a priority queue based on transaction fee rate 110 type MempoolTxFeeMinHeap []*MempoolTx 111 112 func (pq MempoolTxFeeMinHeap) Len() int { return len(pq) } 113 114 func (pq MempoolTxFeeMinHeap) Less(i, j int) bool { 115 // We want Pop to give us the lowest-fee transactions so we use < here. 116 return pq[i].FeePerKB < pq[j].FeePerKB 117 } 118 119 func (pq MempoolTxFeeMinHeap) Swap(i, j int) { 120 pq[i], pq[j] = pq[j], pq[i] 121 pq[i].index = i 122 pq[j].index = j 123 } 124 125 func (pq *MempoolTxFeeMinHeap) Push(x interface{}) { 126 n := len(*pq) 127 item := x.(*MempoolTx) 128 item.index = n 129 *pq = append(*pq, item) 130 } 131 132 func (pq *MempoolTxFeeMinHeap) Pop() interface{} { 133 old := *pq 134 n := len(old) 135 item := old[n-1] 136 old[n-1] = nil // avoid memory leak 137 item.index = -1 // for safety 138 *pq = old[0 : n-1] 139 return item 140 } 141 142 // UnconnectedTx is a transaction that has dependencies that we haven't added yet. 143 type UnconnectedTx struct { 144 tx *MsgDeSoTxn 145 // The ID of the Peer who initially sent the unconnected txn. Useful for 146 // removing unconnected transactions when a Peer disconnects. 147 peerID uint64 148 expiration time.Time 149 } 150 151 // DeSoMempool is the core mempool object. It's what any outside service should use 152 // to aggregate transactions and mine them into blocks. 153 type DeSoMempool struct { 154 // Stops the mempool's services. 155 quit chan struct{} 156 157 // A reference to a blockchain object that can be used to validate transactions before 158 // adding them to the pool. 159 bc *Blockchain 160 161 // Transactions with a feerate below this threshold are outright rejected. 162 minFeeRateNanosPerKB uint64 163 164 // rateLimitFeeRateNanosPerKB defines the minimum transaction feerate in "nanos per KB" 165 // before a transaction is considered for rate-limiting. Note that even if a 166 // transaction with a feerate below this threshold is not rate-limited, it must 167 // still have a high enough feerate to be considered as part of the mempool. 168 rateLimitFeeRateNanosPerKB uint64 169 170 mtx deadlock.RWMutex 171 172 // poolMap contains all of the transactions that have been validated by the pool. 173 // Transactions in poolMap should be directly consumable by a miner and formed into 174 // a block by taking them in order of when they were Added. 175 poolMap map[BlockHash]*MempoolTx 176 // txFeeMinHeap organizes transactions stored in poolMap by their FeePerKB. It is used 177 // in order to prevent the pool from exhausing memory due to having to store too 178 // many low-fee transactions. 179 txFeeMinheap MempoolTxFeeMinHeap 180 // totalTxSizeBytes is the total size of all of the transactions stored in poolMap. We 181 // use it to determine when the pool is nearing memory-exhaustion so we can start 182 // evicting transactions. 183 totalTxSizeBytes uint64 184 // Stores the inputs for every transaction stored in poolMap. Used to quickly check 185 // if a transaction is double-spending. 186 outpoints map[UtxoKey]*MsgDeSoTxn 187 // Unconnected contains transactions whose inputs reference UTXOs that are not yet 188 // present in either our UTXO database or the transactions stored in pool. 189 unconnectedTxns map[BlockHash]*UnconnectedTx 190 // Organizes unconnectedTxns by their UTXOs. Used when adding a transaction to determine 191 // which unconnectedTxns are no longer missing parents. 192 unconnectedTxnsByPrev map[UtxoKey]map[BlockHash]*MsgDeSoTxn 193 // An exponentially-decayed accumulator of "low-fee" transactions we've relayed. 194 // This is used to prevent someone from flooding the network with low-fee 195 // transactions. 196 lowFeeTxSizeAccumulator float64 197 // The UNIX time (in seconds) when the last "low-fee" transaction was relayed. 198 lastLowFeeTxUnixTime int64 199 200 // pubKeyToTxnMap stores a mapping from the public key of outputs added 201 // to the mempool to the corresponding transaction that resulted in their 202 // addition. It is useful for figuring out how much DeSo a particular public 203 // key has available to spend. 204 pubKeyToTxnMap map[PkMapKey]map[BlockHash]*MempoolTx 205 206 // The next time the unconnectTxn pool will be scanned for expired unconnectedTxns. 207 nextExpireScan time.Time 208 209 // Optional. When set, we use the BlockCypher API to detect double-spends. 210 blockCypherAPIKey string 211 212 // These two views are used to check whether a transaction is valid before 213 // adding it to the mempool. This is done by applying the transaction to the 214 // backup view, and then restoring the backup view if there's an error. In 215 // the future, if we can figure out an easy way to rollback bad transactions 216 // on a single view, then we won't need the second view anymore. 217 backupUniversalUtxoView *UtxoView 218 universalUtxoView *UtxoView 219 universalTransactionList []*MempoolTx 220 221 // When set, transactions are initially read from this dir and dumped 222 // to this dir. 223 mempoolDir string 224 225 // Whether or not we should be computing readOnlyUtxoViews. 226 generateReadOnlyUtxoView bool 227 // A view that contains a *near* up-to-date snapshot of the mempool. It is 228 // updated periodically after N transactions OR after M seconds, whichever 229 // comes first. It's useful because it can be obtained without acquiring a 230 // lock on the mempool. 231 // 232 // This field isn't reset with ResetPool. It requires an explicit call to 233 // UpdateReadOnlyView. 234 readOnlyUtxoView *UtxoView 235 // Keep a list of all transactions in the mempool. This is useful for dumping 236 // to the database periodically. 237 readOnlyUniversalTransactionList []*MempoolTx 238 readOnlyUniversalTransactionMap map[BlockHash]*MempoolTx 239 readOnlyOutpoints map[UtxoKey]*MsgDeSoTxn 240 // Every time the readOnlyUtxoView is updated, this is incremented. It can 241 // be used by obtainers of the readOnlyUtxoView to wait until a particular 242 // transaction has been run. 243 // 244 // This field isn't reset with ResetPool. It requires an explicit call to 245 // UpdateReadOnlyView. 246 readOnlyUtxoViewSequenceNumber int64 247 // The total number of times we've called processTransaction. Used to 248 // determine whether we should update the readOnlyUtxoView. 249 // 250 // This field isn't reset with ResetPool. It requires an explicit call to 251 // UpdateReadOnlyView. 252 totalProcessTransactionCalls int64 253 254 // We pass a copy of the data dir flag to the tx pool so that we can instantiate 255 // temp badger db instances and dump mempool txns to them. 256 dataDir string 257 } 258 259 // See comment on RemoveUnconnectedTxn. The mempool lock must be called for writing 260 // when calling this function. 261 func (mp *DeSoMempool) removeUnconnectedTxn(tx *MsgDeSoTxn, removeRedeemers bool) { 262 txHash := tx.Hash() 263 if txHash == nil { 264 // If an error occurs hashing the transaction then there's nothing to do. Just 265 // log and reteurn. 266 glog.Error("removeUnconnectedTxn: Problem hashing txn: ") 267 return 268 } 269 unconnectedTxn, exists := mp.unconnectedTxns[*txHash] 270 if !exists { 271 return 272 } 273 274 // Remove the unconnected txn from the unconnectedTxnsByPrev index 275 for _, txIn := range unconnectedTxn.tx.TxInputs { 276 unconnectedTxns, exists := mp.unconnectedTxnsByPrev[UtxoKey(*txIn)] 277 if exists { 278 delete(unconnectedTxns, *txHash) 279 280 // Remove the map entry altogether if there are no 281 // longer any unconnectedTxns which depend on it. 282 if len(unconnectedTxns) == 0 { 283 delete(mp.unconnectedTxnsByPrev, UtxoKey(*txIn)) 284 } 285 } 286 } 287 288 // Remove any unconnectedTxns that spend this txn 289 if removeRedeemers { 290 prevOut := DeSoInput{TxID: *txHash} 291 for txOutIdx := range tx.TxOutputs { 292 prevOut.Index = uint32(txOutIdx) 293 for _, unconnectedTx := range mp.unconnectedTxnsByPrev[UtxoKey(prevOut)] { 294 mp.removeUnconnectedTxn(unconnectedTx, true) 295 } 296 } 297 } 298 299 // Delete the txn from the unconnectedTxn map 300 delete(mp.unconnectedTxns, *txHash) 301 } 302 303 // ResetPool replaces all of the internal data associated with a pool object with the 304 // data of the pool object passed in. It's useful when we want to do a "scorch the earth" 305 // update of the pool by re-processing all of its transactions into a new pool object 306 // first. 307 // 308 // Note the write lock must be held before calling this function. 309 func (mp *DeSoMempool) resetPool(newPool *DeSoMempool) { 310 // Replace the internal mappings of the original pool with the mappings of the new 311 // pool. 312 mp.poolMap = newPool.poolMap 313 mp.txFeeMinheap = newPool.txFeeMinheap 314 mp.totalTxSizeBytes = newPool.totalTxSizeBytes 315 mp.outpoints = newPool.outpoints 316 mp.pubKeyToTxnMap = newPool.pubKeyToTxnMap 317 mp.unconnectedTxns = newPool.unconnectedTxns 318 mp.unconnectedTxnsByPrev = newPool.unconnectedTxnsByPrev 319 mp.nextExpireScan = newPool.nextExpireScan 320 mp.backupUniversalUtxoView = newPool.backupUniversalUtxoView 321 mp.universalUtxoView = newPool.universalUtxoView 322 mp.universalTransactionList = newPool.universalTransactionList 323 324 // We don't adjust blockCypherAPIKey or blockCypherCheckDoubleSpendChan 325 // since those should be unaffected 326 327 // We don't adjust the following fields without an explicit call to 328 // UpdateReadOnlyView. 329 // - runReadOnlyUtxoView bool 330 // - readOnlyUtxoView *UtxoView 331 // - readOnlyUtxoViewSequenceNumber int64 332 // - totalProcessTransactionCalls int64 333 // - readOnlyUniversalTransactionList []*MempoolTx 334 // - readOnlyUniversalTransactionMap map[BlockHash]*MempoolTx 335 // - readOnlyOutpoints map[UtxoKey]*MsgDeSoTxn 336 // 337 // Regenerate the view if needed. 338 if mp.generateReadOnlyUtxoView { 339 mp.regenerateReadOnlyView() 340 } 341 342 // Don't adjust the lowFeeTxSizeAccumulator or the lastLowFeeTxUnixTime since 343 // the old values should be unaffected. 344 } 345 346 // UpdateAfterConnectBlock updates the mempool after a block has been added to the 347 // blockchain. It does this by basically removing all known transactions in the block 348 // from the mempool as follows: 349 // - Build a map of all of the transactions in the block indexed by their hash. 350 // - Create a new mempool object. 351 // - Iterate through all the transactions in the mempool and add the transactions 352 // to the new pool object *only if* they don't appear in the block. Do this for 353 // transactions in the pool and in the unconnectedTx pool. 354 // - Compute which transactions were newly-accepted into the pool by effectively diffing 355 // the new pool's transactions with the old pool's transactions. 356 // - Once the new pool object is up-to-date, the fields of the new pool object 357 // replace the fields of the original pool object. 358 // - Return the newly added transactions computed earlier. 359 // 360 // TODO: This is fairly inefficient but the story is the same as for 361 // UpdateAfterDisconnectBlock. 362 func (mp *DeSoMempool) UpdateAfterConnectBlock(blk *MsgDeSoBlock) (_txnsAddedToMempool []*MempoolTx) { 363 // Protect concurrent access. 364 mp.mtx.Lock() 365 defer mp.mtx.Unlock() 366 367 // Make a map of all the txns in the block except the block reward. 368 txnsInBlock := make(map[BlockHash]bool) 369 for _, txn := range blk.Txns[1:] { 370 txHash := txn.Hash() 371 txnsInBlock[*txHash] = true 372 } 373 374 // Create a new pool object. No need to set the min fees as we're just using this 375 // as a temporary data structure for validation. 376 // 377 // Don't make the new pool object deal with the BlockCypher API. 378 newPool := NewDeSoMempool( 379 mp.bc, 0, /* rateLimitFeeRateNanosPerKB */ 380 0, /* minFeeRateNanosPerKB */ 381 "", /*blockCypherAPIKey*/ 382 false, /*runReadOnlyViewUpdater*/ 383 "" /*dataDir*/, "") 384 385 // Get all the transactions from the old pool object. 386 oldMempoolTxns, oldUnconnectedTxns, err := mp._getTransactionsOrderedByTimeAdded() 387 if err != nil { 388 glog.Warning(errors.Wrapf(err, "UpdateAfterConnectBlock: ")) 389 } 390 391 // Add all the txns from the old pool into the new pool unless they are already 392 // present in the block. 393 394 for _, mempoolTx := range oldMempoolTxns { 395 if _, exists := txnsInBlock[*mempoolTx.Hash]; exists { 396 continue 397 } 398 399 // Attempt to add the txn to the mempool as we go. If it fails that's fine. 400 txnsAccepted, err := newPool.processTransaction( 401 mempoolTx.Tx, true /*allowUnconnected*/, false, /*rateLimit*/ 402 0 /*peerID*/, false /*verifySignatures*/) 403 if err != nil { 404 glog.Warning(errors.Wrapf(err, "UpdateAfterConnectBlock: ")) 405 } 406 if len(txnsAccepted) == 0 { 407 glog.Warningf("UpdateAfterConnectBlock: Dropping txn %v", mempoolTx.Tx) 408 } 409 } 410 411 // Add all the unconnectedTxns from the old pool into the new pool unless they are already 412 // present in the block. 413 for _, unconnectedTx := range oldUnconnectedTxns { 414 // Only add transactions to the pool if they haven't already been added by the 415 // block. 416 unconnectedTxHash := unconnectedTx.tx.Hash() 417 if _, exists := txnsInBlock[*unconnectedTxHash]; exists { 418 continue 419 } 420 421 // Fully process unconnectedTxns 422 rateLimit := false 423 unconnectedTxns := true 424 verifySignatures := false 425 _, err := newPool.processTransaction(unconnectedTx.tx, unconnectedTxns, rateLimit, unconnectedTx.peerID, verifySignatures) 426 if err != nil { 427 glog.Warning(errors.Wrapf(err, "UpdateAfterConnectBlock: ")) 428 } 429 } 430 431 // At this point, the new pool should contain an up-to-date view of the transactions 432 // that should be in the mempool after connecting this block. 433 434 // Figure out what transactions are in the new pool but not in the old pool. These 435 // are transactions that were newly-added as a result of this block clearing up some 436 // dependencies and so we will likely want to relay these transactions. 437 newlyAcceptedTxns := []*MempoolTx{} 438 for poolHash, newMempoolTx := range newPool.poolMap { 439 // No need to copy poolHash since nothing saves a reference to it. 440 if _, txExistsInOldPool := mp.poolMap[poolHash]; !txExistsInOldPool { 441 newlyAcceptedTxns = append(newlyAcceptedTxns, newMempoolTx) 442 } 443 } 444 445 // Now set the fields on the old pool to match the new pool. 446 mp.resetPool(newPool) 447 448 // Return the newly accepted transactions now that we've fully updated our mempool. 449 return newlyAcceptedTxns 450 } 451 452 // UpdateAfterDisconnectBlock updates the mempool to reflect that a block has been 453 // disconnected from the blockchain. It does this by basically adding all the 454 // transactions in the block back to the mempool as follows: 455 // - A new pool object is created containing no transactions. 456 // - The block's transactions are added to this new pool object. This is done in order 457 // to minimize dependency-related conflicts with transactions already in the mempool. 458 // - Then the transactions in the original pool are layered on top of the block's 459 // transactions in the new pool object. Again this is done to avoid dependency 460 // issues since the ordering of <block txns> followed by <original mempool txns> 461 // is much less likely to have issues. 462 // - Then, once the new pool object is up-to-date, the fields of the new pool object 463 // replace the fields of the original pool object. 464 // 465 // This function is safe for concurrent access. It is assumed the ChainLock is 466 // held before this function is a accessed. 467 // 468 // TODO: This is fairly inefficient and basically only necessary because computing a 469 // transaction's dependencies is a little shaky. If we end up making the dependency 470 // detection logic more robust then we could come back here and change this so that 471 // we're not effectively reprocessing the entire mempool every time we have a new block. 472 // But until then doing it this way significantly reduces complexity and should hold up 473 // for a while. 474 func (mp *DeSoMempool) UpdateAfterDisconnectBlock(blk *MsgDeSoBlock) { 475 // Protect concurrent access. 476 mp.mtx.Lock() 477 defer mp.mtx.Unlock() 478 479 // Create a new DeSoMempool. No need to set the min fees since we're just using 480 // this as a temporary data structure for validation. 481 // 482 // Don't make the new pool object deal with the BlockCypher API. 483 newPool := NewDeSoMempool(mp.bc, 0, /* rateLimitFeeRateNanosPerKB */ 484 0, /* minFeeRateNanosPerKB */ 485 "" /*blockCypherAPIKey*/, false, 486 "" /*dataDir*/, "") 487 488 // Add the transactions from the block to the new pool (except for the block reward, 489 // which should always be the first transaction). Break out if we encounter 490 // an error. 491 for _, txn := range blk.Txns[1:] { 492 // For transactions being added from the block just set the peerID to zero. It 493 // shouldn't matter since these transactions won't be unconnectedTxns. 494 rateLimit := false 495 allowUnconnectedTxns := false 496 peerID := uint64(0) 497 verifySignatures := false 498 _, err := newPool.processTransaction(txn, allowUnconnectedTxns, rateLimit, peerID, verifySignatures) 499 if err != nil { 500 // Log errors but don't stop adding transactions. We do this because we'd prefer 501 // to drop a transaction here or there rather than lose the whole block because 502 // of one bad apple. 503 glog.Warning(errors.Wrapf(err, "UpdateAfterDisconnectBlock: ")) 504 } 505 } 506 507 // At this point the block txns have been added to the new pool. Now we need to 508 // add the txns from the original pool. Start by fetching them in slice form. 509 oldMempoolTxns, oldUnconnectedTxns, err := mp._getTransactionsOrderedByTimeAdded() 510 if err != nil { 511 glog.Warning(errors.Wrapf(err, "UpdateAfterDisconnectBlock: ")) 512 } 513 // Iterate through the pool transactions and add them to our new pool. 514 515 for _, mempoolTx := range oldMempoolTxns { 516 // Attempt to add the txn to the mempool as we go. If it fails that's fine. 517 txnsAccepted, err := newPool.processTransaction( 518 mempoolTx.Tx, true /*allowUnconnectedTxns*/, false, /*rateLimit*/ 519 0 /*peerID*/, false /*verifySignatures*/) 520 if err != nil { 521 glog.Warning(errors.Wrapf(err, "UpdateAfterDisconnectBlock: ")) 522 } 523 if len(txnsAccepted) == 0 { 524 glog.Warningf("UpdateAfterDisconnectBlock: Dropping txn %v", mempoolTx.Tx) 525 } 526 } 527 528 // Iterate through the unconnectedTxns and add them to our new pool as well. 529 for _, oTx := range oldUnconnectedTxns { 530 rateLimit := false 531 allowUnconnectedTxns := true 532 verifySignatures := false 533 _, err := newPool.processTransaction(oTx.tx, allowUnconnectedTxns, rateLimit, oTx.peerID, verifySignatures) 534 if err != nil { 535 glog.Warning(errors.Wrapf(err, "UpdateAfterDisconnectBlock: ")) 536 } 537 } 538 539 // At this point the new mempool should be a duplicate of the original mempool but with 540 // the block's transactions added (with timestamps set before the transactions that 541 // were in the original pool. 542 543 // Replace the internal mappings of the original pool with the mappings of the new 544 // pool. 545 mp.resetPool(newPool) 546 } 547 548 // Acquires a read lock before returning the transactions. 549 func (mp *DeSoMempool) GetTransactionsOrderedByTimeAdded() (_poolTxns []*MempoolTx, _unconnectedTxns []*UnconnectedTx, _err error) { 550 poolTxns := []*MempoolTx{} 551 poolTxns = append(poolTxns, mp.readOnlyUniversalTransactionList...) 552 553 // Sort and return the txns. 554 sort.Slice(poolTxns, func(ii, jj int) bool { 555 return poolTxns[ii].Added.Before(poolTxns[jj].Added) 556 }) 557 558 /* 559 // TODO: We need to support unconnectedTxns as part of the readOnly infrastructure. 560 unconnectedTxns := []*UnconnectedTx{} 561 for _, oTx := range mp.readOnly { 562 unconnectedTxns = append(unconnectedTxns, oTx) 563 } 564 */ 565 566 return poolTxns, nil, nil 567 } 568 569 func (mp *DeSoMempool) GetTransaction(txId *BlockHash) (txn *MempoolTx) { 570 return mp.readOnlyUniversalTransactionMap[*txId] 571 } 572 573 // GetTransactionsOrderedByTimeAdded returns all transactions in the mempool ordered 574 // by when they were added to the mempool. 575 func (mp *DeSoMempool) _getTransactionsOrderedByTimeAdded() (_poolTxns []*MempoolTx, _unconnectedTxns []*UnconnectedTx, _err error) { 576 poolTxns := []*MempoolTx{} 577 for _, mempoolTx := range mp.poolMap { 578 poolTxns = append(poolTxns, mempoolTx) 579 } 580 // Sort the list based on when the transactions were added. 581 sort.Slice(poolTxns, func(ii, jj int) bool { 582 return poolTxns[ii].Added.Before(poolTxns[jj].Added) 583 }) 584 585 unconnectedTxns := []*UnconnectedTx{} 586 for _, oTx := range mp.unconnectedTxns { 587 unconnectedTxns = append(unconnectedTxns, oTx) 588 } 589 590 return poolTxns, unconnectedTxns, nil 591 } 592 593 // Evicts unconnectedTxns if we're over the maximum number of unconnectedTxns allowed, or if 594 // unconnectedTxns have exired. Must be called with the write lock held. 595 func (mp *DeSoMempool) limitNumUnconnectedTxns() error { 596 if now := time.Now(); now.After(mp.nextExpireScan) { 597 prevNumUnconnectedTxns := len(mp.unconnectedTxns) 598 for _, unconnectedTxn := range mp.unconnectedTxns { 599 if now.After(unconnectedTxn.expiration) { 600 mp.removeUnconnectedTxn(unconnectedTxn.tx, true) 601 } 602 } 603 604 numUnconnectedTxns := len(mp.unconnectedTxns) 605 if numExpired := prevNumUnconnectedTxns - numUnconnectedTxns; numExpired > 0 { 606 glog.V(1).Infof("Expired %d unconnectedTxns (remaining: %d)", numExpired, numUnconnectedTxns) 607 } 608 } 609 610 if len(mp.unconnectedTxns)+1 <= MaxUnconnectedTransactions { 611 return nil 612 } 613 614 for _, otx := range mp.unconnectedTxns { 615 mp.removeUnconnectedTxn(otx.tx, false) 616 break 617 } 618 619 return nil 620 } 621 622 // Adds an unconnected txn to the pool. Must be called with the write lock held. 623 func (mp *DeSoMempool) addUnconnectedTxn(tx *MsgDeSoTxn, peerID uint64) { 624 if MaxUnconnectedTransactions <= 0 { 625 return 626 } 627 628 mp.limitNumUnconnectedTxns() 629 630 txHash := tx.Hash() 631 if txHash == nil { 632 glog.Error(fmt.Errorf("addUnconnectedTxn: Problem hashing txn: ")) 633 return 634 } 635 mp.unconnectedTxns[*txHash] = &UnconnectedTx{ 636 tx: tx, 637 peerID: peerID, 638 expiration: time.Now().Add(UnconnectedTxnExpirationInterval), 639 } 640 for _, txIn := range tx.TxInputs { 641 if _, exists := mp.unconnectedTxnsByPrev[UtxoKey(*txIn)]; !exists { 642 mp.unconnectedTxnsByPrev[UtxoKey(*txIn)] = 643 make(map[BlockHash]*MsgDeSoTxn) 644 } 645 mp.unconnectedTxnsByPrev[UtxoKey(*txIn)][*txHash] = tx 646 } 647 648 glog.V(1).Infof("Added unconnected transaction %v with total txns: %d)", txHash, len(mp.unconnectedTxns)) 649 } 650 651 // Consider adding an unconnected txn to the pool. Must be called with the write lock held. 652 func (mp *DeSoMempool) tryAddUnconnectedTxn(tx *MsgDeSoTxn, peerID uint64) error { 653 txBytes, err := tx.ToBytes(false) 654 if err != nil { 655 return errors.Wrapf(err, "tryAddUnconnectedTxn: Problem serializing txn: ") 656 } 657 serializedLen := len(txBytes) 658 if serializedLen > MaxUnconnectedTxSizeBytes { 659 return TxErrorTooLarge 660 } 661 662 mp.addUnconnectedTxn(tx, peerID) 663 664 return nil 665 } 666 667 // Remove unconnectedTxns that are no longer valid after applying the passed-in txn. 668 func (mp *DeSoMempool) removeUnconnectedTxnDoubleSpends(tx *MsgDeSoTxn) { 669 for _, txIn := range tx.TxInputs { 670 for _, unconnectedTx := range mp.unconnectedTxnsByPrev[UtxoKey(*txIn)] { 671 mp.removeUnconnectedTxn(unconnectedTx, true) 672 } 673 } 674 } 675 676 // Must be called with the write lock held. 677 func (mp *DeSoMempool) isTransactionInPool(hash *BlockHash) bool { 678 if _, exists := mp.poolMap[*hash]; exists { 679 return true 680 } 681 682 return false 683 } 684 685 // Whether or not a txn is in the pool. Safe for concurrent access. 686 func (mp *DeSoMempool) IsTransactionInPool(hash *BlockHash) bool { 687 _, exists := mp.readOnlyUniversalTransactionMap[*hash] 688 return exists 689 } 690 691 // Whether or not an unconnected txn is in the unconnected pool. Must be called with the write 692 // lock held. 693 func (mp *DeSoMempool) isUnconnectedTxnInPool(hash *BlockHash) bool { 694 if _, exists := mp.unconnectedTxns[*hash]; exists { 695 return true 696 } 697 698 return false 699 } 700 701 func (mp *DeSoMempool) DumpTxnsToDB() { 702 // Dump all mempool txns into data_dir_path/temp_mempool_dump. 703 err := mp.OpenTempDBAndDumpTxns() 704 if err != nil { 705 glog.Infof("DumpTxnsToDB: Problem opening temp db / dumping mempool txns: %v", err) 706 return 707 } 708 709 // Now we shuffle the directories we created. The temp that we just created will become 710 // the latest dump and the latest dump will become the previous dump. By doing this 711 // shuffle, we ensure that we always have a complete view of the mempool to load from. 712 tempDir := filepath.Join(mp.mempoolDir, "temp_mempool_dump") 713 previousDir := filepath.Join(mp.mempoolDir, "previous_mempool_dump") 714 latestDir := filepath.Join(mp.mempoolDir, "latest_mempool_dump") 715 716 // If latestDir exists, move latestDir --> previousDir. 717 // Check that latestDir exists before trying to move it. 718 _, err = os.Stat(latestDir) 719 if err == nil { 720 err = os.RemoveAll(previousDir) 721 if err != nil { 722 glog.Infof("DumpTxnsToDB: Problem deleting previous dir: %v", err) 723 return 724 } 725 err = os.Rename(latestDir, previousDir) 726 if err != nil { 727 glog.Infof("DumpTxnsToDB: Problem moving latest mempool dir to previous: %v", err) 728 return 729 } 730 } 731 732 // Move tempDir --> latestDir. No need to delete latestDir, it was renamed above. 733 err = os.Rename(tempDir, latestDir) 734 if err != nil { 735 glog.Infof("DumpTxnsToDB: Problem moving temp mempool dir to previous: %v", err) 736 return 737 } 738 } 739 740 // This function attempts to make the file path provided. Returns an =errors if a parent 741 // directory in the path does not exist or another error is encountered. 742 // User permissions are set to "rwx" so that it can be manipulated. 743 // See: https://stackoverflow.com/questions/14249467/os-mkdir-and-os-mkdirall-permission-value/31151508 744 func MakeDirIfNonExistent(filePath string) error { 745 _, err := os.Stat(filePath) 746 if os.IsNotExist(err) { 747 err = os.Mkdir(filePath, 0700) 748 if err != nil { 749 return fmt.Errorf("OpenTempDBAndDumpTxns: Error making dir: %v", err) 750 } 751 } else if err != nil { 752 return fmt.Errorf("OpenTempDBAndDumpTxns: os.Stat() error: %v", err) 753 } 754 return nil 755 } 756 757 func (mp *DeSoMempool) OpenTempDBAndDumpTxns() error { 758 allTxns := mp.readOnlyUniversalTransactionList 759 760 tempMempoolDBDir := filepath.Join(mp.mempoolDir, "temp_mempool_dump") 761 glog.Infof("OpenTempDBAndDumpTxns: Opening new temp db %v", tempMempoolDBDir) 762 // Make the top-level folder if it doesn't exist. 763 err := MakeDirIfNonExistent(mp.mempoolDir) 764 if err != nil { 765 return fmt.Errorf("OpenTempDBAndDumpTxns: Error making top-level dir: %v", err) 766 } 767 tempMempoolDBOpts := badger.DefaultOptions(tempMempoolDBDir) 768 tempMempoolDBOpts.ValueDir = tempMempoolDBDir 769 tempMempoolDBOpts.MemTableSize = 1024 << 20 770 tempMempoolDB, err := badger.Open(tempMempoolDBOpts) 771 if err != nil { 772 return fmt.Errorf("OpenTempDBAndDumpTxns: Could not open temp db to dump mempool: %v", err) 773 } 774 defer tempMempoolDB.Close() 775 776 // Dump txns into the temp mempool db. 777 startTime := time.Now() 778 // Flush the new mempool state to the DB. 779 // 780 // Dump 1k txns at a time to avoid overwhelming badger 781 txnsToDump := []*MempoolTx{} 782 for ii, mempoolTx := range allTxns { 783 txnsToDump = append(txnsToDump, mempoolTx) 784 // If we're at a multiple of 1k or we're at the end of the list 785 // then dump the txns to disk 786 if len(txnsToDump)%1000 == 0 || ii == len(allTxns)-1 { 787 glog.Infof("OpenTempDBAndDumpTxns: Dumping txns %v to %v", ii-len(txnsToDump)+1, ii) 788 err := tempMempoolDB.Update(func(txn *badger.Txn) error { 789 return FlushMempoolToDbWithTxn(txn, txnsToDump) 790 }) 791 if err != nil { 792 return fmt.Errorf("OpenTempDBAndDumpTxns: Error flushing mempool txns to DB: %v", err) 793 } 794 txnsToDump = []*MempoolTx{} 795 } 796 } 797 endTime := time.Now() 798 glog.Infof("OpenTempDBAndDumpTxns: Full txn dump of %v txns completed "+ 799 "in %v seconds. Safe to reboot node", len(allTxns), endTime.Sub(startTime).Seconds()) 800 return nil 801 } 802 803 // Adds a txn to the pool. This function does not do any validation, and so it should 804 // only be called when one is sure that a transaction is valid. Otherwise, it could 805 // mess up the UtxoViews that we store internally. 806 func (mp *DeSoMempool) addTransaction( 807 tx *MsgDeSoTxn, height uint32, fee uint64, updateBackupView bool) (*MempoolTx, error) { 808 809 // Add the transaction to the pool and mark the referenced outpoints 810 // as spent by the pool. 811 txBytes, err := tx.ToBytes(false) 812 if err != nil { 813 return nil, errors.Wrapf(err, "addTransaction: Problem serializing txn: ") 814 } 815 serializedLen := uint64(len(txBytes)) 816 817 txHash := tx.Hash() 818 if txHash == nil { 819 return nil, errors.Wrapf(err, "addTransaction: Problem hashing tx: ") 820 } 821 822 // If this txn would put us over our threshold then don't accept it. 823 // 824 // TODO: We don't replace txns in the mempool right now. Instead, a node can be 825 // rebooted with a higher fee if the transactions start to get rejected due to 826 // the mempool being full. 827 if serializedLen+mp.totalTxSizeBytes > MaxTotalTransactionSizeBytes { 828 return nil, errors.Wrapf(TxErrorInsufficientFeePriorityQueue, "addTransaction: ") 829 } 830 831 // At this point we are certain that the mempool has enough room to accomodate 832 // this transaction. 833 834 mempoolTx := &MempoolTx{ 835 Tx: tx, 836 Hash: txHash, 837 TxSizeBytes: uint64(serializedLen), 838 Added: time.Now(), 839 Height: height, 840 Fee: fee, 841 FeePerKB: fee * 1000 / serializedLen, 842 // index will be set by the heap code. 843 } 844 845 // Add the transaction to the main pool map. 846 mp.poolMap[*txHash] = mempoolTx 847 // Add the transaction to the outpoints map. 848 for _, txIn := range tx.TxInputs { 849 mp.outpoints[UtxoKey(*txIn)] = tx 850 } 851 // Add the transaction to the min heap. 852 heap.Push(&mp.txFeeMinheap, mempoolTx) 853 // Update the size of the mempool to reflect the added transaction. 854 mp.totalTxSizeBytes += mempoolTx.TxSizeBytes 855 856 // Whenever transactions are accepted into the mempool, add a mapping 857 // for each public key that they send an output to. This is useful so 858 // we can find all of these outputs if, for example, the user wants 859 // to know her balance while factoring in mempool transactions. 860 mp._addMempoolTxToPubKeyOutputMap(mempoolTx) 861 862 // Add it to the universal view. We assume the txn was already added to the 863 // backup view. 864 _, _, _, _, err = mp.universalUtxoView._connectTransaction(mempoolTx.Tx, mempoolTx.Hash, int64(mempoolTx.TxSizeBytes), height, 865 false /*verifySignatures*/, false /*ignoreUtxos*/) 866 if err != nil { 867 return nil, fmt.Errorf("ERROR addTransaction: _connectTransaction " + 868 "failed on universalUtxoView; this is a HUGE problem and should never happen") 869 } 870 // Add it to the universalTransactionList if it made it through the view 871 mp.universalTransactionList = append(mp.universalTransactionList, mempoolTx) 872 if updateBackupView { 873 _, _, _, _, err = mp.backupUniversalUtxoView._connectTransaction(mempoolTx.Tx, mempoolTx.Hash, int64(mempoolTx.TxSizeBytes), height, 874 false /*verifySignatures*/, false /*ignoreUtxos*/) 875 if err != nil { 876 return nil, fmt.Errorf("ERROR addTransaction: _connectTransaction " + 877 "failed on backupUniversalUtxoView; this is a HUGE problem and should never happen") 878 } 879 } 880 881 return mempoolTx, nil 882 } 883 884 func (mp *DeSoMempool) CheckSpend(op UtxoKey) *MsgDeSoTxn { 885 txR := mp.readOnlyOutpoints[op] 886 887 return txR 888 } 889 890 // GetAugmentedUtxoViewForPublicKey creates a UtxoView that has connected all of 891 // the transactions that could result in utxos for the passed-in public key 892 // plus all of the dependencies of those transactions. This is useful for 893 // when we want to validate a transaction that builds on a transaction that has 894 // not yet been mined into a block. It is also useful for when we want to fetch all 895 // the unspent UtxoEntrys factoring in what's been spent by transactions in 896 // the mempool. 897 func (mp *DeSoMempool) GetAugmentedUtxoViewForPublicKey(pkBytes []byte, optionalTxn *MsgDeSoTxn) (*UtxoView, error) { 898 return mp.GetAugmentedUniversalView() 899 } 900 901 // GetAugmentedUniversalView creates a view that just connects everything 902 // in the mempool... 903 // TODO(performance): We should make a read-only version of the universal view that 904 // you can get from the mempool. 905 func (mp *DeSoMempool) GetAugmentedUniversalView() (*UtxoView, error) { 906 newView, err := mp.readOnlyUtxoView.CopyUtxoView() 907 if err != nil { 908 return nil, err 909 } 910 return newView, nil 911 } 912 913 func (mp *DeSoMempool) FetchTransaction(txHash *BlockHash) *MempoolTx { 914 if mempoolTx, exists := mp.readOnlyUniversalTransactionMap[*txHash]; exists { 915 return mempoolTx 916 } 917 return nil 918 } 919 920 // TODO(performance): This function is slow, and the only reason we have it is because 921 // we need to validate BitcoinExchange transactions both before they have valid merkle 922 // proofs and *after* they have valid merkle proofs. In the latter case we can't use 923 // the universal view because the transaction is in the "middle" of the sorted list of 924 // transactions ordered by time added. 925 func (mp *DeSoMempool) _quickCheckBitcoinExchangeTxn( 926 tx *MsgDeSoTxn, txHash *BlockHash, checkMerkleProof bool) ( 927 _fees uint64, _err error) { 928 929 // Create a view that we'll use to validate this txn. 930 // 931 // Note that it is safe to use this because we expect that the blockchain 932 // lock is held for the duration of this function call so there shouldn't 933 // be any shifting of the db happening beneath our fee. 934 utxoView, err := NewUtxoView(mp.bc.db, mp.bc.params, mp.bc.postgres) 935 if err != nil { 936 return 0, errors.Wrapf(err, 937 "_helpConnectDepsAndFinalTxn: Problem initializing UtxoView") 938 } 939 940 // Connnect all of this transaction's dependencies to the UtxoView in order. Note 941 // that we can do this because _findMempoolDependencies returns the transactions in 942 // sorted order based on when transactions were added. 943 bestHeight := uint32(mp.bc.blockTip().Height + 1) 944 // Don't verify signatures since this transaction is already in the mempool. 945 // 946 // Additionally mempool verification does not require that BitcoinExchange 947 // transactions meet the MinBurnWork requirement. Note that a BitcoinExchange 948 // transaction will only get this far once we are positive the BitcoinManager 949 // has the block corresponding to the transaction. 950 // We skip verifying txn size for bitcoin exchange transactions. 951 _, _, _, txFee, err := utxoView._connectTransaction( 952 tx, txHash, 0, bestHeight, false, false) 953 if err != nil { 954 // Note this can happen in odd cases where a transaction's dependency was removed 955 // but the transaction depending on it was not. See the comment on 956 // _findMempoolDependencies for more info on this case. 957 return 0, errors.Wrapf( 958 err, "_helpConnectDepsAndFinalTxn: Problem connecting "+ 959 "transaction dependency: ") 960 } 961 962 return txFee, nil 963 } 964 965 func (mp *DeSoMempool) rebuildBackupView() { 966 // We need to rebuild the backup view since the _connectTransaction broke it. 967 var copyErr error 968 mp.backupUniversalUtxoView, copyErr = mp.universalUtxoView.CopyUtxoView() 969 if copyErr != nil { 970 glog.Errorf("ERROR tryAcceptTransaction: Problem copying "+ 971 "view. This should NEVER happen: %v", copyErr) 972 } 973 } 974 975 // See TryAcceptTransaction. The write lock must be held when calling this function. 976 // 977 // TODO: Allow replacing a transaction with a higher fee. 978 func (mp *DeSoMempool) tryAcceptTransaction( 979 tx *MsgDeSoTxn, rateLimit bool, rejectDupUnconnected bool, verifySignatures bool) ( 980 _missingParents []*BlockHash, _mempoolTx *MempoolTx, _err error) { 981 982 // Block reward transactions shouldn't appear individually 983 if tx.TxnMeta != nil && tx.TxnMeta.GetTxnType() == TxnTypeBlockReward { 984 return nil, nil, TxErrorIndividualBlockReward 985 } 986 987 // Compute the hash of the transaction. 988 txHash := tx.Hash() 989 if txHash == nil { 990 return nil, nil, fmt.Errorf("tryAcceptTransaction: Problem computing tx hash: ") 991 } 992 993 // Reject the txn if it already exists. 994 if mp.isTransactionInPool(txHash) || (rejectDupUnconnected && 995 mp.isUnconnectedTxnInPool(txHash)) { 996 997 return nil, nil, TxErrorDuplicate 998 } 999 1000 // Iterate over the transaction's inputs. If any of them don't have utxos in the 1001 // UtxoView that are unspent at this point then the transaction is an unconnected 1002 // txn. Use a map to ensure there are no duplicates. 1003 missingParentsMap := make(map[BlockHash]bool) 1004 for _, txIn := range tx.TxInputs { 1005 utxoKey := UtxoKey(*txIn) 1006 utxoEntry := mp.universalUtxoView.GetUtxoEntryForUtxoKey(&utxoKey) 1007 if utxoEntry == nil { 1008 missingParentsMap[utxoKey.TxID] = true 1009 } 1010 } 1011 if len(missingParentsMap) > 0 { 1012 var missingParents []*BlockHash 1013 for txID := range missingParentsMap { 1014 // Must make a copy of the hash here since the iterator 1015 // is replaced and taking its address directly would 1016 // result in all of the entries pointing to the same 1017 // memory location and thus all be the final hash. 1018 hashCopy := txID 1019 missingParents = append(missingParents, &hashCopy) 1020 } 1021 return missingParents, nil, nil 1022 } 1023 1024 // Attempt to add the transaction to the backup view. If it fails, reconstruct the backup 1025 // view and return an error. 1026 totalNanosPurchasedBefore := mp.backupUniversalUtxoView.NanosPurchased 1027 usdCentsPerBitcoinBefore := mp.backupUniversalUtxoView.GetCurrentUSDCentsPerBitcoin() 1028 bestHeight := uint32(mp.bc.blockTip().Height + 1) 1029 // We can skip verifying the transaction size as related to the minimum fee here. 1030 utxoOps, totalInput, totalOutput, txFee, err := mp.backupUniversalUtxoView._connectTransaction( 1031 tx, txHash, 0, bestHeight, verifySignatures, false) 1032 if err != nil { 1033 mp.rebuildBackupView() 1034 return nil, nil, errors.Wrapf(err, "tryAcceptTransaction: Problem "+ 1035 "connecting transaction after connecting dependencies: ") 1036 } 1037 1038 // Compute the feerate for this transaction for use below. 1039 txBytes, err := tx.ToBytes(false) 1040 if err != nil { 1041 mp.rebuildBackupView() 1042 return nil, nil, errors.Wrapf(err, "tryAcceptTransaction: Problem serializing txn: ") 1043 } 1044 serializedLen := uint64(len(txBytes)) 1045 txFeePerKB := txFee * 1000 / serializedLen 1046 1047 // Transactions with a feerate below the minimum threshold will be outright 1048 // rejected. This is the first line of defense against attacks against the 1049 // mempool. 1050 if rateLimit && txFeePerKB < mp.minFeeRateNanosPerKB { 1051 errRet := fmt.Errorf("tryAcceptTransaction: Fee rate per KB found was %d, which is below the "+ 1052 "minimum required which is %d (= %d * %d / 1000). Total input: %d, total output: %d, "+ 1053 "txn hash: %v, txn hex: %v", 1054 txFeePerKB, mp.minFeeRateNanosPerKB, mp.minFeeRateNanosPerKB, serializedLen, 1055 totalInput, totalOutput, txHash, hex.EncodeToString(txBytes)) 1056 glog.Error(errRet) 1057 mp.rebuildBackupView() 1058 return nil, nil, errors.Wrapf(TxErrorInsufficientFeeMinFee, errRet.Error()) 1059 } 1060 1061 // If the transaction is bigger than half the maximum allowable size, 1062 // then reject it. 1063 maxTxnSize := mp.bc.params.MinerMaxBlockSizeBytes / 2 1064 if serializedLen > maxTxnSize { 1065 mp.rebuildBackupView() 1066 return nil, nil, errors.Wrapf(err, "tryAcceptTransaction: "+ 1067 "Txn size %v exceeds maximum allowable txn size %v", serializedLen, maxTxnSize) 1068 } 1069 1070 // If the feerate is below the minimum we've configured for the node, then apply 1071 // some rate-limiting logic to avoid stalling in situations in which someone is trying 1072 // to flood the network with low-value transacitons. This avoids a form of amplification 1073 // DDOS attack brought on by the fact that a single broadcast results in all nodes 1074 // communicating with each other. 1075 if rateLimit && txFeePerKB < mp.rateLimitFeeRateNanosPerKB { 1076 nowUnix := time.Now().Unix() 1077 1078 // Exponentially decay the accumulator by a factor of 2 every 10m. 1079 mp.lowFeeTxSizeAccumulator /= math.Pow(2.0, 1080 float64(nowUnix-mp.lastLowFeeTxUnixTime)/(10*60)) 1081 mp.lastLowFeeTxUnixTime = nowUnix 1082 1083 // Check to see if the accumulator is over the limit. 1084 if mp.lowFeeTxSizeAccumulator >= float64(LowFeeTxLimitBytesPerTenMinutes) { 1085 mp.rebuildBackupView() 1086 return nil, nil, TxErrorInsufficientFeeRateLimit 1087 } 1088 1089 // Update the accumulator and potentially log the state. 1090 oldTotal := mp.lowFeeTxSizeAccumulator 1091 mp.lowFeeTxSizeAccumulator += float64(serializedLen) 1092 glog.V(2).Infof("tryAcceptTransaction: Rate limit current total ~(%v) bytes/10m, nextTotal: ~(%v) bytes/10m, "+ 1093 "limit ~(%v) bytes/10m", oldTotal, mp.lowFeeTxSizeAccumulator, LowFeeTxLimitBytesPerTenMinutes) 1094 } 1095 1096 // Add to transaction pool. Don't update the backup view since the call above 1097 // will have already done this. 1098 mempoolTx, err := mp.addTransaction(tx, bestHeight, txFee, false /*updateBackupUniversalView*/) 1099 if err != nil { 1100 mp.rebuildBackupView() 1101 return nil, nil, errors.Wrapf(err, "tryAcceptTransaction: ") 1102 } 1103 1104 // Calculate metadata 1105 txnMeta, err := ComputeTransactionMetadata(tx, mp.backupUniversalUtxoView, nil, totalNanosPurchasedBefore, 1106 usdCentsPerBitcoinBefore, totalInput, totalOutput, txFee, uint64(0), utxoOps) 1107 if err == nil { 1108 mempoolTx.TxMeta = txnMeta 1109 } 1110 1111 glog.V(2).Infof("tryAcceptTransaction: Accepted transaction %v (pool size: %v)", txHash, 1112 len(mp.poolMap)) 1113 1114 return nil, mempoolTx, nil 1115 } 1116 1117 func ComputeTransactionMetadata(txn *MsgDeSoTxn, utxoView *UtxoView, blockHash *BlockHash, 1118 totalNanosPurchasedBefore uint64, usdCentsPerBitcoinBefore uint64, totalInput uint64, totalOutput uint64, 1119 fees uint64, txnIndexInBlock uint64, utxoOps []*UtxoOperation) (*TransactionMetadata, error) { 1120 1121 var err error 1122 txnMeta := &TransactionMetadata{ 1123 TxnIndexInBlock: txnIndexInBlock, 1124 TxnType: txn.TxnMeta.GetTxnType().String(), 1125 1126 // This may be overwritten later on, for example if we're dealing with a 1127 // BitcoinExchange txn which doesn't set the txn.PublicKey 1128 TransactorPublicKeyBase58Check: PkToString(txn.PublicKey, utxoView.Params), 1129 1130 // General transaction metadata 1131 BasicTransferTxindexMetadata: &BasicTransferTxindexMetadata{ 1132 TotalInputNanos: totalInput, 1133 TotalOutputNanos: totalOutput, 1134 FeeNanos: fees, 1135 // TODO: This doesn't add much value, and it makes output hard to read because 1136 // it's so long so I'm commenting it out for now. 1137 //UtxoOpsDump: spew.Sdump(utxoOps), 1138 1139 // We need to include the utxoOps because it allows us to compute implicit 1140 // outputs. 1141 UtxoOps: utxoOps, 1142 }, 1143 1144 TxnOutputs: txn.TxOutputs, 1145 } 1146 1147 if blockHash != nil { 1148 txnMeta.BlockHashHex = hex.EncodeToString(blockHash[:]) 1149 } 1150 1151 extraData := txn.ExtraData 1152 1153 // Set the affected public keys for the basic transfer. 1154 for _, output := range txn.TxOutputs { 1155 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1156 PublicKeyBase58Check: PkToString(output.PublicKey, utxoView.Params), 1157 Metadata: "BasicTransferOutput", 1158 }) 1159 } 1160 1161 if txn.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 1162 txnMeta.BitcoinExchangeTxindexMetadata, txnMeta.TransactorPublicKeyBase58Check, err = 1163 _computeBitcoinExchangeFields(utxoView.Params, txn.TxnMeta.(*BitcoinExchangeMetadata), 1164 totalNanosPurchasedBefore, usdCentsPerBitcoinBefore) 1165 if err != nil { 1166 return nil, fmt.Errorf( 1167 "UpdateTxindex: Error computing BitcoinExchange txn metadata: %v", err) 1168 } 1169 1170 // Set the nanos purchased before/after. 1171 txnMeta.BitcoinExchangeTxindexMetadata.TotalNanosPurchasedBefore = totalNanosPurchasedBefore 1172 txnMeta.BitcoinExchangeTxindexMetadata.TotalNanosPurchasedAfter = utxoView.NanosPurchased 1173 1174 // Always associate BitcoinExchange txns with the burn public key. This makes it 1175 // // easy to enumerate all burn txns in the block explorer. 1176 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1177 PublicKeyBase58Check: BurnPubKeyBase58Check, 1178 Metadata: "BurnPublicKey", 1179 }) 1180 } 1181 if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 1182 // Get the txn metadata 1183 realTxMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1184 1185 // Rosetta needs to know the change in DESOLockedNanos so it can model the change in 1186 // total deso locked in the creator coin. Calculate this by comparing the current CoinEntry 1187 // to the previous CoinEntry 1188 profileEntry := utxoView.GetProfileEntryForPublicKey(realTxMeta.ProfilePublicKey) 1189 var prevCoinEntry *CoinEntry 1190 for _, op := range utxoOps { 1191 if op.Type == OperationTypeCreatorCoin { 1192 prevCoinEntry = op.PrevCoinEntry 1193 break 1194 } 1195 } 1196 1197 desoLockedNanosDiff := int64(0) 1198 if profileEntry == nil || prevCoinEntry == nil { 1199 glog.Errorf("Update TxIndex: missing DESOLockedNanosDiff error: %v", txn.Hash().String()) 1200 } else { 1201 desoLockedNanosDiff = int64(profileEntry.DeSoLockedNanos - prevCoinEntry.DeSoLockedNanos) 1202 } 1203 1204 // Set the amount of the buy/sell/add 1205 txnMeta.CreatorCoinTxindexMetadata = &CreatorCoinTxindexMetadata{ 1206 DeSoToSellNanos: realTxMeta.DeSoToSellNanos, 1207 CreatorCoinToSellNanos: realTxMeta.CreatorCoinToSellNanos, 1208 DeSoToAddNanos: realTxMeta.DeSoToAddNanos, 1209 DESOLockedNanosDiff: desoLockedNanosDiff, 1210 } 1211 1212 // Set the type of the operation. 1213 if realTxMeta.OperationType == CreatorCoinOperationTypeBuy { 1214 txnMeta.CreatorCoinTxindexMetadata.OperationType = "buy" 1215 } else if realTxMeta.OperationType == CreatorCoinOperationTypeSell { 1216 txnMeta.CreatorCoinTxindexMetadata.OperationType = "sell" 1217 } else { 1218 txnMeta.CreatorCoinTxindexMetadata.OperationType = "add" 1219 } 1220 1221 // Set the affected public key to the owner of the creator coin so that they 1222 // get notified. 1223 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1224 PublicKeyBase58Check: PkToString(realTxMeta.ProfilePublicKey, utxoView.Params), 1225 Metadata: "CreatorPublicKey", 1226 }) 1227 } 1228 if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoinTransfer { 1229 realTxMeta := txn.TxnMeta.(*CreatorCoinTransferMetadataa) 1230 creatorProfileEntry := utxoView.GetProfileEntryForPublicKey(realTxMeta.ProfilePublicKey) 1231 txnMeta.CreatorCoinTransferTxindexMetadata = &CreatorCoinTransferTxindexMetadata{ 1232 CreatorUsername: string(creatorProfileEntry.Username), 1233 CreatorCoinToTransferNanos: realTxMeta.CreatorCoinToTransferNanos, 1234 } 1235 1236 diamondLevelBytes, hasDiamondLevel := txn.ExtraData[DiamondLevelKey] 1237 diamondPostHash, hasDiamondPostHash := txn.ExtraData[DiamondPostHashKey] 1238 if hasDiamondLevel && hasDiamondPostHash { 1239 diamondLevel, bytesRead := Varint(diamondLevelBytes) 1240 if bytesRead <= 0 { 1241 glog.Errorf("Update TxIndex: Error reading diamond level for txn: %v", txn.Hash().String()) 1242 } else { 1243 txnMeta.CreatorCoinTransferTxindexMetadata.DiamondLevel = diamondLevel 1244 txnMeta.CreatorCoinTransferTxindexMetadata.PostHashHex = hex.EncodeToString(diamondPostHash) 1245 } 1246 } 1247 1248 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1249 PublicKeyBase58Check: PkToString(realTxMeta.ReceiverPublicKey, utxoView.Params), 1250 Metadata: "ReceiverPublicKey", 1251 }) 1252 } 1253 if txn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 1254 realTxMeta := txn.TxnMeta.(*UpdateProfileMetadata) 1255 1256 txnMeta.UpdateProfileTxindexMetadata = &UpdateProfileTxindexMetadata{} 1257 if len(realTxMeta.ProfilePublicKey) == btcec.PubKeyBytesLenCompressed { 1258 txnMeta.UpdateProfileTxindexMetadata.ProfilePublicKeyBase58Check = 1259 PkToString(realTxMeta.ProfilePublicKey, utxoView.Params) 1260 } 1261 txnMeta.UpdateProfileTxindexMetadata.NewUsername = string(realTxMeta.NewUsername) 1262 txnMeta.UpdateProfileTxindexMetadata.NewDescription = string(realTxMeta.NewDescription) 1263 txnMeta.UpdateProfileTxindexMetadata.NewProfilePic = string(realTxMeta.NewProfilePic) 1264 txnMeta.UpdateProfileTxindexMetadata.NewCreatorBasisPoints = realTxMeta.NewCreatorBasisPoints 1265 txnMeta.UpdateProfileTxindexMetadata.NewStakeMultipleBasisPoints = realTxMeta.NewStakeMultipleBasisPoints 1266 txnMeta.UpdateProfileTxindexMetadata.IsHidden = realTxMeta.IsHidden 1267 1268 // Add the ProfilePublicKey to the AffectedPublicKeys 1269 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1270 PublicKeyBase58Check: PkToString(realTxMeta.ProfilePublicKey, utxoView.Params), 1271 Metadata: "ProfilePublicKeyBase58Check", 1272 }) 1273 } 1274 if txn.TxnMeta.GetTxnType() == TxnTypeSubmitPost { 1275 realTxMeta := txn.TxnMeta.(*SubmitPostMetadata) 1276 _ = realTxMeta 1277 1278 txnMeta.SubmitPostTxindexMetadata = &SubmitPostTxindexMetadata{} 1279 if len(realTxMeta.PostHashToModify) == HashSizeBytes { 1280 txnMeta.SubmitPostTxindexMetadata.PostHashBeingModifiedHex = hex.EncodeToString( 1281 realTxMeta.PostHashToModify) 1282 } 1283 if len(realTxMeta.ParentStakeID) == HashSizeBytes { 1284 txnMeta.SubmitPostTxindexMetadata.ParentPostHashHex = hex.EncodeToString( 1285 realTxMeta.ParentStakeID) 1286 } 1287 // If a post hash didn't get set then the hash of the transaction itself will 1288 // end up being used as the post hash so set that here. 1289 if txnMeta.SubmitPostTxindexMetadata.PostHashBeingModifiedHex == "" { 1290 txnMeta.SubmitPostTxindexMetadata.PostHashBeingModifiedHex = 1291 hex.EncodeToString(txn.Hash()[:]) 1292 } 1293 1294 // PosterPublicKeyBase58Check = TransactorPublicKeyBase58Check 1295 1296 // If ParentPostHashHex is set then get the parent posts public key and 1297 // mark it as affected. 1298 // ParentPosterPublicKeyBase58Check is in AffectedPublicKeys 1299 if len(realTxMeta.ParentStakeID) == HashSizeBytes { 1300 postHash := &BlockHash{} 1301 copy(postHash[:], realTxMeta.ParentStakeID) 1302 postEntry := utxoView.GetPostEntryForPostHash(postHash) 1303 if postEntry == nil { 1304 return nil, fmt.Errorf( 1305 "UpdateTxindex: Error creating SubmitPostTxindexMetadata; "+ 1306 "missing parent post for hash %v: %v", postHash, err) 1307 } 1308 1309 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1310 PublicKeyBase58Check: PkToString(postEntry.PosterPublicKey, utxoView.Params), 1311 Metadata: "ParentPosterPublicKeyBase58Check", 1312 }) 1313 } 1314 1315 // The profiles that are mentioned are in the AffectedPublicKeys 1316 // MentionedPublicKeyBase58Check in AffectedPublicKeys. We need to 1317 // parse them out of the post and then look up their public keys. 1318 // 1319 // Start by trying to parse the body JSON 1320 bodyObj := &DeSoBodySchema{} 1321 if err := json.Unmarshal(realTxMeta.Body, &bodyObj); err != nil { 1322 // Don't worry about bad posts unless we're debugging with high verbosity. 1323 glog.V(2).Infof("UpdateTxindex: Error parsing post body for @ mentions: "+ 1324 "%v %v", string(realTxMeta.Body), err) 1325 } else { 1326 terminators := []rune(" ,.\n&*()-+~'\"[]{}") 1327 dollarTagsFound := mention.GetTagsAsUniqueStrings('$', bodyObj.Body, terminators...) 1328 atTagsFound := mention.GetTagsAsUniqueStrings('@', bodyObj.Body, terminators...) 1329 tagsFound := atTagsFound 1330 // We check that cashtag usernames have at least 1 non-numeric character 1331 dollarTagRegex := regexp.MustCompile("\\w*[a-zA-Z_]\\w*") 1332 for _, dollarTagFound := range dollarTagsFound { 1333 if dollarTagRegex.MatchString(dollarTagFound) { 1334 tagsFound = append(tagsFound, dollarTagFound) 1335 } 1336 } 1337 for _, tag := range tagsFound { 1338 profileFound := utxoView.GetProfileEntryForUsername([]byte(strings.ToLower(tag))) 1339 // Don't worry about tags that don't line up to a profile. 1340 if profileFound == nil { 1341 continue 1342 } 1343 // If we found a profile then set it as an affected public key. 1344 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1345 PublicKeyBase58Check: PkToString(profileFound.PublicKey, utxoView.Params), 1346 Metadata: "MentionedPublicKeyBase58Check", 1347 }) 1348 } 1349 // Additionally, we need to check if this post is a repost and 1350 // fetch the original poster 1351 if repostedPostHash, isRepost := extraData[RepostedPostHash]; isRepost { 1352 repostedBlockHash := &BlockHash{} 1353 copy(repostedBlockHash[:], repostedPostHash) 1354 repostPost := utxoView.GetPostEntryForPostHash(repostedBlockHash) 1355 if repostPost != nil { 1356 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1357 PublicKeyBase58Check: PkToString(repostPost.PosterPublicKey, utxoView.Params), 1358 Metadata: "RepostedPublicKeyBase58Check", 1359 }) 1360 } 1361 } 1362 } 1363 } 1364 if txn.TxnMeta.GetTxnType() == TxnTypeLike { 1365 realTxMeta := txn.TxnMeta.(*LikeMetadata) 1366 _ = realTxMeta 1367 1368 // LikerPublicKeyBase58Check = TransactorPublicKeyBase58Check 1369 1370 txnMeta.LikeTxindexMetadata = &LikeTxindexMetadata{ 1371 IsUnlike: realTxMeta.IsUnlike, 1372 PostHashHex: hex.EncodeToString(realTxMeta.LikedPostHash[:]), 1373 } 1374 1375 // Get the public key of the poster and set it as having been affected 1376 // by this like. 1377 // 1378 // PosterPublicKeyBase58Check in AffectedPublicKeys 1379 postHash := &BlockHash{} 1380 copy(postHash[:], realTxMeta.LikedPostHash[:]) 1381 postEntry := utxoView.GetPostEntryForPostHash(postHash) 1382 if postEntry == nil { 1383 return nil, fmt.Errorf( 1384 "UpdateTxindex: Error creating LikeTxindexMetadata; "+ 1385 "missing post for hash %v: %v", postHash, err) 1386 } 1387 1388 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1389 PublicKeyBase58Check: PkToString(postEntry.PosterPublicKey, utxoView.Params), 1390 Metadata: "PosterPublicKeyBase58Check", 1391 }) 1392 } 1393 if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 1394 realTxMeta := txn.TxnMeta.(*FollowMetadata) 1395 _ = realTxMeta 1396 1397 txnMeta.FollowTxindexMetadata = &FollowTxindexMetadata{ 1398 IsUnfollow: realTxMeta.IsUnfollow, 1399 } 1400 1401 // FollowerPublicKeyBase58Check = TransactorPublicKeyBase58Check 1402 1403 // FollowedPublicKeyBase58Check in AffectedPublicKeys 1404 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1405 PublicKeyBase58Check: PkToString(realTxMeta.FollowedPublicKey, utxoView.Params), 1406 Metadata: "FollowedPublicKeyBase58Check", 1407 }) 1408 } 1409 if txn.TxnMeta.GetTxnType() == TxnTypePrivateMessage { 1410 realTxMeta := txn.TxnMeta.(*PrivateMessageMetadata) 1411 _ = realTxMeta 1412 1413 txnMeta.PrivateMessageTxindexMetadata = &PrivateMessageTxindexMetadata{ 1414 TimestampNanos: realTxMeta.TimestampNanos, 1415 } 1416 1417 // SenderPublicKeyBase58Check = TransactorPublicKeyBase58Check 1418 1419 // RecipientPublicKeyBase58Check in AffectedPublicKeys 1420 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1421 PublicKeyBase58Check: PkToString(realTxMeta.RecipientPublicKey, utxoView.Params), 1422 Metadata: "RecipientPublicKeyBase58Check", 1423 }) 1424 } 1425 if txn.TxnMeta.GetTxnType() == TxnTypeSwapIdentity { 1426 realTxMeta := txn.TxnMeta.(*SwapIdentityMetadataa) 1427 _ = realTxMeta 1428 1429 // Rosetta needs to know the current locked deso in each profile so it can model the swap of 1430 // the creator coins. Rosetta models a swap identity as two INPUTs and two OUTPUTs effectively 1431 // swapping the balances of total deso locked. If no profile exists, from/to is zero. 1432 fromNanos := uint64(0) 1433 fromProfile := utxoView.GetProfileEntryForPublicKey(realTxMeta.FromPublicKey) 1434 if fromProfile != nil { 1435 fromNanos = fromProfile.CoinEntry.DeSoLockedNanos 1436 } 1437 1438 toNanos := uint64(0) 1439 toProfile := utxoView.GetProfileEntryForPublicKey(realTxMeta.ToPublicKey) 1440 if toProfile != nil { 1441 toNanos = toProfile.CoinEntry.DeSoLockedNanos 1442 } 1443 1444 txnMeta.SwapIdentityTxindexMetadata = &SwapIdentityTxindexMetadata{ 1445 FromPublicKeyBase58Check: PkToString(realTxMeta.FromPublicKey, utxoView.Params), 1446 ToPublicKeyBase58Check: PkToString(realTxMeta.ToPublicKey, utxoView.Params), 1447 FromDeSoLockedNanos: fromNanos, 1448 ToDeSoLockedNanos: toNanos, 1449 } 1450 1451 // The to and from public keys are affected by this. 1452 1453 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1454 PublicKeyBase58Check: PkToString(realTxMeta.FromPublicKey, utxoView.Params), 1455 Metadata: "FromPublicKeyBase58Check", 1456 }) 1457 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1458 PublicKeyBase58Check: PkToString(realTxMeta.ToPublicKey, utxoView.Params), 1459 Metadata: "ToPublicKeyBase58Check", 1460 }) 1461 } 1462 if txn.TxnMeta.GetTxnType() == TxnTypeNFTBid { 1463 realTxMeta := txn.TxnMeta.(*NFTBidMetadata) 1464 _ = realTxMeta 1465 1466 txnMeta.NFTBidTxindexMetadata = &NFTBidTxindexMetadata{ 1467 NFTPostHashHex: hex.EncodeToString(realTxMeta.NFTPostHash[:]), 1468 SerialNumber: realTxMeta.SerialNumber, 1469 BidAmountNanos: realTxMeta.BidAmountNanos, 1470 } 1471 1472 // We don't send notifications for standing offers. 1473 if realTxMeta.SerialNumber != 0 { 1474 nftKey := MakeNFTKey(realTxMeta.NFTPostHash, realTxMeta.SerialNumber) 1475 nftEntry := utxoView.GetNFTEntryForNFTKey(&nftKey) 1476 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1477 PublicKeyBase58Check: PkToString(utxoView.GetPublicKeyForPKID(nftEntry.OwnerPKID), utxoView.Params), 1478 Metadata: "NFTOwnerPublicKeyBase58Check", 1479 }) 1480 } 1481 } 1482 if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTBid { 1483 realTxMeta := txn.TxnMeta.(*AcceptNFTBidMetadata) 1484 _ = realTxMeta 1485 1486 // Rosetta needs to know the royalty paid to the creator coin so it can model the change in 1487 // total deso locked in the creator coin correctly. 1488 var prevCoinEntry *CoinEntry 1489 var creatorPublicKey []byte 1490 for _, utxoOp := range utxoOps { 1491 if utxoOp.Type == OperationTypeAcceptNFTBid { 1492 prevCoinEntry = utxoOp.PrevCoinEntry 1493 if utxoOp.PrevPostEntry != nil { 1494 creatorPublicKey = utxoOp.PrevPostEntry.PosterPublicKey 1495 } 1496 break 1497 } 1498 } 1499 1500 creatorCoinRoyaltyNanos := uint64(0) 1501 profileEntry := utxoView.GetProfileEntryForPublicKey(creatorPublicKey) 1502 if profileEntry == nil { 1503 glog.Errorf("Update TxIndex: Missing profile entry: %v", txn.Hash().String()) 1504 } else if prevCoinEntry == nil { 1505 glog.Errorf("Update TxIndex: Missing previous coin entry: %v", txn.Hash().String()) 1506 } else if profileEntry.CoinEntry.DeSoLockedNanos < prevCoinEntry.DeSoLockedNanos { 1507 glog.Errorf("Update TxIndex: CreatorCoinRoyaltyNanos overflow error: %v", txn.Hash().String()) 1508 } else { 1509 creatorCoinRoyaltyNanos = profileEntry.CoinEntry.DeSoLockedNanos - prevCoinEntry.DeSoLockedNanos 1510 } 1511 1512 txnMeta.AcceptNFTBidTxindexMetadata = &AcceptNFTBidTxindexMetadata{ 1513 NFTPostHashHex: hex.EncodeToString(realTxMeta.NFTPostHash[:]), 1514 SerialNumber: realTxMeta.SerialNumber, 1515 BidAmountNanos: realTxMeta.BidAmountNanos, 1516 CreatorCoinRoyaltyNanos: creatorCoinRoyaltyNanos, 1517 CreatorPublicKeyBase58Check: PkToString(creatorPublicKey, utxoView.Params), 1518 } 1519 1520 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1521 PublicKeyBase58Check: PkToString(utxoView.GetPublicKeyForPKID(realTxMeta.BidderPKID), utxoView.Params), 1522 Metadata: "NFTBidderPublicKeyBase58Check", 1523 }) 1524 } 1525 if txn.TxnMeta.GetTxnType() == TxnTypeNFTTransfer { 1526 realTxMeta := txn.TxnMeta.(*NFTTransferMetadata) 1527 _ = realTxMeta 1528 1529 txnMeta.NFTTransferTxindexMetadata = &NFTTransferTxindexMetadata{ 1530 NFTPostHashHex: hex.EncodeToString(realTxMeta.NFTPostHash[:]), 1531 SerialNumber: realTxMeta.SerialNumber, 1532 } 1533 1534 txnMeta.AffectedPublicKeys = append(txnMeta.AffectedPublicKeys, &AffectedPublicKey{ 1535 PublicKeyBase58Check: PkToString(realTxMeta.ReceiverPublicKey, utxoView.Params), 1536 Metadata: "NFTTransferRecipientPublicKeyBase58Check", 1537 }) 1538 1539 } 1540 if txn.TxnMeta.GetTxnType() == TxnTypeBasicTransfer { 1541 diamondLevelBytes, hasDiamondLevel := txn.ExtraData[DiamondLevelKey] 1542 diamondPostHash, hasDiamondPostHash := txn.ExtraData[DiamondPostHashKey] 1543 if hasDiamondLevel && hasDiamondPostHash { 1544 diamondLevel, bytesRead := Varint(diamondLevelBytes) 1545 if bytesRead <= 0 { 1546 glog.Errorf("Update TxIndex: Error reading diamond level for txn: %v", txn.Hash().String()) 1547 } else { 1548 txnMeta.BasicTransferTxindexMetadata.DiamondLevel = diamondLevel 1549 txnMeta.BasicTransferTxindexMetadata.PostHashHex = hex.EncodeToString(diamondPostHash) 1550 } 1551 } 1552 } 1553 1554 return txnMeta, nil 1555 } 1556 1557 func _computeBitcoinExchangeFields(params *DeSoParams, 1558 txMetaa *BitcoinExchangeMetadata, totalNanosPurchasedBefore uint64, usdCentsPerBitcoin uint64) ( 1559 _btcMeta *BitcoinExchangeTxindexMetadata, _spendPkBase58Check string, _err error) { 1560 1561 // Extract a public key from the BitcoinTransaction's inputs. Note that we only 1562 // consider P2PKH inputs to be valid. If no P2PKH inputs are found then we consider 1563 // the transaction as a whole to be invalid since we don't know who to credit the 1564 // new DeSo to. If we find more than one P2PKH input, we consider the public key 1565 // corresponding to the first of these inputs to be the one that will receive the 1566 // DeSo that will be created. 1567 publicKey, err := ExtractBitcoinPublicKeyFromBitcoinTransactionInputs( 1568 txMetaa.BitcoinTransaction, params.BitcoinBtcdParams) 1569 if err != nil { 1570 return nil, "", RuleErrorBitcoinExchangeValidPublicKeyNotFoundInInputs 1571 } 1572 // At this point, we should have extracted a public key from the Bitcoin transaction 1573 // that we expect to credit the newly-created DeSo to. 1574 1575 // The burn address cannot create this type of transaction. 1576 addrFromPubKey, err := btcutil.NewAddressPubKey( 1577 publicKey.SerializeCompressed(), params.BitcoinBtcdParams) 1578 if err != nil { 1579 return nil, "", fmt.Errorf("_connectBitcoinExchange: Error "+ 1580 "converting public key to Bitcoin address: %v", err) 1581 } 1582 addrString := addrFromPubKey.AddressPubKeyHash().EncodeAddress() 1583 if addrString == params.BitcoinBurnAddress { 1584 return nil, "", RuleErrorBurnAddressCannotBurnBitcoin 1585 } 1586 1587 // Go through the transaction's outputs and count up the satoshis that are being 1588 // allocated to the burn address. If no Bitcoin is being sent to the burn address 1589 // then we consider the transaction to be invalid. Watch out for overflow as we do 1590 // this. 1591 totalBurnOutput, err := _computeBitcoinBurnOutput( 1592 txMetaa.BitcoinTransaction, params.BitcoinBurnAddress, 1593 params.BitcoinBtcdParams) 1594 if err != nil { 1595 return nil, "", RuleErrorBitcoinExchangeProblemComputingBurnOutput 1596 } 1597 if totalBurnOutput <= 0 { 1598 return nil, "", RuleErrorBitcoinExchangeTotalOutputLessThanOrEqualZero 1599 } 1600 1601 // At this point we know how many satoshis were burned and we know the public key 1602 // that should receive the DeSo we are going to create. 1603 1604 // Compute the amount of DeSo that we should create as a result of this transaction. 1605 nanosToCreate := CalcNanosToCreate( 1606 totalNanosPurchasedBefore, uint64(totalBurnOutput), usdCentsPerBitcoin) 1607 1608 bitcoinTxHash := txMetaa.BitcoinTransaction.TxHash() 1609 return &BitcoinExchangeTxindexMetadata{ 1610 BitcoinSpendAddress: addrString, 1611 SatoshisBurned: uint64(totalBurnOutput), 1612 NanosCreated: nanosToCreate, 1613 BitcoinTxnHash: bitcoinTxHash.String(), 1614 }, PkToString(publicKey.SerializeCompressed(), params), nil 1615 } 1616 1617 func ConnectTxnAndComputeTransactionMetadata( 1618 txn *MsgDeSoTxn, utxoView *UtxoView, blockHash *BlockHash, 1619 blockHeight uint32, txnIndexInBlock uint64) (*TransactionMetadata, error) { 1620 1621 totalNanosPurchasedBefore := utxoView.NanosPurchased 1622 usdCentsPerBitcoinBefore := utxoView.GetCurrentUSDCentsPerBitcoin() 1623 utxoOps, totalInput, totalOutput, fees, err := utxoView._connectTransaction( 1624 txn, txn.Hash(), 0, blockHeight, false, false) 1625 if err != nil { 1626 return nil, fmt.Errorf( 1627 "UpdateTxindex: Error connecting txn to UtxoView: %v", err) 1628 } 1629 1630 return ComputeTransactionMetadata(txn, utxoView, blockHash, totalNanosPurchasedBefore, 1631 usdCentsPerBitcoinBefore, totalInput, totalOutput, fees, txnIndexInBlock, utxoOps) 1632 } 1633 1634 // This is the main function used for adding a new txn to the pool. It will 1635 // run all needed validation on the txn before adding it, and it will only 1636 // accept the txn if these validations pass. 1637 // 1638 // The ChainLock must be held for reading calling this function. 1639 func (mp *DeSoMempool) TryAcceptTransaction(tx *MsgDeSoTxn, rateLimit bool, verifySignatures bool) ([]*BlockHash, *MempoolTx, error) { 1640 // Protect concurrent access. 1641 mp.mtx.Lock() 1642 defer mp.mtx.Unlock() 1643 1644 hashes, mempoolTx, err := mp.tryAcceptTransaction(tx, rateLimit, true, verifySignatures) 1645 1646 return hashes, mempoolTx, err 1647 } 1648 1649 // See comment on ProcessUnconnectedTransactions 1650 func (mp *DeSoMempool) processUnconnectedTransactions(acceptedTx *MsgDeSoTxn, rateLimit bool, verifySignatures bool) []*MempoolTx { 1651 var acceptedTxns []*MempoolTx 1652 1653 processList := list.New() 1654 processList.PushBack(acceptedTx) 1655 for processList.Len() > 0 { 1656 firstElement := processList.Remove(processList.Front()) 1657 processItem := firstElement.(*MsgDeSoTxn) 1658 1659 processHash := processItem.Hash() 1660 if processHash == nil { 1661 glog.Error(fmt.Errorf("processUnconnectedTransactions: Problem hashing tx: ")) 1662 return nil 1663 } 1664 prevOut := DeSoInput{TxID: *processHash} 1665 for txOutIdx := range processItem.TxOutputs { 1666 prevOut.Index = uint32(txOutIdx) 1667 unconnectedTxns, exists := mp.unconnectedTxnsByPrev[UtxoKey(prevOut)] 1668 if !exists { 1669 continue 1670 } 1671 1672 for _, tx := range unconnectedTxns { 1673 missing, mempoolTx, err := mp.tryAcceptTransaction( 1674 tx, rateLimit, false, verifySignatures) 1675 if err != nil { 1676 mp.removeUnconnectedTxn(tx, true) 1677 break 1678 } 1679 1680 if len(missing) > 0 { 1681 continue 1682 } 1683 1684 acceptedTxns = append(acceptedTxns, mempoolTx) 1685 mp.removeUnconnectedTxn(tx, false) 1686 processList.PushBack(tx) 1687 1688 break 1689 } 1690 } 1691 } 1692 1693 mp.removeUnconnectedTxnDoubleSpends(acceptedTx) 1694 for _, mempoolTx := range acceptedTxns { 1695 mp.removeUnconnectedTxnDoubleSpends(mempoolTx.Tx) 1696 } 1697 1698 return acceptedTxns 1699 } 1700 1701 // ProcessUnconnectedTransactions tries to see if any unconnectedTxns can now be added to the pool. 1702 func (mp *DeSoMempool) ProcessUnconnectedTransactions(acceptedTx *MsgDeSoTxn, rateLimit bool, verifySignatures bool) []*MempoolTx { 1703 mp.mtx.Lock() 1704 acceptedTxns := mp.processUnconnectedTransactions(acceptedTx, rateLimit, verifySignatures) 1705 mp.mtx.Unlock() 1706 1707 return acceptedTxns 1708 } 1709 1710 func (mp *DeSoMempool) _addTxnToPublicKeyMap(mempoolTx *MempoolTx, publicKey []byte) { 1711 pkMapKey := MakePkMapKey(publicKey) 1712 mapForPk, exists := mp.pubKeyToTxnMap[pkMapKey] 1713 if !exists { 1714 mapForPk = make(map[BlockHash]*MempoolTx) 1715 mp.pubKeyToTxnMap[pkMapKey] = mapForPk 1716 } 1717 mapForPk[*mempoolTx.Hash] = mempoolTx 1718 } 1719 1720 func (mp *DeSoMempool) PublicKeyTxnMap(publicKey []byte) (txnMap map[BlockHash]*MempoolTx) { 1721 pkMapKey := MakePkMapKey(publicKey) 1722 return mp.pubKeyToTxnMap[pkMapKey] 1723 } 1724 1725 // TODO: This needs to consolidate with ConnectTxnAndComputeTransactionMetadata which 1726 // does a similar thing. 1727 func _getPublicKeysToIndexForTxn(txn *MsgDeSoTxn, params *DeSoParams) [][]byte { 1728 pubKeysToIndex := [][]byte{} 1729 1730 // For each output in the transaction, add the public key. 1731 for _, txOut := range txn.TxOutputs { 1732 pubKeysToIndex = append(pubKeysToIndex, txOut.PublicKey) 1733 } 1734 1735 // In addition to adding a mapping for each output public key, add a mapping 1736 // for the transaction's overall public key. This helps us find transactions 1737 // where this key is referenced as an input. 1738 if len(txn.PublicKey) == btcec.PubKeyBytesLenCompressed { 1739 pubKeysToIndex = append(pubKeysToIndex, txn.PublicKey) 1740 } 1741 1742 // If the transaction is a PrivateMessage then add a mapping from the 1743 // recipient to this message so that it comes up when the recipient 1744 // creates an augmented view. Note the sender is already covered since 1745 // their public key is the one at the top-level transaction, which we 1746 // index just above. 1747 if txn.TxnMeta.GetTxnType() == TxnTypePrivateMessage { 1748 txnMeta := txn.TxnMeta.(*PrivateMessageMetadata) 1749 1750 pubKeysToIndex = append(pubKeysToIndex, txnMeta.RecipientPublicKey) 1751 } 1752 1753 if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 1754 txnMeta := txn.TxnMeta.(*FollowMetadata) 1755 1756 pubKeysToIndex = append(pubKeysToIndex, txnMeta.FollowedPublicKey) 1757 } 1758 1759 // Index SwapIdentity txns by the pub keys embedded within the metadata 1760 if txn.TxnMeta.GetTxnType() == TxnTypeSwapIdentity { 1761 txnMeta := txn.TxnMeta.(*SwapIdentityMetadataa) 1762 1763 // The ToPublicKey and the FromPublicKey can differ from the txn.PublicKey, 1764 // and so we need to index them separately. 1765 pubKeysToIndex = append(pubKeysToIndex, txnMeta.ToPublicKey) 1766 pubKeysToIndex = append(pubKeysToIndex, txnMeta.FromPublicKey) 1767 } 1768 1769 if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 1770 txnMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1771 1772 // The HODLer public key is indexed when we return the txn.PublicKey 1773 // so we just need to additionally consider the creator. 1774 pubKeysToIndex = append(pubKeysToIndex, txnMeta.ProfilePublicKey) 1775 } 1776 1777 // If the transaction is a BitcoinExchange transaction, add a mapping 1778 // for the implicit output created by it. Also add a mapping for the 1779 // burn public key so that we can easily find all burns in the block 1780 // explorer. 1781 if txn.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 1782 // Add the mapping for the implicit output 1783 { 1784 txnMeta := txn.TxnMeta.(*BitcoinExchangeMetadata) 1785 publicKey, err := ExtractBitcoinPublicKeyFromBitcoinTransactionInputs( 1786 txnMeta.BitcoinTransaction, params.BitcoinBtcdParams) 1787 if err != nil { 1788 glog.Errorf("_addMempoolTxToPubKeyOutputMap: Problem extracting public key "+ 1789 "from Bitcoin transaction for txnMeta %v", txnMeta) 1790 } else { 1791 pubKeysToIndex = append(pubKeysToIndex, publicKey.SerializeCompressed()) 1792 } 1793 } 1794 1795 // Add the mapping for the burn public key. 1796 pubKeysToIndex = append(pubKeysToIndex, MustBase58CheckDecode(BurnPubKeyBase58Check)) 1797 } 1798 1799 return pubKeysToIndex 1800 } 1801 1802 func (mp *DeSoMempool) _addMempoolTxToPubKeyOutputMap(mempoolTx *MempoolTx) { 1803 // Index the transaction by any associated public keys. 1804 publicKeysToIndex := _getPublicKeysToIndexForTxn(mempoolTx.Tx, mp.bc.params) 1805 for _, pkToIndex := range publicKeysToIndex { 1806 mp._addTxnToPublicKeyMap(mempoolTx, pkToIndex) 1807 } 1808 } 1809 1810 func (mp *DeSoMempool) processTransaction( 1811 tx *MsgDeSoTxn, allowUnconnectedTxn, rateLimit bool, 1812 peerID uint64, verifySignatures bool) ([]*MempoolTx, error) { 1813 1814 txHash := tx.Hash() 1815 if txHash == nil { 1816 return nil, fmt.Errorf("ProcessTransaction: Problem hashing tx") 1817 } 1818 glog.V(2).Infof("Processing transaction %v", txHash) 1819 1820 // Run validation and try to add this txn to the pool. 1821 missingParents, mempoolTx, err := mp.tryAcceptTransaction( 1822 tx, rateLimit, true, verifySignatures) 1823 if err != nil { 1824 return nil, err 1825 } 1826 1827 // Update the readOnlyUtxoView if we've accumulated enough calls 1828 // to this function. This needs to be done after the tryAcceptTransaction 1829 // call 1830 if mp.generateReadOnlyUtxoView && 1831 mp.totalProcessTransactionCalls%ReadOnlyUtxoViewRegenerationIntervalTxns == 0 { 1832 // We call the version that doesn't lock. 1833 mp.regenerateReadOnlyView() 1834 } 1835 // Update the total number of transactions we've processed. 1836 mp.totalProcessTransactionCalls += 1 1837 1838 if len(missingParents) == 0 { 1839 newTxs := mp.processUnconnectedTransactions(tx, rateLimit, verifySignatures) 1840 acceptedTxs := make([]*MempoolTx, len(newTxs)+1) 1841 1842 acceptedTxs[0] = mempoolTx 1843 copy(acceptedTxs[1:], newTxs) 1844 1845 return acceptedTxs, nil 1846 } 1847 1848 // Reject the txn if it's an unconnected txn and we're set up to reject unconnectedTxns. 1849 if !allowUnconnectedTxn { 1850 glog.V(2).Infof("DeSoMempool.processTransaction: TxErrorUnconnectedTxnNotAllowed: %v %v", 1851 tx.Hash(), tx.TxnMeta.GetTxnType()) 1852 return nil, TxErrorUnconnectedTxnNotAllowed 1853 } 1854 1855 // Try to add the the transaction to the pool as an unconnected txn. 1856 err = mp.tryAddUnconnectedTxn(tx, peerID) 1857 if err != nil { 1858 glog.V(2).Infof("DeSoMempool.processTransaction: Error adding transaction as unconnected txn: %v", err) 1859 } 1860 return nil, err 1861 } 1862 1863 // ProcessTransaction is the main function called by outside services to potentially 1864 // add a transaction to the mempool. It will try to add the txn to the main pool, and 1865 // then try to add it as an unconnected txn if that fails. 1866 func (mp *DeSoMempool) ProcessTransaction(tx *MsgDeSoTxn, allowUnconnectedTxn bool, rateLimit bool, peerID uint64, verifySignatures bool) ([]*MempoolTx, error) { 1867 // Protect concurrent access. 1868 mp.mtx.Lock() 1869 defer mp.mtx.Unlock() 1870 1871 return mp.processTransaction(tx, allowUnconnectedTxn, rateLimit, peerID, verifySignatures) 1872 } 1873 1874 // Returns an estimate of the number of txns in the mempool. This is an estimate because 1875 // it looks up the number from a readOnly view, which updates at regular intervals and 1876 // *not* every time a txn is added to the pool. 1877 func (mp *DeSoMempool) Count() int { 1878 return len(mp.readOnlyUniversalTransactionList) 1879 } 1880 1881 // Returns the hashes of all the txns in the pool using the readOnly view, which could be 1882 // slightly out of date. 1883 func (mp *DeSoMempool) TxHashes() []*BlockHash { 1884 poolMap := mp.readOnlyUniversalTransactionMap 1885 hashes := make([]*BlockHash, len(poolMap)) 1886 ii := 0 1887 for hash := range poolMap { 1888 hashCopy := hash 1889 hashes[ii] = &hashCopy 1890 ii++ 1891 } 1892 1893 return hashes 1894 } 1895 1896 // Returns all MempoolTxs from the readOnly view. 1897 func (mp *DeSoMempool) MempoolTxs() []*MempoolTx { 1898 poolMap := mp.readOnlyUniversalTransactionMap 1899 descs := make([]*MempoolTx, len(poolMap)) 1900 i := 0 1901 for _, desc := range poolMap { 1902 descs[i] = desc 1903 i++ 1904 } 1905 1906 return descs 1907 } 1908 1909 func (mp *DeSoMempool) GetMempoolSummaryStats() (_summaryStatsMap map[string]*SummaryStats) { 1910 allTxns := mp.readOnlyUniversalTransactionList 1911 1912 transactionSummaryStats := make(map[string]*SummaryStats) 1913 for _, mempoolTx := range allTxns { 1914 // Update the mempool summary stats. 1915 updatedSummaryStats := &SummaryStats{} 1916 txnType := mempoolTx.Tx.TxnMeta.GetTxnType().String() 1917 summaryStats := transactionSummaryStats[txnType] 1918 if summaryStats == nil { 1919 updatedSummaryStats.Count = 1 1920 updatedSummaryStats.TotalBytes = mempoolTx.TxSizeBytes 1921 } else { 1922 updatedSummaryStats = summaryStats 1923 updatedSummaryStats.Count += 1 1924 updatedSummaryStats.TotalBytes += mempoolTx.TxSizeBytes 1925 } 1926 transactionSummaryStats[txnType] = updatedSummaryStats 1927 1928 } 1929 1930 // Return the map 1931 return transactionSummaryStats 1932 } 1933 1934 func (mp *DeSoMempool) inefficientRemoveTransaction(tx *MsgDeSoTxn) { 1935 // In this case we remove the transaction by re-adding all the txns we can 1936 // to the mempool except this one. 1937 // TODO(performance): This could be a bit slow. 1938 // 1939 // Create a new DeSoMempool. No need to set the min fees since we're just using 1940 // this as a temporary data structure for validation. 1941 // 1942 // Don't make the new pool object deal with the BlockCypher API. 1943 newPool := NewDeSoMempool(mp.bc, 0, /* rateLimitFeeRateNanosPerKB */ 1944 0, /* minFeeRateNanosPerKB */ 1945 "" /*blockCypherAPIKey*/, false, 1946 "" /*dataDir*/, "") 1947 // At this point the block txns have been added to the new pool. Now we need to 1948 // add the txns from the original pool. Start by fetching them in slice form. 1949 oldMempoolTxns, oldUnconnectedTxns, err := mp._getTransactionsOrderedByTimeAdded() 1950 if err != nil { 1951 glog.Warning(errors.Wrapf(err, "inefficientRemoveTransaction: ")) 1952 } 1953 // Iterate through the pool transactions and add them to our new pool. 1954 1955 for _, mempoolTx := range oldMempoolTxns { 1956 if *(mempoolTx.Tx.Hash()) == *(tx.Hash()) { 1957 continue 1958 } 1959 1960 // Attempt to add the txn to the mempool as we go. If it fails that's fine. 1961 txnsAccepted, err := newPool.processTransaction( 1962 mempoolTx.Tx, false /*allowUnconnectedTxn*/, false, /*rateLimit*/ 1963 0 /*peerID*/, false /*verifySignatures*/) 1964 if err != nil { 1965 glog.Warning(errors.Wrapf(err, "inefficientRemoveTransaction: ")) 1966 } 1967 if len(txnsAccepted) == 0 { 1968 glog.Warningf("inefficientRemoveTransaction: Dropping txn %v", mempoolTx.Tx) 1969 } 1970 } 1971 // Iterate through the unconnectedTxns and add them to our new pool as well. 1972 for _, oTx := range oldUnconnectedTxns { 1973 rateLimit := false 1974 allowUnconnectedTxn := true 1975 verifySignatures := false 1976 _, err := newPool.processTransaction(oTx.tx, allowUnconnectedTxn, rateLimit, oTx.peerID, verifySignatures) 1977 if err != nil { 1978 glog.Warning(errors.Wrapf(err, "inefficientRemoveTransaction: ")) 1979 } 1980 } 1981 1982 // At this point the new mempool should be a duplicate of the original mempool but with 1983 // the non-double-spend transactions added (with timestamps set before the transactions that 1984 // were in the original pool. 1985 1986 // Replace the internal mappings of the original pool with the mappings of the new 1987 // pool. 1988 mp.resetPool(newPool) 1989 } 1990 1991 func (mp *DeSoMempool) InefficientRemoveTransaction(tx *MsgDeSoTxn) { 1992 mp.mtx.Lock() 1993 defer mp.mtx.Unlock() 1994 1995 mp.inefficientRemoveTransaction(tx) 1996 } 1997 1998 func (mp *DeSoMempool) StartReadOnlyUtxoViewRegenerator() { 1999 glog.Info("Calling StartReadOnlyUtxoViewRegenerator...") 2000 2001 go func() { 2002 var oldSeqNum int64 2003 out: 2004 for { 2005 select { 2006 case <-time.After(time.Duration(ReadOnlyUtxoViewRegenerationIntervalSeconds) * time.Second): 2007 glog.V(2).Infof("StartReadOnlyUtxoViewRegenerator: Woke up!") 2008 2009 // When we wake up, only do an update if one didn't occur since before 2010 // we slept. Note that the number of transactions being processed can 2011 // also trigger an update, which is why this check is necessary. 2012 newSeqNum := atomic.LoadInt64(&mp.readOnlyUtxoViewSequenceNumber) 2013 if oldSeqNum == newSeqNum { 2014 glog.V(2).Infof("StartReadOnlyUtxoViewRegenerator: Updating view at prescribed interval") 2015 // Acquire a read lock when we do this. 2016 mp.RegenerateReadOnlyView() 2017 glog.V(2).Infof("StartReadOnlyUtxoViewRegenerator: Finished view update at prescribed interval") 2018 } else { 2019 glog.V(2).Infof("StartReadOnlyUtxoViewRegenerator: View updated while sleeping; nothing to do") 2020 } 2021 2022 // Get the sequence number before our timer hits. 2023 oldSeqNum = atomic.LoadInt64(&mp.readOnlyUtxoViewSequenceNumber) 2024 2025 case <-mp.quit: 2026 break out 2027 } 2028 } 2029 }() 2030 } 2031 2032 func (mp *DeSoMempool) regenerateReadOnlyView() error { 2033 newView, err := mp.universalUtxoView.CopyUtxoView() 2034 if err != nil { 2035 return fmt.Errorf("Error generating readOnlyUtxoView: %v", err) 2036 } 2037 2038 // Update the view and bump the sequence number. This is how callers will 2039 // know that the view was updated. 2040 mp.readOnlyUtxoView = newView 2041 2042 newTxnList := []*MempoolTx{} 2043 txMap := make(map[BlockHash]*MempoolTx) 2044 for _, mempoolTx := range mp.universalTransactionList { 2045 newTxnList = append(newTxnList, mempoolTx) 2046 txMap[*mempoolTx.Hash] = mempoolTx 2047 } 2048 2049 mp.readOnlyUniversalTransactionList = newTxnList 2050 mp.readOnlyUniversalTransactionMap = txMap 2051 2052 atomic.AddInt64(&mp.readOnlyUtxoViewSequenceNumber, 1) 2053 return nil 2054 } 2055 2056 func (mp *DeSoMempool) RegenerateReadOnlyView() error { 2057 mp.mtx.RLock() 2058 defer mp.mtx.RUnlock() 2059 2060 return mp.regenerateReadOnlyView() 2061 } 2062 2063 func (mp *DeSoMempool) BlockUntilReadOnlyViewRegenerated() { 2064 oldSeqNum := atomic.LoadInt64(&mp.readOnlyUtxoViewSequenceNumber) 2065 newSeqNum := oldSeqNum 2066 for newSeqNum == oldSeqNum { 2067 // Check fairly often. Not too often. 2068 time.Sleep(100 * time.Millisecond) 2069 2070 newSeqNum = atomic.LoadInt64(&mp.readOnlyUtxoViewSequenceNumber) 2071 } 2072 } 2073 2074 func (mp *DeSoMempool) StartMempoolDBDumper() { 2075 // If we were instructed to dump txns to the db, then do so periodically 2076 // Note this acquired a very minimal lock on the universalTransactionList 2077 go func() { 2078 out: 2079 for { 2080 select { 2081 case <-time.After(30 * time.Second): 2082 glog.Info("StartMempoolDBDumper: Waking up! Dumping txns now...") 2083 2084 // Dump the txns and time it. 2085 mp.DumpTxnsToDB() 2086 2087 case <-mp.quit: 2088 break out 2089 } 2090 } 2091 }() 2092 } 2093 2094 func (mp *DeSoMempool) LoadTxnsFromDB() { 2095 glog.Infof("LoadTxnsFromDB: Loading mempool txns from db because --load_mempool_txns_from_db was set") 2096 startTime := time.Now() 2097 2098 // The mempool shuffles dumped txns between temp, previous, and latest dirs. By dumping txns 2099 // to temp first, we ensure that we always have a full set of txns in latest and previous dir. 2100 // Note that it is possible for previousDir to exist even if latestDir does not because 2101 // the machine could crash after moving latest to previous. Thus, we check both. 2102 savedTxnsDir := filepath.Join(mp.mempoolDir, "latest_mempool_dump") 2103 _, err := os.Stat(savedTxnsDir) 2104 if os.IsNotExist(err) { 2105 savedTxnsDir = filepath.Join(mp.mempoolDir, "previous_mempool_dump") 2106 _, err = os.Stat(savedTxnsDir) 2107 if err != nil { 2108 glog.Infof("LoadTxnsFromDB: os.Stat(previousDir) error: %v", err) 2109 return 2110 } 2111 } else if err != nil { 2112 glog.Infof("LoadTxnsFromDB: os.Stat(latestDir) error: %v", err) 2113 return 2114 } 2115 2116 // If we make it this far, we found a mempool dump to load. Woohoo! 2117 tempMempoolDBOpts := badger.DefaultOptions(savedTxnsDir) 2118 tempMempoolDBOpts.ValueDir = savedTxnsDir 2119 tempMempoolDBOpts.MemTableSize = 1024 << 20 2120 glog.Infof("LoadTxnsFrom: Opening new temp db %v", savedTxnsDir) 2121 tempMempoolDB, err := badger.Open(tempMempoolDBOpts) 2122 if err != nil { 2123 glog.Infof("LoadTxnsFrom: Could not open temp db to dump mempool: %v", err) 2124 return 2125 } 2126 defer tempMempoolDB.Close() 2127 2128 // Get all saved mempool transactions from the DB. 2129 dbMempoolTxnsOrderedByTime, err := DbGetAllMempoolTxnsSortedByTimeAdded(tempMempoolDB) 2130 if err != nil { 2131 log.Fatalf("NewDeSoMempool: Failed to get mempoolTxs from the DB: %v", err) 2132 } 2133 2134 for _, mempoolTxn := range dbMempoolTxnsOrderedByTime { 2135 _, err := mp.processTransaction(mempoolTxn, false, false, 0, false) 2136 if err != nil { 2137 // Log errors but don't stop adding transactions. We do this because we'd prefer 2138 // to drop a transaction here or there rather than lose the whole block because 2139 // of one bad apple. 2140 glog.Warning(errors.Wrapf(err, "NewDeSoMempool: Not adding txn from DB "+ 2141 "because it had an error: ")) 2142 } 2143 } 2144 endTime := time.Now() 2145 glog.Infof("LoadTxnsFromDB: Loaded %v txns in %v seconds", len(dbMempoolTxnsOrderedByTime), endTime.Sub(startTime).Seconds()) 2146 } 2147 2148 func (mp *DeSoMempool) Stop() { 2149 close(mp.quit) 2150 } 2151 2152 // Create a new pool with no transactions in it. 2153 func NewDeSoMempool(_bc *Blockchain, _rateLimitFeerateNanosPerKB uint64, 2154 _minFeerateNanosPerKB uint64, _blockCypherAPIKey string, 2155 _runReadOnlyViewUpdater bool, _dataDir string, _mempoolDumpDir string) *DeSoMempool { 2156 2157 utxoView, _ := NewUtxoView(_bc.db, _bc.params, _bc.postgres) 2158 backupUtxoView, _ := NewUtxoView(_bc.db, _bc.params, _bc.postgres) 2159 readOnlyUtxoView, _ := NewUtxoView(_bc.db, _bc.params, _bc.postgres) 2160 newPool := &DeSoMempool{ 2161 quit: make(chan struct{}), 2162 bc: _bc, 2163 rateLimitFeeRateNanosPerKB: _rateLimitFeerateNanosPerKB, 2164 minFeeRateNanosPerKB: _minFeerateNanosPerKB, 2165 poolMap: make(map[BlockHash]*MempoolTx), 2166 unconnectedTxns: make(map[BlockHash]*UnconnectedTx), 2167 unconnectedTxnsByPrev: make(map[UtxoKey]map[BlockHash]*MsgDeSoTxn), 2168 outpoints: make(map[UtxoKey]*MsgDeSoTxn), 2169 pubKeyToTxnMap: make(map[PkMapKey]map[BlockHash]*MempoolTx), 2170 blockCypherAPIKey: _blockCypherAPIKey, 2171 backupUniversalUtxoView: backupUtxoView, 2172 universalUtxoView: utxoView, 2173 mempoolDir: _mempoolDumpDir, 2174 generateReadOnlyUtxoView: _runReadOnlyViewUpdater, 2175 readOnlyUtxoView: readOnlyUtxoView, 2176 readOnlyUniversalTransactionMap: make(map[BlockHash]*MempoolTx), 2177 readOnlyOutpoints: make(map[UtxoKey]*MsgDeSoTxn), 2178 dataDir: _dataDir, 2179 } 2180 2181 if newPool.mempoolDir != "" { 2182 newPool.LoadTxnsFromDB() 2183 } 2184 2185 // If the caller wants the readOnlyUtxoView to update periodically then kick 2186 // that off here. 2187 if newPool.generateReadOnlyUtxoView { 2188 newPool.StartReadOnlyUtxoViewRegenerator() 2189 } 2190 2191 if newPool.mempoolDir != "" { 2192 newPool.StartMempoolDBDumper() 2193 } 2194 2195 return newPool 2196 }