美文网首页程序员区块链研习社
比特币探究之交易创建

比特币探究之交易创建

作者: 魏兆华 | 来源:发表于2018-07-23 11:31 被阅读84次

    比特币的交易是怎么创建的?除了创币交易(Coinbase)是从挖矿产生的之外,其他的交易来源于各个地址之间的转账,或者说是从钱包发起的转账。

    当用户从一个钱包发起交易的时候,将调用SendMoney函数,它在src/wallet/rpcwallet.cpp中:

    static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount,
        const CCoinControl& coin_control, mapValue_t mapValue, std::string fromAccount)
    {
        //检查转账金额,不能是负值或零,不能超支,且P2P网络正常
        CAmount curBalance = pwallet->GetBalance();
        if (nValue <= 0)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
        if (nValue > curBalance)
            throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
        if (pwallet->GetBroadcastTransactions() && !g_connman) {
            throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
        }
    
        //处理目的地址,生成脚本
        CScript scriptPubKey = GetScriptForDestination(address);
    
        //创建交易
        CReserveKey reservekey(pwallet);
        CAmount nFeeRequired;
        std::string strError;
        std::vector<CRecipient> vecSend;
        int nChangePosRet = -1;
        CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
        vecSend.push_back(recipient);
        CTransactionRef tx;
        if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) {
            if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
                strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
            throw JSONRPCError(RPC_WALLET_ERROR, strError);
        }
    
        //确认并发送交易
        CValidationState state;
        if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, std::move(fromAccount), reservekey, g_connman.get(), state)) {
            strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
            throw JSONRPCError(RPC_WALLET_ERROR, strError);
        }
        return tx;
    }
    

    函数流程比较清晰。下面逐一解析几个重要步骤:

    首先是GetScriptForDestination函数,它根据给定的地址生成脚本。其源码很简单:

    CScript GetScriptForDestination(const CTxDestination& dest)
    {
        CScript script;
        boost::apply_visitor(CScriptVisitor(&script), dest);
        return script;
    }
    

    作为输入参数的CTxDestination类,允许多种输入格式,其中最常用的当然是对方的公钥了。这个时候怎么生成脚本呢?看下面:

    bool operator()(const CKeyID &keyID) const {
        script->clear();
        *script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
        return true;
    }
    

    熟悉脚本的应该一看就明白,这是标准的支付输出脚本。脚本的详细解析留待后续文章,这里暂且略过。

    回到SendMoney函数,接下来的重要步骤就是CreateTransaction,这是个大函数,相当复杂。先看笔者整理的总体流程图: 创建交易总体流程

    上图只是总体流程,具体的各类分支判断很多,没办法一一画出。函数的详细源码及解析如下(有所删节):

    bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,
                                    int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
    {
        CAmount nValue = 0;
        int nChangePosRequest = nChangePosInOut;
        unsigned int nSubtractFeeFromAmount = 0;
        //交易金额不能为负,至少有一个收款人
        for (const auto& recipient : vecSend)
        {
            if (nValue < 0 || recipient.nAmount < 0)
            {
                strFailReason = _("Transaction amounts must not be negative");
                return false;
            }
            nValue += recipient.nAmount;
            if (recipient.fSubtractFeeFromAmount)
                nSubtractFeeFromAmount++;
        }
        if (vecSend.empty())
        {
            strFailReason = _("Transaction must have at least one recipient");
            return false;
        }
    
        CMutableTransaction txNew;
    
        //把nLockTime设定为当前的区块高度,只允许新块在当前链的后面继续生成。
        //这样做的目的是防止费用狙击,比如大矿工可以从当前块的前一个块开始挖,
        //连续挖两个块之后就能超越当前块,那么当前块中的交易可能就无效了。
        txNew.nLockTime = chainActive.Height();
    
        //但是凡事总有例外,偶尔还得允许nLockTime往前推,
        //因为确实有些交易会被耽误,比如高延迟混合网络,或者为了保护隐私的压缩交易
        if (GetRandInt(10) == 0)
            txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
    
        assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
        assert(txNew.nLockTime < LOCKTIME_THRESHOLD);  //指向的是区块高度
        FeeCalculation feeCalc;
        CAmount nFeeNeeded;
        int nBytes;
        {
            std::set<CInputCoin> setCoins;
            LOCK2(cs_main, cs_wallet);
            {
                //找到所有可用的币
                std::vector<COutput> vAvailableCoins;
                AvailableCoins(vAvailableCoins, true, &coin_control);
                CoinSelectionParams coin_selection_params; //选币参数
                CScript scriptChange; //改变找零地址的脚本
    
                //已设定了改变地址,直接用它
                if (!boost::get<CNoDestination>(&coin_control.destChange)) {
                    scriptChange = GetScriptForDestination(coin_control.destChange);
                } else { //没有设,生成一个新地址(钥匙)
                    //一般来说最好用新生成的钥匙,但如果要进行备份恢复,新钥就可能丢掉,
                    //所以用旧钥匙稍微好一些。下面从钥匙池中取一个新的公钥/私钥对
                    CPubKey vchPubKey;
                    bool ret = reservekey.GetReservedKey(vchPubKey, true);
                    if (!ret)
                    {
                        strFailReason = _("Keypool ran out, please call keypoolrefill first");
                        return false;
                    }
    
                    const OutputType change_type = TransactionChangeType(coin_control.m_change_type 
                                                   ? *coin_control.m_change_type : m_default_change_type, vecSend);
                    LearnRelatedScripts(vchPubKey, change_type);
                    scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
                }
                CTxOut change_prototype_txout(0, scriptChange);
                coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
                //太小的找零会被放弃,算作手续费
                CFeeRate discard_rate = GetDiscardRate(*this, ::feeEstimator);
                //计算交易需要的最低费率
                CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc);
    
                nFeeRet = 0;
                bool pick_new_inputs = true;
                CAmount nValueIn = 0;
    
                //如果手续费从转账金额里走,就不使用BnB
                coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0;
                while (true)  //从零费用开始循环,直到手续费足够
                {
                    nChangePosInOut = nChangePosRequest;
                    txNew.vin.clear();
                    txNew.vout.clear();
                    bool fFirst = true;
    
                    CAmount nValueToSelect = nValue;
                    if (nSubtractFeeFromAmount == 0)
                        nValueToSelect += nFeeRet;  //加上另算的手续费
    
                    coin_selection_params.tx_noinputs_size = 11; //4版本号, 4锁定点, 1输入数, 1输出数, 1见证开销
                    for (const auto& recipient : vecSend)
                    {
                        CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
    
                        if (recipient.fSubtractFeeFromAmount)
                        {   //如果手续费从金额走,转账金额就需要减掉手续费
                            assert(nSubtractFeeFromAmount != 0);
                            txout.nValue -= nFeeRet / nSubtractFeeFromAmount;
                            if (fFirst)  //剩下的余数交给第一个接受者承担
                            {
                                fFirst = false;
                                txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
                            }
                        }
                        //包含手续费的大小
                        coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, SER_NETWORK, PROTOCOL_VERSION);
    
                        if (IsDust(txout, ::dustRelayFee))   //如果交易太小
                        {
                            if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
                            {
                                if (txout.nValue < 0)  //金额太小,付费都不够
                                    strFailReason = _("The transaction amount is too small to pay the fee");
                                else  //付完费后,剩不下几个子了
                                    strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
                            }
                            else
                                strFailReason = _("Transaction amount too small");
                            return false;
                        }
                        txNew.vout.push_back(txout);  //输出搞定了
                    }
    
                    //找用于输入的币,找不到说明金额不够了
                    bool bnb_used;
                    if (pick_new_inputs) {
                        nValueIn = 0;
                        setCoins.clear();
                        coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
                        coin_selection_params.effective_fee = nFeeRateNeeded;
                        if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
                        {
                            if (bnb_used) {
                                coin_selection_params.use_bnb = false;
                                continue;
                            }
                            else {
                                strFailReason = _("Insufficient funds");
                                return false;
                            }
                        }
                    }
    
                    const CAmount nChange = nValueIn - nValueToSelect;
                    if (nChange > 0)  //需要找零
                    {
                        CTxOut newTxOut(nChange, scriptChange);  //换钥匙找零给自己
    
                        //永远不要创建垃圾输出,如果有,就加到手续费
                        if (IsDust(newTxOut, discard_rate) || bnb_used)
                        {
                            nChangePosInOut = -1;
                            nFeeRet += nChange;
                        }
                        else
                        {
                            if (nChangePosInOut == -1)
                            {
                                //把找零放到一个随机位置,可以保护隐私
                                nChangePosInOut = GetRandInt(txNew.vout.size()+1);
                            }
                            else if ((unsigned int)nChangePosInOut > txNew.vout.size())
                            {
                                strFailReason = _("Change index out of range");
                                return false;
                            }
    
                            std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
                            txNew.vout.insert(position, newTxOut);  //找零加入输出
                        }
                    } else {
                        nChangePosInOut = -1;
                    }
    
                    //先填上输入币,便于估算大小
                    for (const auto& coin : setCoins) {
                        txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
                    }
                    nBytes = CalculateMaximumSignedTxSize(txNew, this);
                    if (nBytes < 0) {
                        strFailReason = _("Signing transaction failed");
                        return false;
                    }
                    //估算最低费用
                    nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
                    if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
                        strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
                        return false;
                    }
    
                    //如果连下一遍的延迟费都搞不定,说明交易太大了,放弃
                    if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
                    {
                        strFailReason = _("Transaction too large for fee policy");
                        return false;
                    }
    
                    if (nFeeRet >= nFeeNeeded) {
                        //防止手续费过高。不添加输入(大小、费用都会降低)再试一次
                        if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
                            unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; //加2作为缓冲
                            CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr);
                            CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate);
                            if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
                                pick_new_inputs = false;
                                nFeeRet = fee_needed_with_change;
                                continue;
                            }
                        }
    
                        //交易费输出已经有了,直接调整就行
                        if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
                            CAmount extraFeePaid = nFeeRet - nFeeNeeded;
                            std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
                            change_position->nValue += extraFeePaid;
                            nFeeRet -= extraFeePaid;
                        }
                        break;  //有了足够费用,搞定,退出循环
                    }
                    else if (!pick_new_inputs) {
                        //不应当发生。要么够付手续费,要么减少转账金额,最低费用不会变
                        strFailReason = _("Transaction fee and change calculation failed");
                        return false;
                    }
    
                    //尝试减少找零,增加手续费
                    if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
                        CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet;
                        std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
                        //如果找零金额比较大,够付手续费
                        if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
                            change_position->nValue -= additionalFeeNeeded;
                            nFeeRet += additionalFeeNeeded;
                            break;  //降低找零搞定了手续费,成功退出
                        }
                    }
    
                    //如果要从收款人那减去手续费,现在已经知道费用多少,用不着重新选币了
                    if (nSubtractFeeFromAmount > 0) {
                        pick_new_inputs = false;
                    }
    
                    //调高手续费,再试一次
                    nFeeRet = nFeeNeeded;
                    coin_selection_params.use_bnb = false;
                    continue;
                }
            }
    
            if (nChangePosInOut == -1) reservekey.ReturnKey();
    
            //打乱币序,填进输入
            txNew.vin.clear();
            std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
            std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
    
            //根据BIP125,调整nSequence,允许交易自行更换
            const uint32_t nSequence = coin_control.m_signal_bip125_rbf.get_value_or(m_signal_rbf) 
                                       ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
            for (const auto& coin : selected_coins) {
                txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
            }
    
            if (sign)
            {
                int nIn = 0;
                for (const auto& coin : selected_coins)
                {
                    const CScript& scriptPubKey = coin.txout.scriptPubKey;
                    SignatureData sigdata;
                    //交易签名
                    if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
                    {
                        strFailReason = _("Signing transaction failed");
                        return false;
                    } else {
                        UpdateInput(txNew.vin.at(nIn), sigdata);
                    }
                    nIn++;
                }
            }
            tx = MakeTransactionRef(std::move(txNew));  //返回生成的交易
        }
        return true;
    }
    

    下一节再分析交易签名。


    欢迎转载,请注明出处。

    相关文章

      网友评论

        本文标题:比特币探究之交易创建

        本文链接:https://www.haomeiwen.com/subject/okurmftx.html