1、创建交易。
前提是钱包有比特币,可以启动一个测试结点,自己挖矿。通过RPC `getnewaddress获取个地址`,`sendtoaddress`来创建交易。如下:
> bitcoin-cli getnewaddress
1LXVDTasMALkmkLRw7J35pzSdN38VG1Fvp
>bitcoin-cli sendtoaddress 1LXVDTasMALkmkLRw7J35pzSdN38VG1Fvp 1
txid
sendtoaddress会调用到SendMoney,然后调用到CWallet::CreateTransaction。CWallet::CreateTransaction会做如下事情:
调用AvailableCoins获取钱包可用的币(UTXO)。
初始化CScript scriptChange找零脚本。
然后在while (true)的循环找到合适的币(交易的输入)使其满足输出和交易费。
接下来对输入selected_coins逐一签名ProduceSignature(先跳过签名,后面再讲)。CreateTransaction完成。
然后调用CWallet::CommitTransaction将交易放进内存池。其中AcceptToMemoryPool会调用到AcceptToMemoryPoolWorker。该Worker会检查交易的输出值范围是否合理,输入是否重复,是否存在(未花费),手续费是否合理,CheckInputs检查输入的签名是否正确。accepttomemorypool后调用CWalletTx::RelayWalletTransaction。再调用
void relayTransaction(const uint256& txid) override
{
CInv inv(MSG_TX, txid);
g_connman->ForEachNode([&inv](CNode* node) { node->PushInventory(inv); });
}
通过MSG_TX将该交易的id广播到邻居结点。
2、网络广播交易。
上面PushInventory只是将交易id放到集合setInventoryTxToSend里,然后在net_processing.cpp中
// Determine transactions to relay
if (fSendTrickle) {
// Produce a vector with all candidates for sending
....
if (vInv.size() == MAX_INV_SZ) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
....
满足发送时间间隔时将交易id以数组的形式发送出去。邻居结点接收到在net_processing.cpp中处理
bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic& interruptMsgProc, bool enable_bip61)
{
...
if (strCommand == NetMsgType::INV) {
...
for (CInv &inv : vInv)
{
...
else
{
pfrom->AddInventoryKnown(inv);
if (fBlocksOnly) {
LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId());
} else if (!fAlreadyHave && !fImporting && !fReindex && !IsInitialBlockDownload()) {
RequestTx(State(pfrom->GetId()), inv.hash, nNow);
}
}
}
调用到RequestTx,将交易id放到 peer_download_state.m_tx_announced.insert(txid);和peer_download_state.m_tx_process_time.emplace(process_time, txid);里。
满足处理时间后,调用connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));来向广播该交易id的邻居结点请求交易的具体数据。
邻居结点在处理GETDATA时,将交易通过NetMsgType::TX回复请求方。
结点在接收到NetMsgType::TX时,处理逻辑在net_processing.cpp
bool static ProcessMessage(...
...
if (strCommand == NetMsgType::TX) {
主要是AcceptToMemoryPool(这个过程和上面一样),只是发现输入不存在,会先把交易放到独儿池,等所有输入到齐后再从重AcceptToMemoryPool。
3.打包进块。
通过RPC命令创建区块
>bitcoin-cli generatetoaddress 1LXVDTasMALkmkLRw7J35pzSdN38VG1Fvp
然后执行到BlockAssembler::CreateNewBlock,负责区块的初始化和创建coinbase交费。
然后执行BlockAssembler::addPackageTxs,负责将内存池中的交易放进区块。
到此,交易完成,并被网络记录。