1 // Copyright (c) 2011-2015 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include "transactiondesc.h"
6
7 #include "bitcoinunits.h"
8 #include "guiutil.h"
9 #include "paymentserver.h"
10 #include "transactionrecord.h"
11
12 #include "base58.h"
13 #include "consensus/consensus.h"
14 #include "main.h"
15 #include "script/script.h"
16 #include "timedata.h"
17 #include "util.h"
18 #include "wallet/db.h"
19 #include "wallet/wallet.h"
20
21 #include <stdint.h>
22 #include <string>
23
FormatTxStatus(const CWalletTx & wtx)24 QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
25 {
26 AssertLockHeld(cs_main);
27 if (!CheckFinalTx(wtx))
28 {
29 if (wtx.nLockTime < LOCKTIME_THRESHOLD)
30 return tr("Open for %n more block(s)", "", wtx.nLockTime - chainActive.Height());
31 else
32 return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.nLockTime));
33 }
34 else
35 {
36 int nDepth = wtx.GetDepthInMainChain();
37 if (nDepth < 0)
38 return tr("conflicted with a transaction with %1 confirmations").arg(-nDepth);
39 else if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
40 return tr("%1/offline").arg(nDepth);
41 else if (nDepth == 0)
42 return tr("0/unconfirmed, %1").arg((wtx.InMempool() ? tr("in memory pool") : tr("not in memory pool"))) + (wtx.isAbandoned() ? ", "+tr("abandoned") : "");
43 else if (nDepth < 6)
44 return tr("%1/unconfirmed").arg(nDepth);
45 else
46 return tr("%1 confirmations").arg(nDepth);
47 }
48 }
49
toHTML(CWallet * wallet,CWalletTx & wtx,TransactionRecord * rec,int unit)50 QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionRecord *rec, int unit)
51 {
52 QString strHTML;
53
54 LOCK2(cs_main, wallet->cs_wallet);
55 strHTML.reserve(4000);
56 strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
57
58 int64_t nTime = wtx.GetTxTime();
59 CAmount nCredit = wtx.GetCredit(ISMINE_ALL);
60 CAmount nDebit = wtx.GetDebit(ISMINE_ALL);
61 CAmount nNet = nCredit - nDebit;
62
63 strHTML += "<b>" + tr("Status") + ":</b> " + FormatTxStatus(wtx);
64 int nRequests = wtx.GetRequestCount();
65 if (nRequests != -1)
66 {
67 if (nRequests == 0)
68 strHTML += tr(", has not been successfully broadcast yet");
69 else if (nRequests > 0)
70 strHTML += tr(", broadcast through %n node(s)", "", nRequests);
71 }
72 strHTML += "<br>";
73
74 strHTML += "<b>" + tr("Date") + ":</b> " + (nTime ? GUIUtil::dateTimeStr(nTime) : "") + "<br>";
75
76 //
77 // From
78 //
79 if (wtx.IsCoinBase())
80 {
81 strHTML += "<b>" + tr("Source") + ":</b> " + tr("Generated") + "<br>";
82 }
83 else if (wtx.mapValue.count("from") && !wtx.mapValue["from"].empty())
84 {
85 // Online transaction
86 strHTML += "<b>" + tr("From") + ":</b> " + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "<br>";
87 }
88 else
89 {
90 // Offline transaction
91 if (nNet > 0)
92 {
93 // Credit
94 if (CBitcoinAddress(rec->address).IsValid())
95 {
96 CTxDestination address = CBitcoinAddress(rec->address).Get();
97 if (wallet->mapAddressBook.count(address))
98 {
99 strHTML += "<b>" + tr("From") + ":</b> " + tr("unknown") + "<br>";
100 strHTML += "<b>" + tr("To") + ":</b> ";
101 strHTML += GUIUtil::HtmlEscape(rec->address);
102 QString addressOwned = (::IsMine(*wallet, address) == ISMINE_SPENDABLE) ? tr("own address") : tr("watch-only");
103 if (!wallet->mapAddressBook[address].name.empty())
104 strHTML += " (" + addressOwned + ", " + tr("label") + ": " + GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + ")";
105 else
106 strHTML += " (" + addressOwned + ")";
107 strHTML += "<br>";
108 }
109 }
110 }
111 }
112
113 //
114 // To
115 //
116 if (wtx.mapValue.count("to") && !wtx.mapValue["to"].empty())
117 {
118 // Online transaction
119 std::string strAddress = wtx.mapValue["to"];
120 strHTML += "<b>" + tr("To") + ":</b> ";
121 CTxDestination dest = CBitcoinAddress(strAddress).Get();
122 if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty())
123 strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " ";
124 strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
125 }
126
127 //
128 // Amount
129 //
130 if (wtx.IsCoinBase() && nCredit == 0)
131 {
132 //
133 // Coinbase
134 //
135 CAmount nUnmatured = 0;
136 BOOST_FOREACH(const CTxOut& txout, wtx.vout)
137 nUnmatured += wallet->GetCredit(txout, ISMINE_ALL);
138 strHTML += "<b>" + tr("Credit") + ":</b> ";
139 if (wtx.IsInMainChain())
140 strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")";
141 else
142 strHTML += "(" + tr("not accepted") + ")";
143 strHTML += "<br>";
144 }
145 else if (nNet > 0)
146 {
147 //
148 // Credit
149 //
150 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet) + "<br>";
151 }
152 else
153 {
154 isminetype fAllFromMe = ISMINE_SPENDABLE;
155 BOOST_FOREACH(const CTxIn& txin, wtx.vin)
156 {
157 isminetype mine = wallet->IsMine(txin);
158 if(fAllFromMe > mine) fAllFromMe = mine;
159 }
160
161 isminetype fAllToMe = ISMINE_SPENDABLE;
162 BOOST_FOREACH(const CTxOut& txout, wtx.vout)
163 {
164 isminetype mine = wallet->IsMine(txout);
165 if(fAllToMe > mine) fAllToMe = mine;
166 }
167
168 if (fAllFromMe)
169 {
170 if(fAllFromMe & ISMINE_WATCH_ONLY)
171 strHTML += "<b>" + tr("From") + ":</b> " + tr("watch-only") + "<br>";
172
173 //
174 // Debit
175 //
176 BOOST_FOREACH(const CTxOut& txout, wtx.vout)
177 {
178 // Ignore change
179 isminetype toSelf = wallet->IsMine(txout);
180 if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE))
181 continue;
182
183 if (!wtx.mapValue.count("to") || wtx.mapValue["to"].empty())
184 {
185 // Offline transaction
186 CTxDestination address;
187 if (ExtractDestination(txout.scriptPubKey, address))
188 {
189 strHTML += "<b>" + tr("To") + ":</b> ";
190 if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
191 strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
192 strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString());
193 if(toSelf == ISMINE_SPENDABLE)
194 strHTML += " (own address)";
195 else if(toSelf & ISMINE_WATCH_ONLY)
196 strHTML += " (watch-only)";
197 strHTML += "<br>";
198 }
199 }
200
201 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
202 if(toSelf)
203 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
204 }
205
206 if (fAllToMe)
207 {
208 // Payment to self
209 CAmount nChange = wtx.GetChange();
210 CAmount nValue = nCredit - nChange;
211 strHTML += "<b>" + tr("Total debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nValue) + "<br>";
212 strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
213 }
214
215 CAmount nTxFee = nDebit - wtx.GetValueOut();
216 if (nTxFee > 0)
217 strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
218 }
219 else
220 {
221 //
222 // Mixed debit transaction
223 //
224 BOOST_FOREACH(const CTxIn& txin, wtx.vin)
225 if (wallet->IsMine(txin))
226 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
227 BOOST_FOREACH(const CTxOut& txout, wtx.vout)
228 if (wallet->IsMine(txout))
229 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
230 }
231 }
232
233 strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nNet, true) + "<br>";
234
235 //
236 // Message
237 //
238 if (wtx.mapValue.count("message") && !wtx.mapValue["message"].empty())
239 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "<br>";
240 if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty())
241 strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "<br>";
242
243 strHTML += "<b>" + tr("Transaction ID") + ":</b> " + rec->getTxID() + "<br>";
244 strHTML += "<b>" + tr("Output index") + ":</b> " + QString::number(rec->getOutputIndex()) + "<br>";
245
246 // Message from normal bitcoin:URI (bitcoin:123...?message=example)
247 Q_FOREACH (const PAIRTYPE(std::string, std::string)& r, wtx.vOrderForm)
248 if (r.first == "Message")
249 strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
250
251 //
252 // PaymentRequest info:
253 //
254 Q_FOREACH (const PAIRTYPE(std::string, std::string)& r, wtx.vOrderForm)
255 {
256 if (r.first == "PaymentRequest")
257 {
258 PaymentRequestPlus req;
259 req.parse(QByteArray::fromRawData(r.second.data(), r.second.size()));
260 QString merchant;
261 if (req.getMerchant(PaymentServer::getCertStore(), merchant))
262 strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
263 }
264 }
265
266 if (wtx.IsCoinBase())
267 {
268 quint32 numBlocksToMaturity = COINBASE_MATURITY + 1;
269 strHTML += "<br>" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "<br>";
270 }
271
272 //
273 // Debug view
274 //
275 if (fDebug)
276 {
277 strHTML += "<hr><br>" + tr("Debug information") + "<br><br>";
278 BOOST_FOREACH(const CTxIn& txin, wtx.vin)
279 if(wallet->IsMine(txin))
280 strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
281 BOOST_FOREACH(const CTxOut& txout, wtx.vout)
282 if(wallet->IsMine(txout))
283 strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
284
285 strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
286 strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
287
288 strHTML += "<br><b>" + tr("Inputs") + ":</b>";
289 strHTML += "<ul>";
290
291 BOOST_FOREACH(const CTxIn& txin, wtx.vin)
292 {
293 COutPoint prevout = txin.prevout;
294
295 CCoins prev;
296 if(pcoinsTip->GetCoins(prevout.hash, prev))
297 {
298 if (prevout.n < prev.vout.size())
299 {
300 strHTML += "<li>";
301 const CTxOut &vout = prev.vout[prevout.n];
302 CTxDestination address;
303 if (ExtractDestination(vout.scriptPubKey, address))
304 {
305 if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty())
306 strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " ";
307 strHTML += QString::fromStdString(CBitcoinAddress(address).ToString());
308 }
309 strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue);
310 strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + "</li>";
311 strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
312 }
313 }
314 }
315
316 strHTML += "</ul>";
317 }
318
319 strHTML += "</font></html>";
320 return strHTML;
321 }
322