比特币每一笔交易可以包含一个或多个输入和一个或多个输出。先看看一笔交易是如何创建的。创建交易的行为在钱包中发生。可以在比特源码中的 wallet.cpp 的 CreateTransaction 函数中看到具体实现。本文只记录了交易输出的创建过程。比特币交易的类型有几种。为了方便起见,这里假定交易的类型是 P2PKH。
P2PKH
支付到比特币地址的交易包含支付公钥哈希脚本(P2PKH)。由P2PKH脚本锁定的交易输出可以通过给出由相应
私钥创建的公钥和数字签名来解锁(消费)。
其交易模型是这样的
bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
...
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;
}
...
/*
recipient 就是比特的接收方
struct CRecipient
{
CScript scriptPubKey; // 锁定脚本
CAmount nAmount; // 比特币数量
bool fSubtractFeeFromAmount; //字面上是 “从金额中扣除费用”。
};
要求满足以下条件才能交易的创建工作才能继续往前
1.接收方的比特币数目不能为负数
2.接收方的人数要大于1
*/
...
CMutableTransaction txNew;
txNew.nLockTime = chainActive.Height();
/*
针对费用狙击的时间锁定。《精通比特币》中有详细的介绍。
*/
...
FeeCalculation feeCalc;
CAmount nFeeNeeded; //手续费
unsigned int nBytes;
...
std::vector vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control);//从钱包中找到可花费的比特币
...
if (!boost::get(&coin_control.destChange)) {//???
scriptChange = GetScriptForDestination(coin_control.destChange);
} else {
// Reserve a new key pair from key pool
CPubKey vchPubKey; //包装的公钥
bool ret;
ret = reservekey.GetReservedKey(vchPubKey, true);
if (!ret)
{
strFailReason = _("Keypool ran out, please call keypoolrefill first");
return false;
}
scriptChange = GetScriptForDestination(vchPubKey.GetID()); //用于找零
}
/*
跟进 GetScriptForDestination,如果是P2PKH 类型的交易, 会发现创建了一个锁定脚本
bool operator()(const CKeyID &keyID) const {
script->clear();
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
return true;
}
*/
...
// Start with no fee and loop until there is enough fee
// 计算交易费
while (true)
{
...
// vouts to the payees
for (const auto& recipient : vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);//构建输出
if (recipient.fSubtractFeeFromAmount)
{
...
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
// 从接收方的接收比特币数量减去手续费?有点像快递到付
...
}
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);//把创建的交易输出放进交交易中
}
// Choose coins to use
if (pick_new_inputs) { // true
...
//找出做够的比特币用来支付,比特币保存在 setcoins 中
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control))
{
strFailReason = _("Insufficient funds");
return false;
}
}
...
const CAmount nChange = nValueIn - nValueToSelect;//支付给接收方后多出来的比特币
...
if (nChange > 0)
{
// Fill a vout to ourself
CTxOut newTxOut(nChange, scriptChange);//--找零 scriptChange 是发送方的
// Never create dust outputs; if we would, just
// add the dust to the fee.
//nChange 如果很小直接用作手续费
if (IsDust(newTxOut, discard_rate))
{
nChangePosInOut = -1;
nFeeRet += nChange;
}
else
{
if (nChangePosInOut == -1)
{
// Insert change txn at random position:
nChangePosInOut = GetRandInt(txNew.vout.size()+1);
}
else if ((unsigned int)nChangePosInOut > txNew.vout.size())
{
strFailReason = _("Change index out of range");
return false;
}
std::vector::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut); // 把找零输出加入交易
}
} else {
nChangePosInOut = -1;
}
//构建交易的输出完毕。
...
总结:
- 判断接收方的合法性,合法继续,不合法则终止创建交易。
- 从钱包中找到可用的比特币(UTXO),从可用的比特币中选出足够的支付的比特币,如果不足以支付,终止创建交易。
- 计算手续费,找零。
备注:
本文只记录了比特币交易中交易输出的过程,省略了很多代码(主要是看不太懂:)),肯定有理解错误的地方,欢迎指正。
作者:区块链研习社比特币源码研读班 wwgz