diff options
Diffstat (limited to 'src/qt/walletmodel.cpp')
| -rw-r--r-- | src/qt/walletmodel.cpp | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp new file mode 100644 index 000000000..14f29c933 --- /dev/null +++ b/src/qt/walletmodel.cpp @@ -0,0 +1,556 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "walletmodel.h" + +#include "addresstablemodel.h" +#include "guiconstants.h" +#include "recentrequeststablemodel.h" +#include "transactiontablemodel.h" + +#include "base58.h" +#include "db.h" +#include "keystore.h" +#include "main.h" +#include "sync.h" +#include "ui_interface.h" +#include "wallet.h" +#include "walletdb.h" // for BackupWallet + +#include <stdint.h> + +#include <QDebug> +#include <QSet> +#include <QTimer> + +WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : + QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), + transactionTableModel(0), + recentRequestsTableModel(0), + cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), + cachedNumTransactions(0), + cachedEncryptionStatus(Unencrypted), + cachedNumBlocks(0) +{ + addressTableModel = new AddressTableModel(wallet, this); + transactionTableModel = new TransactionTableModel(wallet, this); + recentRequestsTableModel = new RecentRequestsTableModel(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 CCoinControl *coinControl) const +{ + if (coinControl) + { + qint64 nBalance = 0; + std::vector<COutput> vCoins; + wallet->AvailableCoins(vCoins, true, coinControl); + BOOST_FOREACH(const COutput& out, vCoins) + nBalance += out.tx->vout[out.i].nValue; + + return nBalance; + } + + return wallet->GetBalance(); +} + +qint64 WalletModel::getUnconfirmedBalance() const +{ + return wallet->GetUnconfirmedBalance(); +} + +qint64 WalletModel::getImmatureBalance() const +{ + return wallet->GetImmatureBalance(); +} + +int WalletModel::getNumTransactions() const +{ + int numTransactions = 0; + { + LOCK(wallet->cs_wallet); + // the size of mapWallet contains the number of unique transaction IDs + // (e.g. payments to yourself generate 2 transactions, but both share the same transaction ID) + numTransactions = wallet->mapWallet.size(); + } + return numTransactions; +} + +void WalletModel::updateStatus() +{ + EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + + if(cachedEncryptionStatus != newEncryptionStatus) + emit encryptionStatusChanged(newEncryptionStatus); +} + +void WalletModel::pollBalanceChanged() +{ + if(chainActive.Height() != cachedNumBlocks) + { + // Balance and number of transactions might have changed + cachedNumBlocks = chainActive.Height(); + checkBalanceChanged(); + } +} + +void WalletModel::checkBalanceChanged() +{ + qint64 newBalance = getBalance(); + qint64 newUnconfirmedBalance = getUnconfirmedBalance(); + qint64 newImmatureBalance = getImmatureBalance(); + + if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance) + { + cachedBalance = newBalance; + cachedUnconfirmedBalance = newUnconfirmedBalance; + cachedImmatureBalance = newImmatureBalance; + emit balanceChanged(newBalance, 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, const QString &purpose, int status) +{ + if(addressTableModel) + addressTableModel->updateEntry(address, label, isMine, purpose, status); +} + +bool WalletModel::validateAddress(const QString &address) +{ + CBitcoinAddress addressParsed(address.toStdString()); + return addressParsed.IsValid(); +} + +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) +{ + qint64 total = 0; + QList<SendCoinsRecipient> recipients = transaction.getRecipients(); + std::vector<std::pair<CScript, int64_t> > vecSend; + + if(recipients.empty()) + { + return OK; + } + + QSet<QString> setAddress; // Used to detect duplicates + int nAddresses = 0; + + // Pre-check input data for validity + foreach(const SendCoinsRecipient &rcp, recipients) + { + if (rcp.paymentRequest.IsInitialized()) + { // PaymentRequest... + int64_t subtotal = 0; + const payments::PaymentDetails& details = rcp.paymentRequest.getDetails(); + for (int i = 0; i < details.outputs_size(); i++) + { + const payments::Output& out = details.outputs(i); + if (out.amount() <= 0) continue; + subtotal += out.amount(); + const unsigned char* scriptStr = (const unsigned char*)out.script().data(); + CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); + vecSend.push_back(std::pair<CScript, int64_t>(scriptPubKey, out.amount())); + } + if (subtotal <= 0) + { + return InvalidAmount; + } + total += subtotal; + } + else + { // User-entered bitcoin address / amount: + if(!validateAddress(rcp.address)) + { + return InvalidAddress; + } + if(rcp.amount <= 0) + { + return InvalidAmount; + } + setAddress.insert(rcp.address); + ++nAddresses; + + CScript scriptPubKey; + scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + vecSend.push_back(std::pair<CScript, int64_t>(scriptPubKey, rcp.amount)); + + total += rcp.amount; + } + } + if(setAddress.size() != nAddresses) + { + return DuplicateAddress; + } + + qint64 nBalance = getBalance(coinControl); + + if(total > nBalance) + { + return AmountExceedsBalance; + } + + if((total + nTransactionFee) > nBalance) + { + transaction.setTransactionFee(nTransactionFee); + return SendCoinsReturn(AmountWithFeeExceedsBalance); + } + + { + LOCK2(cs_main, wallet->cs_wallet); + + transaction.newPossibleKeyChange(wallet); + int64_t nFeeRequired = 0; + std::string strFailReason; + + CWalletTx *newTx = transaction.getTransaction(); + CReserveKey *keyChange = transaction.getPossibleKeyChange(); + bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl); + transaction.setTransactionFee(nFeeRequired); + + if(!fCreated) + { + if((total + nFeeRequired) > nBalance) + { + return SendCoinsReturn(AmountWithFeeExceedsBalance); + } + emit message(tr("Send Coins"), QString::fromStdString(strFailReason), + CClientUIInterface::MSG_ERROR); + return TransactionCreationFailed; + } + } + + return SendCoinsReturn(OK); +} + +WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) +{ + QByteArray transaction_array; /* store serialized transaction */ + + { + LOCK2(cs_main, wallet->cs_wallet); + CWalletTx *newTx = transaction.getTransaction(); + + // Store PaymentRequests in wtx.vOrderForm in wallet. + foreach(const SendCoinsRecipient &rcp, transaction.getRecipients()) + { + if (rcp.paymentRequest.IsInitialized()) + { + std::string key("PaymentRequest"); + std::string value; + rcp.paymentRequest.SerializeToString(&value); + newTx->vOrderForm.push_back(make_pair(key, value)); + } + } + + CReserveKey *keyChange = transaction.getPossibleKeyChange(); + if(!wallet->CommitTransaction(*newTx, *keyChange)) + return TransactionCommitFailed; + + CTransaction* t = (CTransaction*)newTx; + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << *t; + transaction_array.append(&(ssTx[0]), ssTx.size()); + } + + // Add addresses / update labels that we've sent to to the address book, + // and emit coinsSent signal for each recipient + foreach(const SendCoinsRecipient &rcp, transaction.getRecipients()) + { + // Don't touch the address book when we have a payment request + if (!rcp.paymentRequest.IsInitialized()) + { + std::string strAddress = rcp.address.toStdString(); + CTxDestination dest = CBitcoinAddress(strAddress).Get(); + std::string strLabel = rcp.label.toStdString(); + { + LOCK(wallet->cs_wallet); + + std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest); + + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end()) + { + wallet->SetAddressBook(dest, strLabel, "send"); + } + else if (mi->second.name != strLabel) + { + wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + } + } + } + emit coinsSent(wallet, rcp, transaction_array); + } + + return SendCoinsReturn(OK); +} + +OptionsModel *WalletModel::getOptionsModel() +{ + return optionsModel; +} + +AddressTableModel *WalletModel::getAddressTableModel() +{ + return addressTableModel; +} + +TransactionTableModel *WalletModel::getTransactionTableModel() +{ + return transactionTableModel; +} + +RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() +{ + return recentRequestsTableModel; +} + +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; +} + +bool WalletModel::backupWallet(const QString &filename) +{ + return BackupWallet(*wallet, filename.toLocal8Bit().data()); +} + +// Handlers for core signals +static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) +{ + qDebug() << "NotifyKeyStoreStatusChanged"; + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, + const CTxDestination &address, const std::string &label, bool isMine, + const std::string &purpose, ChangeType status) +{ + QString strAddress = QString::fromStdString(CBitcoinAddress(address).ToString()); + QString strLabel = QString::fromStdString(label); + QString strPurpose = QString::fromStdString(purpose); + + qDebug() << "NotifyAddressBookChanged : " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, strAddress), + Q_ARG(QString, strLabel), + Q_ARG(bool, isMine), + Q_ARG(QString, strPurpose), + Q_ARG(int, status)); +} + +static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) +{ + QString strHash = QString::fromStdString(hash.GetHex()); + + qDebug() << "NotifyTransactionChanged : " + strHash + " status= " + QString::number(status); + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, strHash), + 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, _6)); + 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, _6)); + 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) + { + // 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); +} + +WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool relock): + wallet(wallet), + valid(valid), + relock(relock) +{ +} + +WalletModel::UnlockContext::~UnlockContext() +{ + if(valid && relock) + { + 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<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs) +{ + LOCK(wallet->cs_wallet); + 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()); + vOutputs.push_back(out); + } +} + +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const +{ + std::vector<COutput> vCoins; + wallet->AvailableCoins(vCoins); + + LOCK(wallet->cs_wallet); // ListLockedCoins, mapWallet + std::vector<COutPoint> vLockedCoins; + wallet->ListLockedCoins(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()); + 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])) + { + 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); + } + + CTxDestination address; + if(!ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) continue; + mapCoins[CBitcoinAddress(address).ToString().c_str()].push_back(out); + } +} + +bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const +{ + LOCK(wallet->cs_wallet); + return wallet->IsLockedCoin(hash, n); +} + +void WalletModel::lockCoin(COutPoint& output) +{ + LOCK(wallet->cs_wallet); + wallet->LockCoin(output); +} + +void WalletModel::unlockCoin(COutPoint& output) +{ + LOCK(wallet->cs_wallet); + wallet->UnlockCoin(output); +} + +void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts) +{ + LOCK(wallet->cs_wallet); + wallet->ListLockedCoins(vOutpts); +} |