neobytes/src/qt/walletmodel.cpp

750 lines
26 KiB
C++
Raw Normal View History

// Copyright (c) 2011-2015 The Bitcoin Core developers
2016-12-20 14:26:45 +01:00
// Copyright (c) 2014-2017 The Dash Core developers
2014-12-13 05:09:33 +01:00
// 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 "guiutil.h"
#include "paymentserver.h"
#include "recentrequeststablemodel.h"
#include "transactiontablemodel.h"
#include "base58.h"
#include "keystore.h"
#include "main.h"
#include "sync.h"
#include "ui_interface.h"
#include "wallet/wallet.h"
#include "wallet/walletdb.h" // for BackupWallet
#include "instantx.h"
#include "spork.h"
#include "privatesend-client.h"
#include <stdint.h>
#include <QDebug>
2011-07-16 19:01:05 +02:00
#include <QSet>
#include <QTimer>
2015-07-05 14:17:46 +02:00
#include <boost/foreach.hpp>
WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, OptionsModel *optionsModel, QObject *parent) :
QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0),
transactionTableModel(0),
recentRequestsTableModel(0),
cachedBalance(0),
cachedUnconfirmedBalance(0),
cachedImmatureBalance(0),
cachedAnonymizedBalance(0),
cachedWatchOnlyBalance(0),
cachedWatchUnconfBalance(0),
cachedWatchImmatureBalance(0),
cachedEncryptionStatus(Unencrypted),
cachedNumBlocks(0),
cachedTxLocks(0),
cachedPrivateSendRounds(0)
{
fHaveWatchOnly = wallet->HaveWatchOnly();
fForceCheckBalanceChanged = false;
addressTableModel = new AddressTableModel(wallet, this);
transactionTableModel = new TransactionTableModel(platformStyle, 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();
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getBalance(const CCoinControl *coinControl) const
{
2013-08-12 17:03:03 +02:00
if (coinControl)
{
2014-04-23 00:46:19 +02:00
CAmount nBalance = 0;
2013-08-12 17:03:03 +02:00
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;
2013-08-12 17:03:03 +02:00
return nBalance;
}
return wallet->GetBalance();
}
CAmount WalletModel::getAnonymizedBalance() const
{
return wallet->GetAnonymizedBalance();
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getUnconfirmedBalance() const
{
return wallet->GetUnconfirmedBalance();
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getImmatureBalance() const
{
return wallet->GetImmatureBalance();
}
bool WalletModel::haveWatchOnly() const
{
return fHaveWatchOnly;
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getWatchBalance() const
{
return wallet->GetWatchOnlyBalance();
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getWatchUnconfirmedBalance() const
{
return wallet->GetUnconfirmedWatchOnlyBalance();
}
2014-04-23 00:46:19 +02:00
CAmount WalletModel::getWatchImmatureBalance() const
{
return wallet->GetImmatureWatchOnlyBalance();
}
void WalletModel::updateStatus()
{
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
if(cachedEncryptionStatus != newEncryptionStatus)
Q_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 || privateSendClient.nPrivateSendRounds != cachedPrivateSendRounds || cachedTxLocks != nCompleteTXLocks)
{
fForceCheckBalanceChanged = false;
// Balance and number of transactions might have changed
cachedNumBlocks = chainActive.Height();
cachedPrivateSendRounds = privateSendClient.nPrivateSendRounds;
checkBalanceChanged();
2016-02-14 05:51:06 +01:00
if(transactionTableModel)
transactionTableModel->updateConfirmations();
}
}
void WalletModel::checkBalanceChanged()
{
2014-04-23 00:46:19 +02:00
CAmount newBalance = getBalance();
CAmount newUnconfirmedBalance = getUnconfirmedBalance();
CAmount newImmatureBalance = getImmatureBalance();
2015-04-03 00:51:08 +02:00
CAmount newAnonymizedBalance = getAnonymizedBalance();
2014-04-23 00:46:19 +02:00
CAmount newWatchOnlyBalance = 0;
CAmount newWatchUnconfBalance = 0;
CAmount newWatchImmatureBalance = 0;
if (haveWatchOnly())
{
newWatchOnlyBalance = getWatchBalance();
newWatchUnconfBalance = getWatchUnconfirmedBalance();
newWatchImmatureBalance = getWatchImmatureBalance();
}
if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance ||
2015-04-03 00:51:08 +02:00
cachedAnonymizedBalance != newAnonymizedBalance || cachedTxLocks != nCompleteTXLocks ||
cachedWatchOnlyBalance != newWatchOnlyBalance || cachedWatchUnconfBalance != newWatchUnconfBalance || cachedWatchImmatureBalance != newWatchImmatureBalance)
{
cachedBalance = newBalance;
cachedUnconfirmedBalance = newUnconfirmedBalance;
cachedImmatureBalance = newImmatureBalance;
cachedAnonymizedBalance = newAnonymizedBalance;
2015-02-02 16:04:09 +01:00
cachedTxLocks = nCompleteTXLocks;
cachedWatchOnlyBalance = newWatchOnlyBalance;
cachedWatchUnconfBalance = newWatchUnconfBalance;
cachedWatchImmatureBalance = newWatchImmatureBalance;
Q_EMIT balanceChanged(newBalance, newUnconfirmedBalance, newImmatureBalance, newAnonymizedBalance,
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;
Q_EMIT notifyWatchonlyChanged(fHaveWatchonly);
}
2011-07-16 19:01:05 +02:00
bool WalletModel::validateAddress(const QString &address)
{
CBitcoinAddress addressParsed(address.toStdString());
return addressParsed.IsValid();
2011-07-16 19:01:05 +02:00
}
2013-08-12 17:03:03 +02:00
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
2011-07-16 19:01:05 +02:00
{
2014-04-23 00:46:19 +02:00
CAmount total = 0;
bool fSubtractFeeFromAmount = false;
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
std::vector<CRecipient> vecSend;
2011-07-16 19:01:05 +02:00
if(recipients.empty())
{
2011-07-16 19:01:05 +02:00
return OK;
}
// This should never really happen, yet another safety check, just in case.
if(wallet->IsLocked()) {
return TransactionCreationFailed;
}
QSet<QString> setAddress; // Used to detect duplicates
int nAddresses = 0;
2011-07-16 19:01:05 +02:00
// Pre-check input data for validity
Q_FOREACH(const SendCoinsRecipient &rcp, recipients)
{
if (rcp.fSubtractFeeFromAmount)
fSubtractFeeFromAmount = true;
if (rcp.paymentRequest.IsInitialized())
{ // PaymentRequest...
2014-04-23 00:46:19 +02:00
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());
CAmount nAmount = out.amount();
CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
if (subtotal <= 0)
{
return InvalidAmount;
}
total += subtotal;
2011-07-16 19:01:05 +02:00
}
else
{ // User-entered dash address / amount:
if(!validateAddress(rcp.address))
{
return InvalidAddress;
}
if(rcp.amount <= 0)
{
return InvalidAmount;
}
setAddress.insert(rcp.address);
++nAddresses;
2011-07-16 19:01:05 +02:00
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount};
vecSend.push_back(recipient);
total += rcp.amount;
2011-07-16 19:01:05 +02:00
}
}
if(setAddress.size() != nAddresses)
2011-07-16 19:01:05 +02:00
{
return DuplicateAddress;
}
2014-04-23 00:46:19 +02:00
CAmount nBalance = getBalance(coinControl);
2013-08-12 17:03:03 +02:00
if(total > nBalance)
{
return AmountExceedsBalance;
}
{
LOCK2(cs_main, wallet->cs_wallet);
transaction.newPossibleKeyChange(wallet);
2014-04-23 00:46:19 +02:00
CAmount nFeeRequired = 0;
int nChangePosRet = -1;
std::string strFailReason;
CWalletTx *newTx = transaction.getTransaction();
CReserveKey *keyChange = transaction.getPossibleKeyChange();
if(recipients[0].fUseInstantSend && total > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN){
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH.").arg(sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)),
2015-02-11 15:47:21 +01:00
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl, true, recipients[0].inputType, recipients[0].fUseInstantSend);
transaction.setTransactionFee(nFeeRequired);
if (fSubtractFeeFromAmount && fCreated)
transaction.reassignAmounts(nChangePosRet);
if(recipients[0].fUseInstantSend) {
if(newTx->GetValueOut() > sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)*COIN) {
Q_EMIT message(tr("Send Coins"), tr("InstantSend doesn't support sending values that high yet. Transactions are currently limited to %1 DASH.").arg(sporkManager.GetSporkValue(SPORK_5_INSTANTSEND_MAX_VALUE)),
CClientUIInterface::MSG_ERROR);
return TransactionCreationFailed;
}
if(newTx->vin.size() > CTxLockRequest::WARN_MANY_INPUTS) {
Q_EMIT message(tr("Send Coins"), tr("Used way too many inputs (>%1) for this InstantSend transaction, fees could be huge.").arg(CTxLockRequest::WARN_MANY_INPUTS),
CClientUIInterface::MSG_WARNING);
}
}
2011-07-16 19:01:05 +02:00
if(!fCreated)
{
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
2011-07-16 19:01:05 +02:00
{
return SendCoinsReturn(AmountWithFeeExceedsBalance);
2011-07-16 19:01:05 +02:00
}
Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason),
CClientUIInterface::MSG_ERROR);
2011-07-16 19:01:05 +02:00
return TransactionCreationFailed;
}
2014-11-02 00:14:47 +01:00
2015-10-25 01:27:24 +02:00
// reject absurdly high fee. (This can never happen because the
// wallet caps the fee at maxTxFee. This merely serves as a
// belt-and-suspenders check)
if (nFeeRequired > maxTxFee)
return AbsurdFee;
}
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();
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
Q_FOREACH(const SendCoinsRecipient &rcp, recipients)
{
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 dash:URI (dash:XyZ...?message=example)
{
newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString()));
}
}
CReserveKey *keyChange = transaction.getPossibleKeyChange();
Backport Bitcoin PR#8085: p2p: Begin encapsulation (#1537) * net: move CBanDB and CAddrDB out of net.h/cpp This will eventually solve a circular dependency * net: Create CConnman to encapsulate p2p connections * net: Move socket binding into CConnman * net: move OpenNetworkConnection into CConnman * net: move ban and addrman functions into CConnman * net: Add oneshot functions to CConnman * net: move added node functions to CConnman * net: Add most functions needed for vNodes to CConnman * net: handle nodesignals in CConnman * net: Pass CConnection to wallet rather than using the global * net: Add rpc error for missing/disabled p2p functionality * net: Pass CConnman around as needed * gui: add NodeID to the peer table * net: create generic functor accessors and move vNodes to CConnman * net: move whitelist functions into CConnman * net: move nLastNodeId to CConnman * net: move nLocalHostNonce to CConnman This behavior seems to have been quite racy and broken. Move nLocalHostNonce into CNode, and check received nonces against all non-fully-connected nodes. If there's a match, assume we've connected to ourself. * net: move messageHandlerCondition to CConnman * net: move send/recv statistics to CConnman * net: move SendBufferSize/ReceiveFloodSize to CConnman * net: move nLocalServices/nRelevantServices to CConnman These are in-turn passed to CNode at connection time. This allows us to offer different services to different peers (or test the effects of doing so). * net: move semOutbound and semMasternodeOutbound to CConnman * net: SocketSendData returns written size * net: move max/max-outbound to CConnman * net: Pass best block known height into CConnman CConnman then passes the current best height into CNode at creation time. This way CConnman/CNode have no dependency on main for height, and the signals only move in one direction. This also helps to prevent identity leakage a tiny bit. Before this change, an attacker could theoretically make 2 connections on different interfaces. They would connect fully on one, and only establish the initial connection on the other. Once they receive a new block, they would relay it to your first connection, and immediately commence the version handshake on the second. Since the new block height is reflected immediately, they could attempt to learn whether the two connections were correlated. This is, of course, incredibly unlikely to work due to the small timings involved and receipt from other senders. But it doesn't hurt to lock-in nBestHeight at the time of connection, rather than letting the remote choose the time. * net: pass CClientUIInterface into CConnman * net: Drop StartNode/StopNode and use CConnman directly * net: Introduce CConnection::Options to avoid passing so many params * net: add nSendBufferMaxSize/nReceiveFloodSize to CConnection::Options * net: move vNodesDisconnected into CConnman * Made the ForEachNode* functions in src/net.cpp more pragmatic and self documenting * Convert ForEachNode* functions to take a templated function argument rather than a std::function to eliminate std::function overhead * net: move MAX_FEELER_CONNECTIONS into connman
2017-07-21 11:35:19 +02:00
if(!wallet->CommitTransaction(*newTx, *keyChange, g_connman.get(), recipients[0].fUseInstantSend ? NetMsgType::TXLOCKREQUEST : NetMsgType::TX))
2011-07-16 19:01:05 +02:00
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
Q_FOREACH(const SendCoinsRecipient &rcp, transaction.getRecipients())
{
// Don't touch the address book when we have a payment request
if (!rcp.paymentRequest.IsInitialized())
2011-07-16 19:01:05 +02:00
{
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
}
}
2011-07-16 19:01:05 +02:00
}
Q_EMIT coinsSent(wallet, rcp, transaction_array);
}
checkBalanceChanged(); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
2011-07-30 19:28:41 +02:00
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(true))
{
return Locked;
}
else if (wallet->IsLocked())
{
return UnlockedForMixingOnly;
}
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, bool fMixing)
{
if(locked)
{
// Lock
return wallet->Lock(fMixing);
}
else
{
// Unlock
return wallet->Unlock(passPhrase, fMixing);
}
}
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)
{
2016-02-14 05:51:06 +01:00
Q_UNUSED(wallet);
Q_UNUSED(hash);
Q_UNUSED(status);
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
}
2014-03-19 00:26:14 +01:00
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));
2014-03-19 00:26:14 +01:00
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));
2014-03-19 00:26:14 +01:00
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 fForMixingOnly)
{
EncryptionStatus encStatusOld = getEncryptionStatus();
// Wallet was completely locked
bool was_locked = (encStatusOld == Locked);
// Wallet was unlocked for mixing
bool was_mixing = (encStatusOld == UnlockedForMixingOnly);
// Wallet was unlocked for mixing and now user requested to fully unlock it
bool fMixingToFullRequested = !fForMixingOnly && was_mixing;
if(was_locked || fMixingToFullRequested) {
// Request UI to unlock wallet
Q_EMIT requireUnlock(fForMixingOnly);
}
EncryptionStatus encStatusNew = getEncryptionStatus();
// Wallet was locked, user requested to unlock it for mixing and failed to do so
bool fMixingUnlockFailed = fForMixingOnly && !(encStatusNew == UnlockedForMixingOnly);
// Wallet was unlocked for mixing, user requested to fully unlock it and failed
bool fMixingToFullFailed = fMixingToFullRequested && !(encStatusNew == Unlocked);
// If wallet is still locked, unlock failed or was cancelled, mark context as invalid
bool fInvalid = (encStatusNew == Locked) || fMixingUnlockFailed || fMixingToFullFailed;
// Wallet was not locked in any way or user tried to unlock it for mixing only and succeeded, keep it unlocked
bool fKeepUnlocked = !was_locked || (fForMixingOnly && !fMixingUnlockFailed);
return UnlockContext(this, !fInvalid, !fKeepUnlocked, was_mixing);
}
WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool was_locked, bool was_mixing):
wallet(wallet),
valid(valid),
was_locked(was_locked),
was_mixing(was_mixing)
{
}
WalletModel::UnlockContext::~UnlockContext()
{
if(valid && (was_locked || was_mixing))
{
wallet->setWalletLocked(true, "", was_mixing);
}
}
void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs)
{
// Transfer context; old object no longer relocks wallet
*this = rhs;
rhs.was_locked = false;
rhs.was_mixing = false;
}
2013-08-12 17:03:03 +02:00
bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
{
return wallet->GetPubKey(address, vchPubKeyOut);
}
bool WalletModel::havePrivKey(const CKeyID &address) const
{
return wallet->HaveKey(address);
}
2013-08-12 17:03:03 +02:00
// 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);
2013-08-12 17:03:03 +02:00
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);
2013-08-12 17:03:03 +02:00
vOutputs.push_back(out);
}
}
bool WalletModel::isSpent(const COutPoint& outpoint) const
{
LOCK2(cs_main, wallet->cs_wallet);
return wallet->IsSpent(outpoint.hash, outpoint.n);
}
2013-08-12 17:03:03 +02:00
// 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
2013-08-12 17:03:03 +02:00
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);
2014-07-26 21:05:11 +02:00
if (outpoint.n < out.tx->vout.size() && wallet->IsMine(out.tx->vout[outpoint.n]) == ISMINE_SPENDABLE)
vCoins.push_back(out);
2013-08-12 17:03:03 +02:00
}
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);
2013-08-12 17:03:03 +02:00
}
CTxDestination address;
if(!out.fSpendable || !ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address))
continue;
mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out);
2013-08-12 17:03:03 +02:00
}
}
bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const
{
LOCK2(cs_main, wallet->cs_wallet);
2013-08-12 17:03:03 +02:00
return wallet->IsLockedCoin(hash, n);
}
void WalletModel::lockCoin(COutPoint& output)
{
LOCK2(cs_main, wallet->cs_wallet);
2013-08-12 17:03:03 +02:00
wallet->LockCoin(output);
}
void WalletModel::unlockCoin(COutPoint& output)
{
LOCK2(cs_main, wallet->cs_wallet);
2013-08-12 17:03:03 +02:00
wallet->UnlockCoin(output);
}
void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
{
LOCK2(cs_main, wallet->cs_wallet);
2013-08-12 17:03:03 +02:00
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);
}
HD wallet (#1405) * HD wallet Minimal set of changes (no refactoring) backported from Bitcoin upstream to make HD wallets work in Dash 0.12.1.x+ * minimal bip44 (hardcoded account and change) * minimal bip39 Additional cmd-line options for new wallet: -mnemonic -mnemonicpassphrase * Do not recreate HD wallet on encryption Adjusted keypool.py test * Do not store any private keys for hd wallet besides the master one Derive all keys on the fly. Original idea/implementation - btc PR9298, backported and improved * actually use bip39 * pbkdf2 test * backport wallet-hd.py test * Allow specifying hd seed, add dumphdseed rpc, fix bugs - -hdseed cmd-line param to specify HD seed on wallet creation - dumphdseed rpc to dump HD seed - allow seed of any size - fix dumpwallet rpc bug (wasn't decrypting HD seed) - print HD seed and extended public masterkey on dumpwallet * top up keypool on HD wallet encryption * split HD chain: external/internal * add missing cs_wallet lock in init.cpp * fix `const char *` issues (use strings) * default mnemonic passphrase is an empty string in all cases * store mnemonic/mnemonicpassphrase replace dumphdseed with dumphdinfo * Add fCrypted flag to CHDChain * prepare internal structures for multiple HD accounts (plus some code cleanup) * use secure allocator for storing sensitive HD data * use secure strings for mnemonic(passphrase) * small fix in GenerateNewHDChain * use 24 words for mnemonic by default * make sure mnemonic passphrase provided by user does not exceed 256 symbols * more usage of secure allocators and memory_cleanse * code cleanup * rename: CSecureVector -> SecureVector * add missing include * fix warning in rpcdump.cpp * refactor mnemonic_check (also fix a bug) * move bip39 functions to CMnemonic * Few fixes for CMnemonic: - use `SecureVector` for data, bits, seed - `Check` should return bool * init vectors with desired size where possible
2017-05-29 13:51:40 +02:00
bool WalletModel::hdEnabled() const
{
return wallet->IsHDEnabled();
}