1 #include <qt/delegationitemmodel.h>
2 #include <qt/walletmodel.h>
3 #include <interfaces/wallet.h>
4 #include <validation.h>
5 #include <qt/bitcoinunits.h>
6 #include <interfaces/node.h>
7 #include <interfaces/handler.h>
8 #include <algorithm>
9 
10 #include <QDateTime>
11 #include <QFont>
12 #include <QDebug>
13 #include <QThread>
14 
15 class DelegationItemEntry
16 {
17 public:
DelegationItemEntry()18     DelegationItemEntry():
19         balance(0),
20         stake(0),
21         weight(0),
22         status(0)
23     {}
24 
DelegationItemEntry(const interfaces::DelegationInfo & delegationInfo)25     DelegationItemEntry(const interfaces::DelegationInfo &delegationInfo)
26     {
27         hash = delegationInfo.hash;
28         createTime.setTime_t(delegationInfo.time);
29         delegateAddress = QString::fromStdString(delegationInfo.delegate_address);
30         stakerName = QString::fromStdString(delegationInfo.staker_name);
31         stakerAddress = QString::fromStdString(delegationInfo.staker_address);
32         fee = delegationInfo.fee;
33         blockNumber = delegationInfo.block_number;
34         createTxHash = delegationInfo.create_tx_hash;
35         removeTxHash = delegationInfo.remove_tx_hash;
36         balance = 0;
37         stake = 0;
38         weight = 0;
39         status = 0;
40     }
41 
DelegationItemEntry(const DelegationItemEntry & obj)42     DelegationItemEntry( const DelegationItemEntry &obj)
43     {
44         hash = obj.hash;
45         createTime = obj.createTime;
46         delegateAddress = obj.delegateAddress;
47         stakerName = obj.stakerName;
48         stakerAddress = obj.stakerAddress;
49         fee = obj.fee;
50         blockNumber = obj.blockNumber;
51         createTxHash = obj.createTxHash;
52         removeTxHash = obj.removeTxHash;
53         balance = obj.balance;
54         stake = obj.stake;
55         weight = obj.weight;
56         status = obj.status;
57     }
58 
~DelegationItemEntry()59     ~DelegationItemEntry()
60     {}
61 
62     uint256 hash;
63     QDateTime createTime;
64     QString delegateAddress;
65     QString stakerName;
66     QString stakerAddress;
67     quint8 fee;
68     qint32 blockNumber;
69     uint256 createTxHash;
70     uint256 removeTxHash;
71     qint64 balance;
72     qint64 stake;
73     qint64 weight;
74     qint32 status;
75 };
76 
77 class DelegationWorker : public QObject
78 {
79     Q_OBJECT
80 public:
81     WalletModel *walletModel;
82     bool first;
DelegationWorker(WalletModel * _walletModel)83     DelegationWorker(WalletModel *_walletModel):
84         walletModel(_walletModel), first(true) {}
85 
86 private Q_SLOTS:
updateDelegationData(QString hash,QString delegateAddress,QString stakerAddress,quint8 fee,qint32 blockNumber)87     void updateDelegationData(QString hash, QString delegateAddress, QString stakerAddress, quint8 fee, qint32 blockNumber)
88     {
89         if(walletModel && walletModel->node().shutdownRequested())
90             return;
91 
92         // Find delegation details
93         std::string sHash = hash.toStdString();
94         std::string sDelegateAddress = delegateAddress.toStdString();
95         std::string sStakerAddress = stakerAddress.toStdString();
96         interfaces::DelegationDetails details = walletModel->wallet().getDelegationDetails(sDelegateAddress);
97 
98         // Get delegation info
99         interfaces::DelegationInfo info = details.toInfo();
100 
101         // No delegation contract, no update
102         if(!details.c_contract_return)
103             return;
104 
105         if(details.w_hash.ToString() == sHash)
106         {
107             if(details.c_entry_exist)
108             {
109                 // Update the entry when the delegation exist
110                 if(details.c_delegate_address == sDelegateAddress && details.c_staker_address == sStakerAddress)
111                 {
112                     if(details.c_fee != fee || details.c_block_number != blockNumber)
113                     {
114                         info.fee = details.c_fee;
115                         info.block_number = details.c_block_number;
116                         walletModel->wallet().addDelegationEntry(info);
117                     }
118                 }
119                 // Update the entry when staker changed
120                 else if(details.c_delegate_address == sDelegateAddress && details.c_staker_address != sStakerAddress)
121                 {
122                     walletModel->wallet().removeDelegationEntry(sHash);
123                     info = details.toInfo(false);
124                     walletModel->wallet().addDelegationEntry(info);
125                 }
126                 else
127                 {
128                     info.block_number = -1;
129                     walletModel->wallet().addDelegationEntry(info);
130                 }
131             }
132             else
133             {
134                 // Update the entry when the delegation not exist
135                 if(details.w_remove_exist)
136                 {
137                     // Remove the entry when marked for deletion
138                     if(details.w_remove_in_main_chain)
139                     {
140                         // The entry is deleted
141                         walletModel->wallet().removeDelegationEntry(sHash);
142                     }
143                 }
144                 else
145                 {
146                     // Update the entry when not marked for deletion
147                     if(info.block_number != -1)
148                     {
149                         info.block_number = -1;
150                         walletModel->wallet().addDelegationEntry(info);
151                     }
152                 }
153             }
154         }
155 
156         // Get address balance
157         CAmount balance = 0;
158         CAmount stake = 0;
159         CAmount weight = 0;
160 
161         // Get status for the create and remove transactions
162         qint32 status = DelegationItemModel::NoTx;
163         if(details.c_block_number > 0 && !details.w_remove_exist)
164         {
165             status = DelegationItemModel::CreateTxConfirmed;
166         }
167         else if(details.c_block_number <= 0 && details.w_create_exist)
168         {
169             if(details.w_create_in_mempool)
170             {
171                 status = DelegationItemModel::CreateTxNotConfirmed;
172             }
173             else
174             {
175                 status = DelegationItemModel::CreateTxError;
176             }
177         }
178         else if(details.w_remove_exist)
179         {
180             if(details.w_remove_in_mempool)
181             {
182                 status = DelegationItemModel::RemoveTxNotConfirmed;
183             }
184             else
185             {
186                 status = DelegationItemModel::RemoveTxError;
187             }
188         }
189 
190         std::string sAddress = delegateAddress.toStdString();
191         walletModel->wallet().getStakerAddressBalance(sAddress, balance, stake, weight);
192         Q_EMIT itemChanged(hash, balance, stake, weight, status);
193     }
194 
195 Q_SIGNALS:
196     // Signal that item in changed
197     void itemChanged(QString hash, qint64 balance, qint64 stake, qint64 weight, qint32 status);
198 };
199 
200 #include <qt/delegationitemmodel.moc>
201 
202 struct DelegationItemEntryLessThan
203 {
operator ()DelegationItemEntryLessThan204     bool operator()(const DelegationItemEntry &a, const DelegationItemEntry &b) const
205     {
206         return a.hash < b.hash;
207     }
operator ()DelegationItemEntryLessThan208     bool operator()(const DelegationItemEntry &a, const uint256 &b) const
209     {
210         return a.hash < b;
211     }
operator ()DelegationItemEntryLessThan212     bool operator()(const uint256 &a, const DelegationItemEntry &b) const
213     {
214         return a < b.hash;
215     }
216 };
217 
218 class DelegationItemPriv
219 {
220 public:
221     QList<DelegationItemEntry> cachedDelegationItem;
222     DelegationItemModel *parent;
223 
DelegationItemPriv(DelegationItemModel * _parent)224     DelegationItemPriv(DelegationItemModel *_parent):
225         parent(_parent) {}
226 
refreshDelegationItem(interfaces::Wallet & wallet)227     void refreshDelegationItem(interfaces::Wallet& wallet)
228     {
229         cachedDelegationItem.clear();
230         {
231             for(interfaces::DelegationInfo delegation : wallet.getDelegations())
232             {
233                 DelegationItemEntry delegationItem(delegation);
234                 if(parent)
235                 {
236                     parent->updateDelegationData(delegationItem);
237                 }
238                 cachedDelegationItem.append(delegationItem);
239             }
240         }
241         std::sort(cachedDelegationItem.begin(), cachedDelegationItem.end(), DelegationItemEntryLessThan());
242     }
243 
updateEntry(const DelegationItemEntry & item,int status)244     void updateEntry(const DelegationItemEntry &item, int status)
245     {
246         // Find delegation in model
247         QList<DelegationItemEntry>::iterator lower = qLowerBound(
248             cachedDelegationItem.begin(), cachedDelegationItem.end(), item, DelegationItemEntryLessThan());
249         QList<DelegationItemEntry>::iterator upper = qUpperBound(
250             cachedDelegationItem.begin(), cachedDelegationItem.end(), item, DelegationItemEntryLessThan());
251         int lowerIndex = (lower - cachedDelegationItem.begin());
252         int upperIndex = (upper - cachedDelegationItem.begin());
253         bool inModel = (lower != upper);
254         DelegationItemEntry _item = item;
255 
256         switch(status)
257         {
258         case CT_NEW:
259             if(inModel)
260             {
261                 qWarning() << "DelegationItemPriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
262                 break;
263             }
264             parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
265             cachedDelegationItem.insert(lowerIndex, item);
266             parent->endInsertRows();
267             break;
268         case CT_UPDATED:
269             if(!inModel)
270             {
271                 qWarning() << "DelegationItemPriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
272                 break;
273             }
274             _item.balance = cachedDelegationItem[lowerIndex].balance;
275             _item.stake = cachedDelegationItem[lowerIndex].stake;
276             _item.weight = cachedDelegationItem[lowerIndex].weight;
277             _item.status = cachedDelegationItem[lowerIndex].status;
278             cachedDelegationItem[lowerIndex] = _item;
279             parent->emitDataChanged(lowerIndex);
280             break;
281         case CT_DELETED:
282             if(!inModel)
283             {
284                 qWarning() << "DelegationItemPriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
285                 break;
286             }
287             parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
288             cachedDelegationItem.erase(lower, upper);
289             parent->endRemoveRows();
290             break;
291         }
292     }
293 
size()294     int size()
295     {
296         return cachedDelegationItem.size();
297     }
298 
index(int idx)299     DelegationItemEntry *index(int idx)
300     {
301         if(idx >= 0 && idx < cachedDelegationItem.size())
302         {
303             return &cachedDelegationItem[idx];
304         }
305         else
306         {
307             return 0;
308         }
309     }
310 };
311 
DelegationItemModel(WalletModel * parent)312 DelegationItemModel::DelegationItemModel(WalletModel *parent):
313     QAbstractItemModel(parent),
314     walletModel(parent),
315     priv(0),
316     worker(0)
317 {
318     columns << tr("Delegate") << tr("Staker Name") << tr("Staker Address") << tr("Fee") << tr("Height") << tr("Time");
319 
320     priv = new DelegationItemPriv(this);
321     priv->refreshDelegationItem(walletModel->wallet());
322 
323     worker = new DelegationWorker(walletModel);
324     worker->moveToThread(&(t));
325     connect(worker, &DelegationWorker::itemChanged, this, &DelegationItemModel::itemChanged);
326 
327     t.start();
328 
329     subscribeToCoreSignals();
330 }
331 
~DelegationItemModel()332 DelegationItemModel::~DelegationItemModel()
333 {
334     unsubscribeFromCoreSignals();
335 
336     t.quit();
337     t.wait();
338 
339     if(priv)
340     {
341         delete priv;
342         priv = 0;
343     }
344 }
345 
index(int row,int column,const QModelIndex & parent) const346 QModelIndex DelegationItemModel::index(int row, int column, const QModelIndex &parent) const
347 {
348     Q_UNUSED(parent);
349     DelegationItemEntry *data = priv->index(row);
350     if(data)
351     {
352         return createIndex(row, column, priv->index(row));
353     }
354     return QModelIndex();
355 }
356 
parent(const QModelIndex & child) const357 QModelIndex DelegationItemModel::parent(const QModelIndex &child) const
358 {
359     Q_UNUSED(child);
360     return QModelIndex();
361 }
362 
rowCount(const QModelIndex & parent) const363 int DelegationItemModel::rowCount(const QModelIndex &parent) const
364 {
365     Q_UNUSED(parent);
366     return priv->size();
367 }
368 
columnCount(const QModelIndex & parent) const369 int DelegationItemModel::columnCount(const QModelIndex &parent) const
370 {
371     Q_UNUSED(parent);
372     return columns.length();
373 }
374 
data(const QModelIndex & index,int role) const375 QVariant DelegationItemModel::data(const QModelIndex &index, int role) const
376 {
377     if(!index.isValid())
378         return QVariant();
379 
380     DelegationItemEntry *rec = static_cast<DelegationItemEntry*>(index.internalPointer());
381 
382     switch (role) {
383     case Qt::DisplayRole:
384         switch(index.column())
385         {
386         case Address:
387             return rec->delegateAddress;
388         case StakerName:
389             return rec->stakerName;
390         case StakerAddress:
391             return rec->stakerAddress;
392         case Fee:
393             return rec->fee;
394         case Height:
395             return rec->blockNumber;
396         case Time:
397             return rec->createTime;
398         default:
399             break;
400         }
401         break;
402     case DelegationItemModel::HashRole:
403         return QString::fromStdString(rec->hash.ToString());
404         break;
405     case DelegationItemModel::AddressRole:
406         return rec->delegateAddress;
407         break;
408     case DelegationItemModel::StakerNameRole:
409         return rec->stakerName;
410         break;
411     case DelegationItemModel::StakerAddressRole:
412         return rec->stakerAddress;
413         break;
414     case DelegationItemModel::FeeRole:
415         return rec->fee;
416         break;
417     case DelegationItemModel::BlockHeightRole:
418         return rec->blockNumber;
419         break;
420     case DelegationItemModel::CreateTxHashRole:
421         return QString::fromStdString(rec->createTxHash.ToString());
422         break;
423     case DelegationItemModel::RemoveTxHashRole:
424         return QString::fromStdString(rec->removeTxHash.ToString());
425         break;
426     case DelegationItemModel::FormattedFeeRole:
427         return formatFee(rec);
428         break;
429     case DelegationItemModel::BalanceRole:
430         return rec->balance;
431         break;
432     case DelegationItemModel::StakeRole:
433         return rec->stake;
434         break;
435     case DelegationItemModel::WeightRole:
436         return rec->weight;
437         break;
438     case DelegationItemModel::FormattedWeightRole:
439         return rec->weight / COIN;
440         break;
441     case DelegationItemModel::TxStatusRole:
442         return rec->status;
443         break;
444     default:
445         break;
446     }
447 
448     return QVariant();
449 }
450 
updateDelegationData(const QString & hash,int status,bool showDelegation)451 void DelegationItemModel::updateDelegationData(const QString &hash, int status, bool showDelegation)
452 {
453     // Find delegation in wallet
454     uint256 updated;
455     updated.SetHex(hash.toStdString());
456     interfaces::DelegationInfo delegation =walletModel->wallet().getDelegation(updated);
457     showDelegation &= delegation.hash == updated;
458 
459     DelegationItemEntry delegationEntry;
460     if(showDelegation)
461     {
462         delegationEntry = DelegationItemEntry(delegation);
463         updateDelegationData(delegationEntry);
464     }
465     else
466     {
467         delegationEntry.hash = updated;
468     }
469     priv->updateEntry(delegationEntry, status);
470 }
471 
checkDelegationChanged()472 void DelegationItemModel::checkDelegationChanged()
473 {
474     if(!priv)
475         return;
476 
477     // Update delegation from contract
478     for(int i = 0; i < priv->cachedDelegationItem.size(); i++)
479     {
480         DelegationItemEntry delegationEntry = priv->cachedDelegationItem[i];
481         updateDelegationData(delegationEntry);
482     }
483 }
484 
emitDataChanged(int idx)485 void DelegationItemModel::emitDataChanged(int idx)
486 {
487     Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
488 }
489 
490 struct DelegationNotification
491 {
492 public:
DelegationNotificationDelegationNotification493     DelegationNotification() {}
DelegationNotificationDelegationNotification494     DelegationNotification(uint256 _hash, ChangeType _status, bool _showDelegation):
495         hash(_hash), status(_status), showDelegation(_showDelegation) {}
496 
invokeDelegationNotification497     void invoke(QObject *tim)
498     {
499         QString strHash = QString::fromStdString(hash.GetHex());
500         qDebug() << "NotifyDelegationChanged: " + strHash + " status= " + QString::number(status);
501 
502         QMetaObject::invokeMethod(tim, "updateDelegationData", Qt::QueuedConnection,
503                                   Q_ARG(QString, strHash),
504                                   Q_ARG(int, status),
505                                   Q_ARG(bool, showDelegation));
506     }
507 private:
508     uint256 hash;
509     ChangeType status;
510     bool showDelegation;
511 };
512 
NotifyDelegationChanged(DelegationItemModel * tim,const uint256 & hash,ChangeType status)513 static void NotifyDelegationChanged(DelegationItemModel *tim, const uint256 &hash, ChangeType status)
514 {
515     DelegationNotification notification(hash, status, true);
516     notification.invoke(tim);
517 }
518 
subscribeToCoreSignals()519 void DelegationItemModel::subscribeToCoreSignals()
520 {
521     // Connect signals to wallet
522     m_handler_delegation_changed = walletModel->wallet().handleDelegationChanged(boost::bind(NotifyDelegationChanged, this, boost::placeholders::_1, boost::placeholders::_2));
523 }
524 
unsubscribeFromCoreSignals()525 void DelegationItemModel::unsubscribeFromCoreSignals()
526 {
527     // Disconnect signals from wallet
528     m_handler_delegation_changed->disconnect();
529 }
530 
updateDelegationData(const DelegationItemEntry & entry)531 void DelegationItemModel::updateDelegationData(const DelegationItemEntry &entry)
532 {
533     QString hash = QString::fromStdString(entry.hash.ToString());
534     QMetaObject::invokeMethod(worker, "updateDelegationData", Qt::QueuedConnection,
535                               Q_ARG(QString, hash),
536                               Q_ARG(QString, entry.delegateAddress),
537                               Q_ARG(QString, entry.stakerAddress),
538                               Q_ARG(quint8, entry.fee),
539                               Q_ARG(qint32, entry.blockNumber));
540 }
541 
formatFee(const DelegationItemEntry * rec) const542 QString DelegationItemModel::formatFee(const DelegationItemEntry *rec) const
543 {
544     return QString("%1%").arg(rec->fee);
545 }
546 
itemChanged(QString hash,qint64 balance,qint64 stake,qint64 weight,qint32 status)547 void DelegationItemModel::itemChanged(QString hash, qint64 balance, qint64 stake, qint64 weight, qint32 status)
548 {
549     if(!priv)
550         return;
551 
552     uint256 updated;
553     updated.SetHex(hash.toStdString());
554 
555     // Update delegation
556     for(int i = 0; i < priv->cachedDelegationItem.size(); i++)
557     {
558         DelegationItemEntry delegationEntry = priv->cachedDelegationItem[i];
559         if(delegationEntry.hash == updated)
560         {
561             delegationEntry.balance = balance;
562             delegationEntry.stake = stake;
563             delegationEntry.weight = weight;
564             delegationEntry.status = status;
565             priv->cachedDelegationItem[i] = delegationEntry;
566             priv->updateEntry(delegationEntry, CT_UPDATED);
567         }
568     }
569 }
570