dcd0b0775e
Correctly use the purpose of addresses that are added after the start of the client. Addresses with purpose "refund" and "change" should not be visible in the GUI. This is now handled correctly.
429 lines
13 KiB
C++
429 lines
13 KiB
C++
#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);
|
|
// 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::sendCoins(const QList<SendCoinsRecipient> &recipients)
|
|
{
|
|
qint64 total = 0;
|
|
std::vector<std::pair<CScript, int64> > vecSend;
|
|
QByteArray transaction;
|
|
|
|
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())
|
|
{
|
|
return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee);
|
|
}
|
|
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
|
|
CReserveKey keyChange(wallet);
|
|
int64 nFeeRequired = 0;
|
|
std::string strFailReason;
|
|
CWalletTx wtx;
|
|
bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason);
|
|
|
|
if(!fCreated)
|
|
{
|
|
if((total + nFeeRequired) > wallet->GetBalance())
|
|
{
|
|
return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired);
|
|
}
|
|
emit message(tr("Send Coins"), QString::fromStdString(strFailReason),
|
|
CClientUIInterface::MSG_ERROR);
|
|
return TransactionCreationFailed;
|
|
}
|
|
// Store PaymentRequests in wtx.vOrderForm in wallet.
|
|
foreach(const SendCoinsRecipient &rcp, recipients)
|
|
{
|
|
if (rcp.paymentRequest.IsInitialized())
|
|
{
|
|
std::string key("PaymentRequest");
|
|
std::string value;
|
|
rcp.paymentRequest.SerializeToString(&value);
|
|
wtx.vOrderForm.push_back(make_pair(key, value));
|
|
}
|
|
}
|
|
|
|
if(!uiInterface.ThreadSafeAskFee(nFeeRequired))
|
|
{
|
|
return Aborted;
|
|
}
|
|
if(!wallet->CommitTransaction(wtx, keyChange))
|
|
{
|
|
return TransactionCommitFailed;
|
|
}
|
|
|
|
CTransaction* t = (CTransaction*)&wtx;
|
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
|
ssTx << *t;
|
|
transaction.append(&(ssTx[0]), ssTx.size());
|
|
}
|
|
|
|
// Add addresses / update labels that we've sent to to the address book,
|
|
// and emit coinsSent signal
|
|
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, 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);
|
|
}
|
|
|
|
return SendCoinsReturn(OK, 0);
|
|
}
|
|
|
|
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,
|
|
const std::string &purpose, ChangeType status)
|
|
{
|
|
OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i purpose=%s status=%i\n",
|
|
CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, purpose.c_str(), 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(QString, QString::fromStdString(purpose)),
|
|
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, _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;
|
|
}
|