Transaction creation of bitcoin exploration

Keywords: network REST

How are bitcoin transactions created? In addition to Coinbase transactions, which are generated from mining, other transactions come from transfers between addresses, or from wallets.

When a user initiates a transaction from a wallet, the SendMoney function is called. It is in 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)
{
    //Check the transfer amount, no negative value or zero, no overspending, and P2P network is normal
    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");
    }

    //Process destination address, generate script
    CScript scriptPubKey = GetScriptForDestination(address);

    //Create transactions
    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);
    }

    //Confirm and send transaction
    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;
}

The function flow is clear. Here are several important steps:

The first is the GetScriptForDestination function, which generates a script based on a given address. The source code is very simple:

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

As an input parameter, the CTxDestination class allows multiple input formats, of which the most commonly used is the public key of the other party. How to generate scripts at this time? Look at the following:

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

Familiar with the script should be clear at a glance, this is the standard payment output script. The detailed analysis of the script will be left for future articles, which will be skipped here.

Back to the SendMoney function, the next important step is to create transaction, which is a large function and quite complex. First look at the overall flow chart arranged by the author:
Create the overall transaction process

The above figure is just the overall process, and there are many specific branch judgments, which can't be drawn one by one. The detailed source code and resolution of the function are as follows (Abridged):

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;
    //Transaction amount cannot be negative, at least one payee
    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;

    //Set nLockTime to the current block height, and only new blocks are allowed to continue to be generated after the current chain.
    //The purpose of this is to prevent cost sniping. For example, the big miner can start digging from the previous block of the current block,
    //If you can surpass the current block after digging two blocks in a row, the transaction in the current block may be invalid.
    txNew.nLockTime = chainActive.Height();

    //But there are always exceptions, occasionally allowing nLockTime to push forward,
    //Because it's true that some transactions will be delayed, such as high latency hybrid networks, or compressed transactions to protect privacy
    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);  //Point to block height
    FeeCalculation feeCalc;
    CAmount nFeeNeeded;
    int nBytes;
    {
        std::set<CInputCoin> setCoins;
        LOCK2(cs_main, cs_wallet);
        {
            //Find all available currencies
            std::vector<COutput> vAvailableCoins;
            AvailableCoins(vAvailableCoins, true, &coin_control);
            CoinSelectionParams coin_selection_params; //Currency parameter selection
            CScript scriptChange; //Script to change the change address

            //Change address has been set, use it directly
            if (!boost::get<CNoDestination>(&coin_control.destChange)) {
                scriptChange = GetScriptForDestination(coin_control.destChange);
            } else { //No setting, generate a new address (key)
                //In general, it is better to use the newly generated key, but if you want to perform backup recovery, the new key may be lost,
                //So it's a little better to use the old key. Next, take a new public / private key pair from the key pool
                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);
            //Too small change will be given up as a handling fee
            CFeeRate discard_rate = GetDiscardRate(*this, ::feeEstimator);
            //Calculate the minimum rate required for the transaction
            CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc);

            nFeeRet = 0;
            bool pick_new_inputs = true;
            CAmount nValueIn = 0;

            //BnB is not used if the handling fee is from the transfer amount
            coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0;
            while (true)  //Cycle from zero until the handling fee is sufficient
            {
                nChangePosInOut = nChangePosRequest;
                txNew.vin.clear();
                txNew.vout.clear();
                bool fFirst = true;

                CAmount nValueToSelect = nValue;
                if (nSubtractFeeFromAmount == 0)
                    nValueToSelect += nFeeRet;  //Plus additional service charge

                coin_selection_params.tx_noinputs_size = 11; //4 version number, 4 lock point, 1 input number, 1 output number, 1 witness cost
                for (const auto& recipient : vecSend)
                {
                    CTxOut txout(recipient.nAmount, recipient.scriptPubKey);

                    if (recipient.fSubtractFeeFromAmount)
                    {   //If the handling fee goes from the amount, the transfer amount needs to reduce the handling fee
                        assert(nSubtractFeeFromAmount != 0);
                        txout.nValue -= nFeeRet / nSubtractFeeFromAmount;
                        if (fFirst)  //The rest goes to the first recipient
                        {
                            fFirst = false;
                            txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
                        }
                    }
                    //Including the size of handling charges
                    coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, SER_NETWORK, PROTOCOL_VERSION);

                    if (IsDust(txout, ::dustRelayFee))   //If the deal is too small
                    {
                        if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
                        {
                            if (txout.nValue < 0)  //The amount is too small, the payment is not enough
                                strFailReason = _("The transaction amount is too small to pay the fee");
                            else  //After paying the fee, there are not many left
                                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);  //The output is done
                }

                //The currency used for input is found. The description amount is not enough
                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)  //Need change
                {
                    CTxOut newTxOut(nChange, scriptChange);  //Change the key for yourself

                    //Never create garbage output, if any, add it to the handling fee
                    if (IsDust(newTxOut, discard_rate) || bnb_used)
                    {
                        nChangePosInOut = -1;
                        nFeeRet += nChange;
                    }
                    else
                    {
                        if (nChangePosInOut == -1)
                        {
                            //Put the change in a random place to protect your privacy
                            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);  //Zero change input output
                    }
                } else {
                    nChangePosInOut = -1;
                }

                //Fill in the input currency to estimate the size
                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;
                }
                //Estimated minimum cost
                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 you can't figure out the next delay fee, it means the transaction is too big. Give up
                if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
                {
                    strFailReason = _("Transaction too large for fee policy");
                    return false;
                }

                if (nFeeRet >= nFeeNeeded) {
                    //To prevent excessive handling charges. Try again without adding input (size and cost will be reduced)
                    if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
                        unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; //Add 2 as buffer
                        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;
                        }
                    }

                    //Transaction fee output already exists, just adjust it directly
                    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;  //With enough money, get it done. Get out of the loop
                }
                else if (!pick_new_inputs) {
                    //It should not happen. Either pay the handling fee or reduce the transfer amount, the minimum fee will not change
                    strFailReason = _("Transaction fee and change calculation failed");
                    return false;
                }

                //Try to reduce change and increase service charge
                if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
                    CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet;
                    std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
                    //If the change amount is large enough to pay the handling fee
                    if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
                        change_position->nValue -= additionalFeeNeeded;
                        nFeeRet += additionalFeeNeeded;
                        break;  //Reduce the change, settle the handling fee and exit successfully
                    }
                }

                //If you want to deduct the handling fee from the payee, now you know how much the fee is, so you don't need to reselect the currency
                if (nSubtractFeeFromAmount > 0) {
                    pick_new_inputs = false;
                }

                //Increase the service charge and try again
                nFeeRet = nFeeNeeded;
                coin_selection_params.use_bnb = false;
                continue;
            }
        }

        if (nChangePosInOut == -1) reservekey.ReturnKey();

        //Scrambling currency order and filling in input
        txNew.vin.clear();
        std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
        std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());

        //According to BIP125, adjust nSequence and allow the transaction to change itself
        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;
                //Transaction signature
                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 generated transactions
    }
    return true;
}

The next section analyzes the transaction signature.

Welcome to reprint, please indicate the source.

Posted by jl9148 on Fri, 31 Jan 2020 10:03:34 -0800