比特幣交易源碼分析

比特幣使用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 }
區塊CBlock

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 };
交易CTransaction

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 };
交易輸入CTxIn

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 };
交易輸出CTxOut

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章