钱包软件通过收集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 }