比特币源码-一个交易的产生

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37847176/article/details/81906838

在这一篇里系统得讲讲客户端如何生成比特币地址,并创建一个交易
我们知道比特币的所有权是通过数字密钥、比特币地址和数字签名来确定的。数字密钥并不存储在网络中,而是由客户端生成后保存在名为钱包的文件(wallet.dat)或者简单的数据库中。存储在用户钱包中的数字密钥完全独立于比特币协议,可由用户的钱包软件生成并管理,而无需参照区块链或访问网络。

生成比特币地址

从钱包的rpc指令getnewaddress开始看,先来看下调用结果
这里写图片描述
可以看到是返回一个比特币地址(测试网),如果是主网的话是以1开头的地址
然后在日志信息中会增加相应的记录
这里写图片描述
下面来看这个指定对应调用的函数

getnewaddress

位于/src/rpc/wallet/rpcwallet.cpp

UniValue getnewaddress(const UniValue& params, bool fHelp)
{
    if (!EnsureWalletIsAvailable(fHelp))
        return NullUniValue;

    if (fHelp || params.size() > 1)
        throw runtime_error(
            "getnewaddress ( \"account\" )\n"
            "\nReturns a new Bitcoin address for receiving payments.\n"
            "If 'account' is specified (DEPRECATED), it is added to the address book \n"
            "so payments received with the address will be credited to 'account'.\n"
            "\nArguments:\n"
            "1. \"account\"        (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
            "\nResult:\n"
            "\"bitcoinaddress\"    (string) The new bitcoin address\n"
            "\nExamples:\n"
            + HelpExampleCli("getnewaddress", "")
            + HelpExampleRpc("getnewaddress", "")
        );

    LOCK2(cs_main, pwalletMain->cs_wallet);

    // Parse the account first so we don't generate a key if there's an error
    string strAccount;
    if (params.size() > 0)
        strAccount = AccountFromValue(params[0]);

    if (!pwalletMain->IsLocked())
        pwalletMain->TopUpKeyPool();

    // Generate a new key that is added to wallet
    CPubKey newKey;
    if (!pwalletMain->GetKeyFromPool(newKey))
        throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
    CKeyID keyID = newKey.GetID();//获取的是hash160的值
//pwalletMain是CWllat类的指针
    pwalletMain->SetAddressBook(keyID, strAccount, "receive");
//CBitcoinAddress函数调用Base58编码转换
    return CBitcoinAddress(keyID).ToString();
}

这里的pwalletMain是指向CWallet类对象的指针。生成一个新的密钥在这里是通过调用函数GetKeyFromPool。可以看到这段代码最后是调用CBitcoinAddress函数返回比特币地址。

GetKeyFromPool

在这个函数中首先调用ReserveKeyFromKeyPool查看密钥储备池中的密钥,如果没有储备的密钥,就通过GenerateNewKey生成一个新的密钥,否则根据索引获取储备池中的下一个密钥

//src/wallet/wallet.cpp
bool CWallet::GetKeyFromPool(CPubKey& result)
{
    int64_t nIndex = 0;
    CKeyPool keypool;
    {
        LOCK(cs_wallet);
        ReserveKeyFromKeyPool(nIndex, keypool);
        if (nIndex == -1)
        {
            if (IsLocked()) return false;
            result = GenerateNewKey();
            return true;
        }
        KeepKey(nIndex);
        result = keypool.vchPubKey;
    }
    return true;
}

GenerateNewKey

CKey可以参考https://blog.csdn.net/m0_37847176/article/details/81450432#2-initsanitycheck里面有写到这个类,以及makenewkey()的实现

