主页 > 官网imtoken钱包苹果版下载 > 比特币交易源码分析
比特币交易源码分析
交易部分可以先阅读《精通比特币》第5章
本文内容参考自
初步分析比特币源码,推荐阅读比特币源码original-bitcoin原版。 该版本源码比较简单,可以帮助您快速了解比特币各个阶段的工作流程和原理。
1.发送钱()
当比特币客户端将比特币发送到地址时调用此函数。 “src/main.cpp”第 2625 行的函数。
bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) { CRITICAL_BLOCK(cs_main) { int64 nFeeRequired; if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) { strig 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; }
该方法包含三个参数:
SendMoney()
第一次打电话
CreateTransaction()
函数,该函数的作用是构造一个新的交易,也是本文重点关注的函数。 该函数源码如下:
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 setsetCoins; if (!SelectCoins(nValue, setCoins)) return false; int64 nValueIn = 0; foreach(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 vchPubKey; CTransaction& txFirst = *(*setCoins.begin()); foreach(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 foreach(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; foreach(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; }
调用该方法时,需要的四个参数如下:
该函数首先初始化实例wtxNew,然后计算总费用nValue=转账金额+交易手续费,并调用
SelectCoin()
寻找合适的交易输入。
事实上,没有地方可以存储比特币地址或账户余额,只有被其所有者锁定的去中心化 UTXO。 “用户的比特币余额”的概念是通过比特币钱包应用程序创建的衍生产品。 比特币钱包通过扫描区块链并汇总属于该用户的所有 UTXO 来计算用户的余额。
bool SelectCoins(int64 nTargetValue, set& setCoinsRet) { setCoinsRet.clear(); // List of values less than target int64 nLowestLarger = _I64_MAX; CWalletTx* pcoinLowestLarger = NULL; vector > vValue; int64 nTotalLower = 0; ... }
我们知道比特币是基于 UTXO 模型的,所以 SelectCoin 负责从属于用户的所有 UTXO 中找到一组匹配转账金额的输入。 具体的搜索算法这里就不具体分析了。
在得到一组输入后,它会计算所有输入的总量 nValueIn。 一般输入的总金额大于转账的金额,所以后面会构造一个转账到自己地址的输出进行变更。
随后打电话
wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey))
构造第一个output,指向交易的转账地址。CTxIn和CTxOut的数据结构可以参考
如果需要更改 (nValueIn > nValue),请将另一个输出事务添加到 wtxNew 并将更改发回给我。 该过程包括以下步骤:
从setCoin中获取第一笔交易txFirst,依次检查txFirst.vout中的输出是否属于你。如果是,则从输出交易中提取公钥
ExtractPubKey
, 并放入局部变量 vchPubKey
将vchPubKey放入脚本vchPubKey OP_CHECKSIG,并使用此脚本代码为wtxNew添加一个支付给自己的输出交易。
由于 setCoins 包括支付给该人的交易,因此每笔交易必须至少包括一笔支付给该人的交易。 从第一笔交易txFirst可以查到。
至此,wtxNew的输出交易容器vout就准备好了。 现在,安装程序进入交易容器 vin。 请记住,每个输入交易列表 vin 都指向一个源交易,而 wtxNew 的每个源交易都可以在 setCoins 中找到。 对于setCoins中的每一笔交易pcoin,逐一遍历其输出交易pcoin->vout[nOut]。 如果 nOut 输出支付给我(意味着 wtxNew 从这个输出交易中获得硬币)比特币6位mi比特币6位mi,添加一个新的输入交易到 wtxNew (wtxNew.vin(wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut)) , line 51). 输入交易指向pcoin中的nOut输出交易,从而将wtxNew.vin与pcoin的nOut输出连接起来。
对于setCoins中的每一笔交易pcoin,将其输出的所有交易pcoin->vout[nOut]一一遍历。 如果这笔交易是我的,调用SignSignature(*pcoin,wtxNew, nIn++)为第nIn笔交易添加签名。 注意nIn是wtxNew的输入交易位置。
对于交易签名功能
SignSignature
,以下是源代码:
bool SignSignature(const CTransaction& txFrom, CTransaction& txTo, unsigned int nIn, int nHashType, CScript scriptPrereq) { assert(nIn < txTo.vin.size()); CTxIn& txin = txTo.vin[nIn]; assert(txin.prevout.n < txFrom.vout.size()); const CTxOut& txout = txFrom.vout[txin.prevout.n]; // Leave out the signature from the hash, since a signature can't sign itself. // The checksig op will also drop the signatures from its hash. uint256 hash = SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType); if (!Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)) return false; txin.scriptSig = scriptPrereq + txin.scriptSig; // Test solution if (scriptPrereq.empty()) if (!EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)) return false; return true; }
首先要注意的是这个函数有5个参数,而CreateTransaction()只有3个,这是因为在script.h文件中,默认给定了最后两个参数。
SignSignature(*pcoin, wtxNew, nIn++)
这是 SignSignature() 的作用:
这些功能介绍如下:
签名哈希()
uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType) { if (nIn >= txTo.vin.size()) { printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn); return 1; } CTransaction txTmp(txTo); // In case concatenating two scripts ends up with two codeseparators, // or an extra one at the end, this prevents all those possible incompatibilities. scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR)); // Blank out other inputs' signatures for (int i = 0; i < txTmp.vin.size(); i++) txTmp.vin[i].scriptSig = CScript(); txTmp.vin[nIn].scriptSig = scriptCode; // Blank out some of the outputs if ((nHashType & 0x1f) == SIGHASH_NONE) { // Wildcard payee txTmp.vout.clear(); // Let the others update at will for (int i = 0; i < txTmp.vin.size(); i++) if (i != nIn) txTmp.vin[i].nSequence = 0; } else if ((nHashType & 0x1f) == SIGHASH_SINGLE) { // Only lockin the txout payee at same index as txin unsigned int nOut = nIn; if (nOut >= txTmp.vout.size()) { printf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut); return 1; } txTmp.vout.resize(nOut+1); for (int i = 0; i < nOut; i++) txTmp.vout[i].SetNull(); // Let the others update at will for (int i = 0; i < txTmp.vin.size(); i++) if (i != nIn) txTmp.vin[i].nSequence = 0; } // Blank out other inputs completely, not recommended for open transactions if (nHashType & SIGHASH_ANYONECANPAY) { txTmp.vin[0] = txTmp.vin[nIn]; txTmp.vin.resize(1); } // Serialize and hash CDataStream ss(SER_GETHASH); ss.reserve(10000); ss << txTmp << nHashType; return Hash(ss.begin(), ss.end()); }
SignatureHash(scriptPrereq + txout.scriptPubKey, txTo, nIn, nHashType);
下面是这个函数需要的参数:
了解了输入交易后,我们来了解一下 SignatureHash() 是如何工作的。
SignatureHash() 首先将 txTO 复制到 txTmp,然后清除 txTmp.vin 中每个输入交易的 scriptSig,除了 txTmp.vin[nIn],输入交易的 scriptSig 被设置为 scriptCode(第 14 和 15 行)。
接下来,该函数检查 nHashType 的值。 根据不同的nHAshType选择不同的消隐操作。
在最后 4 行代码中,txTmp 和 nHashType 成为 CDataStream 类型的序列化对象。 此类型包括保存数据的字符容器类型。 返回的哈希值是序列化后的数据通过Hash()方法计算得到的。
至此我们已经生成了txOut的哈希值hash,然后调用Solver()函数对刚刚生成的hash进行签名。
求解器()
Solver(txout.scriptPubKey, hash, nHashType, txin.scriptSig)
其源代码如下:
bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& scriptSigRet) { scriptSigRet.clear(); vector> vSolution; if (!Solver(scriptPubKey, vSolution)) return false; // Compile solution CRITICAL_BLOCK(cs_mapKeys) { foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) { if (item.first == OP_PUBKEY) { // Sign const valtype& vchPubKey = item.second; if (!mapKeys.count(vchPubKey)) return false; if (hash != 0) { vector vchSig; if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig; } } else if (item.first == OP_PUBKEYHASH) { // Sign and give pubkey map ::iterator mi = mapPubKeys.find(uint160(item.second)); if (mi == mapPubKeys.end()) return false; const vector & vchPubKey = (*mi).second; if (!mapKeys.count(vchPubKey)) return false; if (hash != 0) { vector vchSig; if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig << vchPubKey; } } } } return true; }
以下是该方法需要的4个参数:
该函数首先清除scriptSigRet,然后调用
Solver(scriptPubKey, vSolution)
, 这个 Solver 函数有两个输入。 它的源代码是:
bool Solver(const CScript& scriptPubKey, vector>& vSolutionRet) { // Templates static vector vTemplates; if (vTemplates.empty()) { // Standard tx, sender provides pubkey, receiver adds signature vTemplates.push_back(CScript() << OP_PUBKEY << OP_CHECKSIG); // Short account number tx, sender provides hash of pubkey, receiver provides signature and pubkey vTemplates.push_back(CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG); } // Scan templates const CScript& script1 = scriptPubKey; foreach(const CScript& script2, vTemplates) { vSolutionRet.clear(); opcodetype opcode1, opcode2; vector vch1, vch2; // Compare CScript::const_iterator pc1 = script1.begin(); CScript::const_iterator pc2 = script2.begin(); loop { bool f1 = script1.GetOp(pc1, opcode1, vch1); bool f2 = script2.GetOp(pc2, opcode2, vch2); if (!f1 && !f2) { // Success reverse(vSolutionRet.begin(), vSolutionRet.end()); return true; } else if (f1 != f2) { break; } else if (opcode2 == OP_PUBKEY) { if (vch1.size() <= sizeof(uint256)) break; vSolutionRet.push_back(make_pair(opcode2, vch1)); } else if (opcode2 == OP_PUBKEYHASH) { if (vch1.size() != sizeof(uint160)) break; vSolutionRet.push_back(make_pair(opcode2, vch1)); } else if (opcode1 != opcode2) { break; } } } vSolutionRet.clear(); return false; }
此函数的作用是将 scriptPubKey 与两个模板进行比较:
如果输入的脚本是脚本A,将模板A中的OP_PUBKEYHASH与脚本A中的OP_PUBKEYHASH配对,放入vSolutionRet中。
如果输入的脚本是脚本B,从模板B中提取算子OP_PUBKEY,从脚本B中提取操作数,配对放入vSolutionRet。
如果输入脚本与任一模板都不匹配,则返回 false。
回到带4个参数的Solver()继续分析这个函数。 现在我们清楚了这个函数是如何工作的。 它会选择两个分支中的一个来执行,这取决于从 vSolutionRet 中获得的 pair 是来自脚本 A 还是来自脚本 B。如果来自脚本 A,则 item.first == OP_PUBKEYHASH; 如果来自脚本 B,item.first == OP_PUBKEY。
评估脚本()
最后,将调用 EvalScript() 来运行一个小脚本并检查签名是否有效。
EvalScript(txin.scriptSig + CScript(OP_CODESEPARATOR) + txout.scriptPubKey, txTo, nIn)
其源代码如下:
bool EvalScript(const CScript& script, const CTransaction& txTo, unsigned int nIn, int nHashType, vector>* pvStackRet) { CAutoBN_CTX pctx; CScript::const_iterator pc = script.begin(); CScript::const_iterator pend = script.end(); CScript::const_iterator pbegincodehash = script.begin(); vector vfExec; vector stack; vector altstack; if (pvStackRet) pvStackRet->clear(); while (pc < pend) { bool fExec = !count(vfExec.begin(), vfExec.end(), false); ... } if (pvStackRet) *pvStackRet = stack; return (stack.empty() ? false : CastToBool(stack.back())); }
EvalScript() 接受 3 个参数,即:
该函数会根据你输入的脚本,依次取出脚本中的操作码进行相应的操作,判断最终的仿真执行结果,并返回执行结果,如果结果为真,则大功告成
SignSignature()
, 一个新的交易被生成。
返回 SendMoney()
生成新交易后,使用函数
CommitTransactionSpent(wtxNet)
尝试将这个事务提交到数据库,然后判断事务是否提交成功,如果事务提交成功
wtxNew.AcceptTransaction()=true
, 将这笔交易广播给其他对等节点
wtxNew.RelayWalletTransaction()
.
矿工收到这笔交易的广播后,会对这笔交易进行相应的操作。 在后面的章节中,我们将详细分析新区块的处理部分。
总结
比特币产生一笔新的交易大致分为以下几个阶段:
轩素红发表原创文章11篇 · 点赞0 · 访问111 关注私信