比特幣使用UTXO模型做為交易底層數據結構,UTXO 是 Unspent Transaction Output 的縮寫,也就是未被使用的交易輸出。本質上,就是只記錄交易本身,而不記錄交易的結果。比特幣使用前后鏈接的區塊(可以簡單的理解為交易組成的集合)記錄所有交易,每筆交易都有若干交易輸入,也就是資金來源,也都有若干筆交易輸出,也就是資金去向。一般來說,每一筆交易都要花費(spend)一筆輸入,產生一筆輸出,而其所產生的輸出,就是“未花費過的交易輸出”,也就是 UTXO。當之前的 UTXO 出現在后續交易的輸入時,就表示這個 UTXO 已經花費掉了,不再是 UTXO 了。如果從第一個區塊開始逐步計算所有比特幣地址中的余額,就可以計算出不同時間的各個比特幣賬戶的余額了。下面將結合比特幣錢包源碼0.1.0對比特幣中的交易做詳細說明。
1 數據結構及相關定義
1.1 區塊
交易會被打包到區塊中,打包成功的區塊會被序列化到本地文件中,區塊定義如下(只給出了主要類成員):

1 class CBlock 2 { 3 public: 4 // header 5 int nVersion; // 版本 6 uint256 hashPrevBlock; // 上一個塊哈希值 7 uint256 hashMerkleRoot; // MerkleRoot哈希值 8 unsigned int nTime; // 時間戳 9 unsigned int nBits; // 塊目標值 10 unsigned int nNonce; // nonce值 11 12 // network and disk 13 vector<CTransaction> vtx; // 交易 14 ... 15 }
1.2 交易
版本nVersion | vin0 | ... | vinn | vout0 | ... | voutm | 鎖定時間nLockTime |
如表所示,單個交易由版本、若干輸入、若干輸出和鎖定時間構成,其中當前版本值為1,輸入和輸出后續有更詳細介紹,nLockTime定義了一個最早時間,只有過了這個最早時間,這個transaction可以被發送到比特幣網絡,當前版本用塊高度來定義該時間,即只有交易中nLockTime小於當前比特幣網絡塊高度,該交易才會被發送到比特幣網絡(其實后續版本的比特幣引入了LOCKTIME_THRESHOLD=500000000,當nLock小於該值時為區塊高度,否則為時間戳),nLockTime通常被設置為0,表示transaction一創建好就馬上發送到比特幣網絡,交易源碼定義如下:

1 class CTransaction 2 { 3 public: 4 int nVersion; 5 vector<CTxIn> vin; 6 vector<CTxOut> vout; 7 int nLockTime; 8 9 CTransaction() 10 { 11 SetNull(); 12 } 13 14 IMPLEMENT_SERIALIZE 15 ( 16 READWRITE(this->nVersion); 17 nVersion = this->nVersion; 18 READWRITE(vin); 19 READWRITE(vout); 20 READWRITE(nLockTime); 21 ) 22 23 void SetNull() 24 { 25 nVersion = 1; 26 vin.clear(); 27 vout.clear(); 28 nLockTime = 0; 29 } 30 31 bool IsNull() const 32 { 33 return (vin.empty() && vout.empty()); 34 } 35 36 uint256 GetHash() const 37 { 38 return SerializeHash(*this); 39 } 40 41 bool IsFinal() const 42 { 43 if (nLockTime == 0 || nLockTime < nBestHeight) 44 return true; 45 foreach(const CTxIn& txin, vin) 46 if (!txin.IsFinal()) 47 return false; 48 return true; 49 } 50 51 bool IsNewerThan(const CTransaction& old) const 52 { 53 if (vin.size() != old.vin.size()) 54 return false; 55 for (int i = 0; i < vin.size(); i++) 56 if (vin[i].prevout != old.vin[i].prevout) 57 return false; 58 59 bool fNewer = false; 60 unsigned int nLowest = UINT_MAX; 61 for (int i = 0; i < vin.size(); i++) 62 { 63 if (vin[i].nSequence != old.vin[i].nSequence) 64 { 65 if (vin[i].nSequence <= nLowest) 66 { 67 fNewer = false; 68 nLowest = vin[i].nSequence; 69 } 70 if (old.vin[i].nSequence < nLowest) 71 { 72 fNewer = true; 73 nLowest = old.vin[i].nSequence; 74 } 75 } 76 } 77 return fNewer; 78 } 79 80 bool IsCoinBase() const 81 { 82 return (vin.size() == 1 && vin[0].prevout.IsNull()); 83 } 84 85 bool CheckTransaction() const 86 { 87 // Basic checks that don't depend on any context 88 if (vin.empty() || vout.empty()) 89 return error("CTransaction::CheckTransaction() : vin or vout empty"); 90 91 // Check for negative values 92 foreach(const CTxOut& txout, vout) 93 if (txout.nValue < 0) 94 return error("CTransaction::CheckTransaction() : txout.nValue negative"); 95 96 if (IsCoinBase()) 97 { 98 if (vin[0].scriptSig.size() < 2 || vin[0].scriptSig.size() > 100) 99 return error("CTransaction::CheckTransaction() : coinbase script size"); 100 } 101 else 102 { 103 foreach(const CTxIn& txin, vin) 104 if (txin.prevout.IsNull()) 105 return error("CTransaction::CheckTransaction() : prevout is null"); 106 } 107 108 return true; 109 } 110 111 bool IsMine() const 112 { 113 foreach(const CTxOut& txout, vout) 114 if (txout.IsMine()) 115 return true; 116 return false; 117 } 118 119 int64 GetDebit() const 120 { 121 int64 nDebit = 0; 122 foreach(const CTxIn& txin, vin) 123 nDebit += txin.GetDebit(); 124 return nDebit; 125 } 126 127 int64 GetCredit() const 128 { 129 int64 nCredit = 0; 130 foreach(const CTxOut& txout, vout) 131 nCredit += txout.GetCredit(); 132 return nCredit; 133 } 134 135 int64 GetValueOut() const 136 { 137 int64 nValueOut = 0; 138 foreach(const CTxOut& txout, vout) 139 { 140 if (txout.nValue < 0) 141 throw runtime_error("CTransaction::GetValueOut() : negative value"); 142 nValueOut += txout.nValue; 143 } 144 return nValueOut; 145 } 146 147 int64 GetMinFee(bool fDiscount=false) const 148 { 149 unsigned int nBytes = ::GetSerializeSize(*this, SER_NETWORK); 150 if (fDiscount && nBytes < 10000) 151 return 0; 152 return (1 + (int64)nBytes / 1000) * CENT; 153 } 154 155 bool ReadFromDisk(CDiskTxPos pos, FILE** pfileRet=NULL) 156 { 157 CAutoFile filein = OpenBlockFile(pos.nFile, 0, pfileRet ? "rb+" : "rb"); 158 if (!filein) 159 return error("CTransaction::ReadFromDisk() : OpenBlockFile failed"); 160 161 // Read transaction 162 if (fseek(filein, pos.nTxPos, SEEK_SET) != 0) 163 return error("CTransaction::ReadFromDisk() : fseek failed"); 164 filein >> *this; 165 166 // Return file pointer 167 if (pfileRet) 168 { 169 if (fseek(filein, pos.nTxPos, SEEK_SET) != 0) 170 return error("CTransaction::ReadFromDisk() : second fseek failed"); 171 *pfileRet = filein.release(); 172 } 173 return true; 174 } 175 176 friend bool operator==(const CTransaction& a, const CTransaction& b) 177 { 178 return (a.nVersion == b.nVersion && 179 a.vin == b.vin && 180 a.vout == b.vout && 181 a.nLockTime == b.nLockTime); 182 } 183 184 friend bool operator!=(const CTransaction& a, const CTransaction& b) 185 { 186 return !(a == b); 187 } 188 189 string ToString() const 190 { 191 string str; 192 str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%d, vout.size=%d, nLockTime=%d)\n", 193 GetHash().ToString().substr(0,6).c_str(), 194 nVersion, 195 vin.size(), 196 vout.size(), 197 nLockTime); 198 for (int i = 0; i < vin.size(); i++) 199 str += " " + vin[i].ToString() + "\n"; 200 for (int i = 0; i < vout.size(); i++) 201 str += " " + vout[i].ToString() + "\n"; 202 return str; 203 } 204 205 void print() const 206 { 207 printf("%s", ToString().c_str()); 208 } 209 210 bool DisconnectInputs(CTxDB& txdb); 211 bool ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx, int nHeight, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee=0); 212 bool ClientConnectInputs(); 213 214 bool AcceptTransaction(CTxDB& txdb, bool fCheckInputs=true, bool* pfMissingInputs=NULL); 215 216 bool AcceptTransaction(bool fCheckInputs=true, bool* pfMissingInputs=NULL) 217 { 218 CTxDB txdb("r"); 219 return AcceptTransaction(txdb, fCheckInputs, pfMissingInputs); 220 } 221 222 protected: 223 bool AddToMemoryPool(); 224 public: 225 bool RemoveFromMemoryPool(); 226 };
GetHash:獲取交易哈希值
IsFinal:交易是否已確定,可以看到該函數中用到了nLockTime
CheckTransaction:交易的合法性檢查
IsMine:交易是否和當前錢包相關
GetDebit:錢包進賬
GetCredit:錢包出賬
ReadFromDisk:從本地文件讀取交易
1.3 交易輸入
上個交易輸出點prevout | 解鎖腳本scriptSig | 序列號nSequence |
如表所示,交易輸入由上個交易輸出點、交易解鎖腳本及序列號組成,其中上個交易輸出點包含兩個元素,一個是上一個交易的哈希值,另一個是上一個交易輸出的索引號,由這兩個元素便可確定唯一的UTXO,一個UTXO中包含一個鎖定腳本,要想花費該UTXO必須提供有效的解鎖腳本,解鎖腳本由簽名和公鑰組成,nSequence字段默認填最大值0xffffffff,該字段在替換交易時有用,這里不做過多的解釋。交易輸入源碼定義如下:

1 class CTxIn 2 { 3 public: 4 COutPoint prevout; 5 CScript scriptSig; 6 unsigned int nSequence; 7 8 CTxIn() 9 { 10 nSequence = UINT_MAX; 11 } 12 13 explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=UINT_MAX) 14 { 15 prevout = prevoutIn; 16 scriptSig = scriptSigIn; 17 nSequence = nSequenceIn; 18 } 19 20 CTxIn(uint256 hashPrevTx, unsigned int nOut, CScript scriptSigIn=CScript(), unsigned int nSequenceIn=UINT_MAX) 21 { 22 prevout = COutPoint(hashPrevTx, nOut); 23 scriptSig = scriptSigIn; 24 nSequence = nSequenceIn; 25 } 26 27 IMPLEMENT_SERIALIZE 28 ( 29 READWRITE(prevout); 30 READWRITE(scriptSig); 31 READWRITE(nSequence); 32 ) 33 34 bool IsFinal() const 35 { 36 return (nSequence == UINT_MAX); 37 } 38 39 friend bool operator==(const CTxIn& a, const CTxIn& b) 40 { 41 return (a.prevout == b.prevout && 42 a.scriptSig == b.scriptSig && 43 a.nSequence == b.nSequence); 44 } 45 46 friend bool operator!=(const CTxIn& a, const CTxIn& b) 47 { 48 return !(a == b); 49 } 50 51 string ToString() const 52 { 53 string str; 54 str += strprintf("CTxIn("); 55 str += prevout.ToString(); 56 if (prevout.IsNull()) 57 str += strprintf(", coinbase %s", HexStr(scriptSig.begin(), scriptSig.end(), false).c_str()); 58 else 59 str += strprintf(", scriptSig=%s", scriptSig.ToString().substr(0,24).c_str()); 60 if (nSequence != UINT_MAX) 61 str += strprintf(", nSequence=%u", nSequence); 62 str += ")"; 63 return str; 64 } 65 66 void print() const 67 { 68 printf("%s\n", ToString().c_str()); 69 } 70 71 bool IsMine() const; 72 int64 GetDebit() const; 73 };
1.4 交易輸出
比特幣數量nValue | 鎖定腳本scriptPubKey |
如表所示,交易輸出由比特幣數量、鎖定腳本組成,其中比特幣數量表明了該輸出包含的比特幣數量,鎖定腳本對UTXO上了“鎖”,誰能提供有效的解鎖腳本,誰就能花費該UTXO。交易輸出源碼定義如下:

1 // 2 // An output of a transaction. It contains the public key that the next input 3 // must be able to sign with to claim it. 4 // 5 class CTxOut 6 { 7 public: 8 int64 nValue; 9 CScript scriptPubKey; 10 11 public: 12 CTxOut() 13 { 14 SetNull(); 15 } 16 17 CTxOut(int64 nValueIn, CScript scriptPubKeyIn) 18 { 19 nValue = nValueIn; 20 scriptPubKey = scriptPubKeyIn; 21 } 22 23 IMPLEMENT_SERIALIZE 24 ( 25 READWRITE(nValue); 26 READWRITE(scriptPubKey); 27 ) 28 29 void SetNull() 30 { 31 nValue = -1; 32 scriptPubKey.clear(); 33 } 34 35 bool IsNull() 36 { 37 return (nValue == -1); 38 } 39 40 uint256 GetHash() const 41 { 42 return SerializeHash(*this); 43 } 44 45 bool IsMine() const 46 { 47 return ::IsMine(scriptPubKey); 48 } 49 50 int64 GetCredit() const 51 { 52 if (IsMine()) 53 return nValue; 54 return 0; 55 } 56 57 friend bool operator==(const CTxOut& a, const CTxOut& b) 58 { 59 return (a.nValue == b.nValue && 60 a.scriptPubKey == b.scriptPubKey); 61 } 62 63 friend bool operator!=(const CTxOut& a, const CTxOut& b) 64 { 65 return !(a == b); 66 } 67 68 string ToString() const 69 { 70 if (scriptPubKey.size() < 6) 71 return "CTxOut(error)"; 72 return strprintf("CTxOut(nValue=%I64d.%08I64d, scriptPubKey=%s)", nValue / COIN, nValue % COIN, scriptPubKey.ToString().substr(0,24).c_str()); 73 } 74 75 void print() const 76 { 77 printf("%s\n", ToString().c_str()); 78 } 79 };
1.5 加密算法及簽名驗證
交易驗證時會用到加密算法中的簽名及簽名驗證,所以先對比特幣系統的加解密算法進行說明。比特幣系統加解密算法用的是橢圓曲線加密算法,該算法屬於非對稱加密算法,包含公鑰和私鑰,公鑰對外公開,私鑰秘密保存,比特幣錢包的私鑰保存於wallet.dat文件中,所以該文件一定要秘密保存。對於橢圓曲線加密算法來說,公鑰和私鑰是成對的,它們可以互相加解密,總得來說可以用“公鑰加密,私鑰簽名”八個字總結兩個密鑰的作用。在應用到加密場景時,可以自己對本地文件用公鑰進行加密,當該加密文件被其他人盜取時,由於其他人不知道私鑰,所以他們看不了文件內容;另外,其他人可以用公鑰對文件加密,並通過網絡傳輸給你,即便文件被截獲,截獲者不知道私鑰也無法獲得文件內容,只有擁有私鑰的你可以正確解密文件並獲取正確內容。在應用到簽名場景時,可以用私鑰對文件A進行加密(簽名)生成結果B,並把文件A和簽名結果B發送給其他人或對外公布,由於公鑰是公開的,其他人用公鑰對簽名結果解密發現和文件A一致,所以就可以確定是文件確實是你發布的(因為只有你擁有私鑰),這個加密操作好比你給文件進行了“簽名”,由於其他人沒有私鑰所以不能仿冒,進行簽名時如果文件A比較大,一般不會直接對A進行簽名,而是對A進行哈希操作獲得所謂的摘要,再對摘要進行簽名,簽名驗證時也是對相應的摘要進行驗證。
比特幣交易中的輸入和輸出可能有多個,對應有不同的簽名類型,目前有三類:SIGHASH_ALL,SIGHASH_NONE,SIGHASH_SINGLE。
SIGHASH_ALL
該簽名類型為默認類型,也是目前絕大部分交易采用的,顧名思義即簽名整單交易。首先,組織所有輸出、輸入,就像上文分解Hex過程一樣,每個輸入都對應一個簽名,暫時留空,其他包括sequence等字段均須填寫,這樣就形成了一個完整的交易Hex(只缺簽名字段)。然后,每一個輸入均需使用私鑰對該段數據進行簽名,簽名完成后各自填入相應的位置,N個輸入N個簽名。簡單理解就是:對於該筆單子,認可且只認可的這些輸入、輸出,並同意花費我的那筆輸入。
SIGHASH_NONE
該簽名類型是最自由松散的,僅對輸入簽名,不對輸出簽名,輸出可以任意指定。某人對某筆幣簽名后交給你,你可以在任意時刻填入任意接受地址,廣播出去令其生效。簡單理解就是:我同意花費我的那筆錢,至於給誰,我不關心。
SIGHASH_SINGLE
該簽名類型其次自由松散,僅對自己的輸入、輸出簽名,並留空sequence字段。其輸入的次序對應其輸出的次序,比如輸入是第3個,那么簽名的輸出也是第三個。簡單理解就是:我同意花費我的那筆錢,且只能花費到我認可的輸出,至於單子里的其他輸入、輸出,我不關心。
當我們拿到一筆交易時,如何驗證這個交易輸入是否有效,也就是如何校驗該輸入所引用的輸出是否有效。首先,將當前輸入的解鎖腳本,和該輸入所引用的上一筆交易輸出的鎖定腳本如圖8一樣組合在一起,並進行下的驗證過程,最終若返回TRUE,說明交易有效。
2 交易類型及實例
2.1 Coinbase交易
也稱作產量交易(Generation TX),每個Block都對應一個產量交易,該類交易是沒有輸入交易的,產量交易產生的幣是所有幣的源頭。以創世塊包含的Coinbase交易為例來進行分析,打開比特區塊文件blk00000.dat,內容如下:
F9BEB4D9 - 神奇數
0x0000011D - 區塊大小285字節,不包含該長度字段
01000000 - version
0000000000000000000000000000000000000000000000000000000000000000 - prev block
3BA3EDFD7A7B12B27AC72C3E67768F617FC81BC3888A51323A9FB8AA4B1E5E4A - merkle root
29AB5F49 - timestamp
FFFF001D - bits
1DAC2B7C - nonce
01 - number of transactions
01000000 - version
01 - input
0000000000000000000000000000000000000000000000000000000000000000 - prev output
FFFFFFFF - index
4D04FFFF001D0104455468652054696D65732030332F4A616E2F32303039204368616E63656C6C6F72206F6E206272696E6B206F66207365636F6E64206261696C6F757420666F722062616E6B73 - scriptSig
FFFFFFFF - sequence
01 - outputs
00F2052A01000000 - 50 BTC
434104678AFDB0FE5548271967F1A67130B7105CD6A828E03909A67962E0EA1F61DEB649F6BC3F4CEF38C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5FAC - scriptPubKey
00000000 - lock time
2.2 通用地址交易
該類交易是最常見的交易類型,由N個輸入、M個輸出構成。以交易cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79為例進行說明,其Json格式內容如下:
該交易包含1個輸入2個輸出,位於塊0000000013ab9f8ed78b254a429d3d5ad52905362e01bf6c682940337721eb51中,該塊包含兩個交易,我們要分析的交易是第2個交易,塊的二進制內容如下:
圖中第1部分是塊頭信息,該部分的最后一個字節0x02說明該塊中包含兩個交易;第2部分是塊中第一個交易,該交易是coinbase交易,不再詳述;第3部分是第二個交易,開始4個字節0x00000001是交易版本號,之后該部分第1個紅色字節0x01表示該交易有一個輸入,再后面是上一個交易的哈希值0xa1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d及輸出索引號0x00000000,之后粉色0x8B是交易輸入解鎖腳本的長度,后面藍色部分是相應的解鎖腳本,再之后紅色字節0x02表示該交易有兩個輸出,黃色內容是第一個交易輸出value值0x00000086819a7100(577700000000Satoshi=5777BTC),粉色0x19是第一個交易輸出鎖定腳本的長度,之后藍色是相應鎖定腳本,再后面是第二個交易輸出value值0x00000062530A9F00(422300000000Satoshi=4223BTC),0x43是第二個交易輸出鎖定腳本的長度,之后藍色是相應的鎖定腳本,最后4個字節是交易的nLockTime,分析可知二進制內容和之前的Json格式的交易內容是能對應上的。下面看一下該交易對應的輸出引用交易,由於引用交易的內容比較多,我們只列出引用交易的輸出部分Json及二進制內容,如下圖:
兩個交易所在塊的二進制文件可自行下載:https://files.cnblogs.com/files/zhaoweiwei/blockfiles.rar
3 相關源碼分析
源碼都是基於最初始比特幣版本0.1.0,文章最后參考部分給出了源碼的下載鏈接,讀者可自行下載。
3.1 創建交易並廣播
當單擊發送按鈕后,會獲取目標地址及發送金額nValue,並調用如下代碼
1 uint160 hash160; 2 bool fBitcoinAddress = AddressToHash160(strAddress, hash160); // 公鑰SHA-256再執行RIPEMD-160后的值 3 4 if (fBitcoinAddress) 5 { 6 // Send to bitcoin address 7 CScript scriptPubKey; 8 scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; 9 10 if (!SendMoney(scriptPubKey, nValue, wtx)) 11 return; 12 13 wxMessageBox("Payment sent ", "Sending..."); 14 }
以上代碼中第8行產生了鎖定腳本scriptPubKey,並在第10行發送函數SendMoney中創建交易並進行了一些后續操作
1 bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew) 2 { 3 CRITICAL_BLOCK(cs_main) 4 { 5 int64 nFeeRequired; 6 if (!CreateTransaction(scriptPubKey, nValue, wtxNew, nFeeRequired)) 7 { 8 string strError; 9 if (nValue + nFeeRequired > GetBalance()) 10 strError = strprintf("Error: This is an oversized transaction that requires a transaction fee of %s ", FormatMoney(nFeeRequired).c_str()); 11 else 12 strError = "Error: Transaction creation failed "; 13 wxMessageBox(strError, "Sending..."); 14 return error("SendMoney() : %s\n", strError.c_str()); 15 } 16 if (!CommitTransactionSpent(wtxNew)) 17 { 18 wxMessageBox("Error finalizing transaction", "Sending..."); 19 return error("SendMoney() : Error finalizing transaction"); 20 } 21 22 printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str()); 23 24 // Broadcast 25 if (!wtxNew.AcceptTransaction()) 26 { 27 // This must not fail. The transaction has already been signed and recorded. 28 throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n"); 29 wxMessageBox("Error: Transaction not valid", "Sending..."); 30 return error("SendMoney() : Error: Transaction not valid"); 31 } 32 wtxNew.RelayWalletTransaction(); 33 } 34 MainFrameRepaint(); 35 return true; 36 }
3.1.1 新建交易
第6行代碼處調用CreateTransaction函數創建新的交易,在該函數中又有以下主要關鍵點
(1)選取未花費的目標交易集(目標UTXO集)
從賬戶中選取目標UTXO集,選取主要遵循這樣的原則:
1)如果存在某UTXO值正好等於發送金額nValue(已包含手續費nFee),則將該UTXO加入目標交易集並返回成功
2)找出賬戶中UTXO值小於發送金額nValue的UTXO集vValue,並將vValue中所有UTXO值求和為nTotalLower,並找出所有UTXO值大於nValue的最小值nLowestLarger,再分兩種情況
2.1)nTotalLower小於nValue,如果nLowestLarger存在,則將該值對應的pcoinLowestLarger交易加入目標交易集並返回成功,如果nLowestLarger不存在,則說明“余額”不足,返回失敗
2.2)nTotalLower大於nValue,則使用隨進逼近法(最多1000次)找出UTXO值的和nBest最接近nValue的集合vfBest,看nBest和nLowestLarger(如果存在)誰更接近nValue,則選擇誰為相應的目標UTXO集,並返回成功
以上總結為一句話就是,選擇賬戶中最接近發送金額nValue的UTXO優先花費,該部分內容可參考:http://www.360bchain.com/article/123.html
1 // Choose coins to use 2 set<CWalletTx*> setCoins; 3 if (!SelectCoins(nValue, setCoins)) 4 return false; 5 int64 nValueIn = 0; 6 foreach(CWalletTx* pcoin, setCoins) 7 nValueIn += pcoin->GetCredit();
(2)填充輸出和輸入
1 // Fill vout[0] to the payee 2 wtxNew.vout.push_back(CTxOut(nValueOut, scriptPubKey)); 3 4 // Fill vout[1] back to self with any change 5 if (nValueIn > nValue) 6 { 7 // Use the same key as one of the coins 8 vector<unsigned char> vchPubKey; 9 CTransaction& txFirst = *(*setCoins.begin()); 10 foreach(const CTxOut& txout, txFirst.vout) 11 if (txout.IsMine()) 12 if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey)) 13 break; 14 if (vchPubKey.empty()) 15 return false; 16 17 // Fill vout[1] to ourself 18 CScript scriptPubKey; 19 scriptPubKey << vchPubKey << OP_CHECKSIG; 20 wtxNew.vout.push_back(CTxOut(nValueIn - nValue, scriptPubKey)); 21 } 22 23 // Fill vin 24 foreach(CWalletTx* pcoin, setCoins) 25 for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) 26 if (pcoin->vout[nOut].IsMine()) 27 wtxNew.vin.push_back(CTxIn(pcoin->GetHash(), nOut));
注意5~21行,如果目標UTXO集值的和大於發送目標則將剩余的再還給本賬戶。
(3)簽名
1 // Sign 2 int nIn = 0; 3 foreach(CWalletTx* pcoin, setCoins) 4 for (int nOut = 0; nOut < pcoin->vout.size(); nOut++) 5 if (pcoin->vout[nOut].IsMine()) 6 SignSignature(*pcoin, wtxNew, nIn++);
在SignSignature函數中,調用SignatureHash來獲取交易哈希值,調用Solver對交易哈希值進行簽名。
(4)重新計算交易費
1 // Check that enough fee is included 2 if (nFee < wtxNew.GetMinFee(true)) 3 { 4 nFee = nFeeRequiredRet = wtxNew.GetMinFee(true); 5 continue; 6 }
如果默認的交易費小於當前計算的交易費用,則需要根據當前計算的交易費重新填充交易。
(5)后續處理
1 // Fill vtxPrev by copying from previous transactions vtxPrev 2 wtxNew.AddSupportingTransactions(txdb); 3 wtxNew.fTimeReceivedIsTxTime = true;
該函數作用還不太明白。
3.1.2 提交請求
本節開始部分源碼中的CommitTransactionSpent函數用於“提交請求”,函數中會修改本地的一些存儲信息(CommitTransactionSpent),在修改本地的存儲信息中有一點很關鍵,就是標記該交易是已被花費過的。注意這里的標記是和CWalletTx相綁定的,並且標記的是當前的這個新產生的交易的TxIn所關聯的交易。因為我們一般都認為在一個交易中一個參與者只應該提供一個地址,所以對於這個交易者來說,CWalletTx的fSpend標記可以代表這個交易對於該交易者的Out有沒有有被花費(也就是說fSpend是針對該交易者的),之后在檢索的時候可以節省很多。