CPubKey CWallet::GenerateNewKey()
{
    AssertLockHeld(cs_wallet); // mapKeyMetadata
    bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
    // CKey是私钥的类
    CKey secret;

    // Create new metadata
    int64_t nCreationTime = GetTime();
    CKeyMetadata metadata(nCreationTime);

    // use HD key derivation if HD was enabled during wallet creation 
    //1.如果在钱包创建时使用确定分层钱包,则使用HD密钥
    if (!hdChain.masterKeyID.IsNull()) {
        // for now we use a fixed keypath scheme of m/0'/0'/k
        CKey key;                      //master key seed (256bit)
        CExtKey masterKey;             //hd master key
        CExtKey accountKey;            //key at m/0'
        CExtKey externalChainChildKey; //key at m/0'/0'
        CExtKey childKey;              //key at m/0'/0'/<n>'

        // try to get the master key
        if (!GetKey(hdChain.masterKeyID, key))
            throw std::runtime_error(std::string(__func__) + ": Master key not found");

        masterKey.SetMaster(key.begin(), key.size());

        // derive m/0' 派生
        // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
        //使用硬化的派生,const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
        masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);

        // derive m/0'/0'
        accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT);

        // derive child key at next index, skip keys already known to the wallet
        do
        {
            // always derive hardened keys
            // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
            // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
            externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
            metadata.hdKeypath     = "m/0'/0'/"+std::to_string(hdChain.nExternalChainCounter)+"'";
            metadata.hdMasterKeyID = hdChain.masterKeyID;
            // increment childkey index
            hdChain.nExternalChainCounter++;
        } while(HaveKey(childKey.key.GetPubKey().GetID()));
        secret = childKey.key;

        // update the chain model in the database
        if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
            throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
    } else {
    //2.如果在创建钱包的时候是使用随机钱包
        secret.MakeNewKey(fCompressed);
    }

    // Compressed public keys were introduced in version 0.6.0
    if (fCompressed)
        SetMinVersion(FEATURE_COMPRPUBKEY);
//验证公钥
    CPubKey pubkey = secret.GetPubKey();
    assert(secret.VerifyPubKey(pubkey));
//判断第一把密钥的创建时间
    mapKeyMetadata[pubkey.GetID()] = metadata;
    if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
        nTimeFirstKey = nCreationTime;

    if (!AddKeyPubKey(secret, pubkey))
        throw std::runtime_error(std::string(__func__) + ": AddKey failed");
    return pubkey;
}

其中的HD钱包部分是依据标准BIP32实现的,写在另一篇里https://blog.csdn.net/m0_37847176/article/details/82011876
如果是随机钱包,则调用CKey类的MakeNewKey

//key.cpp
void CKey::MakeNewKey(bool fCompressedIn) {
    do {
        GetStrongRandBytes(vch, sizeof(vch));////! The actual byte data unsigned char vch[32];
    } while (!Check(vch));
    fValid = true;
    fCompressed = fCompressedIn;
}

具体实现就是新建一个CKey类型的对象,获取强随机数,私钥是32位的,知道获取的随机数通过验证,此时的私钥是有效的。
然后获取对应私钥的公钥,通过椭圆曲线算法调用库,验证公钥
最后添加公钥,调用CWallet类的AddKeyPubKey

//wallet.cpp
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
    AssertLockHeld(cs_wallet); // mapKeyMetadata
    if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
        return false;

    // check if we need to remove from watch-only
    CScript script;
    script = GetScriptForDestination(pubkey.GetID());
    if (HaveWatchOnly(script))
        RemoveWatchOnly(script);
    script = GetScriptForRawPubKey(pubkey);
    if (HaveWatchOnly(script))
        RemoveWatchOnly(script);

    if (!fFileBacked)
        return true;
    if (!IsCrypted()) {
        return CWalletDB(strWalletFile).WriteKey(pubkey,
                                                 secret.GetPrivKey(),
                                                 mapKeyMetadata[pubkey.GetID()]);
    }
    return true;
}

首先调用CCryptoKeyStore的AddKeyPubKey,CCryptoKeyStore是保存加密过的私钥的密钥库,继承自CBasicKeyStore,如果没有激活加密,则使用CBasicKeyStore。

bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
    {
        LOCK(cs_KeyStore);
        if (!IsCrypted())//是否激活加密
            return CBasicKeyStore::AddKeyPubKey(key, pubkey);

        if (IsLocked())
            return false;

        std::vector<unsigned char> vchCryptedSecret;
        CKeyingMaterial vchSecret(key.begin(), key.end());
        if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret))
            return false;

        if (!AddCryptedKey(pubkey, vchCryptedSecret))
            return false;
    }
    return true;
}

未激活加密私钥的话

bool CBasicKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
    LOCK(cs_KeyStore);
    mapKeys[pubkey.GetID()] = key;//私钥和公钥(hash值)保存在键值对mapKeys中
    return true;
}

激活加密私钥
首先调用EncryptSecret加密秘密,接着添加加密的私钥

bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
    {
        LOCK(cs_KeyStore);
        if (!SetCrypted())
            return false;

        mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
    }
    return true;
}

