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.
445 lines
14 KiB
445 lines
14 KiB
#include "walletmodel.h" |
|
#include "guiconstants.h" |
|
#include "optionsmodel.h" |
|
#include "addresstablemodel.h" |
|
#include "transactiontablemodel.h" |
|
|
|
#include "ui_interface.h" |
|
#include "walletdb.h" // for BackupWallet |
|
#include "base58.h" |
|
|
|
#include <QSet> |
|
#include <QTimer> |
|
#include <QDebug> |
|
|
|
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); |
|
// 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(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, 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) |
|
{ |
|
qint64 total = 0; |
|
QList<SendCoinsRecipient> recipients = transaction.getRecipients(); |
|
std::vector<std::pair<CScript, int64> > 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 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>(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>(scriptPubKey, rcp.amount)); |
|
|
|
total += rcp.amount; |
|
} |
|
} |
|
if(setAddress.size() != nAddresses) |
|
{ |
|
return DuplicateAddress; |
|
} |
|
|
|
if(total > getBalance()) |
|
{ |
|
return AmountExceedsBalance; |
|
} |
|
|
|
if((total + nTransactionFee) > getBalance()) |
|
{ |
|
transaction.setTransactionFee(nTransactionFee); |
|
return SendCoinsReturn(AmountWithFeeExceedsBalance); |
|
} |
|
|
|
{ |
|
LOCK2(cs_main, wallet->cs_wallet); |
|
|
|
transaction.newPossibleKeyChange(wallet); |
|
int64 nFeeRequired = 0; |
|
std::string strFailReason; |
|
|
|
CWalletTx *newTx = transaction.getTransaction(); |
|
CReserveKey *keyChange = transaction.getPossibleKeyChange(); |
|
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason); |
|
transaction.setTransactionFee(nFeeRequired); |
|
|
|
if(!fCreated) |
|
{ |
|
if((total + nFeeRequired) > wallet->GetBalance()) |
|
{ |
|
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()) |
|
{ |
|
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; |
|
} |
|
|
|
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; |
|
}
|
|
|