walletmodel.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. // ECOin - Copyright (c) - 2014/2024 - GPLv3 - epsylon@riseup.net (https://03c8.net)
  2. #include "walletmodel.h"
  3. #include "guiconstants.h"
  4. #include "optionsmodel.h"
  5. #include "addresstablemodel.h"
  6. #include "transactiontablemodel.h"
  7. #include "ui_interface.h"
  8. #include "wallet.h"
  9. #include "walletdb.h" // for BackupWallet
  10. #include "base58.h"
  11. #include <QSet>
  12. #include <QTimer>
  13. WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) :
  14. QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0),
  15. transactionTableModel(0),
  16. cachedBalance(0), cachedStake(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0),
  17. cachedNumTransactions(0),
  18. cachedEncryptionStatus(Unencrypted),
  19. cachedNumBlocks(0)
  20. {
  21. addressTableModel = new AddressTableModel(wallet, this);
  22. transactionTableModel = new TransactionTableModel(wallet, this);
  23. // This timer will be fired repeatedly to update the balance
  24. pollTimer = new QTimer(this);
  25. connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged()));
  26. pollTimer->start(MODEL_UPDATE_DELAY);
  27. subscribeToCoreSignals();
  28. }
  29. WalletModel::~WalletModel()
  30. {
  31. unsubscribeFromCoreSignals();
  32. }
  33. qint64 WalletModel::getBalance() const
  34. {
  35. return wallet->GetBalance();
  36. }
  37. qint64 WalletModel::getUnconfirmedBalance() const
  38. {
  39. return wallet->GetUnconfirmedBalance();
  40. }
  41. qint64 WalletModel::getStake() const
  42. {
  43. return wallet->GetStake();
  44. }
  45. qint64 WalletModel::getImmatureBalance() const
  46. {
  47. return wallet->GetImmatureBalance();
  48. }
  49. int WalletModel::getNumTransactions() const
  50. {
  51. int numTransactions = 0;
  52. {
  53. LOCK(wallet->cs_wallet);
  54. numTransactions = wallet->mapWallet.size();
  55. }
  56. return numTransactions;
  57. }
  58. void WalletModel::updateStatus()
  59. {
  60. EncryptionStatus newEncryptionStatus = getEncryptionStatus();
  61. if(cachedEncryptionStatus != newEncryptionStatus)
  62. emit encryptionStatusChanged(newEncryptionStatus);
  63. }
  64. void WalletModel::pollBalanceChanged()
  65. {
  66. if(nBestHeight != cachedNumBlocks)
  67. {
  68. // Balance and number of transactions might have changed
  69. cachedNumBlocks = nBestHeight;
  70. checkBalanceChanged();
  71. }
  72. }
  73. void WalletModel::checkBalanceChanged()
  74. {
  75. qint64 newBalance = getBalance();
  76. qint64 newStake = getStake();
  77. qint64 newUnconfirmedBalance = getUnconfirmedBalance();
  78. qint64 newImmatureBalance = getImmatureBalance();
  79. if(cachedBalance != newBalance || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance)
  80. {
  81. cachedBalance = newBalance;
  82. cachedStake = newStake;
  83. cachedUnconfirmedBalance = newUnconfirmedBalance;
  84. cachedImmatureBalance = newImmatureBalance;
  85. emit balanceChanged(newBalance, newStake, newUnconfirmedBalance, newImmatureBalance);
  86. }
  87. }
  88. void WalletModel::updateTransaction(const QString &hash, int status)
  89. {
  90. if(transactionTableModel)
  91. transactionTableModel->updateTransaction(hash, status);
  92. // Balance and number of transactions might have changed
  93. checkBalanceChanged();
  94. int newNumTransactions = getNumTransactions();
  95. if(cachedNumTransactions != newNumTransactions)
  96. {
  97. cachedNumTransactions = newNumTransactions;
  98. emit numTransactionsChanged(newNumTransactions);
  99. }
  100. }
  101. void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status)
  102. {
  103. if(addressTableModel)
  104. addressTableModel->updateEntry(address, label, isMine, status);
  105. }
  106. bool WalletModel::validateAddress(const QString &address)
  107. {
  108. CEcoinAddress addressParsed(address.toStdString());
  109. return addressParsed.IsValid();
  110. }
  111. WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients, const CCoinControl *coinControl)
  112. {
  113. qint64 total = 0;
  114. QSet<QString> setAddress;
  115. QString hex;
  116. if(recipients.empty())
  117. {
  118. return OK;
  119. }
  120. // Pre-check input data for validity
  121. foreach(const SendCoinsRecipient &rcp, recipients)
  122. {
  123. if(!validateAddress(rcp.address))
  124. {
  125. return InvalidAddress;
  126. }
  127. setAddress.insert(rcp.address);
  128. if(rcp.amount <= 0)
  129. {
  130. return InvalidAmount;
  131. }
  132. total += rcp.amount;
  133. }
  134. if(recipients.size() > setAddress.size())
  135. {
  136. return DuplicateAddress;
  137. }
  138. int64 nBalance = 0;
  139. std::vector<COutput> vCoins;
  140. wallet->AvailableCoins(vCoins, true, coinControl);
  141. BOOST_FOREACH(const COutput& out, vCoins)
  142. nBalance += out.tx->vout[out.i].nValue;
  143. if(total > nBalance)
  144. {
  145. return AmountExceedsBalance;
  146. }
  147. if((total + nTransactionFee) > nBalance)
  148. {
  149. return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee);
  150. }
  151. {
  152. LOCK2(cs_main, wallet->cs_wallet);
  153. // Sendmany
  154. std::vector<std::pair<CScript, int64> > vecSend;
  155. foreach(const SendCoinsRecipient &rcp, recipients)
  156. {
  157. CScript scriptPubKey;
  158. scriptPubKey.SetDestination(CEcoinAddress(rcp.address.toStdString()).Get());
  159. vecSend.push_back(make_pair(scriptPubKey, rcp.amount));
  160. }
  161. CWalletTx wtx;
  162. CReserveKey keyChange(wallet);
  163. int64 nFeeRequired = 0;
  164. bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, coinControl);
  165. if(!fCreated)
  166. {
  167. if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future
  168. {
  169. return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired);
  170. }
  171. return TransactionCreationFailed;
  172. }
  173. if(!uiInterface.ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString()))
  174. {
  175. return Aborted;
  176. }
  177. if(!wallet->CommitTransaction(wtx, keyChange))
  178. {
  179. return TransactionCommitFailed;
  180. }
  181. hex = QString::fromStdString(wtx.GetHash().GetHex());
  182. }
  183. // Add addresses / update labels that we've sent to to the address book
  184. foreach(const SendCoinsRecipient &rcp, recipients)
  185. {
  186. std::string strAddress = rcp.address.toStdString();
  187. CTxDestination dest = CEcoinAddress(strAddress).Get();
  188. std::string strLabel = rcp.label.toStdString();
  189. {
  190. LOCK(wallet->cs_wallet);
  191. std::map<CTxDestination, std::string>::iterator mi = wallet->mapAddressBook.find(dest);
  192. // Check if we have a new address or an updated label
  193. if (mi == wallet->mapAddressBook.end() || mi->second != strLabel)
  194. {
  195. wallet->SetAddressBookName(dest, strLabel);
  196. }
  197. }
  198. }
  199. return SendCoinsReturn(OK, 0, hex);
  200. }
  201. OptionsModel *WalletModel::getOptionsModel()
  202. {
  203. return optionsModel;
  204. }
  205. AddressTableModel *WalletModel::getAddressTableModel()
  206. {
  207. return addressTableModel;
  208. }
  209. TransactionTableModel *WalletModel::getTransactionTableModel()
  210. {
  211. return transactionTableModel;
  212. }
  213. WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
  214. {
  215. if(!wallet->IsCrypted())
  216. {
  217. return Unencrypted;
  218. }
  219. else if(wallet->IsLocked())
  220. {
  221. return Locked;
  222. }
  223. else
  224. {
  225. return Unlocked;
  226. }
  227. }
  228. bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase)
  229. {
  230. if(encrypted)
  231. {
  232. // Encrypt
  233. return wallet->EncryptWallet(passphrase);
  234. }
  235. else
  236. {
  237. // Decrypt -- TODO; not supported yet
  238. return false;
  239. }
  240. }
  241. bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase)
  242. {
  243. if(locked)
  244. {
  245. // Lock
  246. return wallet->Lock();
  247. }
  248. else
  249. {
  250. // Unlock
  251. return wallet->Unlock(passPhrase);
  252. }
  253. }
  254. bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass)
  255. {
  256. bool retval;
  257. {
  258. LOCK(wallet->cs_wallet);
  259. wallet->Lock(); // Make sure wallet is locked before attempting pass change
  260. retval = wallet->ChangeWalletPassphrase(oldPass, newPass);
  261. }
  262. return retval;
  263. }
  264. bool WalletModel::backupWallet(const QString &filename)
  265. {
  266. return BackupWallet(*wallet, filename.toLocal8Bit().data());
  267. }
  268. // Handlers for core signals
  269. static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet)
  270. {
  271. OutputDebugStringF("NotifyKeyStoreStatusChanged\n");
  272. QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection);
  273. }
  274. static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status)
  275. {
  276. OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CEcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status);
  277. QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection,
  278. Q_ARG(QString, QString::fromStdString(CEcoinAddress(address).ToString())),
  279. Q_ARG(QString, QString::fromStdString(label)),
  280. Q_ARG(bool, isMine),
  281. Q_ARG(int, status));
  282. }
  283. static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status)
  284. {
  285. OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status);
  286. QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection,
  287. Q_ARG(QString, QString::fromStdString(hash.GetHex())),
  288. Q_ARG(int, status));
  289. }
  290. void WalletModel::subscribeToCoreSignals()
  291. {
  292. // Connect signals to wallet
  293. wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, boost::placeholders::_1));
  294. wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3, boost::placeholders::_4, boost::placeholders::_5));
  295. wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3));
  296. }
  297. void WalletModel::unsubscribeFromCoreSignals()
  298. {
  299. // Disconnect signals from wallet
  300. wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, boost::placeholders::_1));
  301. wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3, boost::placeholders::_4, boost::placeholders::_5));
  302. wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3));
  303. }
  304. // WalletModel::UnlockContext implementation
  305. WalletModel::UnlockContext WalletModel::requestUnlock()
  306. {
  307. bool was_locked = getEncryptionStatus() == Locked;
  308. if ((!was_locked) && fWalletUnlockStakingOnly)
  309. {
  310. setWalletLocked(true);
  311. was_locked = getEncryptionStatus() == Locked;
  312. }
  313. if(was_locked)
  314. {
  315. // Request UI to unlock wallet
  316. emit requireUnlock();
  317. }
  318. // If wallet is still locked, unlock was failed or cancelled, mark context as invalid
  319. bool valid = getEncryptionStatus() != Locked;
  320. return UnlockContext(this, valid, was_locked && !fWalletUnlockStakingOnly);
  321. }
  322. WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool relock):
  323. wallet(wallet),
  324. valid(valid),
  325. relock(relock)
  326. {
  327. }
  328. WalletModel::UnlockContext::~UnlockContext()
  329. {
  330. if(valid && relock)
  331. {
  332. wallet->setWalletLocked(true);
  333. }
  334. }
  335. void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs)
  336. {
  337. // Transfer context; old object no longer relocks wallet
  338. *this = rhs;
  339. rhs.relock = false;
  340. }
  341. bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
  342. {
  343. return wallet->GetPubKey(address, vchPubKeyOut);
  344. }
  345. // returns a list of COutputs from COutPoints
  346. void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs)
  347. {
  348. BOOST_FOREACH(const COutPoint& outpoint, vOutpoints)
  349. {
  350. if (!wallet->mapWallet.count(outpoint.hash)) continue;
  351. COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain());
  352. vOutputs.push_back(out);
  353. }
  354. }
  355. // AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address)
  356. void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const
  357. {
  358. std::vector<COutput> vCoins;
  359. wallet->AvailableCoins(vCoins);
  360. std::vector<COutPoint> vLockedCoins;
  361. // add locked coins
  362. BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins)
  363. {
  364. if (!wallet->mapWallet.count(outpoint.hash)) continue;
  365. COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain());
  366. vCoins.push_back(out);
  367. }
  368. BOOST_FOREACH(const COutput& out, vCoins)
  369. {
  370. COutput cout = out;
  371. while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0]))
  372. {
  373. if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break;
  374. cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0);
  375. }
  376. CTxDestination address;
  377. if(!ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) continue;
  378. mapCoins[CEcoinAddress(address).ToString().c_str()].push_back(out);
  379. }
  380. }
  381. bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const
  382. {
  383. return false;
  384. }
  385. void WalletModel::lockCoin(COutPoint& output)
  386. {
  387. return;
  388. }
  389. void WalletModel::unlockCoin(COutPoint& output)
  390. {
  391. return;
  392. }
  393. void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
  394. {
  395. return;
  396. }