X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fqt%2Fwalletmodel.cpp;h=b14388aed3314f5b0c880ce4803764b0cb7c8913;hb=b0368da0e15b5506548da3462e14b038ca21ae69;hp=f962b9a7c7445bc554a10cc66044069199017164;hpb=ebff5c40a234f38429965c391da020bbf8312b1b;p=novacoin.git diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f962b9a..b14388a 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -4,23 +4,36 @@ #include "addresstablemodel.h" #include "transactiontablemodel.h" -#include "headers.h" +#include "ui_interface.h" +#include "wallet.h" +#include "walletdb.h" // for BackupWallet +#include "base58.h" +#include #include -WalletModel::WalletModel(CWallet *wallet, QObject *parent) : - QObject(parent), wallet(wallet), optionsModel(0), addressTableModel(0), - transactionTableModel(0) +WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : + QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), + transactionTableModel(0), + cachedBalance(0), cachedStake(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), + cachedNumTransactions(0), + cachedEncryptionStatus(Unencrypted), + cachedNumBlocks(0) { - // Until signal notifications is built into the bitcoin core, - // simply update everything after polling using a timer. - QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(update())); - timer->start(MODEL_UPDATE_DELAY); - - optionsModel = new OptionsModel(wallet, this); addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(wallet, this); + + // This timer will be fired repeatedly to update the balance + pollTimer = new QTimer(this); + connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); + pollTimer->start(MODEL_UPDATE_DELAY); + + subscribeToCoreSignals(); +} + +WalletModel::~WalletModel() +{ + unsubscribeFromCoreSignals(); } qint64 WalletModel::getBalance() const @@ -28,84 +41,207 @@ qint64 WalletModel::getBalance() const return wallet->GetBalance(); } +void WalletModel::getBalance(qint64 &nTotal, qint64 &nWatchOnly) const +{ + wallet->GetBalance(nTotal, nWatchOnly); +} + +qint64 WalletModel::getUnconfirmedBalance() const +{ + return wallet->GetUnconfirmedBalance(); +} + +qint64 WalletModel::getStake() const +{ + return wallet->GetStake(); +} + +qint64 WalletModel::getImmatureBalance() const +{ + return wallet->GetImmatureBalance(); +} + int WalletModel::getNumTransactions() const { int numTransactions = 0; - CRITICAL_BLOCK(wallet->cs_mapWallet) { + LOCK(wallet->cs_wallet); numTransactions = wallet->mapWallet.size(); } return numTransactions; } -void WalletModel::update() +void WalletModel::updateStatus() +{ + EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + + if(cachedEncryptionStatus != newEncryptionStatus) + emit encryptionStatusChanged(newEncryptionStatus); +} + +void WalletModel::pollBalanceChanged() +{ + if(nBestHeight != cachedNumBlocks) + { + // Balance and number of transactions might have changed + cachedNumBlocks = nBestHeight; + checkBalanceChanged(); + } +} + +void WalletModel::checkBalanceChanged() +{ + qint64 newBalanceTotal=0, newBalanceWatchOnly=0; + getBalance(newBalanceTotal, newBalanceWatchOnly); + + qint64 newStake = getStake(); + qint64 newUnconfirmedBalance = getUnconfirmedBalance(); + qint64 newImmatureBalance = getImmatureBalance(); + + if(cachedBalance != newBalanceTotal || cachedStake != newStake || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance) + { + cachedBalance = newBalanceTotal; + cachedStake = newStake; + cachedUnconfirmedBalance = newUnconfirmedBalance; + cachedImmatureBalance = newImmatureBalance; + emit balanceChanged(newBalanceTotal, newBalanceWatchOnly, newStake, newUnconfirmedBalance, newImmatureBalance); + } +} + +void WalletModel::updateTransaction(const QString &hash, int status) +{ + if(transactionTableModel) + transactionTableModel->updateTransaction(hash, status); + + // Balance and number of transactions might have changed + checkBalanceChanged(); + + int newNumTransactions = getNumTransactions(); + if(cachedNumTransactions != newNumTransactions) + { + cachedNumTransactions = newNumTransactions; + emit numTransactionsChanged(newNumTransactions); + } +} + +void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) { - // Plainly emit all signals for now. To be more efficient this should check - // whether the values actually changed first, although it'd be even better if these - // were events coming in from the bitcoin core. - emit balanceChanged(getBalance()); - emit numTransactionsChanged(getNumTransactions()); + if(addressTableModel) + addressTableModel->updateEntry(address, label, isMine, status); +} - addressTableModel->update(); +bool WalletModel::validateAddress(const QString &address) +{ + CBitcoinAddress addressParsed(address.toStdString()); + return addressParsed.IsValid(); } -WalletModel::StatusCode WalletModel::sendCoins(const QString &payTo, qint64 payAmount, const QString &addToAddressBookAs) +WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList &recipients, const CCoinControl *coinControl) { - uint160 hash160 = 0; - bool valid = false; + qint64 total = 0; + QSet setAddress; + QString hex; - if(!AddressToHash160(payTo.toUtf8().constData(), hash160)) + if(recipients.empty()) { - return InvalidAddress; + return OK; } - if(payAmount <= 0) + // Pre-check input data for validity + foreach(const SendCoinsRecipient &rcp, recipients) { - return InvalidAmount; + if(!validateAddress(rcp.address)) + { + return InvalidAddress; + } + setAddress.insert(rcp.address); + + if(rcp.amount <= 0) + { + return InvalidAmount; + } + total += rcp.amount; } - if(payAmount > getBalance()) + if(recipients.size() > setAddress.size()) + { + return DuplicateAddress; + } + + int64 nBalance = 0; + std::vector vCoins; + wallet->AvailableCoins(vCoins, true, coinControl); + + BOOST_FOREACH(const COutput& out, vCoins) + if(out.fSpendable) + nBalance += out.tx->vout[out.i].nValue; + + if(total > nBalance) { return AmountExceedsBalance; } - if((payAmount + nTransactionFee) > getBalance()) + if((total + nTransactionFee) > nBalance) { - return AmountWithFeeExceedsBalance; + return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee); } - CRITICAL_BLOCK(cs_main) { - // Send to bitcoin address + LOCK2(cs_main, wallet->cs_wallet); + + // Sendmany + std::vector > vecSend; + foreach(const SendCoinsRecipient &rcp, recipients) + { + CScript scriptPubKey; + scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + vecSend.push_back(make_pair(scriptPubKey, rcp.amount)); + } + CWalletTx wtx; - CScript scriptPubKey; - scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG; + CReserveKey keyChange(wallet); + int64 nFeeRequired = 0; + bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, coinControl); - std::string strError = wallet->SendMoney(scriptPubKey, payAmount, wtx, true); - if (strError == "") + if(!fCreated) { - // OK + if((total + nFeeRequired) > nBalance) // FIXME: could cause collisions in the future + { + return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired); + } + return TransactionCreationFailed; } - else if (strError == "ABORTED") + if(!uiInterface.ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString())) { return Aborted; } - else + if(!wallet->CommitTransaction(wtx, keyChange)) { - emit error(tr("Sending..."), QString::fromStdString(strError)); - return MiscError; + return TransactionCommitFailed; } + hex = QString::fromStdString(wtx.GetHash().GetHex()); } - // Add addresses that we've sent to to the address book - std::string strAddress = payTo.toStdString(); - CRITICAL_BLOCK(wallet->cs_mapAddressBook) + // Add addresses / update labels that we've sent to to the address book + foreach(const SendCoinsRecipient &rcp, recipients) { - if (!wallet->mapAddressBook.count(strAddress)) - wallet->SetAddressBookName(strAddress, addToAddressBookAs.toStdString()); + std::string strAddress = rcp.address.toStdString(); + CTxDestination dest = CBitcoinAddress(strAddress).Get(); + std::string strLabel = rcp.label.toStdString(); + { + LOCK(wallet->cs_wallet); + + std::map::iterator mi = wallet->mapAddressBook.find(dest); + + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end() || mi->second != strLabel) + { + wallet->SetAddressBookName(dest, strLabel); + } + } } - return OK; + return SendCoinsReturn(OK, 0, hex); } OptionsModel *WalletModel::getOptionsModel() @@ -123,18 +259,235 @@ TransactionTableModel *WalletModel::getTransactionTableModel() return transactionTableModel; } -/* Look up label for address in address book, if not found return empty string. - */ -QString WalletModel::labelForAddress(const QString &address) const +WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const +{ + if(!wallet->IsCrypted()) + { + return Unencrypted; + } + else if(wallet->IsLocked()) + { + return Locked; + } + else + { + return Unlocked; + } +} + +bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase) +{ + if(encrypted) + { + // Encrypt + return wallet->EncryptWallet(passphrase); + } + else + { + // Decrypt -- TODO; not supported yet + return false; + } +} + +bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) +{ + if(locked) + { + // Lock + return wallet->Lock(); + } + else + { + // Unlock + return wallet->Unlock(passPhrase); + } +} + +bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) +{ + bool retval; + { + LOCK(wallet->cs_wallet); + wallet->Lock(); // Make sure wallet is locked before attempting pass change + retval = wallet->ChangeWalletPassphrase(oldPass, newPass); + } + return retval; +} + +void WalletModel::getStakeWeight(uint64& nMinWeight, uint64& nMaxWeight, uint64& nWeight) +{ + wallet->GetStakeWeight(*wallet, nMinWeight, nMaxWeight, nWeight); +} + +void WalletModel::getStakeWeightFromValue(const int64& nTime, const int64& nValue, uint64& nWeight) +{ + wallet->GetStakeWeightFromValue(nTime, nValue, nWeight); +} + +bool WalletModel::dumpWallet(const QString &filename) +{ + return DumpWallet(wallet, filename.toLocal8Bit().data()); +} + +bool WalletModel::importWallet(const QString &filename) +{ + return ImportWallet(wallet, filename.toLocal8Bit().data()); +} + +bool WalletModel::backupWallet(const QString &filename) +{ + return BackupWallet(*wallet, filename.toLocal8Bit().data()); +} + +// Handlers for core signals +static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) +{ + OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status) +{ + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(CBitcoinAddress(address).ToString())), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); +} + +static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void WalletModel::subscribeToCoreSignals() +{ + // Connect signals to wallet + wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + +void WalletModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from wallet + wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + +// WalletModel::UnlockContext implementation +WalletModel::UnlockContext WalletModel::requestUnlock() +{ + bool was_locked = getEncryptionStatus() == Locked; + + if ((!was_locked) && fWalletUnlockMintOnly) + { + setWalletLocked(true); + was_locked = getEncryptionStatus() == Locked; + + } + if(was_locked) + { + // Request UI to unlock wallet + emit requireUnlock(); + } + // If wallet is still locked, unlock was failed or cancelled, mark context as invalid + bool valid = getEncryptionStatus() != Locked; + + return UnlockContext(this, valid, was_locked && !fWalletUnlockMintOnly); +} + +WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool relock): + wallet(wallet), + valid(valid), + relock(relock) { - CRITICAL_BLOCK(wallet->cs_mapAddressBook) +} + +WalletModel::UnlockContext::~UnlockContext() +{ + if(valid && relock) { - std::map::iterator mi = wallet->mapAddressBook.find(address.toStdString()); - if (mi != wallet->mapAddressBook.end()) + wallet->setWalletLocked(true); + } +} + +void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs) +{ + // Transfer context; old object no longer relocks wallet + *this = rhs; + rhs.relock = false; +} + +bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + return wallet->GetPubKey(address, vchPubKeyOut); +} + +// returns a list of COutputs from COutPoints +void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) +{ + BOOST_FOREACH(const COutPoint& outpoint, vOutpoints) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain(), true); + vOutputs.push_back(out); + } +} + +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listCoins(std::map >& mapCoins) const +{ + std::vector vCoins; + wallet->AvailableCoins(vCoins); + std::vector vLockedCoins; + + // add locked coins + BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain(), true); + vCoins.push_back(out); + } + + BOOST_FOREACH(const COutput& out, vCoins) + { + COutput cout = out; + + while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0])) { - return QString::fromStdString(mi->second); + if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break; + cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0, true); } + + CTxDestination address; + if(!out.fSpendable || !ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) + continue; + mapCoins[CBitcoinAddress(address).ToString().c_str()].push_back(out); } - return QString(); } +bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const +{ + return false; +} + +void WalletModel::lockCoin(COutPoint& output) +{ + return; +} + +void WalletModel::unlockCoin(COutPoint& output) +{ + return; +} + +void WalletModel::listLockedCoins(std::vector& vOutpts) +{ + return; +}