錢包軟體通過收集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
}