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