1.从哪里开始
我们现在来创建一笔正常的交易,从哪里开始呢?我们按照源码中的方式。
还是从ui.cpp里的:
cpp
void CSendDialog::OnButtonSend(wxCommandEvent& event)
这里开始,然后调用了SendMoney函数,
相关过程可以去第三十章了解,以及如何调用SendMoney函数,可以到第三十八章了解。
2.tx索引
在此之前,在上一章忘记了一件事,就是关于tx索引的读取,就是blkindex.dat里不仅存储有CBlockIndex, 还有CTxIndex。
在这里我们测试一下tx索引的读取,一种是遍历blkindex.dat文件读取键值对,读出类型为tx索引的键值对。
另一种是读取区块文件,得到tx哈希值,然后再根据哈希值去读取对应的tx索引。
这两种方案我都问AI要了下,注意要使用好一点的AI,否则会犯一些基本常识性的错误,比如一个类里的protected函数,它在外面就调用了。代码走不通。
然后我换了一个ai,找到了代码,但又犯一个逻辑性的错误,就是在读取索引的时候,我叫他遍历索引tx,但是他读取创世块里的tx哈希时候,他去找索引。这个肯定是没有的,一读不到,就直接退出了,后面的也不再读取了。我叫他自己找错。我也没提示,他也能找到问题。
代码如下:(放在之前的print.h里面)
cpp
class CTxDBIndexReader : public CTxDB
{
public:
CTxDBIndexReader() : CTxDB("r") {}
bool PrintAllTxIndexes()
{
Dbc* pcursor = GetCursor();
if (!pcursor)
return error("PrintAllTxIndexes() : GetCursor failed");
unsigned int fFlags = DB_FIRST;
unsigned int nCount = 0;
bool fSuccess = true;
loop
{
CDataStream ssKey;
CDataStream ssValue;
int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
fFlags = DB_NEXT;
if (ret == DB_NOTFOUND)
break;
if (ret != 0)
{
fSuccess = false;
break;
}
string strType;
ssKey >> strType;
if (strType != "tx")
continue;
uint256 hash;
ssKey >> hash;
CTxIndex txindex;
ssValue >> txindex;
printf("tx hash=%s, pos=%s, spent=%u\n",
hash.ToString().c_str(),
txindex.pos.ToString().c_str(),
(unsigned int)txindex.vSpent.size());
++nCount;
}
int nCloseRet = pcursor->close();
if (!fSuccess)
return error("PrintAllTxIndexes() : ReadAtCursor failed");
if (nCloseRet != 0)
return error("PrintAllTxIndexes() : cursor close failed");
printf("Read %u tx indexes\n", nCount);
return true;
}
bool PrintBlockTxIndexes()
{
if (!pindexGenesisBlock)
return error("PrintBlockTxIndexes() : genesis block index not loaded");
unsigned int nFound = 0;
unsigned int nMissing = 0;
for (CBlockIndex* pindex = pindexGenesisBlock; pindex; pindex = pindex->pnext)
{
CBlock block;
if (!block.ReadFromDisk(pindex, true))
return error("PrintBlockTxIndexes() : failed to read block %s",
pindex->GetBlockHash().ToString().c_str());
printf("Block %s contains %u transactions\n",
pindex->GetBlockHash().ToString().c_str(),
(unsigned int)block.vtx.size());
for (int i = 0; i < block.vtx.size(); ++i)
{
uint256 hashTx = block.vtx[i].GetHash();
CTxIndex txindex;
if (!ReadTxIndex(hashTx, txindex))
{
// The genesis transaction is not indexed in this version.
printf("txindex missing at height=%d tx=%s\n",
pindex->nHeight, hashTx.ToString().c_str());
++nMissing;
continue;
}
printf("tx[%d] hash=%s, pos=%s, spent=%u\n",
i,
hashTx.ToString().c_str(),
txindex.pos.ToString().c_str(),
(unsigned int)txindex.vSpent.size());
++nFound;
}
}
printf("Read %u tx indexes; %u block transactions have no tx index\n",
nFound, nMissing);
return true;
}
};
好,关于tx索引的情况,以及关系,自己看代码逻辑了解,我就不说明了。
调用如下:
cpp
void Init2()
{
LoadBlockIndex();
LoadWallet();
}
int main()
{
Init2();
CTxDBIndexReader reader;
reader.PrintAllTxIndexes();
cout << "第二个函数:" << endl;
reader.PrintBlockTxIndexes();
}
效果:
cpp
sending: version (46 bytes)
dbenv.open strAppDir=c:\test\btc
LoadBlockIndex(): hashBestChain=000003bca273ae height=1
fGenerateBitcoins = 0
nTransactionFee = 0
tx hash=624e209fa64e8905d828ba54664ca3d7a6c045d8f889d18418fbf6a4df2fa5f0, pos=(nFile=1, nBlockPos=301, nTxPos=382), spent=1
Read 1 tx indexes
第二个函数:
Block 00000b60c6943717840ec4df7c4fc9386d9c81babf3d293761e7c31a6ae04e6b contains 1 transactions
txindex missing at height=0 tx=a564aab2c6e83f5f36b08a0a9a7a30c1fa76f662f806a4b5c431b77497e5a31d
Block 000003bca273aedfb3295e35e1f9771343b5fdd0cbb8954d91d3f4210ca938d5 contains 1 transactions
tx[0] hash=624e209fa64e8905d828ba54664ca3d7a6c045d8f889d18418fbf6a4df2fa5f0, pos=(nFile=1, nBlockPos=301, nTxPos=382), spent=1
Read 1 tx indexes; 1 block transactions have no tx index
C:\work\btc\ConsoleApplication2\x64\Debug\ConsoleApplication2.exe (进程 23196)已退出,代码为 0 (0x0)。
要在调试停止时自动关闭控制台,请启用"工具"->"选项"->"调试"->"调试停止时自动关闭控制台"。
按任意键关闭此窗口. . .
可以看到,读取出来了,两个区块,只有一笔tx索引写到了blkindex.dat,就是第二个区块的coinbase。创世块的那笔coinbase不属于正常的交易的,所以没有tx索引。
所以在第二个函数,报1block have no tx index,就是它在读取创世块下的tx后,根据这笔tx哈希值,到索引库里找不到。才产生的结果。这个是正常的。
因为创世块的tx没有走ConnectInputs函数,也就不会有后面的这句:
cpp
if (fBlock)
{
// Add transaction to disk index
if (!txdb.AddTxIndex(*this, posThisTx, nHeight))
return error("ConnectInputs() : AddTxPos failed");
}
所以它的tx也没有添加进索引库里的。
3.SendMoney
回到开始,我们调用这个函数(展示代码):
cpp
int main()
{
Init2();
CWalletTx wtx;
// Parse amount
int64 nValue = 50 * 100000000;
string strAddress = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
// Parse bitcoin address
uint160 hash160;
bool fBitcoinAddress = AddressToHash160(strAddress, hash160);
if (fBitcoinAddress)
{
// Send to bitcoin address
CScript scriptPubKey;
scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG;
if (!SendMoney(scriptPubKey, nValue, wtx))
return;
}
}
SendMoney函数定义:
cpp
bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
{
CRITICAL_BLOCK(cs_main)
{
int64 nFeeRequired;
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired))
{
string strError;
if (nValue + nFeeRequired > GetBalance())
strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str());
else
strError = "Error: Transaction creation failed ";
//wxMessageBox(strError, "Sending...");
return error("SendMoney() : %s\n", strError.c_str());
}
if (!CommitTransactionSpent(wtxNew))
{
//wxMessageBox("Error finalizing transaction", "Sending...");
return error("SendMoney() : Error finalizing transaction");
}
printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0, 6).c_str());
// Broadcast
if (!wtxNew.AcceptTransaction())
{
// This must not fail. The transaction has already been signed and recorded.
throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n");
//wxMessageBox("Error: Transaction not valid", "Sending...");
return error("SendMoney() : Error: Transaction not valid");
}
wtxNew.RelayWalletTransaction();
}
//MainFrameRepaint();
return true;
}
需要一个参数wtx,类型是CWalletTx,为什么需要这个呢?
你进行转账,是不是要构建一笔tx,然后广播上链之类的,那么这个wtx就是这笔tx的载体,容器。那直接为什么不用CTransaction呢?
是为了方便构造,真正广播接收时还是CTransaction类型的。
具体情况我们看代码即可。
这个类的继承关系是这样:
cpp
CWalletTx : public CMerkleTx
CMerkleTx : public CTransaction
所以接下来的工作是,修复SendMoney函数,以及CWalletTx类相关。
4.CreateTransaction
cpp
if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired))
在SendMoney里调用CreateTransaction函数来创建交易。
此函数未定义,我们在main.h和main.cpp声明和定义一下:
cpp
bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, int64& nFeeRequiredRet)
{
nFeeRequiredRet = 0;
CRITICAL_BLOCK(cs_main)
{
// txdb must be opened before the mapWallet lock
CTxDB txdb("r");
CRITICAL_BLOCK(cs_mapWallet)
{
int64 nFee = nTransactionFee;
loop
{
wtxNew.vin.clear();
wtxNew.vout.clear();
if (nValue < 0)
return false;
int64 nValueOut = nValue;
nValue += nFee;
// Choose coins to use
set<CWalletTx*> setCoins;
if (!SelectCoins(nValue, setCoins))
return false;
int64 nValueIn = 0;
for(CWalletTx * pcoin:setCoins)
nValueIn += pcoin->GetCredit();
// Fill vout[0] to the payee
wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey));
// Fill vout[1] back to self with any change
if (nValueIn > nValue)
{
// Use the same key as one of the coins
vector<unsigned char> vchPubKey;
CTransaction& txFirst = *(*setCoins.begin());
for(const CTxOut & txout:txFirst.vout)
if (txout.IsMine())
if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
break;
if (vchPubKey.empty())
return false;
// Fill vout[1] to ourself
CScript scriptPubKey;
scriptPubKey << vchPubKey << OP_CHECKSIG;
wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey));
}
// Fill vin
for(CWalletTx * pcoin:setCoins)
for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)
if (pcoin->vout[nOut].IsMine())
wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut));
// Sign
int nIn = 0;
for(CWalletTx * pcoin:setCoins)
for (int nOut = 0; nOut < pcoin->vout.size(); nOut++)
if (pcoin->vout[nOut].IsMine())
SignSignature(*pcoin, wtxNew, nIn++);
// Check that enough fee is included
if (nFee < wtxNew.GetMinFee(true))
{
nFee = nFeeRequiredRet = wtxNew.GetMinFee(true);
continue;
}
// Fill vtxPrev by copying from previous transactions vtxPrev
wtxNew.AddSupportingTransactions(txdb);
wtxNew.fTimeReceivedIsTxTime = true;
break;
}
}
}
return true;
}
5.SelectCoins
CreateTransaction是通过SelectCoins来选币的。但是这个函数选币逻辑(挑选余额),是从你这个节点的所有账号里挑选tx,不能指定账号。
而且这个tx,还是通过钱包来获取tx的,是遍历mapWallet实现的,获取tx,是CWalletTx形式的。
我们在main.cpp里CreateTransaction函数定义一下:
cpp
bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet)
{
setCoinsRet.clear();
// List of values less than target
int64 nLowestLarger = _I64_MAX;
CWalletTx* pcoinLowestLarger = NULL;
vector<pair<int64, CWalletTx*> > vValue;
int64 nTotalLower = 0;
CRITICAL_BLOCK(cs_mapWallet)
{
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* pcoin = &(*it).second;
if (!pcoin->IsFinal() || pcoin->fSpent)
continue;
int64 n = pcoin->GetCredit();
if (n <= 0)
continue;
if (n < nTargetValue)
{
vValue.push_back(make_pair(n, pcoin));
nTotalLower += n;
}
else if (n == nTargetValue)
{
setCoinsRet.insert(pcoin);
return true;
}
else if (n < nLowestLarger)
{
nLowestLarger = n;
pcoinLowestLarger = pcoin;
}
}
}
if (nTotalLower < nTargetValue)
{
if (pcoinLowestLarger == NULL)
return false;
setCoinsRet.insert(pcoinLowestLarger);
return true;
}
// Solve subset sum by stochastic approximation
sort(vValue.rbegin(), vValue.rend());
vector<char> vfIncluded;
vector<char> vfBest(vValue.size(), true);
int64 nBest = nTotalLower;
for (int nRep = 0; nRep < 1000 && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(vValue.size(), false);
int64 nTotal = 0;
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
{
for (int i = 0; i < vValue.size(); i++)
{
if (nPass == 0 ? rand() % 2 : !vfIncluded[i])
{
nTotal += vValue[i].first;
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
fReachedTarget = true;
if (nTotal < nBest)
{
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].first;
vfIncluded[i] = false;
}
}
}
}
}
// If the next larger is still closer, return it
if (pcoinLowestLarger && nLowestLarger - nTargetValue <= nBest - nTargetValue)
setCoinsRet.insert(pcoinLowestLarger);
else
{
for (int i = 0; i < vValue.size(); i++)
if (vfBest[i])
setCoinsRet.insert(vValue[i].second);
//// debug print
printf("SelectCoins() best subset: ");
for (int i = 0; i < vValue.size(); i++)
if (vfBest[i])
printf("%s ", FormatMoney(vValue[i].first).c_str());
printf("total %s\n", FormatMoney(nBest).c_str());
}
return true;
}
对这个函数不懂的,可以去看一下第二十二,二十三章,那里有详细说明。
关于CWalletTx里需要修复哪些函数,这里暂时不管。
我们先把另一个问题解决,即他是从mapWallet里先获取tx,而不是通过blkindex.dat,事实上如果是后者会增加难度,你得遍历每一笔tx,还得判断是不是属于该节点下的账号拥有。
这个mapWallet是从哪里来的呢?
6.mapWallet
我们来看LoadWallet函数这部分代码:
cpp
else if (strType == "tx")
{
uint256 hash;
ssKey >> hash;
CWalletTx& wtx = mapWallet[hash];
ssValue >> wtx;
if (wtx.GetHash() != hash)
printf("Error in wallet.dat, hash mismatch\n");
//// debug print
//printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str());
//printf(" %12I64d %s %s %s\n",
// wtx.vout[0].nValue,
// DateTimeStr(wtx.nTime).c_str(),
// wtx.hashBlock.ToString().substr(0,14).c_str(),
// wtx.mapValue["message"].c_str());
}
是从钱包文件加载出来的,tx类型的数据,说明钱包文件不仅存储账号,还存储关于你的tx交易数据,类型为CWalletTx。
7.写入钱包tx
但是问题来了,其实我们没有定入第二个区块的那笔tx到我们的钱包里。还刻我们之前在函数注释的语句吗:
cpp
//// Watch for transactions paying to me
//for(CTransaction & tx:vtx)
// AddToWalletIfMine(tx, this);
在ConnectBlock函数里最后一段,当第二个区块接收后,我们没有遍历这个区块下的tx,然后查看是否跟我的账号有关,并把这笔tx以CWalletTx形式添加到钱包数据文件里(wallet.dat)。
所以我们现在得修复这个功能,然后产生的区块,关于我的tx交易就在wallet.dat里存在了,接着才可以正常转账交易---SelectCoins通过。
先取消所有相关注释。
8.AddToWalletIfMine
然后来看具体的添加函数:
cpp
bool AddToWalletIfMine(const CTransaction& tx, const CBlock* pblock)
{
if (tx.IsMine() || mapWallet.count(tx.GetHash()))
{
CWalletTx wtx(tx);
// Get merkle branch if transaction was found in a block
if (pblock)
wtx.SetMerkleBranch(pblock);
return AddToWallet(wtx);
}
return true;
}
先通过IsMine判断,这笔tx,是否有转账给我的vout。如果有,则进行添加。
然后会调用这个函数SetMerkleBranch
9.SetMerkleBranch
这个是保存该交易在默克尔树的位置,以及兄弟哈希值:
具体是:
nIndex:该交易在区块交易列表(也就是 Merkle 树叶子)中的位置(下标)vMerkleBranch:从该叶子到 Merkle 根路径上的所有兄弟节点哈希(sibling hashes)- 另外还保存了
hashBlock(区块哈希)
主要是为了快速验证,SPV轻节点使用。
这个函数是通过wtx调用的,所以在这里调用一下,wtx就获得了上面那些数据。
然后再一起写进钱包数据里。
此函数虽然是wtx调用,但是在父类class CMerkleTx 定义的。
我们分别在main.h和main.cpp的CMerkleTx类中声明和定义。
然后此函数,又调用了:
cpp
vMerkleBranch = pblock->GetMerkleBranch(nIndex);
我们按照同样的方法,给CBlock类添加GetMerkleBranch函数。
好了,现在:
cpp
int CMerkleTx::SetMerkleBranch(const CBlock* pblock)
这个函数内还剩一个报错:
cpp
if (pblock->vtx[nIndex] == *(CTransaction*)this)
中间那个==符号报错。
因为比较的是类对象,这其实跟obj+obj一样的原理,对象不支持+符合,除非你在类里重载。
所以情况显而易见。
我们将此重载加到类里:
cpp
friend bool operator==(const CTransaction& a, const CTransaction& b)
{
return (a.nVersion == b.nVersion &&
a.vin == b.vin &&
a.vout == b.vout &&
a.nLockTime == b.nLockTime);
}
然后其下的vin和vout又是对象,很明显,相应的类也需要重载支持。
但是这里我没有报错,是因为之前CTxIn和CTxOut类定义是完整复制的,而不像CTransaction类用点加一点。
好,至此CMerkleTx::SetMerkleBranch函数修复完成。我们回到AddToWalletIfMine函数,调用
SetMerkleBranch将wtx的默克尔树状态完成后。
9.AddToWallet
就会调用AddToWallet将wtx写进钱包文件里,我们将此函数声明和定义一下,代码如下:
cpp
// 将一笔交易添加到钱包中,如果已经存在,则更新钱包中的交易信息
// 参数:wtxIn ------ 需要添加到钱包的交易对象
// 返回值:true 表示添加或更新成功,false 表示写入磁盘失败
bool AddToWallet(const CWalletTx& wtxIn)
{
// 获取交易的哈希值(交易唯一标识)
// 通过交易哈希判断该交易是否已经存在于钱包中
uint256 hash = wtxIn.GetHash();
// 对钱包交易映射表加锁,防止多个线程同时修改 mapWallet
// 保证对钱包交易数据的访问是线程安全的
CRITICAL_BLOCK(cs_mapWallet)
{
// 将交易插入钱包交易映射表 mapWallet
// mapWallet 的类型是 map<uint256, CWalletTx>
//
// make_pair(hash, wtxIn):构造一个键值对
// hash:交易哈希,作为 map 的 key
// wtxIn:交易对象,作为 map 的 value
//
// insert() 的行为:
// 1. 如果交易不存在,则插入新交易
// 2. 如果交易已经存在,则不插入,返回已有交易的位置
//
// ret.first:指向插入成功或已经存在的交易的迭代器
// ret.second:bool 类型,true 表示插入成功,false 表示交易已存在
pair<map<uint256, CWalletTx>::iterator, bool> ret =
mapWallet.insert(make_pair(hash, wtxIn));
// 根据迭代器获取钱包中的交易对象的引用
// 无论是新插入的交易,还是之前已经存在的交易,都可以通过该引用访问
CWalletTx& wtx = (*ret.first).second;
// 判断本次是否成功插入了一笔新交易
// true:新交易
// false:交易已经存在
bool fInsertedNew = ret.second;
// 如果是新插入的交易
if (fInsertedNew)
{
// 设置交易接收到的时间
// GetAdjustedTime() 获取经过时间调整后的当前时间
wtx.nTimeReceived = GetAdjustedTime();
}
//// 调试打印
// 输出交易哈希的前6个字符,以及本次操作的类型
// new:表示新增交易
// update:表示交易已经存在,准备更新
printf("AddToWallet %s %s\n",
wtxIn.GetHash().ToString().substr(0, 6).c_str(),
fInsertedNew ? "new" : "update");
// 如果交易不是新插入的,说明钱包中已经存在这笔交易
if (!fInsertedNew)
{
// 用于标记交易信息是否发生变化
// true:交易信息有更新
// false:交易信息没有更新
bool fUpdated = false;
// 检查交易所在区块的哈希是否发生变化
//
// wtxIn.hashBlock != 0:
// 表示传入交易的区块哈希不为空
//
// wtxIn.hashBlock != wtx.hashBlock:
// 表示传入交易所在的区块与钱包中记录的区块不同
if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock)
{
// 更新钱包中交易所在区块的哈希
wtx.hashBlock = wtxIn.hashBlock;
// 标记交易信息已经发生变化
fUpdated = true;
}
// 检查交易的 Merkle 分支信息和交易在区块中的索引是否需要更新
//
// wtxIn.nIndex != -1:
// 表示传入交易具有有效的区块内交易索引
//
// 如果 Merkle 分支不同,或者交易索引不同,
// 就需要更新钱包中保存的相关信息
if (wtxIn.nIndex != -1 &&
(wtxIn.vMerkleBranch != wtx.vMerkleBranch ||
wtxIn.nIndex != wtx.nIndex))
{
// 更新交易的 Merkle 分支
// Merkle 分支用于证明交易属于某个区块
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
// 更新交易在区块中的索引位置
wtx.nIndex = wtxIn.nIndex;
// 标记交易信息已经发生变化
fUpdated = true;
}
// 检查交易是否由本钱包发出
// 如果传入交易标记为本钱包发出,而原来的记录不是,
// 则需要更新该标记
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
// 更新交易的来源标记
// true 表示该交易是由本钱包发出的
wtx.fFromMe = wtxIn.fFromMe;
// 标记交易信息已经发生变化
fUpdated = true;
}
// 检查交易是否已经被标记为已花费
// 如果传入交易已经花费,而钱包原来的记录尚未标记为已花费,
// 则需要更新状态
if (wtxIn.fSpent && wtxIn.fSpent != wtx.fSpent)
{
// 更新交易的已花费状态
// true 表示交易对应的相关资金已经被花费
wtx.fSpent = wtxIn.fSpent;
// 标记交易信息已经发生变化
fUpdated = true;
}
// 如果上述信息都没有发生变化
if (!fUpdated)
{
// 说明钱包中已有的交易信息与本次传入的信息一致
// 不需要重复写入磁盘,直接返回成功
return true;
}
}
// 将交易信息写入磁盘
// 新交易需要保存,已有交易发生变化时也需要重新保存
// WriteToDisk() 成功返回 true,失败返回 false
if (!wtx.WriteToDisk())
{
// 写入磁盘失败,返回 false
return false;
}
// 通知 UI 界面钱包交易信息已经发生变化
// 当前代码已被注释,因此实际上不会执行
//vWalletUpdated.push_back(make_pair(hash, fInsertedNew));
}
// 退出 CRITICAL_BLOCK,释放 cs_mapWallet 锁
// 刷新 UI 界面,让用户看到最新的钱包交易信息
// 当前代码已被注释,因此实际上不会执行
//MainFrameRepaint();
// 添加或更新交易成功,返回 true
return true;
}
此函数代码无报错,就是两个跟ui相关的已经被我注释掉了,不影响。
我们看到最后面的关键语句:
cpp
// Write to disk
if (!wtx.WriteToDisk())
return false;
函数调用:
cpp
bool WriteToDisk()
{
return CWalletDB().WriteTx(GetHash(), *this);
}
如果直接写入wtx就后面这几句就够了,为什么前面还有那么多代码呢?
关键在于这条wtx在mapWallet是否已存在,如果存在,则wtx里面的一些数据需要变一下。
但是这是新tx,为什么还会判断一下存不存在?如果已经添加进去了,它怎么可能又在未来再次产生?而且这里面还可以更改所属区块哈希。是可以改动的。
这是因为AddToWallet他会被多次调用,我们现在看到的是走的挖矿ProcessBlock,那个路径调用。如果只有这一种,那确实会感到很奇怪。
比如:
1.在钱包发一起一笔交易时,构建好wtx,就是SendMoney->CommitTransactionSpent->AddToWallet(wtxNew); 它就会先调用这个函数。main.cpp中2606行。
2.然后在处理节点消息时,接收到一笔tx时,就是这里的代码:
cpp
else if (strCommand == "tx")
{
vector<uint256> vWorkQueue;
CDataStream vMsg(vRecv);
CTransaction tx;
vRecv >> tx;
CInv inv(MSG_TX, tx.GetHash());
pfrom->AddInventoryKnown(inv);
bool fMissingInputs = false;
if (tx.AcceptTransaction(true, &fMissingInputs))
{
AddToWalletIfMine(tx, NULL);
也会调用 AddToWalletIfMine函数。
然后呢,不管是这笔tx,后面组成一个区块,被你挖出来了,或者别人挖出来了,发送给你。
在你的节点上链时(就是成为你节点的最优链上的区块),是不是还得调用ConnectBlock来验证上链。此时还会调用AddToWalletIfMine。所以此时就会判断是否已经存在,然后根据情况改变数据再写入。
然后是关于所属区块哈希,就是由于链重组问题,区块回滚。你虽然tx加入钱包了,但链重组,你那个区块无效,要释放里面的交易。那此时这笔交易,属于重组后的区块。所以会有这些判断和改变。
好,这些情况,在后我们再分析,现在我们的情况是可以无视那些判断逻辑,直接按新交易的逻辑走,那么就是三步:
将交易插入到mapWallet里,然后更新一下时间,然后写入到数据库里。
10.AddToWalletIfMine修复完成
好,现在再挖矿,区块上链,已经能将转账给节点的tx添加进钱包里了,我们将在下一章挖出第三个区块。使用这笔coinbase 在钱包里有记录,这样转账时能被查找到用来转账。