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