You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
376 lines
11 KiB
376 lines
11 KiB
#include "walletmodel.h" |
|
#include "guiconstants.h" |
|
#include "optionsmodel.h" |
|
#include "addresstablemodel.h" |
|
#include "transactiontablemodel.h" |
|
|
|
#include "ui_interface.h" |
|
#include "wallet.h" |
|
#include "walletdb.h" // for BackupWallet |
|
#include "base58.h" |
|
|
|
#include <QSet> |
|
#include <QTimer> |
|
|
|
WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : |
|
QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), |
|
transactionTableModel(0), |
|
cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), |
|
cachedNumTransactions(0), |
|
cachedEncryptionStatus(Unencrypted), |
|
cachedNumBlocks(0) |
|
{ |
|
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 |
|
{ |
|
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); |
|
numTransactions = wallet->mapWallet.size(); |
|
} |
|
return numTransactions; |
|
} |
|
|
|
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 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, int status) |
|
{ |
|
if(addressTableModel) |
|
addressTableModel->updateEntry(address, label, isMine, status); |
|
} |
|
|
|
bool WalletModel::validateAddress(const QString &address) |
|
{ |
|
CBitcoinAddress addressParsed(address.toStdString()); |
|
return addressParsed.IsValid(); |
|
} |
|
|
|
WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients) |
|
{ |
|
qint64 total = 0; |
|
QSet<QString> setAddress; |
|
QString hex; |
|
|
|
if(recipients.empty()) |
|
{ |
|
return OK; |
|
} |
|
|
|
// Pre-check input data for validity |
|
foreach(const SendCoinsRecipient &rcp, recipients) |
|
{ |
|
if(!validateAddress(rcp.address)) |
|
{ |
|
return InvalidAddress; |
|
} |
|
setAddress.insert(rcp.address); |
|
|
|
if(rcp.amount <= 0) |
|
{ |
|
return InvalidAmount; |
|
} |
|
total += rcp.amount; |
|
} |
|
|
|
if(recipients.size() > setAddress.size()) |
|
{ |
|
return DuplicateAddress; |
|
} |
|
|
|
if(total > getBalance()) |
|
{ |
|
return AmountExceedsBalance; |
|
} |
|
|
|
if((total + nTransactionFee) > getBalance()) |
|
{ |
|
return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee); |
|
} |
|
|
|
{ |
|
LOCK2(cs_main, wallet->cs_wallet); |
|
|
|
// Sendmany |
|
std::vector<std::pair<CScript, int64> > 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; |
|
CReserveKey keyChange(wallet); |
|
int64 nFeeRequired = 0; |
|
bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired); |
|
|
|
if(!fCreated) |
|
{ |
|
if((total + nFeeRequired) > wallet->GetBalance()) |
|
{ |
|
return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired); |
|
} |
|
return TransactionCreationFailed; |
|
} |
|
if(!uiInterface.ThreadSafeAskFee(nFeeRequired)) |
|
{ |
|
return Aborted; |
|
} |
|
if(!wallet->CommitTransaction(wtx, keyChange)) |
|
{ |
|
return TransactionCommitFailed; |
|
} |
|
hex = QString::fromStdString(wtx.GetHash().GetHex()); |
|
} |
|
|
|
// Add addresses / update labels that we've sent to to the address book |
|
foreach(const SendCoinsRecipient &rcp, recipients) |
|
{ |
|
std::string strAddress = rcp.address.toStdString(); |
|
CTxDestination dest = CBitcoinAddress(strAddress).Get(); |
|
std::string strLabel = rcp.label.toStdString(); |
|
{ |
|
LOCK(wallet->cs_wallet); |
|
|
|
std::map<CTxDestination, std::string>::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 SendCoinsReturn(OK, 0, hex); |
|
} |
|
|
|
OptionsModel *WalletModel::getOptionsModel() |
|
{ |
|
return optionsModel; |
|
} |
|
|
|
AddressTableModel *WalletModel::getAddressTableModel() |
|
{ |
|
return addressTableModel; |
|
} |
|
|
|
TransactionTableModel *WalletModel::getTransactionTableModel() |
|
{ |
|
return transactionTableModel; |
|
} |
|
|
|
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) |
|
{ |
|
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) |
|
{ |
|
// 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; |
|
}
|
|
|