mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-10 10:52:31 -05:00
![Philip Kaufmann](/assets/img/avatar_default.png)
- this is based on #4122 (which can be closed) Currently a payment request is only checked for expiration upon receipt. It should be checked again immediately before sending coins to prevent the user from paying to an expired invoice which would then require a customer service interaction. - add static verifyExpired() function to PaymentServer to be able to use the same validation code in GUI and unit-testing code - extend unit tests to use that function and also add an unit test which overflows, because payment requests allow expires as uint64, whereas we use int64_t for verification of expired payment requests
652 lines
21 KiB
C++
652 lines
21 KiB
C++
// Copyright (c) 2011-2014 The Bitcoin Core developers
|
|
// Distributed under the MIT 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 "paymentserver.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>
|
|
|
|
using namespace std;
|
|
|
|
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),
|
|
cachedEncryptionStatus(Unencrypted),
|
|
cachedNumBlocks(0)
|
|
{
|
|
fHaveWatchOnly = wallet->HaveWatchOnly();
|
|
fForceCheckBalanceChanged = false;
|
|
|
|
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();
|
|
}
|
|
|
|
CAmount WalletModel::getBalance(const CCoinControl *coinControl) const
|
|
{
|
|
if (coinControl)
|
|
{
|
|
CAmount nBalance = 0;
|
|
std::vector<COutput> vCoins;
|
|
wallet->AvailableCoins(vCoins, true, coinControl);
|
|
BOOST_FOREACH(const COutput& out, vCoins)
|
|
if(out.fSpendable)
|
|
nBalance += out.tx->vout[out.i].nValue;
|
|
|
|
return nBalance;
|
|
}
|
|
|
|
return wallet->GetBalance();
|
|
}
|
|
|
|
CAmount WalletModel::getUnconfirmedBalance() const
|
|
{
|
|
return wallet->GetUnconfirmedBalance();
|
|
}
|
|
|
|
CAmount WalletModel::getImmatureBalance() const
|
|
{
|
|
return wallet->GetImmatureBalance();
|
|
}
|
|
|
|
bool WalletModel::haveWatchOnly() const
|
|
{
|
|
return fHaveWatchOnly;
|
|
}
|
|
|
|
CAmount WalletModel::getWatchBalance() const
|
|
{
|
|
return wallet->GetWatchOnlyBalance();
|
|
}
|
|
|
|
CAmount WalletModel::getWatchUnconfirmedBalance() const
|
|
{
|
|
return wallet->GetUnconfirmedWatchOnlyBalance();
|
|
}
|
|
|
|
CAmount WalletModel::getWatchImmatureBalance() const
|
|
{
|
|
return wallet->GetImmatureWatchOnlyBalance();
|
|
}
|
|
|
|
void WalletModel::updateStatus()
|
|
{
|
|
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
|
|
|
|
if(cachedEncryptionStatus != newEncryptionStatus)
|
|
emit encryptionStatusChanged(newEncryptionStatus);
|
|
}
|
|
|
|
void WalletModel::pollBalanceChanged()
|
|
{
|
|
// Get required locks upfront. This avoids the GUI from getting stuck on
|
|
// periodical polls if the core is holding the locks for a longer time -
|
|
// for example, during a wallet rescan.
|
|
TRY_LOCK(cs_main, lockMain);
|
|
if(!lockMain)
|
|
return;
|
|
TRY_LOCK(wallet->cs_wallet, lockWallet);
|
|
if(!lockWallet)
|
|
return;
|
|
|
|
if(fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks)
|
|
{
|
|
fForceCheckBalanceChanged = false;
|
|
|
|
// Balance and number of transactions might have changed
|
|
cachedNumBlocks = chainActive.Height();
|
|
|
|
checkBalanceChanged();
|
|
if(transactionTableModel)
|
|
transactionTableModel->updateConfirmations();
|
|
}
|
|
}
|
|
|
|
void WalletModel::checkBalanceChanged()
|
|
{
|
|
CAmount newBalance = getBalance();
|
|
CAmount newUnconfirmedBalance = getUnconfirmedBalance();
|
|
CAmount newImmatureBalance = getImmatureBalance();
|
|
CAmount newWatchOnlyBalance = 0;
|
|
CAmount newWatchUnconfBalance = 0;
|
|
CAmount newWatchImmatureBalance = 0;
|
|
if (haveWatchOnly())
|
|
{
|
|
newWatchOnlyBalance = getWatchBalance();
|
|
newWatchUnconfBalance = getWatchUnconfirmedBalance();
|
|
newWatchImmatureBalance = getWatchImmatureBalance();
|
|
}
|
|
|
|
if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance ||
|
|
cachedWatchOnlyBalance != newWatchOnlyBalance || cachedWatchUnconfBalance != newWatchUnconfBalance || cachedWatchImmatureBalance != newWatchImmatureBalance)
|
|
{
|
|
cachedBalance = newBalance;
|
|
cachedUnconfirmedBalance = newUnconfirmedBalance;
|
|
cachedImmatureBalance = newImmatureBalance;
|
|
cachedWatchOnlyBalance = newWatchOnlyBalance;
|
|
cachedWatchUnconfBalance = newWatchUnconfBalance;
|
|
cachedWatchImmatureBalance = newWatchImmatureBalance;
|
|
emit balanceChanged(newBalance, newUnconfirmedBalance, newImmatureBalance,
|
|
newWatchOnlyBalance, newWatchUnconfBalance, newWatchImmatureBalance);
|
|
}
|
|
}
|
|
|
|
void WalletModel::updateTransaction()
|
|
{
|
|
// Balance and number of transactions might have changed
|
|
fForceCheckBalanceChanged = true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly)
|
|
{
|
|
fHaveWatchOnly = fHaveWatchonly;
|
|
emit notifyWatchonlyChanged(fHaveWatchonly);
|
|
}
|
|
|
|
bool WalletModel::validateAddress(const QString &address)
|
|
{
|
|
CBitcoinAddress addressParsed(address.toStdString());
|
|
return addressParsed.IsValid();
|
|
}
|
|
|
|
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
|
|
{
|
|
CAmount total = 0;
|
|
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
|
std::vector<std::pair<CScript, CAmount> > 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...
|
|
CAmount 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, CAmount>(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 = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
|
|
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
|
|
|
|
total += rcp.amount;
|
|
}
|
|
}
|
|
if(setAddress.size() != nAddresses)
|
|
{
|
|
return DuplicateAddress;
|
|
}
|
|
|
|
CAmount nBalance = getBalance(coinControl);
|
|
|
|
if(total > nBalance)
|
|
{
|
|
return AmountExceedsBalance;
|
|
}
|
|
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
|
|
transaction.newPossibleKeyChange(wallet);
|
|
CAmount 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;
|
|
}
|
|
|
|
// reject insane fee > 0.1 bitcoin
|
|
if (nFeeRequired > 10000000)
|
|
return InsaneFee;
|
|
}
|
|
|
|
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();
|
|
|
|
foreach(const SendCoinsRecipient &rcp, transaction.getRecipients())
|
|
{
|
|
if (rcp.paymentRequest.IsInitialized())
|
|
{
|
|
// Make sure any payment requests involved are still valid.
|
|
if (PaymentServer::verifyExpired(rcp.paymentRequest.getDetails())) {
|
|
return PaymentRequestExpired;
|
|
}
|
|
|
|
// Store PaymentRequests in wtx.vOrderForm in wallet.
|
|
std::string key("PaymentRequest");
|
|
std::string value;
|
|
rcp.paymentRequest.SerializeToString(&value);
|
|
newTx->vOrderForm.push_back(make_pair(key, value));
|
|
}
|
|
else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example)
|
|
newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString()));
|
|
}
|
|
|
|
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);
|
|
}
|
|
checkBalanceChanged(); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
|
|
|
|
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)
|
|
{
|
|
Q_UNUSED(wallet);
|
|
Q_UNUSED(hash);
|
|
Q_UNUSED(status);
|
|
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
|
|
}
|
|
|
|
static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
|
|
{
|
|
// emits signal "showProgress"
|
|
QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
|
|
Q_ARG(QString, QString::fromStdString(title)),
|
|
Q_ARG(int, nProgress));
|
|
}
|
|
|
|
static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly)
|
|
{
|
|
QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection,
|
|
Q_ARG(bool, fHaveWatchonly));
|
|
}
|
|
|
|
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));
|
|
wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
|
|
wallet->NotifyWatchonlyChanged.connect(boost::bind(NotifyWatchonlyChanged, this, _1));
|
|
}
|
|
|
|
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));
|
|
wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));
|
|
wallet->NotifyWatchonlyChanged.disconnect(boost::bind(NotifyWatchonlyChanged, this, _1));
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
BOOST_FOREACH(const COutPoint& outpoint, vOutpoints)
|
|
{
|
|
if (!wallet->mapWallet.count(outpoint.hash)) continue;
|
|
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
|
|
if (nDepth < 0) continue;
|
|
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true);
|
|
vOutputs.push_back(out);
|
|
}
|
|
}
|
|
|
|
bool WalletModel::isSpent(const COutPoint& outpoint) const
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
return wallet->IsSpent(outpoint.hash, outpoint.n);
|
|
}
|
|
|
|
// 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);
|
|
|
|
LOCK2(cs_main, 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;
|
|
int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain();
|
|
if (nDepth < 0) continue;
|
|
COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true);
|
|
if (outpoint.n < out.tx->vout.size() && wallet->IsMine(out.tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
|
|
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, true);
|
|
}
|
|
|
|
CTxDestination address;
|
|
if(!out.fSpendable || !ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address))
|
|
continue;
|
|
mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out);
|
|
}
|
|
}
|
|
|
|
bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
return wallet->IsLockedCoin(hash, n);
|
|
}
|
|
|
|
void WalletModel::lockCoin(COutPoint& output)
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
wallet->LockCoin(output);
|
|
}
|
|
|
|
void WalletModel::unlockCoin(COutPoint& output)
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
wallet->UnlockCoin(output);
|
|
}
|
|
|
|
void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
|
|
{
|
|
LOCK2(cs_main, wallet->cs_wallet);
|
|
wallet->ListLockedCoins(vOutpts);
|
|
}
|
|
|
|
void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
|
|
{
|
|
LOCK(wallet->cs_wallet);
|
|
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
|
|
BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata)
|
|
if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request
|
|
vReceiveRequests.push_back(item2.second);
|
|
}
|
|
|
|
bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest)
|
|
{
|
|
CTxDestination dest = CBitcoinAddress(sAddress).Get();
|
|
|
|
std::stringstream ss;
|
|
ss << nId;
|
|
std::string key = "rr" + ss.str(); // "rr" prefix = "receive request" in destdata
|
|
|
|
LOCK(wallet->cs_wallet);
|
|
if (sRequest.empty())
|
|
return wallet->EraseDestData(dest, key);
|
|
else
|
|
return wallet->AddDestData(dest, key, sRequest);
|
|
}
|