天天看點

比特币全節點Go語言實作BTCD之交易的獨立校驗源碼

錢包軟體通過收集UTXO、提供正确的解鎖腳本、構造一個新的支出(支付)給接收者這一系列的方式來建立交易。産生的交易随後将被發送到比特币網絡臨近的節點,進而使得該交易能夠在整個比特币網絡中傳播。

​然而,在交易傳遞到臨近的節點前,每一個收到交易的比特币節點将會首先驗證該交易,這将確定隻有有效的交易才會 在網絡中傳播,而無效的交易将會在第一個節點處被廢棄。

每一個節點在校驗每一筆交易時,都需要對照一個長長的标準清單:

▷交易的文法和資料結構必須正确。

▷輸入與輸出清單都不能為空。

▷交易的位元組大小是小于 MAX_BLOCK_SIZE 的。

▷每一個輸出值,以及總量,必須在規定值的範圍内 (小于2,100萬個币,大于0)。

▷沒有哈希等于0,N等于-1的輸入(coinbase交易不應當被傳遞)。

▷nLockTime是小于或等于 INT_MAX 的。或者nLocktime and nSequence的值滿足MedianTimePast(譯者注:MedianTime是這個塊的前面11個塊按照block time排序後的中間時間)

▷交易的位元組大小是大于或等于100的。

▷交易中的簽名數量(SIGOPS)應小于簽名操作數量上限。

▷解鎖腳本( scriptSig )隻能夠将數字壓入棧中,并且鎖定腳本( scriptPubkey )必須要符合isStandard的格式 (該格式将會拒絕非标準交易)。

▷池中或位于主分支區塊中的一個比對交易必須是存在的。

▷對于每一個輸入,引用的輸出是必須存在的,并且沒有被花費。

▷對于每一個輸入,如果引用的輸出存在于池中任何别的交易中,該交易将被拒絕。

▷對于每一個輸入,在主分支和交易池中尋找引用的輸出交易。如果輸出交易缺少任何一個輸入,該交易将成為一個孤 立的交易。如果與其比對的交易還沒有出現在池中,那麼将被加入到孤立交易池中。

▷對于每一個輸入,如果引用的輸出交易是一個coinbase輸出,該輸入必須至少獲得 COINBASE_MATURITY(100)個确認。

▷使用引用的輸出交易獲得輸入值,并檢查每一個輸入值和總值是否在規定值的範圍内 (小于2100萬個币,大于0)。

▷如果輸入值的總和小于輸出值的總和,交易将被中止。

▷如果交易費用太低以至于無法進入一個空的區塊,交易将被拒絕。

▷每一個輸入的解鎖腳本必須依據相應輸出的鎖定腳本來驗證。

主要代碼如下:

