四十六.创建一笔交易(9)--第三个区块 添加tx到钱包

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 在钱包里有记录,这样转账时能被查找到用来转账。

相关推荐
2301_776045231 天前
理解Layer 1区块链协议
区块链
幻智星科技2 天前
零知识证明三大核心特性解析
区块链·零知识证明
2301_776045232 天前
山寨币与比特币之间的关系
大数据·人工智能·区块链
JF_Ma3 天前
1000个量化策略第29例:经典红利低波策略
区块链
AlanAshley3 天前
实时湖仓与区块链:数据架构的信任革命
架构·区块链
代码方舟4 天前
零信任架构实战:基于天远企业法人四要素高级版构建自动化电子印章授权与签约准入网关
人工智能·架构·自动化·区块链
AIFQuant4 天前
Python期货行情API接入:股指、黄金、原油多品种数据管道实战
开发语言·python·金融·区块链·量化开发
北冥you鱼4 天前
区块链供应链金融如何降低中小企业融资成本
网络·金融·区块链
2601_962885726 天前
2026 Python 股票数据接口选型指南(横评 + 决策树)
开发语言·python·区块链