23df6ccafd54086f816478feaf210b8834f78d20
[novacoin.git] / src / qt / mintingtablemodel.cpp
1 #include "mintingtablemodel.h"
2 #include "mintingfilterproxy.h"
3 #include "transactiontablemodel.h"
4 #include "guiutil.h"
5 #include "kernelrecord.h"
6 #include "guiconstants.h"
7 #include "transactiondesc.h"
8 #include "walletmodel.h"
9 #include "optionsmodel.h"
10 #include "addresstablemodel.h"
11 #include "bitcoinunits.h"
12 #include "util.h"
13 #include "kernel.h"
14
15 #include "wallet.h"
16
17 #include <QLocale>
18 #include <QList>
19 #include <QColor>
20 #include <QTimer>
21 #include <QIcon>
22 #include <QDateTime>
23 #include <QtAlgorithms>
24
25 extern double GetDifficulty(const CBlockIndex* blockindex);
26
27 static int column_alignments[] = {
28     Qt::AlignLeft|Qt::AlignVCenter,
29     Qt::AlignLeft|Qt::AlignVCenter,
30     Qt::AlignLeft|Qt::AlignVCenter,
31     Qt::AlignLeft|Qt::AlignVCenter,
32     Qt::AlignLeft|Qt::AlignVCenter,
33     Qt::AlignLeft|Qt::AlignVCenter,
34     Qt::AlignLeft|Qt::AlignVCenter
35 };
36
37 struct TxLessThan
38 {
39     bool operator()(const KernelRecord &a, const KernelRecord &b) const
40     {
41         return a.hash < b.hash;
42     }
43     bool operator()(const KernelRecord &a, const uint256 &b) const
44     {
45         return a.hash < b;
46     }
47     bool operator()(const uint256 &a, const KernelRecord &b) const
48     {
49         return a < b.hash;
50     }
51 };
52
53 class MintingTablePriv
54 {
55 public:
56     MintingTablePriv(CWallet *wallet, MintingTableModel *parent):
57         wallet(wallet),
58         parent(parent)
59     {
60     }
61     CWallet *wallet;
62     MintingTableModel *parent;
63
64     QList<KernelRecord> cachedWallet;
65
66     void refreshWallet()
67     {
68 #ifdef WALLET_UPDATE_DEBUG
69         qDebug() << "refreshWallet";
70 #endif
71         cachedWallet.clear();
72         {
73             LOCK(wallet->cs_wallet);
74             for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
75             {
76                 std::vector<KernelRecord> txList = KernelRecord::decomposeOutput(wallet, it->second);
77                 BOOST_FOREACH(KernelRecord& kr, txList) {
78                     if(!kr.spent) {
79                         cachedWallet.append(kr);
80                     }
81                 }
82
83             }
84         }
85     }
86
87     /* Update our model of the wallet incrementally, to synchronize our model of the wallet
88        with that of the core.
89
90        Call with list of hashes of transactions that were added, removed or changed.
91      */
92     void updateWallet(const QList<uint256> &updated)
93     {
94         // Walk through updated transactions, update model as needed.
95 #ifdef WALLET_UPDATE_DEBUG
96         qDebug() << "updateWallet";
97 #endif
98         // Sort update list, and iterate through it in reverse, so that model updates
99         //  can be emitted from end to beginning (so that earlier updates will not influence
100         // the indices of latter ones).
101         QList<uint256> updated_sorted = updated;
102         qSort(updated_sorted);
103
104         {
105             LOCK(wallet->cs_wallet);
106             for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
107             {
108                 const uint256 &hash = updated_sorted.at(update_idx);
109                 // Find transaction in wallet
110                 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
111                 bool inWallet = mi != wallet->mapWallet.end();
112                 // Find bounds of this transaction in model
113                 QList<KernelRecord>::iterator lower = qLowerBound(
114                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
115                 QList<KernelRecord>::iterator upper = qUpperBound(
116                     cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
117                 int lowerIndex = (lower - cachedWallet.begin());
118                 int upperIndex = (upper - cachedWallet.begin());
119
120                 // Determine if transaction is in model already
121                 bool inModel = false;
122                 if(lower != upper)
123                 {
124                     inModel = true;
125                 }
126
127 #ifdef WALLET_UPDATE_DEBUG
128                 qDebug() << "  " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
129                         << lowerIndex << "-" << upperIndex;
130 #endif
131
132                 if(inWallet && !inModel)
133                 {
134                     // Added -- insert at the right position
135                     std::vector<KernelRecord> toInsert =
136                             KernelRecord::decomposeOutput(wallet, mi->second);
137                     if(!toInsert.empty()) /* only if something to insert */
138                     {
139                         int insert_idx = lowerIndex;
140                         BOOST_FOREACH(const KernelRecord &rec, toInsert)
141                         {
142                             if(!rec.spent) 
143                             {
144                                 parent->beginInsertRows(QModelIndex(), insert_idx, insert_idx);
145                                 cachedWallet.insert(insert_idx, rec);
146                                 parent->endInsertRows();
147                                 insert_idx += 1;
148                             }
149                         }
150                     }
151                 }
152                 else if(!inWallet && inModel)
153                 {
154                     // Removed -- remove entire transaction from table
155                     parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
156                     cachedWallet.erase(lower, upper);
157                     parent->endRemoveRows();
158                 }
159                 else if(inWallet && inModel)
160                 {               
161                     // Updated -- remove spent coins from table
162                     std::vector<KernelRecord> toCheck = KernelRecord::decomposeOutput(wallet, mi->second);
163                     BOOST_FOREACH(const KernelRecord &rec, toCheck)
164                     {
165                         if(rec.spent)
166                         {
167                             for(int i = 0; i < cachedWallet.size(); i++)
168                             {
169                                 KernelRecord cachedRec = cachedWallet.at(i);
170                                 if((rec.hash == cachedRec.hash)
171                                     && (rec.nTime == cachedRec.nTime)
172                                     && (rec.nValue == cachedRec.nValue))
173                                 {
174                                     parent->beginRemoveRows(QModelIndex(), i, i);
175                                     cachedWallet.removeAt(i);
176                                     parent->endRemoveRows();
177                                     break;
178                                 }
179                             }
180                         }
181                     }
182                 }
183             }
184         }
185     }
186
187     int size()
188     {
189         return cachedWallet.size();
190     }
191
192     KernelRecord *index(int idx)
193     {
194         if(idx >= 0 && idx < cachedWallet.size())
195         {
196             KernelRecord *rec = &cachedWallet[idx];
197             return rec;
198         }
199         else
200         {
201             return 0;
202         }
203     }
204
205     QString describe(KernelRecord *rec)
206     {
207         {
208             LOCK(wallet->cs_wallet);
209             std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
210             if(mi != wallet->mapWallet.end())
211             {
212                 return TransactionDesc::toHTML(wallet, mi->second);
213             }
214         }
215         return QString("");
216     }
217
218 };
219
220
221 MintingTableModel::MintingTableModel(CWallet *wallet, WalletModel *parent):
222         QAbstractTableModel(parent),
223         wallet(wallet),
224         walletModel(parent),
225         mintingInterval(10),
226         priv(new MintingTablePriv(wallet, this))
227 {
228     columns << tr("Transaction") <<  tr("Address") << tr("Balance") << tr("Age") << tr("CoinDay") << tr("MintProbability") << tr("MintReward");
229     priv->refreshWallet();
230
231     QTimer *timer = new QTimer(this);
232     connect(timer, SIGNAL(timeout()), this, SLOT(update()));
233     timer->start(MODEL_UPDATE_DELAY);
234 }
235
236 MintingTableModel::~MintingTableModel()
237 {
238     delete priv;
239 }
240
241 void MintingTableModel::update()
242 {
243     QList<uint256> updated;
244
245     // Check if there are changes to wallet map
246     {
247         TRY_LOCK(wallet->cs_wallet, lockWallet);
248         if (lockWallet && !wallet->vMintingWalletUpdated.empty())
249         {
250             BOOST_FOREACH(uint256 hash, wallet->vMintingWalletUpdated)
251             {
252                 updated.append(hash);
253
254                 // Also check the inputs to remove spent outputs from the table if necessary
255                 CWalletTx wtx;
256                 if(wallet->GetTransaction(hash, wtx))
257                 {
258                     BOOST_FOREACH(const CTxIn& txin, wtx.vin)
259                     {
260                         updated.append(txin.prevout.hash);
261                     }
262                 }
263             }
264             wallet->vMintingWalletUpdated.clear();
265         }
266     }
267
268     if(!updated.empty())
269     {
270         priv->updateWallet(updated);
271         mintingProxyModel->invalidate(); // Force deletion of empty rows
272     }
273 }
274
275 void MintingTableModel::setMintingProxyModel(MintingFilterProxy *mintingProxy)
276 {
277     mintingProxyModel = mintingProxy;
278 }
279
280 int MintingTableModel::rowCount(const QModelIndex &parent) const
281 {
282     Q_UNUSED(parent);
283     return priv->size();
284 }
285
286 int MintingTableModel::columnCount(const QModelIndex &parent) const
287 {
288     Q_UNUSED(parent);
289     return columns.length();
290 }
291
292 QVariant MintingTableModel::data(const QModelIndex &index, int role) const
293 {
294     if(!index.isValid())
295         return QVariant();
296     KernelRecord *rec = static_cast<KernelRecord*>(index.internalPointer());
297
298     switch(role)
299     {
300       case Qt::DisplayRole:
301         switch(index.column())
302         {
303         case Address:
304             return formatTxAddress(rec, false);
305         case TxHash:
306             return formatTxHash(rec);
307         case Age:
308             return formatTxAge(rec);
309         case Balance:
310             return formatTxBalance(rec);
311         case CoinDay:
312             return formatTxCoinDay(rec);
313         case MintProbability:
314             return formatDayToMint(rec);
315         case MintReward:
316             return formatTxPoSReward(rec);
317         }
318         break;
319       case Qt::TextAlignmentRole:
320         return column_alignments[index.column()];
321         break;
322       case Qt::ToolTipRole:
323         switch(index.column())
324         {
325         case MintProbability:
326             int interval = this->mintingInterval;
327             QString unit = tr("minutes");
328
329             int hours = interval / 60;
330             int days = hours  / 24;
331
332             if(hours > 1) {
333                 interval = hours;
334                 unit = tr("hours");
335             }
336             if(days > 1) {
337                 interval = days;
338                 unit = tr("days");
339             }
340
341             QString str = QString(tr("You have %1 chance to find a POS block if you mint %2 %3 at current difficulty."));
342             return str.arg(index.data().toString().toUtf8().constData()).arg(interval).arg(unit);
343         }
344         break;
345       case Qt::EditRole:
346         switch(index.column())
347         {
348         case Address:
349             return formatTxAddress(rec, false);
350         case TxHash:
351             return formatTxHash(rec);
352         case Age:
353             return static_cast<qlonglong>(rec->getAge());
354         case CoinDay:
355             return static_cast<qlonglong>(rec->getCoinDay());
356         case Balance:
357             return static_cast<qlonglong>(rec->nValue);
358         case MintProbability:
359             return getDayToMint(rec);
360         case MintReward:
361             return formatTxPoSReward(rec);
362         }
363         break;
364       case Qt::BackgroundColorRole:
365         int minAge = nStakeMinAge / 60 / 60 / 24;
366         int maxAge = nStakeMaxAge / 60 / 60 / 24;
367         if(rec->getAge() < minAge)
368         {
369             return COLOR_MINT_YOUNG;
370         }
371         else if (rec->getAge() >= minAge && rec->getAge() < (maxAge + minAge))
372         {
373             return COLOR_MINT_MATURE;
374         }
375         else
376         {
377             return COLOR_MINT_OLD;
378         }
379         break;
380
381     }
382     return QVariant();
383 }
384
385 void MintingTableModel::setMintingInterval(int interval)
386 {
387     mintingInterval = interval;
388 }
389
390 QString MintingTableModel::lookupAddress(const std::string &address, bool tooltip) const
391 {
392     QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
393     QString description;
394     if(!label.isEmpty())
395     {
396         description += label + QString(" ");
397     }
398     if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
399     {
400         description += QString("(") + QString::fromStdString(address) + QString(")");
401     }
402     return description;
403 }
404
405 QString MintingTableModel::formatTxPoSReward(KernelRecord *wtx) const
406 {
407     QString posReward;
408     int nBits = GetLastBlockIndex(pindexBest, true)->nBits;
409     posReward += QString(QObject::tr("from  %1 to %2")).arg(BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), wtx->getPoSReward(nBits, 0)), 
410         BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), wtx->getPoSReward(nBits, mintingInterval))); 
411     return posReward;
412 }
413
414 double MintingTableModel::getDayToMint(KernelRecord *wtx) const
415 {
416     const CBlockIndex *p = GetLastBlockIndex(pindexBest, true);
417     double difficulty = GetDifficulty(p);
418
419     double prob = wtx->getProbToMintWithinNMinutes(difficulty, mintingInterval);
420     prob = prob * 100;
421     return prob;
422 }
423
424 QString MintingTableModel::formatDayToMint(KernelRecord *wtx) const
425 {
426     double prob = getDayToMint(wtx);
427     return QString::number(prob, 'f', 3) + "%";
428 }
429
430 QString MintingTableModel::formatTxAddress(const KernelRecord *wtx, bool tooltip) const
431 {
432     return QString::fromStdString(wtx->address);
433 }
434
435 QString MintingTableModel::formatTxHash(const KernelRecord *wtx) const
436 {
437     return QString::fromStdString(wtx->hash.ToString());
438 }
439
440 QString MintingTableModel::formatTxCoinDay(const KernelRecord *wtx) const
441 {
442     return QString::number(wtx->getCoinDay());
443 }
444
445 QString MintingTableModel::formatTxAge(const KernelRecord *wtx) const
446 {
447     int64_t nAge = wtx->getAge();
448     return QString::number(nAge);
449 }
450
451 QString MintingTableModel::formatTxBalance(const KernelRecord *wtx) const
452 {
453     return BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), wtx->nValue);
454 }
455
456 QVariant MintingTableModel::headerData(int section, Qt::Orientation orientation, int role) const
457 {
458     if(orientation == Qt::Horizontal)
459     {
460         if(role == Qt::DisplayRole)
461         {
462             return columns[section];
463         }
464         else if (role == Qt::TextAlignmentRole)
465         {
466             return column_alignments[section];
467         } else if (role == Qt::ToolTipRole)
468         {
469             switch(section)
470             {
471             case Address:
472                 return tr("Destination address of the output.");
473             case TxHash:
474                 return tr("Original transaction id.");
475             case Age:
476                 return tr("Age of the transaction in days.");
477             case Balance:
478                 return tr("Balance of the output.");
479             case CoinDay:
480                 return tr("Coin age in the output.");
481             case MintProbability:
482                 return tr("Chance to mint a block within given time interval.");
483             case MintReward:
484                 return tr("The size of the potential rewards if the block is found at the beginning and the end given time interval.");
485             }
486         }
487     }
488     return QVariant();
489 }
490
491 QModelIndex MintingTableModel::index(int row, int column, const QModelIndex &parent) const
492 {
493     Q_UNUSED(parent);
494     KernelRecord *data = priv->index(row);
495     if(data)
496     {
497         return createIndex(row, column, priv->index(row));
498     }
499     else
500     {
501         return QModelIndex();
502     }
503 }