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: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.