1 #include <qt/tokenitemmodel.h>
2 #include <qt/token.h>
3 #include <qt/walletmodel.h>
4 #include <interfaces/wallet.h>
5 #include <validation.h>
6 #include <qt/bitcoinunits.h>
7 #include <interfaces/node.h>
8 #include <interfaces/handler.h>
9 #include <algorithm>
10 #include <consensus/consensus.h>
11 #include <chainparams.h>
12 
13 #include <QDateTime>
14 #include <QFont>
15 #include <QDebug>
16 #include <QThread>
17 
18 class TokenItemEntry
19 {
20 public:
TokenItemEntry()21     TokenItemEntry()
22     {}
23 
TokenItemEntry(const interfaces::TokenInfo & tokenInfo)24     TokenItemEntry(const interfaces::TokenInfo &tokenInfo)
25     {
26         hash = tokenInfo.hash;
27         createTime.setTime_t(tokenInfo.time);
28         contractAddress = QString::fromStdString(tokenInfo.contract_address);
29         tokenName = QString::fromStdString(tokenInfo.token_name);
30         tokenSymbol = QString::fromStdString(tokenInfo.token_symbol);
31         decimals = tokenInfo.decimals;
32         senderAddress = QString::fromStdString(tokenInfo.sender_address);
33     }
34 
TokenItemEntry(const TokenItemEntry & obj)35     TokenItemEntry( const TokenItemEntry &obj)
36     {
37         hash = obj.hash;
38         createTime = obj.createTime;
39         contractAddress = obj.contractAddress;
40         tokenName = obj.tokenName;
41         tokenSymbol = obj.tokenSymbol;
42         decimals = obj.decimals;
43         senderAddress = obj.senderAddress;
44         balance = obj.balance;
45     }
46 
~TokenItemEntry()47     ~TokenItemEntry()
48     {}
49 
50     uint256 hash;
51     QDateTime createTime;
52     QString contractAddress;
53     QString tokenName;
54     QString tokenSymbol;
55     quint8 decimals;
56     QString senderAddress;
57     int256_t balance;
58 };
59 
60 class TokenTxWorker : public QObject
61 {
62     Q_OBJECT
63 public:
64     WalletModel *walletModel;
65     bool first;
66     Token tokenAbi;
TokenTxWorker(WalletModel * _walletModel)67     TokenTxWorker(WalletModel *_walletModel):
68         walletModel(_walletModel), first(true) {}
69 
70 private Q_SLOTS:
updateTokenTx(const QString & hash)71     void updateTokenTx(const QString &hash)
72     {
73         if(walletModel && walletModel->node().shutdownRequested())
74             return;
75 
76         // Initialize variables
77         uint256 tokenHash = uint256S(hash.toStdString());
78         int64_t fromBlock = 0;
79         int64_t toBlock = -1;
80         interfaces::TokenInfo tokenInfo;
81         uint256 blockHash;
82         bool found = false;
83 
84         int64_t backInPast = first ? Params().GetConsensus().MaxCheckpointSpan() : 10;
85         first = false;
86 
87         // Get current height and block hash
88         toBlock = walletModel->node().getNumBlocks();
89         blockHash = walletModel->node().getBlockHash(toBlock);
90 
91         if(toBlock > -1)
92         {
93             // Find the token tx in the wallet
94             tokenInfo = walletModel->wallet().getToken(tokenHash);
95             found = tokenInfo.hash == tokenHash;
96             if(found)
97             {
98                 // Get the start location for search the event log
99                 if(tokenInfo.block_number < toBlock)
100                 {
101                     if(walletModel->node().getBlockHash(tokenInfo.block_number) == tokenInfo.block_hash)
102                     {
103                         fromBlock = tokenInfo.block_number;
104                     }
105                     else
106                     {
107                         fromBlock = tokenInfo.block_number - backInPast;
108                     }
109                 }
110                 else
111                 {
112                     fromBlock = toBlock - backInPast;
113                 }
114                 if(fromBlock < 0)
115                     fromBlock = 0;
116 
117                 tokenInfo.block_hash = blockHash;
118                 tokenInfo.block_number = toBlock;
119             }
120         }
121 
122         if(found)
123         {
124             // List the events and update the token tx
125             std::vector<TokenEvent> tokenEvents;
126             tokenAbi.setAddress(tokenInfo.contract_address);
127             tokenAbi.setSender(tokenInfo.sender_address);
128             tokenAbi.transferEvents(tokenEvents, fromBlock, toBlock);
129             tokenAbi.burnEvents(tokenEvents, fromBlock, toBlock);
130             for(size_t i = 0; i < tokenEvents.size(); i++)
131             {
132                 TokenEvent event = tokenEvents[i];
133                 interfaces::TokenTx tokenTx;
134                 tokenTx.contract_address = event.address;
135                 tokenTx.sender_address = event.sender;
136                 tokenTx.receiver_address = event.receiver;
137                 tokenTx.value = event.value;
138                 tokenTx.tx_hash = event.transactionHash;
139                 tokenTx.block_hash = event.blockHash;
140                 tokenTx.block_number = event.blockNumber;
141                 walletModel->wallet().addTokenTxEntry(tokenTx, false);
142             }
143 
144             walletModel->wallet().addTokenEntry(tokenInfo);
145         }
146     }
147 
cleanTokenTxEntries()148     void cleanTokenTxEntries()
149     {
150         if(walletModel && walletModel->node().shutdownRequested())
151             return;
152 
153         if(walletModel) walletModel->wallet().cleanTokenTxEntries();
154     }
155 
updateBalance(QString hash,QString contractAddress,QString senderAddress)156     void updateBalance(QString hash, QString contractAddress, QString senderAddress)
157     {
158         if(walletModel && walletModel->node().shutdownRequested())
159             return;
160 
161         tokenAbi.setAddress(contractAddress.toStdString());
162         tokenAbi.setSender(senderAddress.toStdString());
163         std::string strBalance;
164         if(tokenAbi.balanceOf(strBalance))
165         {
166             QString balance = QString::fromStdString(strBalance);
167             Q_EMIT balanceChanged(hash, balance);
168         }
169     }
170 
171 Q_SIGNALS:
172     // Signal that balance in token changed
173     void balanceChanged(QString hash, QString balance);
174 };
175 
176 #include <qt/tokenitemmodel.moc>
177 
178 struct TokenItemEntryLessThan
179 {
operator ()TokenItemEntryLessThan180     bool operator()(const TokenItemEntry &a, const TokenItemEntry &b) const
181     {
182         return a.hash < b.hash;
183     }
operator ()TokenItemEntryLessThan184     bool operator()(const TokenItemEntry &a, const uint256 &b) const
185     {
186         return a.hash < b;
187     }
operator ()TokenItemEntryLessThan188     bool operator()(const uint256 &a, const TokenItemEntry &b) const
189     {
190         return a < b.hash;
191     }
192 };
193 
194 class TokenItemPriv
195 {
196 public:
197     QList<TokenItemEntry> cachedTokenItem;
198     TokenItemModel *parent;
199 
TokenItemPriv(TokenItemModel * _parent)200     TokenItemPriv(TokenItemModel *_parent):
201         parent(_parent) {}
202 
refreshTokenItem(interfaces::Wallet & wallet)203     void refreshTokenItem(interfaces::Wallet& wallet)
204     {
205         cachedTokenItem.clear();
206         {
207             for(interfaces::TokenInfo token : wallet.getTokens())
208             {
209                 TokenItemEntry tokenItem(token);
210                 if(parent)
211                 {
212                     parent->updateBalance(tokenItem);
213                 }
214                 cachedTokenItem.append(tokenItem);
215             }
216         }
217         std::sort(cachedTokenItem.begin(), cachedTokenItem.end(), TokenItemEntryLessThan());
218     }
219 
updateEntry(const TokenItemEntry & _item,int status)220     void updateEntry(const TokenItemEntry &_item, int status)
221     {
222         // Find address / label in model
223         TokenItemEntry item;
224         QList<TokenItemEntry>::iterator lower = qLowerBound(
225             cachedTokenItem.begin(), cachedTokenItem.end(), _item, TokenItemEntryLessThan());
226         QList<TokenItemEntry>::iterator upper = qUpperBound(
227             cachedTokenItem.begin(), cachedTokenItem.end(), _item, TokenItemEntryLessThan());
228         int lowerIndex = (lower - cachedTokenItem.begin());
229         int upperIndex = (upper - cachedTokenItem.begin());
230         bool inModel = (lower != upper);
231         item = _item;
232         if(inModel)
233         {
234             item.balance = cachedTokenItem[lowerIndex].balance;
235         }
236 
237         switch(status)
238         {
239         case CT_NEW:
240             if(inModel)
241             {
242                 qWarning() << "TokenItemPriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
243                 break;
244             }
245             parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
246             cachedTokenItem.insert(lowerIndex, item);
247             parent->endInsertRows();
248             break;
249         case CT_UPDATED:
250             if(!inModel)
251             {
252                 qWarning() << "TokenItemPriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
253                 break;
254             }
255             cachedTokenItem[lowerIndex] = item;
256             parent->emitDataChanged(lowerIndex);
257             break;
258         case CT_DELETED:
259             if(!inModel)
260             {
261                 qWarning() << "TokenItemPriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
262                 break;
263             }
264             parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
265             cachedTokenItem.erase(lower, upper);
266             parent->endRemoveRows();
267             break;
268         }
269     }
270 
updateBalance(QString hash,QString balance)271     int updateBalance(QString hash, QString balance)
272     {
273         uint256 updated;
274         updated.SetHex(hash.toStdString());
275         int256_t val(balance.toStdString());
276 
277         for(int i = 0; i < cachedTokenItem.size(); i++)
278         {
279             TokenItemEntry item = cachedTokenItem[i];
280             if(item.hash == updated && item.balance != val)
281             {
282                 item.balance = val;
283                 cachedTokenItem[i] = item;
284                 return i;
285             }
286         }
287 
288         return -1;
289     }
290 
size()291     int size()
292     {
293         return cachedTokenItem.size();
294     }
295 
index(int idx)296     TokenItemEntry *index(int idx)
297     {
298         if(idx >= 0 && idx < cachedTokenItem.size())
299         {
300             return &cachedTokenItem[idx];
301         }
302         else
303         {
304             return 0;
305         }
306     }
307 };
308 
TokenItemModel(WalletModel * parent)309 TokenItemModel::TokenItemModel(WalletModel *parent):
310     QAbstractItemModel(parent),
311     walletModel(parent),
312     priv(0),
313     worker(0),
314     tokenTxCleaned(false)
315 {
316     columns << tr("Token Name") << tr("Token Symbol") << tr("Balance");
317 
318     priv = new TokenItemPriv(this);
319     priv->refreshTokenItem(walletModel->wallet());
320 
321     worker = new TokenTxWorker(walletModel);
322     worker->tokenAbi.setModel(walletModel);
323     worker->moveToThread(&(t));
324     connect(worker, &TokenTxWorker::balanceChanged, this, &TokenItemModel::balanceChanged);
325 
326     t.start();
327 
328     subscribeToCoreSignals();
329 }
330 
~TokenItemModel()331 TokenItemModel::~TokenItemModel()
332 {
333     unsubscribeFromCoreSignals();
334 
335     t.quit();
336     t.wait();
337 
338     if(priv)
339     {
340         delete priv;
341         priv = 0;
342     }
343 }
344 
index(int row,int column,const QModelIndex & parent) const345 QModelIndex TokenItemModel::index(int row, int column, const QModelIndex &parent) const
346 {
347     Q_UNUSED(parent);
348     TokenItemEntry *data = priv->index(row);
349     if(data)
350     {
351         return createIndex(row, column, priv->index(row));
352     }
353     return QModelIndex();
354 }
355 
parent(const QModelIndex & child) const356 QModelIndex TokenItemModel::parent(const QModelIndex &child) const
357 {
358     Q_UNUSED(child);
359     return QModelIndex();
360 }
361 
rowCount(const QModelIndex & parent) const362 int TokenItemModel::rowCount(const QModelIndex &parent) const
363 {
364     Q_UNUSED(parent);
365     return priv->size();
366 }
367 
columnCount(const QModelIndex & parent) const368 int TokenItemModel::columnCount(const QModelIndex &parent) const
369 {
370     Q_UNUSED(parent);
371     return columns.length();
372 }
373 
data(const QModelIndex & index,int role) const374 QVariant TokenItemModel::data(const QModelIndex &index, int role) const
375 {
376     if(!index.isValid())
377         return QVariant();
378 
379     TokenItemEntry *rec = static_cast<TokenItemEntry*>(index.internalPointer());
380 
381     switch (role) {
382     case Qt::DisplayRole:
383         switch(index.column())
384         {
385         case Name:
386             return rec->tokenName;
387         case Symbol:
388             return rec->tokenSymbol;
389         case Balance:
390             return BitcoinUnits::formatToken(rec->decimals, rec->balance, false, BitcoinUnits::separatorAlways);
391         default:
392             break;
393         }
394         break;
395     case TokenItemModel::HashRole:
396         return QString::fromStdString(rec->hash.ToString());
397         break;
398     case TokenItemModel::AddressRole:
399         return rec->contractAddress;
400         break;
401     case TokenItemModel::NameRole:
402         return rec->tokenName;
403         break;
404     case TokenItemModel::SymbolRole:
405         return rec->tokenSymbol;
406         break;
407     case TokenItemModel::DecimalsRole:
408         return rec->decimals;
409         break;
410     case TokenItemModel::SenderRole:
411         return rec->senderAddress;
412         break;
413     case TokenItemModel::BalanceRole:
414         return BitcoinUnits::formatToken(rec->decimals, rec->balance, false, BitcoinUnits::separatorAlways);
415         break;
416     case TokenItemModel::RawBalanceRole:
417         return QString::fromStdString(rec->balance.str());
418         break;
419     default:
420         break;
421     }
422 
423     return QVariant();
424 }
425 
updateToken(const QString & hash,int status,bool showToken)426 void TokenItemModel::updateToken(const QString &hash, int status, bool showToken)
427 {
428     // Find token in wallet
429     uint256 updated;
430     updated.SetHex(hash.toStdString());
431     interfaces::TokenInfo token =walletModel->wallet().getToken(updated);
432     showToken &= token.hash == updated;
433 
434     TokenItemEntry tokenEntry;
435     if(showToken)
436     {
437         tokenEntry = TokenItemEntry(token);
438         updateBalance(tokenEntry);
439     }
440     else
441     {
442         tokenEntry.hash = updated;
443     }
444     priv->updateEntry(tokenEntry, status);
445 }
446 
checkTokenBalanceChanged()447 void TokenItemModel::checkTokenBalanceChanged()
448 {
449     if(!priv)
450         return;
451 
452     // Update token balance
453     for(int i = 0; i < priv->cachedTokenItem.size(); i++)
454     {
455         TokenItemEntry tokenEntry = priv->cachedTokenItem[i];
456         updateBalance(tokenEntry);
457     }
458 
459     // Update token transactions
460     if(fLogEvents)
461     {
462         // Search for token transactions
463         for(int i = 0; i < priv->cachedTokenItem.size(); i++)
464         {
465             TokenItemEntry tokenEntry = priv->cachedTokenItem[i];
466             QString hash = QString::fromStdString(tokenEntry.hash.ToString());
467             QMetaObject::invokeMethod(worker, "updateTokenTx", Qt::QueuedConnection,
468                                       Q_ARG(QString, hash));
469         }
470 
471         // Clean token transactions
472         if(!tokenTxCleaned)
473         {
474             tokenTxCleaned = true;
475             QMetaObject::invokeMethod(worker, "cleanTokenTxEntries", Qt::QueuedConnection);
476         }
477     }
478 }
479 
emitDataChanged(int idx)480 void TokenItemModel::emitDataChanged(int idx)
481 {
482     Q_EMIT dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
483 }
484 
485 struct TokenNotification
486 {
487 public:
TokenNotificationTokenNotification488     TokenNotification() {}
TokenNotificationTokenNotification489     TokenNotification(uint256 _hash, ChangeType _status, bool _showToken):
490         hash(_hash), status(_status), showToken(_showToken) {}
491 
invokeTokenNotification492     void invoke(QObject *tim)
493     {
494         QString strHash = QString::fromStdString(hash.GetHex());
495         qDebug() << "NotifyTokenChanged: " + strHash + " status= " + QString::number(status);
496 
497         QMetaObject::invokeMethod(tim, "updateToken", Qt::QueuedConnection,
498                                   Q_ARG(QString, strHash),
499                                   Q_ARG(int, status),
500                                   Q_ARG(bool, showToken));
501     }
502 private:
503     uint256 hash;
504     ChangeType status;
505     bool showToken;
506 };
507 
NotifyTokenChanged(TokenItemModel * tim,const uint256 & hash,ChangeType status)508 static void NotifyTokenChanged(TokenItemModel *tim, const uint256 &hash, ChangeType status)
509 {
510     TokenNotification notification(hash, status, true);
511     notification.invoke(tim);
512 }
513 
subscribeToCoreSignals()514 void TokenItemModel::subscribeToCoreSignals()
515 {
516     // Connect signals to wallet
517     m_handler_token_changed = walletModel->wallet().handleTokenChanged(boost::bind(NotifyTokenChanged, this, boost::placeholders::_1, boost::placeholders::_2));
518 }
519 
unsubscribeFromCoreSignals()520 void TokenItemModel::unsubscribeFromCoreSignals()
521 {
522     // Disconnect signals from wallet
523     m_handler_token_changed->disconnect();
524 }
525 
balanceChanged(QString hash,QString balance)526 void TokenItemModel::balanceChanged(QString hash, QString balance)
527 {
528     int index = priv->updateBalance(hash, balance);
529     if(index > -1)
530     {
531         emitDataChanged(index);
532     }
533 }
534 
updateBalance(const TokenItemEntry & entry)535 void TokenItemModel::updateBalance(const TokenItemEntry &entry)
536 {
537     QString hash = QString::fromStdString(entry.hash.ToString());
538     QMetaObject::invokeMethod(worker, "updateBalance", Qt::QueuedConnection,
539                               Q_ARG(QString, hash), Q_ARG(QString, entry.contractAddress), Q_ARG(QString, entry.senderAddress));
540 }
541