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