交易的产生(一)–生成地址
交易的产生(二)–创建交易
交易的产生(三)–承诺交易
交易的产生(四)–脚本和签名
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
注释部分,这里的 SelectCoins()是选择一组币使得nValueRet >= nTargetValue,大于或等于目标金额
鉴于这段代码很长,分段来讲
参数说明
类型 | 名称 | 说明 |
---|---|---|
vector
|
vecSend | struct CRecipient{CScript scriptPubKey;CAmount nAmount;bool fSubtractFeeFromAmount;}接收者数组 |
CWalletTx& | wtxNew | 包含附加信息的交易信息,这个类只关注本钱包发起(可能包含接收)的交易,比如为啥发送 |
CReserveKey& | reservekey | 密钥池分配的密钥 |
CAmount& | nFeeRet | CAmount nFeeRequired=0 |
int& | nChangePosRet | 改变标志位?=-1 |
std::string& | strFailReason | 失败的原因 |
CCoinControl* | coinControl | 币控制功能=NULL |
bool | sign | 默认为true |
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign)
{
CAmount nValue = 0;//初始化为0
int nChangePosRequest = nChangePosInOut;//赋值-1
unsigned int nSubtractFeeFromAmount = 0;
BOOST_FOREACH (const CRecipient& recipient, vecSend)//解析接收者的信息
{
if (nValue < 0 || recipient.nAmount < 0)
{//交易金额不能为负
strFailReason = _("Transaction amounts must be positive");
return false;
}
nValue += recipient.nAmount;//累加每个接收者的金额
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;//如果要从交易金额中减去交易费,则计数增加
}
if (vecSend.empty() || nValue < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this);//绑定钱包
CMutableTransaction txNew;//A mutable version of CTransaction.
nLockTime是交易类的成员变量,我之前写到过
https://blog.csdn.net/m0_37847176/article/details/81624052#ctransaction
锁定时间也称为 nLocktime,是来自于 Bitcoin Core 代码库中使用的变量名称。在 大多数交易中将其设置为零,以指示即时传播和执行。如果 nLocktime 不为零, 低于 5 亿,则将其解释为块高度,这意味着交易无效,并且在指定的块高度之前 未被中继或包含在块链中。
如果超过 5 亿,它被解释为 Unix 纪元时间戳(自 Jan-1-1970 之后的秒数),并且 交易在指定时间之前无效。指定未来块或时间的 nLocktime 的交易必须由始发系 统持有,并且只有在有效后才被发送到比特币网络。如果交易在指定的 nLocktime之前传输到网络,那么第一个节点就会拒绝该交易,并且不会被中继到其他节点。使用 nLocktime 等同于一张延期支票。
Discourage fee sniping.
For a large miner the value of the transactions in the best block and the mempool can exceed the cost of deliberately attempting to mine two blocks to orphan the current best block. By setting nLockTime such that only the next block can include the transaction, we discourage this practice as the height restricted and limited blocksize gives miners considering fee sniping fewer options for pulling off this attack.
A simple way to think about this is from the wallet’s point of view we always want the blockchain to move forward. By setting nLockTime this way we’re basically making the statement that we only want this transaction to appear in the next block; we don’t want to potentially encourage reorgs by allowing transactions to appear at lower heights than the next block in forks of the best chain.
Of course, the subsidy is high enough, and transaction volume low enough, that fee sniping isn’t a problem yet, but by implementing a fix now we ensure code won’t be written that makes assumptions about nLockTime that preclude a fix later.
以下解说引用自《精通比特币(第二版)》
费用狙击是一种理论攻击情形,矿工试图从将来的块(挑选手续费较高的交易)重写过去的块,实现“狙击”更高费用的交易,以最大限度地提高盈利能力。
例如,假设存在的最高块是块#100,000。如果不是试图把#100,001 号的矿区扩 大到区块链,那么一些矿工们会试图重新挖矿#100,000。这些矿工可以选择在候 选块#100,000 中包括任何有效的交易(尚未开采)。他们不必使用相同的交易 来恢复块。事实上,他们有动力选择最有利可图(最高每 kBB)的交易来包含在 其中。它们可以包括处于“旧”#100,000 中的任何交易,以及来自当前内存池的 任何交易。当他们重新创建块#100,000 时,他们本质上可以将交易从“现在”提取 到重写的“过去”中。
今天,这种袭击并不是非常有利可图,因为回报奖励(因为包括 一定数量的比特币奖励)远远高于每个区块的总费用。但在未来的某个时候,交 易费将是奖励的大部分(甚至是奖励的整体)。那时候这种情况变得不可避免了。
为了防止“费用狙击”,当 Bitcoin Core /钱包 创建交易时,默认情况下,它使用 nLocktime 将它们限制为“下一个块”。在我们的环境中,Bitcoin Core /钱包将在任 何创建的交易上将 nLocktime 设置为 100,001。在正常情况下,这个 nLocktime 没 有任何效果 - 交易只能包含在#100,001 块中,这是下一个区块。 但是在区块链 分叉攻击的情况下,由于所有这些交易都将被时间锁阻止在#100,001,所以矿工 们无法从筹码中提取高额交易。他们只能在当时有效的任何交易中重新挖矿 #100,000,这导致实质上不会获得新的费用。 为了实现这一点,Bitcoin Core/钱 包将所有新交易的 nLocktime 设置为,并将所有输入上的 nSequence 设置为 0xFFFFFFFE 以启用 nLocktime。
txNew.nLockTime = chainActive.Height();//当前有效区块的高度
Secondly occasionally randomly pick a nLockTime even further back, so that transactions that are delayed after signing for whatever reason, e.g. high-latency mix networks and some CoinJoin implementations, have better privacy.
接着偶尔(0.1的概率)随机获取一个甚至可能更早的nLockTime,以便签名后的交易因任意原因延迟,比如高延迟混合网络和一些CoinJoin实现,有更好的隐私性。
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);
先来介绍用到的函数AvailableCoins,就是找到可用utxo
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
{
vCoins.clear();//清空数组
{
LOCK2(cs_main, cs_wallet);
//std::map mapWallet; CWallet的类内成员,保存和钱包有关的交易
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const uint256& wtxid = it->first;
const CWalletTx* pcoin = &(*it).second;
if (!CheckFinalTx(*pcoin))
continue;
if (fOnlyConfirmed && !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0)
continue;
// We should not consider coins which aren't at least in our mempool
// It's possible for these to be conflicted via ancestors which we may never be able to detect
if (nDepth == 0 && !pcoin->InMempool())
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
isminetype mine = IsMine(pcoin->vout[i]);//isminetype是枚举类型
//未被花费,可以被我花费,未被锁定的交易, ISMINE_NO =0
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 0 || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i))))
vCoins.push_back(COutput(pcoin, i, nDepth,
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO),
(mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO));
}
}
}
}
这里开始的while循环很长
{
LOCK2(cs_main, cs_wallet);
{
std::vector<COutput> vAvailableCoins;//可用的交易输出,就是这笔交易的输入
//用可用的交易输出填充vAvailableCoins,vAvailableCoins就是可用的交易输出
AvailableCoins(vAvailableCoins, true, coinControl);
nFeeRet = 0;//交易费是从0开始
// Start with no fee and loop until there is enough fee循环直到有足够的交易金额
while (true)
{
//初始化工作,清零
nChangePosInOut = nChangePosRequest;//-1
txNew.vin.clear();
txNew.vout.clear();
txNew.wit.SetNull();
wtxNew.fFromMe = true;
bool fFirst = true;//第一轮循环
CAmount nValueToSelect = nValue;//需要选择的金额,初始为之前统计的所有接受者的金额
if (nSubtractFeeFromAmount == 0)
nValueToSelect += nFeeRet;//加上交易费,初始为0
double dPriority = 0;
对每个接收者的处理,对每个接收者创建一个CTxOut
// vouts to the payees
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
//如果设置从交易金额中减去交易费,那么需要分摊到每个设置的接收者,减去平均交易费
if (recipient.fSubtractFeeFromAmount)
{
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
//第一个接收者,还需要支出不能被整除的需要的交易费的剩余部分
if (fFirst) // first receiver pays the remainder not divisible by output count
{
fFirst = false;
txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
}
}
//如果这个输出是Dust输出,也就是交易输出太小,称为灰尘交易
if (txout.IsDust(::minRelayTxFee))
{
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
set<pair<const CWalletTx*,unsigned int> > setCoins;
CAmount nValueIn = 0;
//Shuffle and select coins until nTargetValue is reached while avoiding small change;
//打乱重排并选择可用的coins直到达到nTargetValue同时避免小的找零,这里是指到达nValueToSelect
//setCoins包含支付给你本人地址的交易,即你所拥有的币
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl))
{
strFailReason = _("Insufficient funds");
return false;
}
//对选择好的这一组coins的每个来源计算优先级
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;//金额
//The coin age after the next block (depth+1) is used instead of the current,
//reflecting an assumption the user would accept a bit more delay for
//a chance at a free transaction.
//But mempool inputs might still be in the mempool, so their age stays 0
/*用下一个块(深度+ 1)之后的硬币年龄而不是当前,这反映了一个假设,即用户可以接受更多延迟以获得免费交易的机会。但是mempool输入可能仍然在mempool中, 所以他们的年龄保持在0*/
int age = pcoin.first->GetDepthInMainChain();
assert(age >= 0);
if (age != 0)
age += 1;
dPriority += (double)nCredit * age;//增加优先级,优先级为币龄*金额
}
可以来看一下SelectCoins,CCoinControl的值一直为null,看代码的时候可以忽略大部分内容,那么可以看到主要是调用了SelectCoinsMinConf这个函数
bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
{
vector<COutput> vCoins(vAvailableCoins);
// calculate value from preset inputs and store them从预设输入计算金额
set<pair<const CWalletTx*, uint32_t> > setPresetCoins;
CAmount nValueFromPresetInputs = 0;
size_t nMaxChainLength = std::min(GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT), GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT));
bool fRejectLongChains = GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
bool res = nTargetValue <= nValueFromPresetInputs ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0, vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0, vCoins, setCoinsRet, nValueRet) ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2, vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::min((size_t)4, nMaxChainLength/3), vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength/2, vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, nMaxChainLength, vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, std::numeric_limits<uint64_t>::max(), vCoins, setCoinsRet, nValueRet));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
// add preset inputs to the total value selected
nValueRet += nValueFromPresetInputs;
return res;
}
SelectCoinsMinConf
这个函数的注释
随机排列并选择硬币,直到达到nTargetValue,同时避免小的找零; 该方法对于某些输入是随机的,并且在完成时组装硬币组和相应的实际目标值
const CAmount nChange = nValueIn - nValueToSelect;//超出所需支出,那么需要找零
if (nChange > 0)
{
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
/*为自己填写一个vout
*TODO:传入scriptChange而不是reservekey,因此找零交易并不总是付费到比特币地址*/
CScript scriptChange;
// coin control: send change to custom address 找零支付回习惯地址,如果设置的话
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
scriptChange = GetScriptForDestination(coinControl->destChange);
// no coin control: send change to newly generated address
//没有coin control的话默认没有设置)那么找零到一个新创建的地址
else
{
// Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change.
// If we reused the old key, it would be possible to add code to look for and
// rediscover unknown transactions that were written with keys of ours to recover
// post-backup change.
/*我们使用一个新的密钥避免找零地址变得明显。缺点是在不重复使用之前的密钥的情况下,如果没有备份用于找零的新私钥,
*则在恢复备份时可能会丢失找零。如果我们重复使用老的密钥,那么有可能通过添加代码去查找和重新发现
*用我们的密钥编写的未明确的交易以恢复备份后的更改*/
// Reserve a new key pair from key pool 从密钥池中预约一个新的密钥
CPubKey vchPubKey;
bool ret;
ret = reservekey.GetReservedKey(vchPubKey);//获取一个公钥
if (!ret)
{
strFailReason = _("Keypool ran out, please call keypoolrefill first");
return false;
}
scriptChange = GetScriptForDestination(vchPubKey.GetID());//找零脚本
}
CTxOut newTxOut(nChange, scriptChange);//新建一笔找零交易
上述代码调用reservekey
的GetReservedKey
,reservekey
是传入的参数(只是一个初始的对象)
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{
if (nIndex == -1)//初始的时候被设置为-1
{
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex != -1)
vchPubKey = keypool.vchPubKey;
else {
return false;
}
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
return true;
}
GetReservedKey
调用CWallet
类的ReserveKeyFromKeyPool
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();//构造一个无效的PubKey
{
LOCK(cs_wallet);
if (!IsLocked())
TopUpKeyPool();//充值密钥池,这个函数里一个循环,默认创建(最少)100个新的密钥添加到池中
// Get the oldest key
if(setKeyPool.empty())
return;
CWalletDB walletdb(strWalletFile);
nIndex = *(setKeyPool.begin());//返回容器指向的第一个元素,所以说是获取最老的密钥
setKeyPool.erase(setKeyPool.begin());//擦除这个指针指向的元素
if (!walletdb.ReadPool(nIndex, keypool))
throw runtime_error(std::string(__func__) + ": read failed");
if (!HaveKey(keypool.vchPubKey.GetID()))
throw runtime_error(std::string(__func__) + ": unknown key in key pool");
assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
}
}
需要注意这里的setKeyPool是signd long long的set容器类型
在日志信息中可以看到,创建了101把密钥,从1到101,也就是原本这个池中一把都没有,这个池应该是专门用于找零的池子,虽然密钥都是保存在键值对中,但是找零专用的密钥对写在池中“pool“,调用WritePool函数,另一种是写在‘keymate‘中这一句我写的是有点问题的
这里纠正一下,TopUpKeyPool()在填充密钥池的时候也是用到GenerateNewKey()这个函数来生成新的密钥对,生成的密钥对调用AddKeyPubKey函数里面的CWalletDB(strWalletFile).WriteKey()
把私钥公钥哈希值都写入数据库,键值为(“keymeta",vchPubKey)和((“key”,vchPubKey),(vchPrivKey,hash值),false)
然后TopUpKeyPool()的话会再把公钥写入数据库键值对((“pool”,n),keypool),keypool只保存时间和公钥
TopUpKeyPool();
//DEFAULT_KEYPOOL_SIZE=100,填满池子到有100把密钥,nIndex保存在setKeyPool,
/**
walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())
密钥写入berkerly database数据库键值对((“pool",nEne),(ntime,pubkey))
*/
也就是说创建后的密钥公钥写入数据库,保存为ckeypool,创建时的索引保存在setKeyPool这个集合中,新建时以最后一个索引的值递增,也就是用掉1之后,不会再生成索引1,而是101
发送测试币回2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF,使用rpc指令
创建两笔交易
parallels@parallels-vm:~$ bitcoin-cli walletpassphrase aser6789dfgb 300
parallels@parallels-vm:~$ bitcoin-cli sendtoaddress "2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF" 0.2
441bb6516409b37f0b2da928cf4691ff0508f99f5481add15d608ee39ee59b04
parallels@parallels-vm:~$ bitcoin-cli getbalance
1.09896799
parallels@parallels-vm:~$ bitcoin-cli sendtoaddress "2N8hwP1WmJrFF5QWABn38y63uYLhnJYJYTF" 0.2
aceeee8102eb3a922521edb784bf890e29c26e3336ab8e99b5430fc71345b641
parallels@parallels-vm:~$
第一笔交易输出找零到mpCjnRXL2mVbFBU77ixWsCJ88JyqfU2g1c,作为第二笔交易的输入
灰尘交易
// Never create dust outputs; if we would, just add the dust to the fee.
if (newTxOut.IsDust(::minRelayTxFee))
{
nChangePosInOut = -1;
nFeeRet += nChange;
reservekey.ReturnKey();
}
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;
}
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
}
else
reservekey.ReturnKey();
涉及交易的输入vin的序列号的相关背景
// Fill vin
//
// Note how the sequence number is set to max()-1 so that the
// nLockTime set above actually works.
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),std::numeric_limits<unsigned int>::max()-1));
这里设置输入交易容器vin,对于每一笔setCoins中的交易coin,构造CTxIn对象,使用的构造函数CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL);
第1、2个参数用于构造COutPoint,第3、4个参数是CTxIn的成员变量,这里设置序列号为max()-1,不是SEQUENCE_FINAL(=max()),因此nLockTime实际是有效的。最后将CTxIn逐个加入数组中。
接下来看签名
// Sign
int nIn = 0;
CTransaction txNewConst(txNew);
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
{
bool signSuccess;
const CScript& scriptPubKey = coin.first->vout[coin.second].scriptPubKey;
SignatureData sigdata;
if (sign)
//非隔离见证的交易
signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata);
else
//使用隔离见证的交易,这里使用空的签名,在交易本身之外包含一个隔离见证
signSuccess = ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata);
if (!signSuccess)
{
strFailReason = _("Signing transaction failed");
return false;
} else {
//从交易中抽取签名数据然后插入
UpdateTransaction(txNew, nIn, sigdata);
}
nIn++;
}
对于setCoins的每一笔交易coin,coin.first是CWalletTx的指针,CWalletTx没有成员变量,不过他的父类CMerkleTx
的父类CTransaction
有vout成员变量,通过序号获取对应的那一笔交易的锁定脚本scriptPubKey。
调用函数ProduceSignature使用通用签名创建者生成脚本签名,写在另一篇中
//sign.h
/** Produce a script signature using a generic signature creator. */
bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& scriptPubKey, SignatureData& sigdata);
unsigned int nBytes = GetVirtualTransactionSize(txNew);
// Remove scriptSigs if we used dummy signatures for fee calculation
if (!sign) {
BOOST_FOREACH (CTxIn& vin, txNew.vin)
vin.scriptSig = CScript();
txNew.wit.SetNull();
}
// Embed the constructed transaction data in wtxNew.
*static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew);
// Limit size限制大小
if (GetTransactionWeight(txNew) >= MAX_STANDARD_TX_WEIGHT)
{
strFailReason = _("Transaction too large");
return false;
}
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
// Can we complete this as a free transaction?
//构造一个免费的交易,费用不够的话用优先级来凑
if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)//默认值分别为false、1000
{
// Not enough fee: enough priority?
double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget);
// Require at least hard-coded AllowFree.
if (dPriority >= dPriorityNeeded && AllowFree(dPriority))
break;
}
//获取最小交易费
CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
//默认coinControl为空,这段逻辑跳过,以后懂了再说
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
{
strFailReason = _("Transaction too large for fee policy");
return false;
}
if (nFeeRet >= nFeeNeeded)//直到有足够的费用
break; // Done, enough fee included.
// Include more fee and try again.
nFeeRet = nFeeNeeded;//
continue;
}
}
}
到这里是循环结束,满足条件则跳出循环,否则再次循环或报错
这里来关注下GetMinimumFee
的实现,用来计算最小交易费用,和交易的字节相关,但是交易的字节是在交易构造后才能计算,所以用nFeeRet保存预估费用,在此基础上构建新的交易,如果得到的真实交易费小于预估,则需要要替换交易费,再次构建。
CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
{
////! -mintxfee default
//static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
//CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
//static const unsigned int DEFAULT_MIN_RELAY_TX_FEE = 1000;
//CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
}
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
{
// payTxFee is user-set "I want to pay this much"
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0) {
int estimateFoundTarget = nConfirmTarget;
nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
if (nFeeNeeded == 0)//没有足够的信息计算,那么使用这个费用
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
////! -fallbackfee default
//static const CAmount DEFAULT_FALLBACK_FEE = 20,000;
//CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
}
// prevent user from paying a fee below minRelayTxFee or minTxFee
//阻止用户支付低于minRelayTxFee或minTxFee的费用
nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
// But always obey the maximum
if (nFeeNeeded > maxTxFee)//大于最大费用
nFeeNeeded = maxTxFee;
return nFeeNeeded;
}
payTxfee是类CFeeRate的对象,全局变量,表示每千字节的费用,初始化0
/**
* Fee rate in satoshis per kilobyte: CAmount / kB
*/
首先调用该类的成员函数GetFee(),计算字节对应的费用,用到的nSatoshisPerK
是CFeeRate的私有成员变量
CAmount CFeeRate::GetFee(size_t nBytes_) const
{
assert(nBytes_ <= uint64_t(std::numeric_limits<int64_t>::max()));
int64_t nSize = int64_t(nBytes_);
CAmount nFee = nSatoshisPerK * nSize / 1000;
if (nFee == 0 && nSize != 0) {
if (nSatoshisPerK > 0)
nFee = CAmount(1);
if (nSatoshisPerK < 0)
nFee = CAmount(-1);
}
return nFee;
}
可以看到,按默认值计算得到的nFeeNeeded就是0,所以有第二段,针对nFeeNeeded==0的操作语句,注释也表明用户没有设置-txconfirmtarget参数来估算会这样
//wallet.cpp
/** Transaction fee set by the user */
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);//0
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;//2
用到的nTxConfirmTarget是默认设置为2 satoshis/kb,变量pool是传入的参数,类CTxMemPool,调用函数estimateSmartFee这个函数展开又比较大了
大致看了下,里面调用EstimateMedianVal,会统计在这个目标金额下确认的交易数,总交易数和未确认的交易数,统计平均最佳值,然后返回。大概是这个意思,等我想看了再写
if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
LockPoints lp;
CTxMemPoolEntry entry(txNew, 0, 0, 0, 0, false, 0, false, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
std::string errString;
if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
strFailReason = _("Transaction has too long of a mempool chain");
return false;
}
}
return true;
}
那么到这里就创建完交易了,接下来提交交易
附:支付给自己
mwtXCZEe8Wz9TkLHd4L8NAAjzdK8QrxaNw是我的比特币地址
parallels@parallels-vm:~$ bitcoin-cli sendtoaddress "mwtXCZEe8Wz9TkLHd4L8NAAjzdK8QrxaNw" 0.03
ae1d05e0443736379b0184c3c1b9017ed074f232ae8def8fd208848538cb8904
我给自己转了0.03,以及一大笔交易费
如果sendmany里面写两次同一个地址,报重复地址的错误,这个限制是接口里面设置的,因为是用set集合来保存的地址,最后构造成接收者存入vector数组中的,createtransaction函数内部是没有地址重复限制的。
这种创建的方式更加自由,不过需要注意金额的填写,这里没有默认找零,差额全部支付给矿工
parallels@parallels-vm:~$ bitcoin-cli listaddressgroupings
[
[
[
"mi6E46piWPryxA9J7K58J4hswL9815fAPa",
0.00044634
],
[
"mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT",
0.00000000
],
[
"mwtXCZEe8Wz9TkLHd4L8NAAjzdK8QrxaNw",
0.02467234,
"my"
]
]
]
parallels@parallels-vm:~$ bitcoin-cli encryptwallet a123456
parallels@parallels-vm:~$ bitcoin-cli listunspent
[
{
"txid": "16dd8f5f9af40390484dcbd924eefcdf159344b182d97e2503bc1ed3195a03f4",
"vout": 0,
"address": "mi6E46piWPryxA9J7K58J4hswL9815fAPa",
"scriptPubKey": "76a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac",
"amount": 0.00044634,
"confirmations": 21863,
"spendable": true,
"solvable": true
},
{
"txid": "16dd8f5f9af40390484dcbd924eefcdf159344b182d97e2503bc1ed3195a03f4",
"vout": 1,
"address": "mwtXCZEe8Wz9TkLHd4L8NAAjzdK8QrxaNw",
"account": "my",
"scriptPubKey": "76a914b3963733828c665a987a12e9c4eb04868ec4511188ac",
"amount": 0.02467234,
"confirmations": 21863,
"spendable": true,
"solvable": true
},
{
"txid": "50cdfbe406e441c6740a61008a562319ceb228ea9bde91b1e2d0f6d43f7ad2ff",
"vout": 1,
"address": "mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT",
"scriptPubKey": "76a914b0bc2d08863f46f7c279efbeecbd4237de2626c788ac",
"amount": 0.07400000,
"confirmations": 2,
"spendable": true,
"solvable": true
}
]
创建一笔交易,把地址mwtXCZEe8Wz9TkLHd4L8NAAjzdK8QrxaNw的0.02467234转到两个地址,给mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT分配0.024,给mi6E46piWPryxA9J7K58J4hswL9815fAPa分配0.00066,余下的0.00001234作为手续费奖励给矿工
parallels@parallels-vm:~$ bitcoin-cli createrawtransaction '[{"txid":"16dd8f5f9af40390484dcbd924eefcdf159344b182d97e2503bc1ed3195a03f4","vout":1}]' '{"mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT":0.024,"mi6E46piWPryxA9J7K58J4hswL9815fAPa":0.00066}'
0100000001f4035a19d31ebc03257ed982b1449315dffcee24d9cb4d489003f49a5f8fdd160100000000ffffffff02009f2400000000001976a914b0bc2d08863f46f7c279efbeecbd4237de2626c788acd0010100000000001976a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac00000000
然后打开钱包准备转账
parallels@parallels-vm:~$ bitcoin-cli walletpassphrase a123456 1200
对交易签名
parallels@parallels-vm:~$ bitcoin-cli signrawtransaction 0100000001f4035a19d31ebc03257ed982b1449315dffcee24d9cb4d489003f49a5f8fdd160100000000ffffffff02009f2400000000001976a914b0bc2d08863f46f7c279efbeecbd4237de2626c788acd0010100000000001976a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac00000000
{
"hex": "0100000001f4035a19d31ebc03257ed982b1449315dffcee24d9cb4d489003f49a5f8fdd16010000006a47304402204f2e48935f0a2ae76076cb89b800816d5953b772e238bcd22317d4b3e008cb4c02206dd671fdf84b5024d7354653bbd5a285a811baf78780fc1e6605bf8e776e043d012103bfd9224018307649f7741aad40f8e817d9a957d416a65da7632cfc0a68e4e2cdffffffff02009f2400000000001976a914b0bc2d08863f46f7c279efbeecbd4237de2626c788acd0010100000000001976a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac00000000",
"complete": true
}
然后发送交易
parallels@parallels-vm:~$ bitcoin-cli sendrawtransaction 0100000001f4035a19d31ebc03257ed982b1449315dffcee24d9cb4d489003f49a5f8fdd16010000006a47304402204f2e48935f0a2ae76076cb89b800816d5953b772e238bcd22317d4b3e008cb4c02206dd671fdf84b5024d7354653bbd5a285a811baf78780fc1e6605bf8e776e043d012103bfd9224018307649f7741aad40f8e817d9a957d416a65da7632cfc0a68e4e2cdffffffff02009f2400000000001976a914b0bc2d08863f46f7c279efbeecbd4237de2626c788acd0010100000000001976a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac00000000
1b27bfbc22e746eefaba7896765281ceb7a5d0b225c4ccc9154e8780d22a0f44
过段时间后我们来看我们的余额,10分钟左右吧,6个区块确认
parallels@parallels-vm:~$ bitcoin-cli listunspent
[
{
"txid": "1b27bfbc22e746eefaba7896765281ceb7a5d0b225c4ccc9154e8780d22a0f44",
"vout": 0,
"address": "mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT",
"scriptPubKey": "76a914b0bc2d08863f46f7c279efbeecbd4237de2626c788ac",
"amount": 0.02400000,
"confirmations": 7,
"spendable": true,
"solvable": true
},
{
"txid": "1b27bfbc22e746eefaba7896765281ceb7a5d0b225c4ccc9154e8780d22a0f44",
"vout": 1,
"address": "mi6E46piWPryxA9J7K58J4hswL9815fAPa",
"scriptPubKey": "76a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac",
"amount": 0.00066000,
"confirmations": 7,
"spendable": true,
"solvable": true
},
{
"txid": "16dd8f5f9af40390484dcbd924eefcdf159344b182d97e2503bc1ed3195a03f4",
"vout": 0,
"address": "mi6E46piWPryxA9J7K58J4hswL9815fAPa",
"scriptPubKey": "76a9141c3b2234e89210ef29d3063cdc219aa3d673b67c88ac",
"amount": 0.00044634,
"confirmations": 21883,
"spendable": true,
"solvable": true
},
{
"txid": "50cdfbe406e441c6740a61008a562319ceb228ea9bde91b1e2d0f6d43f7ad2ff",
"vout": 1,
"address": "mwdSeWQBe1UmeM5ztQasQGrHiNTiNWwCTT",
"scriptPubKey": "76a914b0bc2d08863f46f7c279efbeecbd4237de2626c788ac",
"amount": 0.07400000,
"confirmations": 22,
"spendable": true,
"solvable": true
}
]
可以看到去掉支出的那一笔,新增两笔支出
除了上面的方法,还有一个fundrawtransaction
fundrawtransaction "hexstring" ( options )
Add inputs to a transaction until it has enough in value to meet its out value.
This will not modify existing inputs, and will add one change output to the outputs.
Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.
The inputs added will not be signed, use signrawtransaction for that.
Note that all existing inputs must have their previous output transaction be in the wallet.
Note that all inputs selected must be of standard form and P2SH scripts must be
in the wallet using importaddress or addmultisigaddress (to calculate fees).
You can see whether this is the case by checking the "solvable" field in the listunspent output.
Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only
Arguments:
1. "hexstring" (string, required) The hex string of the raw transaction
2. options (object, optional)
{
"changeAddress" (string, optional, default pool address) The bitcoin address to receive the change
"changePosition" (numeric, optional, default random) The index of the change output
"includeWatching" (boolean, optional, default false) Also select inputs which are watch only
"lockUnspents" (boolean, optional, default false) Lock selected unspent outputs
"feeRate" (numeric, optional, default not set: makes wallet determine the fee) Set a specific feerate (BTC per KB)
}
for backward compatibility: passing in a true instead of an object will result in {"includeWatching":true}
Result:
{
"hex": "value", (string) The resulting raw transaction (hex-encoded string)
"fee": n, (numeric) Fee in BTC the resulting transaction pays
"changepos": n (numeric) The position of the added change output, or -1
}
"hex"
Examples:
Create a transaction with no inputs
> omnicore-cli createrawtransaction "[]" "{\"myaddress\":0.01}"
Add sufficient unsigned inputs to meet the output value
> omnicore-cli fundrawtransaction "rawtransactionhex"
Sign the transaction
> omnicore-cli signrawtransaction "fundedtransactionhex"
Send the transaction
> omnicore-cli sendrawtransaction "signedtransactionhex"
(code -1)
可以用于设置找零地址,就是在构造rawtransaction的基础上,增加找零地址,可以避免不小心把过多的差额都支付给矿工
下面展示一个我的例子
createrawtransaction '[{"txid":"99fab84162a10ea386a89f4fb11447ddafe7072c20c2d48ecc0b63bfab090d26","vout":0}]' '{"mriJNDgLxUiUQcCEKzX3mTggTaVJxvmkaM":0.17}'
0100000001260d09abbf630bcc8ed4c2202c07e7afdd4714b14f9fa886a30ea16241b8fa990000000000ffffffff0140660301000000001976a9147aceb6dd0a653d80932516e8d6f15cff4c1ad97388ac00000000
fundrawtransaction '0100000001260d09abbf630bcc8ed4c2202c07e7afdd4714b14f9fa886a30ea16241b8fa990000000000ffffffff0140660301000000001976a9147aceb6dd0a653d80932516e8d6f15cff4c1ad97388ac00000000' '{"changeAddress":"mjY2nqs4b2pbCXRYcck3La6dh3jjwcdCnV"}'
{
"hex": "0100000001260d09abbf630bcc8ed4c2202c07e7afdd4714b14f9fa886a30ea16241b8fa990000000000ffffffff0263510500000000001976a9142c14ac274dc5f4866229869170d53e7d628a3a7288ac40660301000000001976a9147aceb6dd0a653d80932516e8d6f15cff4c1ad97388ac00000000",
"changepos": 0,
"fee": 0.00000231
}
decode
{
"txid": "f74835df886e4d0b903ebdf3e517dd4d17c501e1a8c2b8973062570fe6094bd2",
"hash": "f74835df886e4d0b903ebdf3e517dd4d17c501e1a8c2b8973062570fe6094bd2",
"size": 119,
"vsize": 119,
"version": 1,
"locktime": 0,
"vin": [
{
"txid": "99fab84162a10ea386a89f4fb11447ddafe7072c20c2d48ecc0b63bfab090d26",
"vout": 0,
"scriptSig": {
"asm": "",
"hex": ""
},
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.00348515,
"n": 0,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 2c14ac274dc5f4866229869170d53e7d628a3a72 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9142c14ac274dc5f4866229869170d53e7d628a3a7288ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mjY2nqs4b2pbCXRYcck3La6dh3jjwcdCnV"//指定的找零地址
]
}
},
{
"value": 0.17000000,
"n": 1,
"scriptPubKey": {
"asm": "OP_DUP OP_HASH160 7aceb6dd0a653d80932516e8d6f15cff4c1ad973 OP_EQUALVERIFY OP_CHECKSIG",
"hex": "76a9147aceb6dd0a653d80932516e8d6f15cff4c1ad97388ac",
"reqSigs": 1,
"type": "pubkeyhash",
"addresses": [
"mriJNDgLxUiUQcCEKzX3mTggTaVJxvmkaM"
]
}
}
]
}
我把金额从mjY2nqs4b2pbCXRYcck3La6dh3jjwcdCnV打到mriJNDgLxUiUQcCEKzX3mTggTaVJxvmkaM,然后再找零回mjY2nqs4b2pbCXRYcck3La6dh3jjwcdCnV
我使用的utxo是下面这笔,在这个地址上有金额0.17348746,所以我是以这笔为输入,支付0.17到mr,扣除手续费之外的数据找零到指定地址
{
"txid": "99fab84162a10ea386a89f4fb11447ddafe7072c20c2d48ecc0b63bfab090d26",
"vout": 0,
"address": "mjY2nqs4b2pbCXRYcck3La6dh3jjwcdCnV",
"scriptPubKey": "76a9142c14ac274dc5f4866229869170d53e7d628a3a7288ac",
"amount": 0.17348746,
"confirmations": 2392,
"spendable": true,
"solvable": true
}
最后,要说以下sendfrom指令
sendfrom "fromaccount" "tobitcoinaddress" amount ( minconf "comment" "comment-to" )
DEPRECATED (use sendtoaddress). Sent an amount from an account to a bitcoin address.
Requires wallet passphrase to be set with walletpassphrase call.
Arguments:
1. "fromaccount" (string, required) The name of the account to send funds from. May be the default account using "".
2. "tobitcoinaddress" (string, required) The bitcoin address to send funds to.
3. amount (numeric or string, required) The amount in BTC (transaction fee is added on top).
4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.
5. "comment" (string, optional) A comment used to store what the transaction is for.
This is not part of the transaction, just kept in your wallet.
6. "comment-to" (string, optional) An optional comment to store the name of the person or organization
to which you're sending the transaction. This is not part of the transaction,
it is just kept in your wallet.
虽然介绍的是从账户支出,实测是从整个钱包支出,在比特币中账户的概念等于摆设,可以看到这个方法是DEPRECATED弃用的,非要用账户概念的可以自己封装原声交易中对地址所属的账户做判断。
这篇博客前面是学了一点,一边记录一遍写的,后面是学习的多了挑了些内容再加上去的