1 // Call after CreateTransaction unless you want to abort 2 bool CommitTransactionSpent(const CWalletTx& wtxNew) 3 { 4 CRITICAL_BLOCK(cs_main) 5 CRITICAL_BLOCK(cs_mapWallet) 6 { 7 //// todo: make this transactional, never want to add a transaction 8 //// without marking spent transactions 9 10 // Add tx to wallet, because if it has change it's also ours, 11 // otherwise just for transaction history. 12 AddToWallet(wtxNew); 13 14 // Mark old coins as spent 15 set<CWalletTx*> setCoins; 16 foreach(const CTxIn& txin, wtxNew.vin) 17 setCoins.insert(&mapWallet[txin.prevout.hash]); 18 foreach(CWalletTx* pcoin, setCoins) 19 { 20 pcoin->fSpent = true; 21 pcoin->WriteToDisk(); 22 vWalletUpdated.push_back(make_pair(pcoin->GetHash(), false)); 23 } 24 } 25 MainFrameRepaint(); 26 return true; 27 }
3.1.3 接受交易
1 // Broadcast 2 if (!wtxNew.AcceptTransaction()) 3 { 4 // This must not fail. The transaction has already been signed and recorded. 5 throw runtime_error("SendMoney() : wtxNew.AcceptTransaction() failed\n"); 6 wxMessageBox("Error: Transaction not valid", "Sending..."); 7 return error("SendMoney() : Error: Transaction not valid"); 8 }
該函數最終會調用到CTransaction類的AcceptTransaction函數,在其中會進行一系列有效性檢查,通過檢查后會把交易放入到交易內存池。
(1)檢查交易是否有效
1 // Coinbase is only valid in a block, not as a loose transaction 2 if (IsCoinBase()) 3 return error("AcceptTransaction() : coinbase as individual tx"); 4 5 if (!CheckTransaction()) 6 return error("AcceptTransaction() : CheckTransaction failed");
(2)檢查交易是否已經存在
1 // Do we already have it? 2 uint256 hash = GetHash(); 3 CRITICAL_BLOCK(cs_mapTransactions) 4 if (mapTransactions.count(hash)) 5 return false; 6 if (fCheckInputs) 7 if (txdb.ContainsTx(hash)) 8 return false;
(3)檢查交易是否沖突
1 // Check for conflicts with in-memory transactions 2 CTransaction* ptxOld = NULL; 3 for (int i = 0; i < vin.size(); i++) 4 { 5 COutPoint outpoint = vin[i].prevout; 6 if (mapNextTx.count(outpoint)) 7 { 8 // Allow replacing with a newer version of the same transaction 9 if (i != 0) 10 return false; 11 ptxOld = mapNextTx[outpoint].ptx; 12 if (!IsNewerThan(*ptxOld)) 13 return false; 14 for (int i = 0; i < vin.size(); i++) 15 { 16 COutPoint outpoint = vin[i].prevout; 17 if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) 18 return false; 19 } 20 break; 21 } 22 }
(4)檢查交易中的前置交易
1 // Check against previous transactions 2 map<uint256, CTxIndex> mapUnused; 3 int64 nFees = 0; 4 if (fCheckInputs && !ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), 0, nFees, false, false)) 5 { 6 if (pfMissingInputs) 7 *pfMissingInputs = true; 8 return error("AcceptTransaction() : ConnectInputs failed %s", hash.ToString().substr(0,6).c_str()); 9 }
(5)將交易提交到內存池
1 // Store transaction in memory 2 CRITICAL_BLOCK(cs_mapTransactions) 3 { 4 if (ptxOld) 5 { 6 printf("mapTransaction.erase(%s) replacing with new version\n", ptxOld->GetHash().ToString().c_str()); 7 mapTransactions.erase(ptxOld->GetHash()); 8 } 9 AddToMemoryPool(); 10 }
(6)移除舊版本交易
1 ///// are we sure this is ok when loading transactions or restoring block txes 2 // If updated, erase old tx from wallet 3 if (ptxOld) 4 EraseFromWallet(ptxOld->GetHash());
3.1.4 廣播交易
1 wtxNew.RelayWalletTransaction();
最終會調用如下函數把交易廣播到所連接的每個節點
1 void CWalletTx::RelayWalletTransaction(CTxDB& txdb) 2 { 3 foreach(const CMerkleTx& tx, vtxPrev) 4 { 5 if (!tx.IsCoinBase()) 6 { 7 uint256 hash = tx.GetHash(); 8 if (!txdb.ContainsTx(hash)) 9 RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx); 10 } 11 } 12 if (!IsCoinBase()) 13 { 14 uint256 hash = GetHash(); 15 if (!txdb.ContainsTx(hash)) 16 { 17 printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); 18 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); 19 } 20 } 21 }
3.2 接收交易並處理
錢包作為節點會在函數循環ThreadMessageHandler2中會調用函數ProcessMessages不斷接收來自其他節點的各種消息,在該函數中又會調用ProcessMessage來處理接收的各種消息,以下是對交易消息處理的代碼段:
1 else if (strCommand == "tx") 2 { 3 vector<uint256> vWorkQueue; 4 CDataStream vMsg(vRecv); 5 CTransaction tx; 6 vRecv >> tx; 7 8 CInv inv(MSG_TX, tx.GetHash()); 9 pfrom->AddInventoryKnown(inv); 10 11 bool fMissingInputs = false; 12 if (tx.AcceptTransaction(true, &fMissingInputs)) 13 { 14 AddToWalletIfMine(tx, NULL); 15 RelayMessage(inv, vMsg); 16 mapAlreadyAskedFor.erase(inv); 17 vWorkQueue.push_back(inv.hash); 18 19 // Recursively process any orphan transactions that depended on this one 20 for (int i = 0; i < vWorkQueue.size(); i++) 21 { 22 uint256 hashPrev = vWorkQueue[i]; 23 for (multimap<uint256, CDataStream*>::iterator mi = mapOrphanTransactionsByPrev.lower_bound(hashPrev); 24 mi != mapOrphanTransactionsByPrev.upper_bound(hashPrev); 25 ++mi) 26 { 27 const CDataStream& vMsg = *((*mi).second); 28 CTransaction tx; 29 CDataStream(vMsg) >> tx; 30 CInv inv(MSG_TX, tx.GetHash()); 31 32 if (tx.AcceptTransaction(true)) 33 { 34 printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,6).c_str()); 35 AddToWalletIfMine(tx, NULL); 36 RelayMessage(inv, vMsg); 37 mapAlreadyAskedFor.erase(inv); 38 vWorkQueue.push_back(inv.hash); 39 } 40 } 41 } 42 43 foreach(uint256 hash, vWorkQueue) 44 EraseOrphanTx(hash); 45 } 46 else if (fMissingInputs) 47 { 48 printf("storing orphan tx %s\n", inv.hash.ToString().substr(0,6).c_str()); 49 AddOrphanTx(vMsg); 50 } 51 }
可以看到源碼12行調用了和交易生成時相同的函數AcceptTransaction,也會對接收到交易做一系列的合法性檢查,如果通過檢查,該交易會繼續被進行廣播。另外,如果交易中的前置交易缺失而導致無法通過檢查,則認為該交易是OrphanTransaction,會暫時把它放到mapOrphanTransactionsByPrev隊列中,每次有通過檢查的新的交易,都會檢查新的交易是否為mapOrphanTransactionsByPrev隊列中OrphanTransaction的前置交易,如果是則繼續檢查OrphanTransaction合法性,如果合法則繼續廣播該OrphanTransaction交易,並把該OrphanTransaction從隊列里移除。
參考
談談自己對比特幣腳本的理解:https://blog.csdn.net/pony_maggie/article/details/73656597
比特幣源碼解讀之交易發起:http://www.360bchain.com/article/89.html
比特幣交易原理分析:https://blog.csdn.net/wen294299195/article/details/80220651
比特幣0.1.0版本源碼:https://files.cnblogs.com/files/zhaoweiwei/bitcoin-0.1.0.rar