双重支付是成功支付了1次以上的情况。比特币通过对添加到区块中的每笔交易进行验证来防止双重支付,确保交易的输入没有被支付过。
区块中的交易采用的是UTXO模型,每个节点进行打包的时候都会对交易进行验证,交易的输入是否在之前区块存在过,每个节点收到区块时也会对区块及区块中的交易进行合法性校验。
OK,看代码:
func btcdMain(serverChan chan<- *server) error {
主入口
func (s *server) Start() {
启动server对象
go s.peerHandler()
启动节点操作
s.syncManager.Start()
启动同步管理器,负责同步区块、交易等
go s.connManager.Start()
启动连接管理器,负责建立节点的连接,并监听消息
启动syncManager,会调用
go sm.blockHandler()
此方法负责同步各种消息的处理,代码:
func (sm *SyncManager) blockHandler() { out: for { select { case m := <-sm.msgChan: switch msg := m.(type) { case *newPeerMsg: sm.handleNewPeerMsg(msg.peer) case *txMsg: sm.handleTxMsg(msg) msg.reply <- struct{}{} case *blockMsg: sm.handleBlockMsg(msg) msg.reply <- struct{}{} case *invMsg: sm.handleInvMsg(msg) case *headersMsg: sm.handleHeadersMsg(msg) case *donePeerMsg: sm.handleDonePeerMsg(msg.peer) case getSyncPeerMsg: var peerID int32 if sm.syncPeer != nil { peerID = sm.syncPeer.ID() } msg.reply <- peerID case processBlockMsg: _, isOrphan, err := sm.chain.ProcessBlock( msg.block, msg.flags) if err != nil { msg.reply <- processBlockResponse{ isOrphan: false, err: err, } } msg.reply <- processBlockResponse{ isOrphan: isOrphan, err: nil, } case isCurrentMsg: msg.reply <- sm.current() case pauseMsg: // Wait until the sender unpauses the manager. <-msg.unpause default: log.Warnf("Invalid message type in block "+ "handler: %T", msg) } case <-sm.quit: break out } } sm.wg.Done() log.Trace("Block handler done") }
sm.handleTxMsg(msg)
即是处理交易信息,主要代码逻辑:
func (sm *SyncManager) handleTxMsg(tmsg *txMsg) { peer := tmsg.peer state, exists := sm.peerStates[peer] txHash := tmsg.tx.Hash() acceptedTxs, err := sm.txMemPool.ProcessTransaction(tmsg.tx, true, true, mempool.Tag(peer.ID())) delete(state.requestedTxns, *txHash) delete(sm.requestedTxns, *txHash) sm.peerNotifier.AnnounceNewTransactions(acceptedTxs) }
acceptedTxs, err := sm.txMemPool.ProcessTransaction(tmsg.tx, true, true, mempool.Tag(peer.ID()))
这一行代码交给交易内存池处理消息带的交易。
func (mp *TxPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit bool, tag Tag) ([]*TxDesc, error) { // 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 { 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 { 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 }
missingParents, txD, err := mp.maybeAcceptTransaction(tx, true, rateLimit, true)
maybeAcceptTransaction方法主要负责验证交易的合法性,处理逻辑很多,缩减后主要逻辑:
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 } if blockchain.IsCoinBase(tx) { //不能是coinbase交易,coinbase交易单独处理 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) } err = mp.checkPoolDoubleSpend(tx) //在交易池中检查输入是否已经被花费,代码在下方 if err != nil { return nil, nil, err } utxoView, err := mp.fetchInputUtxos(tx) //取这个交易相关的区块上的所有utxo 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) } // Transaction is an orphan var missingParents []*chainhash.Hash for outpoint, entry := range utxoView.Entries() { if entry == nil || entry.IsSpent() { // Must make a copy of the hash here since the iterator // is replaced and taking its address directly would // result in all of the entries pointing to the same // memory location and thus all be the final hash. 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 !mp.cfg.Policy.AcceptNonStd { err := checkInputsStandard(tx, utxoView) //检查输入标准化 } 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 } 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 { //检查交易的合法性 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") } 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) } } // 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) { //是coinbase交易 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 { for _, txIn := range msgTx.TxIn { if isNullOutpoint(&txIn.PreviousOutPoint) { return ruleError(ErrBadTxInput, "transaction "+ "input refers to previous output that "+ "is null") } } } return nil }
func (mp *TxPool) checkPoolDoubleSpend(tx *btcutil.Tx) error { for _, txIn := range tx.MsgTx().TxIn { if txR, exists := mp.outpoints[txIn.PreviousOutPoint]; exists { str := fmt.Sprintf("output %v already spent by "+ "transaction %v in the memory pool", txIn.PreviousOutPoint, txR.Hash()) return txRuleError(wire.RejectDuplicate, str) } } return nil }