MemPool of btcd

Time:2020-8-1

MemPool of btcd

Reference: btcd

  • Btcd provides a memory pool, MemPool, for storing transactions that have not been packaged by miners.
  • Utxo and block index are stored in leveldb instead of the MemPool discussed in this article
  • Before it is inserted, a series of correctness verification (through mabeaccepttransaction) should be carried out.
  • If it is an orphan TX (that is, the transaction TX to which the input cannot be found in the main chain and MemPool) will be temporarily inserted into the orphans pool (through processtransaction).
  • When a new block is connected to the main chain, the TX in the block will be removed from the MemPool, and the TX that depends on the block in the orphans pool will be transferred to the MemPool (through mabeaccepttransaction)
  • When the block is detached from the main chain, the TX in the block will be reprocessed (through mabeaccepttransaction)

1、 Create a MemPool object

  • This method is called when the server is created to generate a MemPool object
  • As you can see, pool is used to store normal TX, and orphans store orphan TX

    // New returns a new memory pool for validating and storing standalone
    // transactions until they are mined into a block.
    func New(cfg *Config) *TxPool {
        return &TxPool{
            cfg:            *cfg,
            pool:           make(map[chainhash.Hash]*TxDesc),
            orphans:        make(map[chainhash.Hash]*orphanTx),
            orphansByPrev:  make(map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx),
            nextExpireScan: time.Now().Add(orphanExpireScanInterval),
            outpoints:      make(map[wire.OutPoint]*btcutil.Tx),
        }
    }

2、 Processtransaction

  • This method is used to process rawtx sent by RPC request (handle send raw transaction) and TX synchronized through P2P network (ontx)
  • Supports the insertion of orphan TX

    // 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.
    //
    // It returns a slice of transactions added to the mempool.  When the
    // error is nil, the list will include the passed transaction itself along
    // with any additional orphan transaactions that were added as a result of
    // the passed one being accepted.
    //
    // This function is safe for concurrent access.
    func (mp *TxPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit bool, tag Tag) ([]*TxDesc, error) {
        log.Tracef("Processing transaction %v", tx.Hash())
    
        // Protect concurrent access.
        mp.mtx.Lock()
        defer mp.mtx.Unlock()
    
        // Potentially accept the transaction to the memory pool.
        missingParents, txD, err := mp.maybeAcceptTransaction(tx, true, rateLimit,
            true)
        if err != nil {
            return nil, err
        }
    
        if len(missingParents) == 0 {
            // 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.
            newTxs := mp.processOrphans(tx)
            acceptedTxs := make([]*TxDesc, len(newTxs)+1)
    
            // Add the parent transaction first so remote nodes
            // do not add orphans.
            acceptedTxs[0] = txD
            copy(acceptedTxs[1:], newTxs)
    
            return acceptedTxs, nil
        }
    
        // 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.Hash(), missingParents[0])
            return nil, txRuleError(wire.RejectDuplicate, str)
        }
    
        // Potentially add the orphan transaction to the orphan pool.
        err = mp.maybeAddOrphan(tx, tag)
        return nil, err
    }

3、 Maybeecceptransaction

  • When the block is connected / disconnected from the main chain( blockchain.NTBlockConnected/blockchain . ntblockdisconnected), use this function to process TX in block (including coinbase of course)
  • Insertion of orphan TX is not supported

    // MaybeAcceptTransaction is the main workhorse for handling insertion of new
    // free-standing transactions into a memory pool.  It includes functionality
    // such as rejecting duplicate transactions, ensuring transactions follow all
    // rules, detecting orphan transactions, and insertion into the memory pool.
    //
    // If the transaction is an orphan (missing parent transactions), the
    // transaction is NOT added to the orphan pool, but each unknown referenced
    // parent is returned.  Use ProcessTransaction instead if new orphans should
    // be added to the orphan pool.
    //
    // This function is safe for concurrent access.
    func (mp *TxPool) MaybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit bool) ([]*chainhash.Hash, *TxDesc, error) {
        // Protect concurrent access.
        mp.mtx.Lock()
        hashes, txD, err := mp.maybeAcceptTransaction(tx, isNew, rateLimit, true)
        mp.mtx.Unlock()
    
        return hashes, txD, err
    }

4、 Mabeaccepttransaction

  • This method is eventually called by both processtransaction and mabeaccepttransaction
  • This method will do a series of validation and then insert TX into MemPool

