比特币全节点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
}

猜你喜欢

转载自blog.csdn.net/vohyeah/article/details/80713417