把公钥(hash)、公钥、加密的秘密保存在键值对mapCryptedKeys中。
返回CWallet::AddKeyPubKey,最后调用 CWalletDB类的WriteKey

bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
    nWalletDBUpdated++;

    if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
               keyMeta, false))
        return false;

    // hash pubkey/privkey to accelerate wallet load
    std::vector<unsigned char> vchKey;
    vchKey.reserve(vchPubKey.size() + vchPrivKey.size());
    vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
    vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());

    return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
}

这里是调用了CWalletDB的父类CDB的成员函数Write,CDB封装了Berkeley数据库的一系列操作接口,上述代码就是把密钥写入Berkeley数据库。
加载钱包在https://blog.csdn.net/m0_37847176/article/details/81739999这一篇中

Berkeley DB (DB)是一个高性能的,嵌入数据库编程库,和C语言,C++,Java,Perl,Python,PHP,Tcl以及其他很多语言都有绑定。Berkeley DB可以保存任意类型的键/值对,而且可以为一个键保存多个数据。Berkeley DB可以支持数千的并发线程同时操作数据库,支持最大256TB的数据,广泛用于各种操作系统包括大多数Unix类操作系统和Windows操作系统以及实时操作系统。

发送金额

这个函数位于rpcwallet.cpp中,需要传入交易目的地,交易金额,fSubtractFeeFromAmount(从交易金额中抽取费用),包含附加的交易信息CWalletTx(比如这笔交易是发送给谁,为什么发起)

/**
* A txout script template with a specific destination. It is either:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination
* * CScriptID: TX_SCRIPTHASH destination
* A CTxDestination is the internal data type encoded in a CBitcoinAddress
*/

typedef boost::variant<CNoDestination, CKeyID, CScriptID> CTxDestination;

CWalletTx:A transaction with a bunch of additional info that only the owner cares about.
It includes any unrecorded transactions needed to link it back to the block chain.

static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
{
    CAmount curBalance = pwalletMain->GetBalance();

    // 1.Check amount 检查余额
    if (nValue <= 0)
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");

    if (nValue > curBalance)
        throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");

    // 2.Parse Bitcoin address 解析比目的地,获得脚本
    CScript scriptPubKey = GetScriptForDestination(address);

    // 3.Create and send the transaction 创建并发送交易
    CReserveKey reservekey(pwalletMain);
    CAmount nFeeRequired;
    std::string strError;
    vector<CRecipient> vecSend;
    int nChangePosRet = -1;
    CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};//接收者(锁定脚本,金额,是否抽取费用)
    vecSend.push_back(recipient);
    if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
        if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance())
            strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
        throw JSONRPCError(RPC_WALLET_ERROR, strError);
    }
    if (!pwalletMain->CommitTransaction(wtxNew, reservekey))//提交交易
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");
}

GetBalance()

首先调用GetBalance()获取钱包的余额

CAmount CWallet::GetBalance() const
{
    CAmount nTotal = 0;
    {
        LOCK2(cs_main, cs_wallet);
        for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
        {
            const CWalletTx* pcoin = &(*it).second;//CWalletTx
            if (pcoin->IsTrusted())
                nTotal += pcoin->GetAvailableCredit();
        }
    }

    return nTotal;
}

IsTrusted()

这里判断交易是否被信任

//wallet.cpp
bool CWalletTx::IsTrusted() const
{
    // Quick answer in most cases
    if (!CheckFinalTx(*this))//判断交易是否是最终的,函数内调用了IsFinalTx
        return false;
    int nDepth = GetDepthInMainChain();
    if (nDepth >= 1)//交易所在区块后面的区块大于等于1即视为信任
        return true;
    if (nDepth < 0)
        return false;
    if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
        return false;

    // Don't trust unconfirmed transactions from us unless they are in the mempool.
    if (!InMempool())//不信任不在内存池中的交易
        return false;

    // Trusted if all inputs are from us and are in the mempool:
    BOOST_FOREACH(const CTxIn& txin, vin)
    {
        // Transactions not sent by us: not trusted
        const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
        if (parent == NULL)
            return false;
        const CTxOut& parentOut = parent->vout[txin.prevout.n];
        if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
            return false;
    }
    return true;
}
//main.h
/**
 * Check if transaction is final and can be included in a block with the
 * specified height and time. Consensus critical.
 */
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime);

GetAvailableCredit

这里有几个逻辑

CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{  
    if (pwallet == 0)//没有钱包信息则返回0
        return 0;

    // Must wait until coinbase is safely deep enough in the chain before valuing it
    //如果是coinbase交易,必须等这笔交易足够安全,否则返回0
    if (IsCoinBase() && GetBlocksToMaturity() > 0)
        return 0;

    if (fUseCache && fAvailableCreditCached)//如果使用缓存数据
        return nAvailableCreditCached;

    CAmount nCredit = 0;
    uint256 hashTx = GetHash();
    for (unsigned int i = 0; i < vout.size(); i++)
    {
        if (!pwallet->IsSpent(hashTx, i))//如果这笔交易的输出未支出
        {
            const CTxOut &txout = vout[i];
            nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);//我可以花费的部分
            if (!MoneyRange(nCredit))
                throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
        }
    }

    nAvailableCreditCached = nCredit;
    fAvailableCreditCached = true;
    return nCredit;
}

CreateTransaction

/**
* 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,大于或等于目标金额
鉴于这段代码很长,分段来讲

1)

参数

类型 名称 说明
vector<CRecipient> vecSend struct CRecipient{CScript scriptPubKey;CAmount nAmount;bool fSubtractFeeFromAmount;}
CWalletTx& wtxNew 包含附加信息的交易信息,这个类只关注本钱包发起(可能包含接收)的交易
CReserveKey& reservekey 密钥池分配的密钥
CAmount& nFeeRet CAmount nFeeRequired;未赋值
int& nChangePosRet 改变标志位?
std::string& strFailReason 失败的原因
CCoinControl* coinControl 币控制功能
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;//赋值
    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.

2)nLockTime

nLockTime是交易类的成员变量,参考这篇
https://blog.csdn.net/m0_37847176/article/details/81624052#ctransaction

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.
阻止费用狙击。
对于大型矿工而言,最佳区块和mempool中的交易价值可能超过尝试故意挖掘两个区块以孤立当前最佳区块的成本。通过设置nLockTime使得只有下一个区块可以包括交易,我们不鼓励这种做法,因为高度限制和有限的区块大小给矿工考虑费用狙击更少的选项来解除这种攻击。
考虑这个问题的一个简单方法是从钱包的角度来看,我们总是希望区块链能够向前发展。通过以这种方式设置nLockTime,我们基本上是在声明我们只希望此交易出现在下一个块中;我们不希望通过允许交易出现在比最佳链的下一个分叉块中更低的高度来促进重新排序。
当然,补贴足够高,交易量足够低,费用狙击就不是问题,但是现在通过实现一个修复,我们确保代码不会被编写对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);

3)vouts

{
        LOCK2(cs_main, cs_wallet);
        {
            std::vector<COutput> vAvailableCoins;
            //用可用的交易输出填充vAvailableCoins,vAvailableCoins就是可用的交易输出
            AvailableCoins(vAvailableCoins, true, coinControl);

            nFeeRet = 0;
            // Start with no fee and loop until there is enough fee
            //循环直到有足够的交易金额
            while (true)
            {
            //初始化工作,清零
                nChangePosInOut = nChangePosRequest;
                txNew.vin.clear();
                txNew.vout.clear();
                txNew.wit.SetNull();
                wtxNew.fFromMe = true;
                bool fFirst = true;

                CAmount nValueToSelect = nValue;//初始为0
                if (nSubtractFeeFromAmount == 0)
                    nValueToSelect += nFeeRet;
                double dPriority = 0;

对每个接收者的处理,对每个接收者创建一个CTxOut
https://blog.csdn.net/m0_37847176/article/details/81624052#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);//写入交易的输出部分
                }

4)

// 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;//增加优先级
}

5)找零

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);//新建一笔找零交易

这里的意思是使用我们之前生成过的保留在钱包中的密钥
上述代码调用reservekeyGetReservedKeyreservekey是传入的参数

bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{
    if (nIndex == -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‘中
这里写图片描述
发送测试币回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,作为第二笔交易的输入
这里写图片描述

这里写图片描述
在创建三笔交易,创建一个地址,创建一个交易
这里写图片描述


6)dust output

// 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();

7)vin & sign

涉及vin的序列号,这个类写在这里https://blog.csdn.net/m0_37847176/article/details/81624052#ctxin

// 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);

8)判断

 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);
                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;
            }
        }
    }

到这里循环结束


9)参数读取

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;
    }

猜你喜欢

转载自blog.csdn.net/m0_37847176/article/details/81906838