5、 Processorhpans

  • When a new block is connected to the main chain, the existence of an orphan TX based on it is detected for each TX in the block, and the existing TX is moved to the MemPool

    // 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.
    //
    // It returns a slice of transactions added to the mempool.  A nil slice means
    // no transactions were moved from the orphan pool to the mempool.
    //
    // This function is safe for concurrent access.
    func (mp *TxPool) ProcessOrphans(acceptedTx *btcutil.Tx) []*TxDesc {
        mp.mtx.Lock()
        acceptedTxns := mp.processOrphans(acceptedTx)
        mp.mtx.Unlock()
    
        return acceptedTxns
    }

6、 Fetchtransaction

  • Get TX corresponding to hash from MEM pool, excluding orphans pool
  • It is used in some methods of RPC request (searchrawtransactions, getrawtransaction, gettxout,) and sending txmsg in P2P network

    // FetchTransaction returns the requested transaction from the transaction pool.
    // This only fetches from the main transaction pool and does not include
    // orphans.
    //
    // This function is safe for concurrent access.
    func (mp *TxPool) FetchTransaction(txHash *chainhash.Hash) (*btcutil.Tx, error) {
        // Protect concurrent access.
        mp.mtx.RLock()
        txDesc, exists := mp.pool[*txHash]
        mp.mtx.RUnlock()
    
        if exists {
            return txDesc.Tx, nil
        }
    
        return nil, fmt.Errorf("transaction is not in the pool")
    }

7、 Miningdescs

  • RPC getblocktemplate uses this method to generate data needed for mining

    // MiningDescs returns a slice of mining descriptors for all the transactions
    // in the pool.
    //
    // This is part of the mining.TxSource interface implementation and is safe for
    // concurrent access as required by the interface contract.
    func (mp *TxPool) MiningDescs() []*mining.TxDesc {
        mp.mtx.RLock()
        descs := make([]*mining.TxDesc, len(mp.pool))
        i := 0
        for _, desc := range mp.pool {
            descs[i] = &desc.TxDesc
            i++
        }
        mp.mtx.RUnlock()
    
        return descs
    }

8、 Removetransaction

  • Remove transaction from MemPool
  • When a new block is connected to the main chain, remove the transaction in the block

    // RemoveTransaction removes the passed transaction from the mempool. When the
    // removeRedeemers flag is set, any transactions that redeem outputs from the
    // removed transaction will also be removed recursively from the mempool, as
    // they would otherwise become orphans.
    //
    // This function is safe for concurrent access.
    func (mp *TxPool) RemoveTransaction(tx *btcutil.Tx, removeRedeemers bool) {
        // Protect concurrent access.
        mp.mtx.Lock()
        mp.removeTransaction(tx, removeRedeemers)
        mp.mtx.Unlock()
    }
    

9、 Checkpool double spend

  • Double flower detection
  • By comparing the previousoutpoint to be inserted into txin and the existing outpoints in the memory pool

    // checkPoolDoubleSpend checks whether or not the passed transaction is
    // attempting to spend coins already spent by other transactions in the pool.
    // Note it does not check for double spends against transactions already in the
    // main chain.
    //
    // This function MUST be called with the mempool lock held (for reads).
    func (mp *TxPool) checkPoolDoubleSpend(tx *hcutil.Tx, txType stake.TxType) error {
        for i, txIn := range tx.MsgTx().TxIn {
            // We don't care about double spends of stake bases.
            if  i == 0 && (txType == stake.TxTypeSSGen || txType == stake.TxTypeSSRtx) {
                continue
            }
    
            if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists {
                str := fmt.Sprintf("transaction %v in the pool "+
                    "already spends the same coins", txR.Hash())
                return txRuleError(wire.RejectDuplicate, str)
            }
        }
    
        return nil
    }
  • Outpoint contains hash and index

    // OutPoint defines a HC data type that is used to track previous
    // transaction outputs.
    type OutPoint struct {
        Hash  chainhash.Hash
        Index uint32
        Tree  int8
    }
    

10、 Pruneexpiredtx

  • Remove expired transactions based on height
  • Occurs when synchronizing from other peers to a block and when reorganizing

    // PruneExpiredTx prunes expired transactions from the mempool that may no longer
    // be able to be included into a block.
    func (mp *TxPool) PruneExpiredTx(height int64) {
        // Protect concurrent access.
        mp.mtx.Lock()
        mp.pruneExpiredTx(height)
        mp.mtx.Unlock()
    }
    
    func (mp *TxPool) pruneExpiredTx(height int64) {
        for _, tx := range mp.pool {
            if tx.Tx.MsgTx().Expiry != 0 {
                if height >= int64(tx.Tx.MsgTx().Expiry) {
                    log.Debugf("Pruning expired transaction %v "+
                        "from the mempool", tx.Tx.Hash())
                    mp.removeTransaction(tx.Tx, true)
                }
            }
        }
    }