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