// ProcessTransaction is the main workhorse for handling insertion of new // free-standing transactions into the memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. // // This function is safe for concurrent access. func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit bool) error { // Protect concurrent access. mp.Lock() defer mp.Unlock() txmpLog.Tracef("Processing transaction %v", tx.Sha()) // Potentially accept the transaction to the memory pool. missingParents, err := mp.maybeAcceptTransaction(tx, true, rateLimit) if err != nil { return err } if len(missingParents) == 0 { // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv, tx) // Accept any orphan transactions that depend on this // transaction (they may no longer be orphans if all inputs // are now available) and repeat for those accepted // transactions until there are no more. err := mp.processOrphans(tx.Sha()) if err != nil { return err } } else { // The transaction is an orphan (has inputs missing). Reject // it if the flag to allow orphans is not set. if !allowOrphan { // Only use the first missing parent transaction in // the error message. // // NOTE: RejectDuplicate is really not an accurate // reject code here, but it matches the reference // implementation and there isn't a better choice due // to the limited number of reject codes. Missing // inputs is assumed to mean they are already spent // which is not really always the case. str := fmt.Sprintf("orphan transaction %v references "+ "outputs of unknown or fully-spent "+ "transaction %v", tx.Sha(), missingParents[0]) return txRuleError(btcwire.RejectDuplicate, str) } // Potentially add the orphan transaction to the orphan pool. err := mp.maybeAddOrphan(tx) if err != nil { return err } } return nil }
// BenchmarkMruInventoryList performs basic benchmarks on the most recently // used inventory handling. func BenchmarkMruInventoryList(b *testing.B) { // Create a bunch of fake inventory vectors to use in benchmarking // the mru inventory code. b.StopTimer() numInvVects := 100000 invVects := make([]*btcwire.InvVect, 0, numInvVects) for i := 0; i < numInvVects; i++ { hashBytes := make([]byte, btcwire.HashSize) rand.Read(hashBytes) hash, _ := btcwire.NewShaHash(hashBytes) iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) invVects = append(invVects, iv) } b.StartTimer() // Benchmark the add plus evicition code. limit := 20000 mruInvMap := NewMruInventoryMap(uint(limit)) for i := 0; i < b.N; i++ { mruInvMap.Add(invVects[i%numInvVects]) } }
// processOrphans determines if there are any orphans which depend on the passed // transaction hash (it is possible that they are no longer orphans) and // potentially accepts them to the memory pool. It repeats the process for the // newly accepted transactions (to detect further orphans which may no longer be // orphans) until there are no more. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error { // Start with processing at least the passed hash. processHashes := list.New() processHashes.PushBack(hash) for processHashes.Len() > 0 { // Pop the first hash to process. firstElement := processHashes.Remove(processHashes.Front()) processHash := firstElement.(*btcwire.ShaHash) // Look up all orphans that are referenced by the transaction we // just accepted. This will typically only be one, but it could // be multiple if the referenced transaction contains multiple // outputs. Skip to the next item on the list of hashes to // process if there are none. orphans, exists := mp.orphansByPrev[*processHash] if !exists || orphans == nil { continue } var enext *list.Element for e := orphans.Front(); e != nil; e = enext { enext = e.Next() tx := e.Value.(*btcutil.Tx) // Remove the orphan from the orphan pool. Current // behavior requires that all saved orphans with // a newly accepted parent are removed from the orphan // pool and potentially added to the memory pool, but // transactions which cannot be added to memory pool // (including due to still being orphans) are expunged // from the orphan pool. // // TODO(jrick): The above described behavior sounds // like a bug, and I think we should investigate // potentially moving orphans to the memory pool, but // leaving them in the orphan pool if not all parent // transactions are known yet. orphanHash := tx.Sha() mp.removeOrphan(orphanHash) // Potentially accept the transaction into the // transaction pool. missingParents, err := mp.maybeAcceptTransaction(tx, true, true) if err != nil { return err } if len(missingParents) == 0 { // Generate and relay the inventory vector for the // newly accepted transaction. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv, tx) } else { // Transaction is still an orphan. // TODO(jrick): This removeOrphan call is // likely unnecessary as it was unconditionally // removed above and maybeAcceptTransaction won't // add it back. mp.removeOrphan(orphanHash) } // Add this transaction to the list of transactions to // process so any orphans that depend on this one are // handled too. // // TODO(jrick): In the case that this is still an orphan, // we know that any other transactions in the orphan // pool with this orphan as their parent are still // orphans as well, and should be removed. While // recursively calling removeOrphan and // maybeAcceptTransaction on these transactions is not // wrong per se, it is overkill if all we care about is // recursively removing child transactions of this // orphan. processHashes.PushBack(orphanHash) } } return nil }