func (mp *TxPool) maybeAcceptTransaction(tx *btcutil.Tx, isNew, rateLimit, rejectDupOrphans bool) ([]*chainhash.Hash, *TxDesc, error) {
   txHash := tx.Hash()

   if tx.MsgTx().HasWitness() {
      segwitActive, err := mp.cfg.IsDeploymentActive(chaincfg.DeploymentSegwit)
      if err != nil {
         return nil, nil, err
      }

      if !segwitActive {
         str := fmt.Sprintf("transaction %v has witness data, "+
            "but segwit isn't active yet", txHash)
         return nil, nil, txRuleError(wire.RejectNonstandard, str)
      }
   }

   if mp.isTransactionInPool(txHash) || (rejectDupOrphans &&
      mp.isOrphanInPool(txHash)) {

      str := fmt.Sprintf("already have transaction %v", txHash)
      return nil, nil, txRuleError(wire.RejectDuplicate, str)
   }

   err := blockchain.CheckTransactionSanity(tx)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }

   // A standalone transaction must not be a coinbase transaction.
   if blockchain.IsCoinBase(tx) {
      str := fmt.Sprintf("transaction %v is an individual coinbase",
         txHash)
      return nil, nil, txRuleError(wire.RejectInvalid, str)
   }

   bestHeight := mp.cfg.BestHeight()
   nextBlockHeight := bestHeight + 1

   medianTimePast := mp.cfg.MedianTimePast()

   if !mp.cfg.Policy.AcceptNonStd {
      err = checkTransactionStandard(tx, nextBlockHeight,
         medianTimePast, mp.cfg.Policy.MinRelayTxFee,
         mp.cfg.Policy.MaxTxVersion)
      if err != nil {
         // Attempt to extract a reject code from the error so
         // it can be retained.  When not possible, fall back to
         // a non standard error.
         rejectCode, found := extractRejectCode(err)
         if !found {
            rejectCode = wire.RejectNonstandard
         }
         str := fmt.Sprintf("transaction %v is not standard: %v",
            txHash, err)
         return nil, nil, txRuleError(rejectCode, str)
      }
   }

   err = mp.checkPoolDoubleSpend(tx)
   if err != nil {
      return nil, nil, err
   }

   utxoView, err := mp.fetchInputUtxos(tx)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }

   // Don't allow the transaction if it exists in the main chain and is not
   // not already fully spent.
   prevOut := wire.OutPoint{Hash: *txHash}
   for txOutIdx := range tx.MsgTx().TxOut {
      prevOut.Index = uint32(txOutIdx)
      entry := utxoView.LookupEntry(prevOut)
      if entry != nil && !entry.IsSpent() {
         return nil, nil, txRuleError(wire.RejectDuplicate,
            "transaction already exists")
      }
      utxoView.RemoveEntry(prevOut)
   }

   var missingParents []*chainhash.Hash
   for outpoint, entry := range utxoView.Entries() {
      if entry == nil || entry.IsSpent() {

         hashCopy := outpoint.Hash
         missingParents = append(missingParents, &hashCopy)
      }
   }
   if len(missingParents) > 0 {
      return missingParents, nil, nil
   }

   sequenceLock, err := mp.cfg.CalcSequenceLock(tx, utxoView)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }
   if !blockchain.SequenceLockActive(sequenceLock, nextBlockHeight,
      medianTimePast) {
      return nil, nil, txRuleError(wire.RejectNonstandard,
         "transaction's sequence locks on inputs not met")
   }

   txFee, err := blockchain.CheckTransactionInputs(tx, nextBlockHeight,
      utxoView, mp.cfg.ChainParams)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }

   if !mp.cfg.Policy.AcceptNonStd {
      err := checkInputsStandard(tx, utxoView)
      if err != nil {
         // Attempt to extract a reject code from the error so
         // it can be retained.  When not possible, fall back to
         // a non standard error.
         rejectCode, found := extractRejectCode(err)
         if !found {
            rejectCode = wire.RejectNonstandard
         }
         str := fmt.Sprintf("transaction %v has a non-standard "+
            "input: %v", txHash, err)
         return nil, nil, txRuleError(rejectCode, str)
      }
   }

   // TODO(roasbeef): last bool should be conditional on segwit activation
   sigOpCost, err := blockchain.GetSigOpCost(tx, false, utxoView, true, true)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }
   if sigOpCost > mp.cfg.Policy.MaxSigOpCostPerTx {
      str := fmt.Sprintf("transaction %v sigop cost is too high: %d > %d",
         txHash, sigOpCost, mp.cfg.Policy.MaxSigOpCostPerTx)
      return nil, nil, txRuleError(wire.RejectNonstandard, str)
   }

   serializedSize := GetTxVirtualSize(tx)
   minFee := calcMinRequiredTxRelayFee(serializedSize,
      mp.cfg.Policy.MinRelayTxFee)
   if serializedSize >= (DefaultBlockPrioritySize-1000) && txFee < minFee {
      str := fmt.Sprintf("transaction %v has %d fees which is under "+
         "the required amount of %d", txHash, txFee,
         minFee)
      return nil, nil, txRuleError(wire.RejectInsufficientFee, str)
   }

   if isNew && !mp.cfg.Policy.DisableRelayPriority && txFee < minFee {
      currentPriority := mining.CalcPriority(tx.MsgTx(), utxoView,
         nextBlockHeight)
      if currentPriority <= mining.MinHighPriority {
         str := fmt.Sprintf("transaction %v has insufficient "+
            "priority (%g <= %g)", txHash,
            currentPriority, mining.MinHighPriority)
         return nil, nil, txRuleError(wire.RejectInsufficientFee, str)
      }
   }
   if rateLimit && txFee < minFee {
      nowUnix := time.Now().Unix()
      // Decay passed data with an exponentially decaying ~10 minute
      // window - matches bitcoind handling.
      mp.pennyTotal *= math.Pow(1.0-1.0/600.0,
         float64(nowUnix-mp.lastPennyUnix))
      mp.lastPennyUnix = nowUnix

      // Are we still over the limit?
      if mp.pennyTotal >= mp.cfg.Policy.FreeTxRelayLimit*10*1000 {
         str := fmt.Sprintf("transaction %v has been rejected "+
            "by the rate limiter due to low fees", txHash)
         return nil, nil, txRuleError(wire.RejectInsufficientFee, str)
      }
      oldTotal := mp.pennyTotal

      mp.pennyTotal += float64(serializedSize)
      log.Tracef("rate limit: curTotal %v, nextTotal: %v, "+
         "limit %v", oldTotal, mp.pennyTotal,
         mp.cfg.Policy.FreeTxRelayLimit*10*1000)
   }
   
   err = blockchain.ValidateTransactionScripts(tx, utxoView,
      txscript.StandardVerifyFlags, mp.cfg.SigCache,
      mp.cfg.HashCache)
   if err != nil {
      if cerr, ok := err.(blockchain.RuleError); ok {
         return nil, nil, chainRuleError(cerr)
      }
      return nil, nil, err
   }

   // Add to transaction pool.
   txD := mp.addTransaction(utxoView, tx, bestHeight, txFee)

   log.Debugf("Accepted transaction %v (pool size: %v)", txHash,
      len(mp.pool))

   return nil, txD, nil
}

func CheckTransactionSanity(tx *btcutil.Tx) error {
   // A transaction must have at least one input.
   msgTx := tx.MsgTx()
   if len(msgTx.TxIn) == 0 {
      return ruleError(ErrNoTxInputs, "transaction has no inputs")
   }

   // A transaction must have at least one output.
   if len(msgTx.TxOut) == 0 {
      return ruleError(ErrNoTxOutputs, "transaction has no outputs")
   }

   // A transaction must not exceed the maximum allowed block payload when
   // serialized.
   serializedTxSize := tx.MsgTx().SerializeSizeStripped()
   if serializedTxSize > MaxBlockBaseSize {
      str := fmt.Sprintf("serialized transaction is too big - got "+
         "%d, max %d", serializedTxSize, MaxBlockBaseSize)
      return ruleError(ErrTxTooBig, str)
   }

   var totalSatoshi int64
   for _, txOut := range msgTx.TxOut {
      satoshi := txOut.Value
      if satoshi < 0 {
         str := fmt.Sprintf("transaction output has negative "+
            "value of %v", satoshi)
         return ruleError(ErrBadTxOutValue, str)
      }
      if satoshi > btcutil.MaxSatoshi {
         str := fmt.Sprintf("transaction output value of %v is "+
            "higher than max allowed value of %v", satoshi,
            btcutil.MaxSatoshi)
         return ruleError(ErrBadTxOutValue, str)
      }

      // Two's complement int64 overflow guarantees that any overflow
      // is detected and reported.  This is impossible for Bitcoin, but
      // perhaps possible if an alt increases the total money supply.
      totalSatoshi += satoshi
      if totalSatoshi < 0 {
         str := fmt.Sprintf("total value of all transaction "+
            "outputs exceeds max allowed value of %v",
            btcutil.MaxSatoshi)
         return ruleError(ErrBadTxOutValue, str)
      }
      if totalSatoshi > btcutil.MaxSatoshi {
         str := fmt.Sprintf("total value of all transaction "+
            "outputs is %v which is higher than max "+
            "allowed value of %v", totalSatoshi,
            btcutil.MaxSatoshi)
         return ruleError(ErrBadTxOutValue, str)
      }
   }

   // Check for duplicate transaction inputs.
   existingTxOut := make(map[wire.OutPoint]struct{})
   for _, txIn := range msgTx.TxIn {
      if _, exists := existingTxOut[txIn.PreviousOutPoint]; exists {
         return ruleError(ErrDuplicateTxInputs, "transaction "+
            "contains duplicate inputs")
      }
      existingTxOut[txIn.PreviousOutPoint] = struct{}{}
   }

   // Coinbase script length must be between min and max length.
   if IsCoinBase(tx) {
      slen := len(msgTx.TxIn[0].SignatureScript)
      if slen < MinCoinbaseScriptLen || slen > MaxCoinbaseScriptLen {
         str := fmt.Sprintf("coinbase transaction script length "+
            "of %d is out of range (min: %d, max: %d)",
            slen, MinCoinbaseScriptLen, MaxCoinbaseScriptLen)
         return ruleError(ErrBadCoinbaseScriptLen, str)
      }
   } else {
      // Previous transaction outputs referenced by the inputs to this
      // transaction must not be null.
      for _, txIn := range msgTx.TxIn {
         if isNullOutpoint(&txIn.PreviousOutPoint) {
            return ruleError(ErrBadTxInput, "transaction "+
               "input refers to previous output that "+
               "is null")
         }
      }
   }

   return nil
}