Skip to content

Instantly share code, notes, and snippets.

@stiucsib86
Created May 6, 2018 06:47
Show Gist options
  • Save stiucsib86/c38be1d50749c8ba741d956ddb3da2fb to your computer and use it in GitHub Desktop.
Save stiucsib86/c38be1d50749c8ba741d956ddb3da2fb to your computer and use it in GitHub Desktop.
DeepOnion src/wallet.cpp
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "txdb.h"
#include "wallet.h"
#include "walletdb.h"
#include "crypter.h"
#include "ui_interface.h"
#include "base58.h"
#include "kernel.h"
#include "coincontrol.h"
#include "smessage.h"
#include <boost/algorithm/string/replace.hpp>
using namespace std;
extern unsigned int nStakeMaxAge;
unsigned int nStakeSplitAge = 20 * 24 * 60 * 60;
int64_t nStakeCombineThreshold = 100 * COIN;
//////////////////////////////////////////////////////////////////////////////
//
// mapWallet
//
struct CompareValueOnly
{
bool operator()(const pair<int64_t, pair<const CWalletTx*, unsigned int> >& t1,
const pair<int64_t, pair<const CWalletTx*, unsigned int> >& t2) const
{
return t1.first < t2.first;
}
};
CPubKey CWallet::GenerateNewKey()
{
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
RandAddSeedPerfmon();
CKey key;
key.MakeNewKey(fCompressed);
// Compressed public keys were introduced in version 0.6.0
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
CPubKey pubkey = key.GetPubKey();
// Create new metadata
int64_t nCreationTime = GetTime();
mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime);
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
nTimeFirstKey = nCreationTime;
if (!AddKey(key))
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
return key.GetPubKey();
}
bool CWallet::AddKey(const CKey& key)
{
CPubKey pubkey = key.GetPubKey();
if (!CCryptoKeyStore::AddKey(key))
return false;
if (!fFileBacked)
return true;
if (!IsCrypted())
return CWalletDB(strWalletFile).WriteKey(pubkey, key.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]);
return true;
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
if (!fFileBacked)
return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
else
return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
return false;
}
bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
{
if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey))
nTimeFirstKey = meta.nCreateTime;
mapKeyMetadata[pubkey.GetID()] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
}
// optional setting to unlock wallet for staking only
// serves to disable the trivial sendmoney when OS account compromised
// provides no real security
bool fWalletUnlockStakingOnly = false;
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
if (!IsLocked())
return false;
CCrypter crypter;
CKeyingMaterial vMasterKey;
{
LOCK(cs_wallet);
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
if (!CCryptoKeyStore::Unlock(vMasterKey))
return false;
break;
}
UnlockStealthAddresses(vMasterKey);
SecureMsgWalletUnlocked();
return true;
}
return false;
}
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
{
bool fWasLocked = IsLocked();
{
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial vMasterKey;
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
if (CCryptoKeyStore::Unlock(vMasterKey)
&& UnlockStealthAddresses(vMasterKey))
{
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
printf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
}
}
}
return false;
}
void CWallet::SetBestChain(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile);
walletdb.WriteBestBlock(loc);
}
// This class implements an addrIncoming entry that causes pre-0.4
// clients to crash on startup if reading a private-key-encrypted wallet.
class CCorruptAddress
{
public:
IMPLEMENT_SERIALIZE
(
if (nType & SER_DISK)
READWRITE(nVersion);
)
};
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
{
if (nWalletVersion >= nVersion)
return true;
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
if (fExplicit && nVersion > nWalletMaxVersion)
nVersion = FEATURE_LATEST;
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
if (fFileBacked)
{
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
if (nWalletVersion >= 40000)
{
// Versions prior to 0.4.0 did not support the "minversion" record.
// Use a CCorruptAddress to make them crash instead.
CCorruptAddress corruptAddress;
pwalletdb->WriteSetting("addrIncoming", corruptAddress);
}
if (nWalletVersion > 40000)
pwalletdb->WriteMinVersion(nWalletVersion);
if (!pwalletdbIn)
delete pwalletdb;
}
return true;
}
bool CWallet::SetMaxVersion(int nVersion)
{
// cannot downgrade below current version
if (nWalletVersion > nVersion)
return false;
nWalletMaxVersion = nVersion;
return true;
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
if (IsCrypted())
return false;
CKeyingMaterial vMasterKey;
RandAddSeedPerfmon();
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey(nDerivationMethodIndex);
RandAddSeedPerfmon();
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
printf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
return false;
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
if (fFileBacked)
{
pwalletdbEncryption = new CWalletDB(strWalletFile);
if (!pwalletdbEncryption->TxnBegin())
return false;
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
}
if (!EncryptKeys(vMasterKey))
{
if (fFileBacked)
pwalletdbEncryption->TxnAbort();
exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet.
}
std::set<CStealthAddress>::iterator it;
for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it)
{
if (it->scan_secret.size() < 32) {
continue; // stealth address is not owned
}
// -- CStealthAddress is only sorted on spend_pubkey
CStealthAddress &sxAddr = const_cast<CStealthAddress&>(*it);
if (fDebug) {
printf("Encrypting stealth key %s\n", sxAddr.Encoded().c_str());
}
std::vector<unsigned char> vchCryptedSecret;
CSecret vchSecret;
vchSecret.resize(32);
memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32);
uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end());
if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) {
printf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded().c_str());
continue;
}
sxAddr.spend_secret = vchCryptedSecret;
pwalletdbEncryption->WriteStealthAddress(sxAddr);
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (fFileBacked)
{
if (!pwalletdbEncryption->TxnCommit())
exit(1); //We now have keys encrypted in memory, but no on disk...die to avoid confusion and let the user reload their unencrypted wallet.
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
}
Lock();
Unlock(strWalletPassphrase);
NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
CDB::Rewrite(strWalletFile);
}
NotifyStatusChanged(this);
return true;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
{
int64_t nRet = nOrderPosNext++;
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
{
CWalletDB walletdb(strWalletFile);
// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;
// Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
// would make this much faster for applications that do this a lot.
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
}
acentries.clear();
walletdb.ListAccountCreditDebit(strAccount, acentries);
BOOST_FOREACH(CAccountingEntry& entry, acentries)
{
txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
}
return txOrdered;
}
void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock)
{
// Anytime a signature is successfully verified, it's proof the outpoint is spent.
// Update the wallet spent flag if it doesn't know due to wallet.dat being
// restored from backup or the user making copies of wallet.dat.
{
LOCK(cs_wallet);
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
CWalletTx& wtx = (*mi).second;
if (txin.prevout.n >= wtx.vout.size())
printf("WalletUpdateSpent: bad wtx %s\n", wtx.GetHash().ToString().c_str());
else if (!wtx.IsSpent(txin.prevout.n) && IsMine(wtx.vout[txin.prevout.n]))
{
printf("WalletUpdateSpent found spent coin %s ONION %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
wtx.MarkSpent(txin.prevout.n);
wtx.WriteToDisk();
NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED);
}
}
}
if (fBlock)
{
uint256 hash = tx.GetHash();
map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
CWalletTx& wtx = (*mi).second;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
if (IsMine(txout))
{
wtx.MarkUnspent(&txout - &tx.vout[0]);
wtx.WriteToDisk();
NotifyTransactionChanged(this, hash, CT_UPDATED);
}
}
}
}
}
void CWallet::MarkDirty()
{
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
item.second.MarkDirty();
}
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn)
{
uint256 hash = wtxIn.GetHash();
{
LOCK(cs_wallet);
// Inserts only if not already there, returns tx inserted or tx found
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
if (fInsertedNew)
{
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext();
wtx.nTimeSmart = wtx.nTimeReceived;
if (wtxIn.hashBlock != 0)
{
if (mapBlockIndex.count(wtxIn.hashBlock))
{
unsigned int latestNow = wtx.nTimeReceived;
unsigned int latestEntry = 0;
{
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
std::list<CAccountingEntry> acentries;
TxItems txOrdered = OrderedTxItems(acentries);
for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx == &wtx)
continue;
CAccountingEntry *const pacentry = (*it).second.second;
int64_t nSmartTime;
if (pwtx)
{
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime)
nSmartTime = pwtx->nTimeReceived;
}
else
nSmartTime = pacentry->nTime;
if (nSmartTime <= latestTolerated)
{
latestEntry = nSmartTime;
if (nSmartTime > latestNow)
latestNow = nSmartTime;
break;
}
}
}
unsigned int& blocktime = mapBlockIndex[wtxIn.hashBlock]->nTime;
wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
}
else
printf("AddToWallet() : found %s in block %s not in index\n",
wtxIn.GetHash().ToString().substr(0,10).c_str(),
wtxIn.hashBlock.ToString().c_str());
}
}
bool fUpdated = false;
if (!fInsertedNew)
{
// Merge
if (wtxIn.hashBlock != 0 && wtxIn.hashBlock != wtx.hashBlock)
{
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
{
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
fUpdated |= wtx.UpdateSpent(wtxIn.vfSpent);
}
//// debug print
printf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString().substr(0,10).c_str(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
if (!wtx.WriteToDisk())
return false;
#ifndef QT_GUI
// If default receiving address gets used, replace it with a new one
CScript scriptDefaultKey;
scriptDefaultKey.SetDestination(vchDefaultKey.GetID());
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{
if (txout.scriptPubKey == scriptDefaultKey)
{
CPubKey newDefaultKey;
if (GetKeyFromPool(newDefaultKey, false))
{
SetDefaultKey(newDefaultKey);
SetAddressBookName(vchDefaultKey.GetID(), "");
}
}
}
#endif
// since AddToWallet is called directly for self-originating transactions, check for consumption of own coins
WalletUpdateSpent(wtx, (wtxIn.hashBlock != 0));
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
if ( !strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
}
return true;
}
// Add a transaction to the wallet, or update it.
// pblock is optional, but should be provided if the transaction is known to be in a block.
// If fUpdate is true, existing transactions will be updated.
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
{
uint256 hash = tx.GetHash();
{
LOCK(cs_wallet);
bool fExisted = mapWallet.count(hash);
if (fExisted && !fUpdate) {
return false;
}
mapValue_t mapNarr;
FindStealthTransactions(tx, mapNarr);
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
CWalletTx wtx(this,tx);
if (!mapNarr.empty()) {
wtx.mapValue.insert(mapNarr.begin(), mapNarr.end());
}
// Get merkle branch if transaction was found in a block
if (pblock) {
wtx.SetMerkleBranch(pblock);
}
return AddToWallet(wtx);
}
else {
WalletUpdateSpent(tx);
}
}
return false;
}
bool CWallet::EraseFromWallet(uint256 hash)
{
if (!fFileBacked)
return false;
{
LOCK(cs_wallet);
if (mapWallet.erase(hash))
CWalletDB(strWalletFile).EraseTx(hash);
}
return true;
}
bool CWallet::IsMine(const CTxIn &txin) const
{
{
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
if (IsMine(prev.vout[txin.prevout.n]))
return true;
}
}
return false;
}
int64_t CWallet::GetDebit(const CTxIn &txin) const
{
{
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
if (IsMine(prev.vout[txin.prevout.n]))
return prev.vout[txin.prevout.n].nValue;
}
}
return 0;
}
bool CWallet::IsChange(const CTxOut& txout) const
{
CTxDestination address;
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a TX_PUBKEYHASH that is mine but isn't in the address book
// is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
if (ExtractDestination(txout.scriptPubKey, address) && ::IsMine(*this, address))
{
LOCK(cs_wallet);
if (!mapAddressBook.count(address))
return true;
}
return false;
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
int CWalletTx::GetRequestCount() const
{
// Returns -1 if it wasn't being tracked
int nRequests = -1;
{
LOCK(pwallet->cs_wallet);
if (IsCoinBase() || IsCoinStake())
{
// Generated block
if (hashBlock != 0)
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
}
}
else
{
// Did anyone request this transaction?
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash());
if (mi != pwallet->mapRequestCount.end())
{
nRequests = (*mi).second;
// How about the block it's in?
if (nRequests == 0 && hashBlock != 0)
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
else
nRequests = 1; // If it's in someone else's block it must have got out
}
}
}
}
return nRequests;
}
void CWalletTx::GetAmounts(list<pair<CTxDestination, int64_t> >& listReceived,
list<pair<CTxDestination, int64_t> >& listSent, int64_t& nFee, string& strSentAccount) const
{
nFee = 0;
listReceived.clear();
listSent.clear();
strSentAccount = strFromAccount;
// Compute fee:
int64_t nDebit = GetDebit();
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
int64_t nValueOut = GetValueOut();
nFee = nDebit - nValueOut;
}
// Sent/received.
BOOST_FOREACH(const CTxOut& txout, vout)
{
// Skip special stake out
if (txout.scriptPubKey.empty())
continue;
bool fIsMine;
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > 0)
{
// Don't report 'change' txouts
if (pwallet->IsChange(txout))
continue;
fIsMine = pwallet->IsMine(txout);
}
else if (!(fIsMine = pwallet->IsMine(txout)))
continue;
// In either case, we need to get the destination address
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
{
printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString().c_str());
address = CNoDestination();
}
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
listSent.push_back(make_pair(address, txout.nValue));
// If we are receiving the output, add it as a "received" entry
if (fIsMine)
listReceived.push_back(make_pair(address, txout.nValue));
}
}
void CWalletTx::GetAccountAmounts(const string& strAccount, int64_t& nReceived,
int64_t& nSent, int64_t& nFee) const
{
nReceived = nSent = nFee = 0;
int64_t allFee;
string strSentAccount;
list<pair<CTxDestination, int64_t> > listReceived;
list<pair<CTxDestination, int64_t> > listSent;
GetAmounts(listReceived, listSent, allFee, strSentAccount);
if (strAccount == strSentAccount)
{
BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& s, listSent)
nSent += s.second;
nFee = allFee;
}
{
LOCK(pwallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CTxDestination,int64_t)& r, listReceived)
{
if (pwallet->mapAddressBook.count(r.first))
{
map<CTxDestination, string>::const_iterator mi = pwallet->mapAddressBook.find(r.first);
if (mi != pwallet->mapAddressBook.end() && (*mi).second == strAccount)
nReceived += r.second;
}
else if (strAccount.empty())
{
nReceived += r.second;
}
}
}
}
void CWalletTx::AddSupportingTransactions(CTxDB& txdb)
{
vtxPrev.clear();
const int COPY_DEPTH = 3;
if (SetMerkleBranch() < COPY_DEPTH)
{
vector<uint256> vWorkQueue;
BOOST_FOREACH(const CTxIn& txin, vin)
vWorkQueue.push_back(txin.prevout.hash);
// This critsect is OK because txdb is already open
{
LOCK(pwallet->cs_wallet);
map<uint256, const CMerkleTx*> mapWalletPrev;
set<uint256> setAlreadyDone;
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{
uint256 hash = vWorkQueue[i];
if (setAlreadyDone.count(hash))
continue;
setAlreadyDone.insert(hash);
CMerkleTx tx;
map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(hash);
if (mi != pwallet->mapWallet.end())
{
tx = (*mi).second;
BOOST_FOREACH(const CMerkleTx& txWalletPrev, (*mi).second.vtxPrev)
mapWalletPrev[txWalletPrev.GetHash()] = &txWalletPrev;
}
else if (mapWalletPrev.count(hash))
{
tx = *mapWalletPrev[hash];
}
else if (!fClient && txdb.ReadDiskTx(hash, tx))
{
;
}
else
{
printf("ERROR: AddSupportingTransactions() : unsupported transaction\n");
continue;
}
int nDepth = tx.SetMerkleBranch();
vtxPrev.push_back(tx);
if (nDepth < COPY_DEPTH)
{
BOOST_FOREACH(const CTxIn& txin, tx.vin)
vWorkQueue.push_back(txin.prevout.hash);
}
}
}
}
reverse(vtxPrev.begin(), vtxPrev.end());
}
bool CWalletTx::WriteToDisk()
{
return CWalletDB(pwallet->strWalletFile).WriteTx(GetHash(), *this);
}
// Scan the block chain (starting in pindexStart) for transactions
// from or to us. If fUpdate is true, found transactions that already
// exist in the wallet will be updated.
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
int ret = 0;
CBlockIndex* pindex = pindexStart;
{
LOCK(cs_wallet);
while (pindex)
{
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) {
pindex = pindex->pnext;
continue;
}
CBlock block;
block.ReadFromDisk(pindex, true);
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
ret++;
}
pindex = pindex->pnext;
}
}
return ret;
}
int CWallet::ScanForWalletTransaction(const uint256& hashTx)
{
CTransaction tx;
tx.ReadFromDisk(COutPoint(hashTx, 0));
if (AddToWalletIfInvolvingMe(tx, NULL, true, true))
return 1;
return 0;
}
void CWallet::ReacceptWalletTransactions()
{
CTxDB txdb("r");
bool fRepeat = true;
while (fRepeat)
{
LOCK(cs_wallet);
fRepeat = false;
vector<CDiskTxPos> vMissingTx;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
if ((wtx.IsCoinBase() && wtx.IsSpent(0)) || (wtx.IsCoinStake() && wtx.IsSpent(1)))
continue;
CTxIndex txindex;
bool fUpdated = false;
if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
{
// Update fSpent if a tx got spent somewhere else by a copy of wallet.dat
if (txindex.vSpent.size() != wtx.vout.size())
{
printf("ERROR: ReacceptWalletTransactions() : txindex.vSpent.size() %" PRIszu " != wtx.vout.size() %" PRIszu "\n", txindex.vSpent.size(), wtx.vout.size());
continue;
}
for (unsigned int i = 0; i < txindex.vSpent.size(); i++)
{
if (wtx.IsSpent(i))
continue;
if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i]))
{
wtx.MarkSpent(i);
fUpdated = true;
vMissingTx.push_back(txindex.vSpent[i]);
}
}
if (fUpdated)
{
printf("ReacceptWalletTransactions found spent coin %s ONION %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str());
wtx.MarkDirty();
wtx.WriteToDisk();
}
}
else
{
// Re-accept any txes of ours that aren't already in a block
if (!(wtx.IsCoinBase() || wtx.IsCoinStake()))
wtx.AcceptWalletTransaction(txdb);
}
}
if (!vMissingTx.empty())
{
// TODO: optimize this to scan just part of the block chain?
if (ScanForWalletTransactions(pindexGenesisBlock))
fRepeat = true; // Found missing transactions: re-do re-accept.
}
}
}
void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
{
BOOST_FOREACH(const CMerkleTx& tx, vtxPrev)
{
if (!(tx.IsCoinBase() || tx.IsCoinStake()))
{
uint256 hash = tx.GetHash();
if (!txdb.ContainsTx(hash))
RelayTransaction((CTransaction)tx, hash);
}
}
if (!(IsCoinBase() || IsCoinStake()))
{
uint256 hash = GetHash();
if (!txdb.ContainsTx(hash))
{
printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str());
RelayTransaction((CTransaction)*this, hash);
}
}
}
void CWalletTx::RelayWalletTransaction()
{
CTxDB txdb("r");
RelayWalletTransaction(txdb);
}
void CWallet::ResendWalletTransactions(bool fForce)
{
if (!fForce)
{
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
static int64_t nNextTime;
if (GetTime() < nNextTime)
return;
bool fFirst = (nNextTime == 0);
nNextTime = GetTime() + GetRand(30 * 60);
if (fFirst)
return;
// Only do it if there's been a new block since last time
static int64_t nLastTime;
if (nTimeBestReceived < nLastTime)
return;
nLastTime = GetTime();
}
// Rebroadcast any of our txes that aren't in a block yet
printf("ResendWalletTransactions()\n");
CTxDB txdb("r");
{
LOCK(cs_wallet);
// Sort them in chronological order
multimap<unsigned int, CWalletTx*> mapSorted;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
// Don't rebroadcast until it's had plenty of time that
// it should have gotten in already by now.
if (fForce || nTimeBestReceived - (int64_t)wtx.nTimeReceived > 5 * 60)
mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
}
BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
{
CWalletTx& wtx = *item.second;
if (wtx.CheckTransaction())
wtx.RelayWalletTransaction(txdb);
else
printf("ResendWalletTransactions() : CheckTransaction failed for transaction %s\n", wtx.GetHash().ToString().c_str());
}
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Actions
//
int64_t CWallet::GetBalance() const
{
int64_t nTotal = 0;
{
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
int64_t CWallet::GetUnconfirmedBalance() const
{
int64_t nTotal = 0;
{
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!pcoin->IsFinal() || !pcoin->IsTrusted())
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
int64_t CWallet::GetImmatureBalance() const
{
int64_t nTotal = 0;
{
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx& pcoin = (*it).second;
if (pcoin.IsCoinBase() && pcoin.GetBlocksToMaturity() > 0 && pcoin.IsInMainChain())
nTotal += GetCredit(pcoin);
}
}
return nTotal;
}
// populate vCoins with vector of spendable COutputs
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const
{
vCoins.clear();
{
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!pcoin->IsFinal())
continue;
if (fOnlyConfirmed && !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
if(pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0)
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue >= nMinimumInputValue &&
(!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
vCoins.push_back(COutput(pcoin, i, nDepth));
}
}
}
void CWallet::AvailableCoinsMinConf(vector<COutput>& vCoins, int nConf) const
{
vCoins.clear();
{
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!pcoin->IsFinal())
continue;
if(pcoin->GetDepthInMainChain() < nConf)
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue >= nMinimumInputValue)
vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain()));
}
}
}
static void ApproximateBestSubset(vector<pair<int64, pair<const CWalletTx*,unsigned int> > >vValue, int64 nTotalLower, int64 nTargetValue,
vector<char>& vfBest, int64& nBest, int iterations = 1000)
{
vector<char> vfIncluded;
vfBest.assign(vValue.size(), true);
nBest = nTotalLower;
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(vValue.size(), false);
int64 nTotal = 0;
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
{
for (unsigned int i = 0; i < vValue.size(); i++)
{
if (nPass == 0 ? rand() % 2 : !vfIncluded[i])
{
nTotal += vValue[i].first;
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
fReachedTarget = true;
if (nTotal < nBest)
{
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].first;
vfIncluded[i] = false;
}
}
}
}
}
}
// DeepOnion: total coins staked (non-spendable until maturity)
int64_t CWallet::GetStake() const
{
int64_t nTotal = 0;
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0)
nTotal += CWallet::GetCredit(*pcoin);
}
return nTotal;
}
int64_t CWallet::GetNewMint() const
{
int64_t nTotal = 0;
LOCK(cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0)
nTotal += CWallet::GetCredit(*pcoin);
}
return nTotal;
}
bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, vector<COutput> vCoins, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const
{
setCoinsRet.clear();
nValueRet = 0;
// List of values less than target
pair<int64, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
coinLowestLarger.first = std::numeric_limits<int64>::max();
coinLowestLarger.second.first = NULL;
vector<pair<int64, pair<const CWalletTx*,unsigned int> > > vValue;
int64 nTotalLower = 0;
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
BOOST_FOREACH(COutput output, vCoins)
{
const CWalletTx *pcoin = output.tx;
if (output.nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs))
continue;
int i = output.i;
// Follow the timestamp rules
if (pcoin->nTime > nSpendTime)
continue;
int64 n = pcoin->vout[i].nValue;
pair<int64,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
if (n == nTargetValue)
{
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
return true;
}
else if (n < nTargetValue + CENT)
{
vValue.push_back(coin);
nTotalLower += n;
}
else if (n < coinLowestLarger.first)
{
coinLowestLarger = coin;
}
}
if (nTotalLower == nTargetValue)
{
for (unsigned int i = 0; i < vValue.size(); ++i)
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
return true;
}
if (nTotalLower < nTargetValue)
{
if (coinLowestLarger.second.first == NULL)
return false;
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
return true;
}
// Solve subset sum by stochastic approximation
sort(vValue.rbegin(), vValue.rend(), CompareValueOnly());
vector<char> vfBest;
int64 nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
// or the next bigger coin is closer), return the bigger coin
if (coinLowestLarger.second.first &&
((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest))
{
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
}
else {
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
if (fDebug && GetBoolArg("-printpriority"))
{
//// debug print
printf("SelectCoins() best subset: ");
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
printf("%s ", FormatMoney(vValue[i].first).c_str());
printf("total %s\n", FormatMoney(nBest).c_str());
}
}
return true;
}
bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet, const CCoinControl* coinControl) const
{
vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coinControl && coinControl->HasSelected())
{
BOOST_FOREACH(const COutput& out, vCoins)
{
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
return (SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 6, vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue, nSpendTime, 1, 1, vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue, nSpendTime, 0, 1, vCoins, setCoinsRet, nValueRet));
}
// Select some coins without random shuffle or best subset approximation
bool CWallet::SelectCoinsSimple(int64_t nTargetValue, unsigned int nSpendTime, int nMinConf, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const
{
vector<COutput> vCoins;
AvailableCoinsMinConf(vCoins, nMinConf);
setCoinsRet.clear();
nValueRet = 0;
BOOST_FOREACH(COutput output, vCoins)
{
const CWalletTx *pcoin = output.tx;
int i = output.i;
// Stop if we've chosen enough inputs
if (nValueRet >= nTargetValue)
break;
// Follow the timestamp rules
if (pcoin->nTime > nSpendTime)
continue;
int64_t n = pcoin->vout[i].nValue;
pair<int64_t,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
if (n >= nTargetValue)
{
// If input value is greater or equal to target then simply insert
// it into the current subset and exit
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
break;
}
else if (n < nTargetValue + CENT)
{
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
}
}
return true;
}
bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> > &vecSend, CWalletTx &wtxNew, CReserveKey &reservekey, int64 &nFeeRet, int32_t &nChangePos, const CCoinControl *coinControl)
{
int64 nValue = 0;
BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend)
{
if (nValue < 0) {
printf("CreateTransaction() : nValue < 0 \n");
return false;
}
nValue += s.second;
}
if (vecSend.empty() || nValue < 0) {
printf("CreateTransaction() : vecSend is empty or nValue < 0 \n");
return false;
}
wtxNew.BindWallet(this);
{
LOCK2(cs_main, cs_wallet);
// txdb must be opened before the mapWallet lock
CTxDB txdb("r");
{
nFeeRet = nTransactionFee;
while (true)
{
wtxNew.vin.clear();
wtxNew.vout.clear();
wtxNew.fFromMe = true;
int64 nTotalValue = nValue + nFeeRet;
double dPriority = 0;
// vouts to the payees
BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend)
{
wtxNew.vout.push_back(CTxOut(s.second, s.first));
}
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
{
printf("CreateTransaction() : SelectCoins Failed \n");
return false;
}
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
int64 nCredit = pcoin.first->vout[pcoin.second].nValue;
dPriority += (double)nCredit * pcoin.first->GetDepthInMainChain();
}
int64 nChange = nValueIn - nValue - nFeeRet;
// if sub-cent change is required, the fee must be raised to at least Min Tx Fee
// or until nChange becomes zero
// NOTE: this depends on the exact behaviour of GetMinFee
if (nFeeRet < GetMinTxFee() && nChange > 0 && nChange < CENT)
{
int64 nMoveToFee = min(nChange, GetMinTxFee() - nFeeRet);
nChange -= nMoveToFee;
nFeeRet += nMoveToFee;
}
// DeepOnion: sub-cent change is moved to fee
if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT)
{
nFeeRet += nChange;
nChange = 0;
}
if (nChange > 0)
{
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
scriptChange.SetDestination(coinControl->destChange);
// no coin control: send change to newly generated address
else
{
// Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change.
// If we reused the old key, it would be possible to add code to look for and
// rediscover unknown transactions that were written with keys of ours to recover
// post-backup change.
// Reserve a new key pair from key pool
CPubKey vchPubKey = reservekey.GetReservedKey();
scriptChange.SetDestination(vchPubKey.GetID());
}
// Insert change txn at random position:
vector<CTxOut>::iterator position = wtxNew.vout.begin() + GetRandInt(wtxNew.vout.size() + 1);
// -- don't put change output between value and narration outputs
if (position > wtxNew.vout.begin() && position < wtxNew.vout.end())
{
while (position > wtxNew.vout.begin())
{
if (position->nValue != 0)
break;
position--;
};
};
wtxNew.vout.insert(position, CTxOut(nChange, scriptChange));
nChangePos = std::distance(wtxNew.vout.begin(), position);
}
else
reservekey.ReturnKey();
// Fill vin
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
wtxNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second));
// Sign
int nIn = 0;
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
if (!SignSignature(*this, *coin.first, wtxNew, nIn++)) {
printf("CreateTransaction() : Sign Signature Failed \n");
return false;
}
// Limit size
unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
if (nBytes >= MAX_BLOCK_SIZE_GEN/5) {
printf("CreateTransaction() : Transaction too large \n");
return false;
}
dPriority /= nBytes;
// Check that enough fee is included
int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000);
int64 nMinFee = wtxNew.GetMinFee(1, GMF_SEND, nBytes);
if (nFeeRet < max(nPayFee, nMinFee))
{
nFeeRet = max(nPayFee, nMinFee);
continue;
}
// Fill vtxPrev by copying from previous transactions vtxPrev
wtxNew.AddSupportingTransactions(txdb);
wtxNew.fTimeReceivedIsTxTime = true;
break;
}
}
}
return true;
}
bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl)
{
vector< pair<CScript, int64_t> > vecSend;
vecSend.push_back(make_pair(scriptPubKey, nValue));
if (sNarr.length() > 0)
{
std::vector<uint8_t> vNarr(sNarr.c_str(), sNarr.c_str() + sNarr.length());
std::vector<uint8_t> vNDesc;
vNDesc.resize(2);
vNDesc[0] = 'n';
vNDesc[1] = 'p';
CScript scriptN = CScript() << OP_RETURN << vNDesc << OP_RETURN << vNarr;
vecSend.push_back(make_pair(scriptN, 0));
}
// -- CreateTransaction won't place change between value and narr output.
// narration output will be for preceding output
// -- narration will be added to mapValue later in FindStealthTransactions From CommitTransaction
int nChangePos;
bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, coinControl);
// -- narration will be added to mapValue later in FindStealthTransactions From CommitTransaction
return rv;
//return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, coinControl);
}
// NovaCoin: get current stake weight
bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64_t& nMinWeight, uint64_t& nMaxWeight, uint64_t& nWeight)
{
// Choose coins to use
int64_t nBalance = GetBalance();
if (nBalance <= nReserveBalance)
return false;
vector<const CWalletTx*> vwtxPrev;
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64_t nValueIn = 0;
if (!SelectCoinsSimple(nBalance - nReserveBalance, GetTime(), nCoinbaseMaturity + 10, setCoins, nValueIn))
return false;
if (setCoins.empty())
return false;
CTxDB txdb("r");
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CTxIndex txindex;
{
LOCK2(cs_main, cs_wallet);
if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
continue;
}
int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)GetTime());
CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60);
// Weight is greater than zero
if (nTimeWeight > 0)
{
nWeight += bnCoinDayWeight.getuint64();
}
// Weight is greater than zero, but the maximum value isn't reached yet
if (nTimeWeight > 0 && nTimeWeight < nStakeMaxAge)
{
nMinWeight += bnCoinDayWeight.getuint64();
}
// Maximum weight was reached
if (nTimeWeight == nStakeMaxAge)
{
nMaxWeight += bnCoinDayWeight.getuint64();
}
}
return true;
}
bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64_t nSearchInterval, int64_t nFees, CTransaction& txNew, CKey& key)
{
CBlockIndex* pindexPrev = pindexBest;
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
txNew.vin.clear();
txNew.vout.clear();
// Mark coin stake transaction
CScript scriptEmpty;
scriptEmpty.clear();
txNew.vout.push_back(CTxOut(0, scriptEmpty));
// Choose coins to use
int64_t nBalance = GetBalance();
if (nBalance <= nReserveBalance)
return false;
vector<const CWalletTx*> vwtxPrev;
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64_t nValueIn = 0;
// Select coins with suitable depth
if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity + 10, setCoins, nValueIn))
return false;
if (setCoins.empty())
return false;
int64_t nCredit = 0;
CScript scriptPubKeyKernel;
CTxDB txdb("r");
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CTxIndex txindex;
{
LOCK2(cs_main, cs_wallet);
if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
continue;
}
// Read block header
CBlock block;
{
LOCK2(cs_main, cs_wallet);
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
continue;
}
static int nMaxStakeSearchInterval = 60;
if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
continue; // only count coins meeting min age requirement
bool fKernelFound = false;
for (unsigned int n=0; n<min(nSearchInterval,(int64_t)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown && pindexPrev == pindexBest; n++)
{
// Search backward in time from the given txNew timestamp
// Search nSearchInterval seconds back up to nMaxStakeSearchInterval
uint256 hashProofOfStake = 0, targetProofOfStake = 0;
COutPoint prevoutStake = COutPoint(pcoin.first->GetHash(), pcoin.second);
if (CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake))
{
// Found a kernel
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : kernel found\n");
vector<valtype> vSolutions;
txnouttype whichType;
CScript scriptPubKeyOut;
scriptPubKeyKernel = pcoin.first->vout[pcoin.second].scriptPubKey;
if (!Solver(scriptPubKeyKernel, whichType, vSolutions))
{
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : failed to parse kernel\n");
break;
}
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : parsed kernel type=%d\n", whichType);
if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH)
{
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : no support for kernel type=%d\n", whichType);
break; // only support pay to public key and pay to address
}
if (whichType == TX_PUBKEYHASH) // pay to address type
{
// convert to pay to public key type
if (!keystore.GetKey(uint160(vSolutions[0]), key))
{
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType);
break; // unable to find corresponding public key
}
scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG;
}
if (whichType == TX_PUBKEY)
{
valtype& vchPubKey = vSolutions[0];
if (!keystore.GetKey(Hash160(vchPubKey), key))
{
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType);
break; // unable to find corresponding public key
}
if (key.GetPubKey() != vchPubKey)
{
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : invalid key for kernel type=%d\n", whichType);
break; // keys mismatch
}
scriptPubKeyOut = scriptPubKeyKernel;
}
txNew.nTime -= n;
txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
nCredit += pcoin.first->vout[pcoin.second].nValue;
vwtxPrev.push_back(pcoin.first);
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut));
if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) < nStakeSplitAge)
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : added kernel type=%d\n", whichType);
fKernelFound = true;
break;
}
}
if (fKernelFound || fShutdown)
break; // if kernel is found stop searching
}
if (nCredit == 0 || nCredit > nBalance - nReserveBalance)
return false;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
// Attempt to add more inputs
// Only add coins of the same key/address as kernel
if (txNew.vout.size() == 2 && ((pcoin.first->vout[pcoin.second].scriptPubKey == scriptPubKeyKernel || pcoin.first->vout[pcoin.second].scriptPubKey == txNew.vout[1].scriptPubKey))
&& pcoin.first->GetHash() != txNew.vin[0].prevout.hash)
{
int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)txNew.nTime);
// Stop adding more inputs if already too many inputs
if (txNew.vin.size() >= 100)
break;
// Stop adding more inputs if value is already pretty significant
if (nCredit >= nStakeCombineThreshold)
break;
// Stop adding inputs if reached reserve limit
if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nReserveBalance)
break;
// Do not add additional significant input
if (pcoin.first->vout[pcoin.second].nValue >= nStakeCombineThreshold)
continue;
// Do not add input that is still too young
if (nTimeWeight < nStakeMinAge)
continue;
txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
nCredit += pcoin.first->vout[pcoin.second].nValue;
vwtxPrev.push_back(pcoin.first);
}
}
// Calculate coin age reward
{
uint64_t nCoinAge;
CTxDB txdb("r");
if (!txNew.GetCoinAge(txdb, nCoinAge))
return error("CreateCoinStake : failed to calculate coin age");
int64_t nReward = GetProofOfStakeReward(nCoinAge, pindexBest);
if (nReward <= 0)
return false;
nCredit += nReward;
}
// Set output amount
if (txNew.vout.size() == 3)
{
txNew.vout[1].nValue = (nCredit / 2 / CENT) * CENT;
txNew.vout[2].nValue = nCredit - txNew.vout[1].nValue;
}
else
txNew.vout[1].nValue = nCredit;
// Sign
int nIn = 0;
BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev)
{
if (!SignSignature(*this, *pcoin, txNew, nIn++))
return error("CreateCoinStake : failed to sign coinstake");
}
// Limit size
unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION);
if (nBytes >= MAX_BLOCK_SIZE_GEN/5)
return error("CreateCoinStake : exceeded coinstake size limit");
// Successfully generated coinstake
return true;
}
// Call after CreateTransaction unless you want to abort
bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
{
mapValue_t mapNarr;
FindStealthTransactions(wtxNew, mapNarr);
if (!mapNarr.empty())
{
BOOST_FOREACH(const PAIRTYPE(string,string)& item, mapNarr)
wtxNew.mapValue[item.first] = item.second;
};
{
LOCK2(cs_main, cs_wallet);
printf("CommitTransaction:\n%s", wtxNew.ToString().c_str());
{
// This is only to keep the database open to defeat the auto-flush for the
// duration of this scope. This is the only place where this optimization
// maybe makes sense; please don't do it anywhere else.
CWalletDB* pwalletdb = fFileBacked ? new CWalletDB(strWalletFile,"r") : NULL;
// Take key pair from key pool so it won't be used again
reservekey.KeepKey();
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
AddToWallet(wtxNew);
// Mark old coins as spent
set<CWalletTx*> setCoins;
BOOST_FOREACH(const CTxIn& txin, wtxNew.vin)
{
CWalletTx &coin = mapWallet[txin.prevout.hash];
coin.BindWallet(this);
coin.MarkSpent(txin.prevout.n);
coin.WriteToDisk();
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
if (fFileBacked)
delete pwalletdb;
}
// Track how many getdata requests our transaction gets
mapRequestCount[wtxNew.GetHash()] = 0;
// Broadcast
if (!wtxNew.AcceptToMemoryPool())
{
// This must not fail. The transaction has already been signed and recorded.
printf("CommitTransaction() : Error: Transaction not valid\n");
return false;
}
wtxNew.RelayWalletTransaction();
}
return true;
}
string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee)
{
CReserveKey reservekey(this);
int64 nFeeRequired;
if (IsLocked())
{
string strError = _("Error: Wallet locked, unable to create transaction ");
printf("SendMoney() : %s", strError.c_str());
return strError;
}
if (fWalletUnlockStakingOnly)
{
string strError = _("Error: Wallet unlocked for block staking only, unable to create transaction.");
printf("SendMoney() : %s", strError.c_str());
return strError;
}
if (!CreateTransaction(scriptPubKey, nValue, sNarr, wtxNew, reservekey, nFeeRequired))
{
string strError;
if (nValue + nFeeRequired > GetBalance())
strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str());
else
strError = _("Error: Transaction creation failed ");
printf("SendMoney() : %s", strError.c_str());
return strError;
}
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
return "";
}
string CWallet::SendMoneyToDestination(const CTxDestination& address, int64 nValue, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee)
{
// Check amount
if (nValue <= 0)
return _("Invalid amount");
if (nValue + nTransactionFee > GetBalance())
return _("Insufficient funds");
if (sNarr.length() > 24)
return _("Narration must be 24 characters or less.");
// Parse Bitcoin address
CScript scriptPubKey;
scriptPubKey.SetDestination(address);
return SendMoney(scriptPubKey, nValue, sNarr, wtxNew, fAskFee);
}
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (!fFileBacked)
return DB_LOAD_OK;
fFirstRunRet = false;
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// the requires a new key.
}
}
if (nLoadWalletRet != DB_LOAD_OK)
return nLoadWalletRet;
fFirstRunRet = !vchDefaultKey.IsValid();
NewThread(ThreadFlushWalletDB, &strWalletFile);
return DB_LOAD_OK;
}
bool CWallet::SetAddressBookName(const CTxDestination& address, const string& strName)
{
bool fOwned;
ChangeType nMode;
{
LOCK(cs_wallet); // mapAddressBook
std::map<CTxDestination, std::string>::iterator mi = mapAddressBook.find(address);
nMode = (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED;
fOwned = ::IsMine(*this, address);
mapAddressBook[address] = strName;
}
if (fOwned)
{
const CBitcoinAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), strName, nMode);
}
NotifyAddressBookChanged(this, address, strName, fOwned, nMode);
if (!fFileBacked)
return false;
return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName);
}
bool CWallet::DelAddressBookName(const CTxDestination& address)
{
{
LOCK(cs_wallet); // mapAddressBook
mapAddressBook.erase(address);
}
bool fOwned = ::IsMine(*this, address);
string sName = "";
if (fOwned)
{
const CBitcoinAddress& caddress = address;
SecureMsgWalletKeyChanged(caddress.ToString(), sName, CT_DELETED);
}
NotifyAddressBookChanged(this, address, "", fOwned, CT_DELETED);
if (!fFileBacked)
return false;
return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString());
}
void CWallet::PrintWallet(const CBlock& block)
{
{
LOCK(cs_wallet);
if (block.IsProofOfWork() && mapWallet.count(block.vtx[0].GetHash()))
{
CWalletTx& wtx = mapWallet[block.vtx[0].GetHash()];
printf(" mine: %d %d %" PRId64 "", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit());
}
if (block.IsProofOfStake() && mapWallet.count(block.vtx[1].GetHash()))
{
CWalletTx& wtx = mapWallet[block.vtx[1].GetHash()];
printf(" stake: %d %d %" PRId64 "", wtx.GetDepthInMainChain(), wtx.GetBlocksToMaturity(), wtx.GetCredit());
}
}
printf("\n");
}
bool CWallet::GetTransaction(const uint256 &hashTx, CWalletTx& wtx)
{
{
LOCK(cs_wallet);
map<uint256, CWalletTx>::iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end())
{
wtx = (*mi).second;
return true;
}
}
return false;
}
bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
{
if (fFileBacked)
{
if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey))
return false;
}
vchDefaultKey = vchPubKey;
return true;
}
bool GetWalletFile(CWallet* pwallet, string &strWalletFileOut)
{
if (!pwallet->fFileBacked)
return false;
strWalletFileOut = pwallet->strWalletFile;
return true;
}
//
// Mark old keypool keys as used,
// and generate all new keys
//
bool CWallet::NewKeyPool()
{
{
LOCK(cs_wallet);
CWalletDB walletdb(strWalletFile);
BOOST_FOREACH(int64_t nIndex, setKeyPool)
walletdb.ErasePool(nIndex);
setKeyPool.clear();
if (IsLocked())
return false;
int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0);
for (int i = 0; i < nKeys; i++)
{
int64_t nIndex = i+1;
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
setKeyPool.insert(nIndex);
}
printf("CWallet::NewKeyPool wrote %" PRId64 " new keys\n", nKeys);
}
return true;
}
bool CWallet::TopUpKeyPool(unsigned int nSize)
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
CWalletDB walletdb(strWalletFile);
// Top up key pool
unsigned int nTargetSize;
if (nSize > 0)
nTargetSize = nSize;
else
nTargetSize = max(GetArg("-keypool", 100), (int64_t)0);
while (setKeyPool.size() < (nTargetSize + 1))
{
int64_t nEnd = 1;
if (!setKeyPool.empty())
nEnd = *(--setKeyPool.end()) + 1;
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
throw runtime_error("TopUpKeyPool() : writing generated key failed");
setKeyPool.insert(nEnd);
printf("keypool added key %" PRId64 ", size=%" PRIszu "\n", nEnd, setKeyPool.size());
}
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
{
LOCK(cs_wallet);
if (!IsLocked())
TopUpKeyPool();
// Get the oldest key
if(setKeyPool.empty())
return;
CWalletDB walletdb(strWalletFile);
nIndex = *(setKeyPool.begin());
setKeyPool.erase(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool))
throw runtime_error("ReserveKeyFromKeyPool() : read failed");
if (!HaveKey(keypool.vchPubKey.GetID()))
throw runtime_error("ReserveKeyFromKeyPool() : unknown key in key pool");
assert(keypool.vchPubKey.IsValid());
if (fDebug && GetBoolArg("-printkeypool"))
printf("keypool reserve %" PRId64 "\n", nIndex);
}
}
int64_t CWallet::AddReserveKey(const CKeyPool& keypool)
{
{
LOCK2(cs_main, cs_wallet);
CWalletDB walletdb(strWalletFile);
int64_t nIndex = 1 + *(--setKeyPool.end());
if (!walletdb.WritePool(nIndex, keypool))
throw runtime_error("AddReserveKey() : writing added key failed");
setKeyPool.insert(nIndex);
return nIndex;
}
return -1;
}
void CWallet::KeepKey(int64_t nIndex)
{
// Remove from key pool
if (fFileBacked)
{
CWalletDB walletdb(strWalletFile);
walletdb.ErasePool(nIndex);
}
if(fDebug)
printf("keypool keep %" PRId64 "\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex)
{
// Return to key pool
{
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
}
if(fDebug)
printf("keypool return %" PRId64 "\n", nIndex);
}
bool CWallet::GetKeyFromPool(CPubKey& result, bool fAllowReuse)
{
int64_t nIndex = 0;
CKeyPool keypool;
{
LOCK(cs_wallet);
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
{
if (fAllowReuse && vchDefaultKey.IsValid())
{
result = vchDefaultKey;
return true;
}
if (IsLocked()) return false;
result = GenerateNewKey();
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
}
return true;
}
int64_t CWallet::GetOldestKeyPoolTime()
{
int64_t nIndex = 0;
CKeyPool keypool;
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
return GetTime();
ReturnKey(nIndex);
return keypool.nTime;
}
std::map<CTxDestination, int64_t> CWallet::GetAddressBalances()
{
map<CTxDestination, int64_t> balances;
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (!pcoin->IsFinal() || !pcoin->IsTrusted())
continue;
if ((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < (pcoin->IsFromMe() ? 0 : 1))
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
{
CTxDestination addr;
if (!IsMine(pcoin->vout[i]))
continue;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr))
continue;
int64_t n = pcoin->IsSpent(i) ? 0 : pcoin->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
balances[addr] += n;
}
}
}
return balances;
}
set< set<CTxDestination> > CWallet::GetAddressGroupings()
{
set< set<CTxDestination> > groupings;
set<CTxDestination> grouping;
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (pcoin->vin.size() > 0 && IsMine(pcoin->vin[0]))
{
// group all input addresses with each other
BOOST_FOREACH(CTxIn txin, pcoin->vin)
{
CTxDestination address;
if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
}
// group change with input addresses
BOOST_FOREACH(CTxOut txout, pcoin->vout)
if (IsChange(txout))
{
CWalletTx tx = mapWallet[pcoin->vin[0].prevout.hash];
CTxDestination txoutAddr;
if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
continue;
grouping.insert(txoutAddr);
}
groupings.insert(grouping);
grouping.clear();
}
// group lone addrs by themselves
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
if (IsMine(pcoin->vout[i]))
{
CTxDestination address;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address))
continue;
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
set< set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
map< CTxDestination, set<CTxDestination>* > setmap; // map addresses to the unique group containing it
BOOST_FOREACH(set<CTxDestination> grouping, groupings)
{
// make a set of all the groups hit by this new group
set< set<CTxDestination>* > hits;
map< CTxDestination, set<CTxDestination>* >::iterator it;
BOOST_FOREACH(CTxDestination address, grouping)
if ((it = setmap.find(address)) != setmap.end())
hits.insert((*it).second);
// merge all hit groups into a new single group and delete old groups
set<CTxDestination>* merged = new set<CTxDestination>(grouping);
BOOST_FOREACH(set<CTxDestination>* hit, hits)
{
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// update setmap
BOOST_FOREACH(CTxDestination element, *merged)
setmap[element] = merged;
}
set< set<CTxDestination> > ret;
BOOST_FOREACH(set<CTxDestination>* uniqueGrouping, uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
// DeepOnion: check 'spent' consistency between wallet and txindex
// DeepOnion: fix wallet spent state according to txindex
void CWallet::FixSpentCoins(int& nMismatchFound, int64_t& nBalanceInQuestion, bool fCheckOnly)
{
nMismatchFound = 0;
nBalanceInQuestion = 0;
LOCK(cs_wallet);
vector<CWalletTx*> vCoins;
vCoins.reserve(mapWallet.size());
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
vCoins.push_back(&(*it).second);
CTxDB txdb("r");
BOOST_FOREACH(CWalletTx* pcoin, vCoins)
{
// Find the corresponding transaction index
CTxIndex txindex;
if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex))
continue;
for (unsigned int n=0; n < pcoin->vout.size(); n++)
{
if (IsMine(pcoin->vout[n]) && pcoin->IsSpent(n) && (txindex.vSpent.size() <= n || txindex.vSpent[n].IsNull()))
{
printf("FixSpentCoins found lost coin %s ONION %s[%d], %s\n",
FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
if (!fCheckOnly)
{
pcoin->MarkUnspent(n);
pcoin->WriteToDisk();
}
}
else if (IsMine(pcoin->vout[n]) && !pcoin->IsSpent(n) && (txindex.vSpent.size() > n && !txindex.vSpent[n].IsNull()))
{
printf("FixSpentCoins found spent coin %s ONION %s[%d], %s\n",
FormatMoney(pcoin->vout[n].nValue).c_str(), pcoin->GetHash().ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
nMismatchFound++;
nBalanceInQuestion += pcoin->vout[n].nValue;
if (!fCheckOnly)
{
pcoin->MarkSpent(n);
pcoin->WriteToDisk();
}
}
}
}
}
// DeepOnion: disable transaction (only for coinstake)
void CWallet::DisableTransaction(const CTransaction &tx)
{
if (!tx.IsCoinStake() || !IsFromMe(tx))
return; // only disconnecting coinstake requires marking input unspent
LOCK(cs_wallet);
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
map<uint256, CWalletTx>::iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size() && IsMine(prev.vout[txin.prevout.n]))
{
prev.MarkUnspent(txin.prevout.n);
prev.WriteToDisk();
}
}
}
}
CPubKey CReserveKey::GetReservedKey()
{
if (nIndex == -1)
{
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex != -1)
vchPubKey = keypool.vchPubKey;
else
{
printf("CReserveKey::GetReservedKey(): Warning: Using default key instead of a new key, top up your keypool!");
vchPubKey = pwallet->vchDefaultKey;
}
}
assert(vchPubKey.IsValid());
return vchPubKey;
}
void CReserveKey::KeepKey()
{
if (nIndex != -1)
pwallet->KeepKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CReserveKey::ReturnKey()
{
if (nIndex != -1)
pwallet->ReturnKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const
{
setAddress.clear();
CWalletDB walletdb(strWalletFile);
LOCK2(cs_main, cs_wallet);
BOOST_FOREACH(const int64_t& id, setKeyPool)
{
CKeyPool keypool;
if (!walletdb.ReadPool(id, keypool))
throw runtime_error("GetAllReserveKeyHashes() : read failed");
assert(keypool.vchPubKey.IsValid());
CKeyID keyID = keypool.vchPubKey.GetID();
if (!HaveKey(keyID))
throw runtime_error("GetAllReserveKeyHashes() : unknown key in key pool");
setAddress.insert(keyID);
}
}
void CWallet::UpdatedTransaction(const uint256 &hashTx)
{
{
LOCK(cs_wallet);
// Only notify UI if this transaction is in this wallet
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end())
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
}
}
void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
mapKeyBirth.clear();
// get birth times for keys with metadata
for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++)
if (it->second.nCreateTime)
mapKeyBirth[it->first] = it->second.nCreateTime;
// map in which we'll infer heights of other keys
CBlockIndex *pindexMax = FindBlockByHeight(std::max(0, nBestHeight - 144)); // the tip can be reorganised; use a 144-block safety margin
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
std::set<CKeyID> setKeys;
GetKeys(setKeys);
BOOST_FOREACH(const CKeyID &keyid, setKeys) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax;
}
setKeys.clear();
// if there are no such keys, we're done
if (mapKeyFirstBlock.empty())
return;
// find first block that affects those keys, if there are any left
std::vector<CKeyID> vAffected;
for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) {
// iterate over all wallet transactions...
const CWalletTx &wtx = (*it).second;
std::map<uint256, CBlockIndex*>::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && blit->second->IsInMainChain()) {
// ... which are already in a block
int nHeight = blit->second->nHeight;
BOOST_FOREACH(const CTxOut &txout, wtx.vout) {
// iterate over all their outputs
::ExtractAffectedKeys(*this, txout.scriptPubKey, vAffected);
BOOST_FOREACH(const CKeyID &keyid, vAffected) {
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
rit->second = blit->second;
}
vAffected.clear();
}
}
}
// Extract block timestamps for those keys
for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off
}
int CWallet::GetBestBlockHeight()
{
return nBestHeight;
}
bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CStealthAddress& sxAddr)
{
ec_secret scan_secret;
ec_secret spend_secret;
if (GenerateRandomSecret(scan_secret) != 0
|| GenerateRandomSecret(spend_secret) != 0)
{
sError = "GenerateRandomSecret failed.";
printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str());
return false;
}
ec_point scan_pubkey, spend_pubkey;
if (SecretToPublicKey(scan_secret, scan_pubkey) != 0)
{
sError = "Could not get scan public key.";
printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str());
return false;
}
if (SecretToPublicKey(spend_secret, spend_pubkey) != 0)
{
sError = "Could not get spend public key.";
printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str());
return false;
}
if (fDebug)
{
printf("getnewstealthaddress: ");
printf("scan_pubkey ");
for (uint32_t i = 0; i < scan_pubkey.size(); ++i)
printf("%02x", scan_pubkey[i]);
printf("\n");
printf("spend_pubkey ");
for (uint32_t i = 0; i < spend_pubkey.size(); ++i)
printf("%02x", spend_pubkey[i]);
printf("\n");
}
sxAddr.label = sLabel;
sxAddr.scan_pubkey = scan_pubkey;
sxAddr.spend_pubkey = spend_pubkey;
sxAddr.scan_secret.resize(32);
memcpy(&sxAddr.scan_secret[0], &scan_secret.e[0], 32);
sxAddr.spend_secret.resize(32);
memcpy(&sxAddr.spend_secret[0], &spend_secret.e[0], 32);
return true;
}
bool CWallet::AddStealthAddress(CStealthAddress& sxAddr)
{
LOCK(cs_wallet);
// must add before changing spend_secret
stealthAddresses.insert(sxAddr);
bool fOwned = sxAddr.scan_secret.size() == ec_secret_size;
if (fOwned)
{
// -- owned addresses can only be added when wallet is unlocked
if (IsLocked())
{
printf("Error: CWallet::AddStealthAddress wallet must be unlocked.\n");
stealthAddresses.erase(sxAddr);
return false;
}
if (IsCrypted())
{
std::vector<unsigned char> vchCryptedSecret;
CSecret vchSecret;
vchSecret.resize(32);
memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32);
uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end());
if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret))
{
printf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded().c_str());
stealthAddresses.erase(sxAddr);
return false;
}
sxAddr.spend_secret = vchCryptedSecret;
}
}
bool rv = CWalletDB(strWalletFile).WriteStealthAddress(sxAddr);
if (rv)
NotifyAddressBookChanged(this, sxAddr, sxAddr.label, fOwned, CT_NEW);
return rv;
}
bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn)
{
// -- decrypt spend_secret of stealth addresses
std::set<CStealthAddress>::iterator it;
for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it)
{
if (it->scan_secret.size() < 32)
continue; // stealth address is not owned
// -- CStealthAddress are only sorted on spend_pubkey
CStealthAddress &sxAddr = const_cast<CStealthAddress&>(*it);
if (fDebug)
printf("Decrypting stealth key %s\n", sxAddr.Encoded().c_str());
CSecret vchSecret;
uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end());
if(!DecryptSecret(vMasterKeyIn, sxAddr.spend_secret, iv, vchSecret)
|| vchSecret.size() != 32)
{
printf("Error: Failed decrypting stealth key %s\n", sxAddr.Encoded().c_str());
continue;
}
ec_secret testSecret;
memcpy(&testSecret.e[0], &vchSecret[0], 32);
ec_point pkSpendTest;
if (SecretToPublicKey(testSecret, pkSpendTest) != 0
|| pkSpendTest != sxAddr.spend_pubkey)
{
printf("Error: Failed decrypting stealth key, public key mismatch %s\n", sxAddr.Encoded().c_str());
continue;
}
sxAddr.spend_secret.resize(32);
memcpy(&sxAddr.spend_secret[0], &vchSecret[0], 32);
}
CryptedKeyMap::iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi)
{
CPubKey &pubKey = (*mi).second.first;
std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
if (vchCryptedSecret.size() != 0)
continue;
CKeyID ckid = pubKey.GetID();
CBitcoinAddress addr(ckid);
StealthKeyMetaMap::iterator mi = mapStealthKeyMeta.find(ckid);
if (mi == mapStealthKeyMeta.end())
{
printf("Error: No metadata found to add secret for %s\n", addr.ToString().c_str());
continue;
}
CStealthKeyMetadata& sxKeyMeta = mi->second;
CStealthAddress sxFind;
sxFind.scan_pubkey = sxKeyMeta.pkScan.Raw();
std::set<CStealthAddress>::iterator si = stealthAddresses.find(sxFind);
if (si == stealthAddresses.end())
{
printf("No stealth key found to add secret for %s\n", addr.ToString().c_str());
continue;
}
if (fDebug)
printf("Expanding secret for %s\n", addr.ToString().c_str());
ec_secret sSpendR;
ec_secret sSpend;
ec_secret sScan;
if (si->spend_secret.size() != ec_secret_size
|| si->scan_secret.size() != ec_secret_size)
{
printf("Stealth address has no secret key for %s\n", addr.ToString().c_str());
continue;
}
memcpy(&sScan.e[0], &si->scan_secret[0], ec_secret_size);
memcpy(&sSpend.e[0], &si->spend_secret[0], ec_secret_size);
ec_point pkEphem = sxKeyMeta.pkEphem.Raw();
if (StealthSecretSpend(sScan, pkEphem, sSpend, sSpendR) != 0)
{
printf("StealthSecretSpend() failed.\n");
continue;
}
ec_point pkTestSpendR;
if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0)
{
printf("SecretToPublicKey() failed.\n");
continue;
}
CSecret vchSecret;
vchSecret.resize(ec_secret_size);
memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size);
CKey ckey;
try {
ckey.SetSecret(vchSecret, true);
} catch (std::exception& e) {
printf("ckey.SetSecret() threw: %s.\n", e.what());
continue;
}
CPubKey cpkT = ckey.GetPubKey();
if (!cpkT.IsValid())
{
printf("cpkT is invalid.\n");
continue;
}
if (cpkT != pubKey)
{
printf("Error: Generated secret does not match.\n");
if (fDebug)
{
printf("cpkT %s\n", HexStr(cpkT.Raw()).c_str());
printf("pubKey %s\n", HexStr(pubKey.Raw()).c_str());
}
continue;
}
if (!ckey.IsValid())
{
printf("Reconstructed key is invalid.\n");
continue;
}
if (fDebug)
{
CKeyID keyID = cpkT.GetID();
CBitcoinAddress coinAddress(keyID);
printf("Adding secret to key %s.\n", coinAddress.ToString().c_str());
}
if (!AddKey(ckey))
{
printf("AddKey failed.\n");
continue;
}
if (!CWalletDB(strWalletFile).EraseStealthKeyMeta(ckid))
printf("EraseStealthKeyMeta failed for %s\n", addr.ToString().c_str());
}
return true;
}
bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist)
{
if (fDebug)
printf("UpdateStealthAddress %s\n", addr.c_str());
CStealthAddress sxAddr;
if (!sxAddr.SetEncoded(addr))
return false;
std::set<CStealthAddress>::iterator it;
it = stealthAddresses.find(sxAddr);
ChangeType nMode = CT_UPDATED;
CStealthAddress sxFound;
if (it == stealthAddresses.end())
{
if (addIfNotExist)
{
sxFound = sxAddr;
sxFound.label = label;
stealthAddresses.insert(sxFound);
nMode = CT_NEW;
} else
{
printf("UpdateStealthAddress %s, not in set\n", addr.c_str());
return false;
}
} else
{
sxFound = const_cast<CStealthAddress&>(*it);
if (sxFound.label == label)
{
// no change
return true;
}
}
sxFound.label = label;
if (!CWalletDB(strWalletFile).WriteStealthAddress(sxFound))
{
printf("UpdateStealthAddress(%s) Write to db failed.\n", addr.c_str());
return false;
}
bool fOwned = sxFound.scan_secret.size() == ec_secret_size;
NotifyAddressBookChanged(this, sxFound, sxFound.label, fOwned, nMode);
return true;
}
bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector<uint8_t> &P, std::vector<uint8_t> &narr, std::string &sNarr, CWalletTx &wtxNew, CReserveKey &reservekey, int64 &nFeeRet, const CCoinControl *coinControl)
{
vector< pair<CScript, int64_t> > vecSend;
vecSend.push_back(make_pair(scriptPubKey, nValue));
CScript scriptP = CScript() << OP_RETURN << P;
if (narr.size() > 0) {
scriptP = scriptP << OP_RETURN << narr;
}
vecSend.push_back(make_pair(scriptP, nTransactionFee));
// -- shuffle inputs, change output won't mix enough as it must be not fully random for plantext narrations
std::random_shuffle(vecSend.begin(), vecSend.end());
int nChangePos;
bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, coinControl);
// -- the change txn is inserted in a random pos, check here to match narr to output
if (rv && narr.size() > 0)
{
for (unsigned int k = 0; k < wtxNew.vout.size(); ++k)
{
if (wtxNew.vout[k].scriptPubKey != scriptPubKey
|| wtxNew.vout[k].nValue != nValue)
continue;
char key[64];
if (snprintf(key, sizeof(key), "n_%u", k) < 1)
{
printf("CreateStealthTransaction(): Error creating narration key.");
break;
}
wtxNew.mapValue[key] = sNarr;
break;
}
}
return rv;
}
string CWallet::SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector<uint8_t>& P, std::vector<uint8_t>& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee)
{
CReserveKey reservekey(this);
int64 nFeeRequired;
if (IsLocked())
{
string strError = _("Error: Wallet locked, unable to create transaction ");
printf("SendStealthMoney() : %s", strError.c_str());
return strError;
}
if (fWalletUnlockStakingOnly)
{
string strError = _("Error: Wallet unlocked for staking only, unable to create transaction.");
printf("SendStealthMoney() : %s", strError.c_str());
return strError;
}
if (!CreateStealthTransaction(scriptPubKey, nValue, P, narr, sNarr, wtxNew, reservekey, nFeeRequired))
{
string strError;
if (nValue + nFeeRequired > GetBalance())
strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str());
else
strError = _("Error: Transaction creation failed ");
printf("SendStealthMoney() : %s", strError.c_str());
return strError;
}
if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending...")))
return "ABORTED";
if (!CommitTransaction(wtxNew, reservekey))
return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.");
return "";
}
bool CWallet::SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee)
{
// -- Check amount
if (nValue <= 0)
{
sError = "Invalid amount";
return false;
}
if (nValue + nTransactionFee > GetBalance())
{
sError = "Insufficient funds";
return false;
}
ec_secret ephem_secret;
ec_secret secretShared;
ec_point pkSendTo;
ec_point ephem_pubkey;
if (GenerateRandomSecret(ephem_secret) != 0)
{
sError = "GenerateRandomSecret failed.";
return false;
}
if (StealthSecret(ephem_secret, sxAddress.scan_pubkey, sxAddress.spend_pubkey, secretShared, pkSendTo) != 0)
{
sError = "Could not generate receiving public key.";
return false;
}
CPubKey cpkTo(pkSendTo);
if (!cpkTo.IsValid())
{
sError = "Invalid public key generated.";
return false;
}
CKeyID ckidTo = cpkTo.GetID();
CBitcoinAddress addrTo(ckidTo);
if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0)
{
sError = "Could not generate ephem public key.";
return false;
}
if (fDebug)
{
printf("Stealth send to generated pubkey %" PRIszu": %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str());
printf("hash %s\n", addrTo.ToString().c_str());
printf("ephem_pubkey %" PRIszu": %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey).c_str());
}
std::vector<unsigned char> vchNarr;
if (sNarr.length() > 0)
{
SecMsgCrypter crypter;
crypter.SetKey(&secretShared.e[0], &ephem_pubkey[0]);
if (!crypter.Encrypt((uint8_t*)&sNarr[0], sNarr.length(), vchNarr))
{
sError = "Narration encryption failed.";
return false;
}
if (vchNarr.size() > 48)
{
sError = "Encrypted narration is too long.";
return false;
}
}
// -- Parse Bitcoin address
CScript scriptPubKey;
scriptPubKey.SetDestination(addrTo.Get());
if ((sError = SendStealthMoney(scriptPubKey, nValue, ephem_pubkey, vchNarr, sNarr, wtxNew, fAskFee)) != "")
return false;
return true;
}
bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNarr)
{
if (fDebug)
printf("FindStealthTransactions() tx: %s\n", tx.GetHash().GetHex().c_str());
mapNarr.clear();
LOCK(cs_wallet);
ec_secret sSpendR;
ec_secret sSpend;
ec_secret sScan;
ec_secret sShared;
ec_point pkExtracted;
std::vector<uint8_t> vchEphemPK;
std::vector<uint8_t> vchDataB;
std::vector<uint8_t> vchENarr;
opcodetype opCode;
char cbuf[256];
int32_t nOutputIdOuter = -1;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
{
nOutputIdOuter++;
// -- for each OP_RETURN need to check all other valid outputs
//printf("txout scriptPubKey %s\n", txout.scriptPubKey.ToString().c_str());
CScript::const_iterator itTxA = txout.scriptPubKey.begin();
if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK)
|| opCode != OP_RETURN)
continue;
else
if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK)
|| vchEphemPK.size() != 33)
{
// -- look for plaintext narrations
if (vchEphemPK.size() > 1
&& vchEphemPK[0] == 'n'
&& vchEphemPK[1] == 'p')
{
if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr)
&& opCode == OP_RETURN
&& txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr)
&& vchENarr.size() > 0)
{
std::string sNarr = std::string(vchENarr.begin(), vchENarr.end());
snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputIdOuter-1); // plaintext narration always matches preceding value output
mapNarr[cbuf] = sNarr;
} else
{
printf("Warning: FindStealthTransactions() tx: %s, Could not extract plaintext narration.\n", tx.GetHash().GetHex().c_str());
}
}
continue;
}
int32_t nOutputId = -1;
nStealth++;
BOOST_FOREACH(const CTxOut& txoutB, tx.vout)
{
nOutputId++;
if (&txoutB == &txout)
continue;
bool txnMatch = false; // only 1 txn will match an ephem pk
//printf("txoutB scriptPubKey %s\n", txoutB.scriptPubKey.ToString().c_str());
CTxDestination address;
if (!ExtractDestination(txoutB.scriptPubKey, address))
continue;
if (address.type() != typeid(CKeyID))
continue;
CKeyID ckidMatch = boost::get<CKeyID>(address);
if (HaveKey(ckidMatch)) // no point checking if already have key
continue;
std::set<CStealthAddress>::iterator it;
for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it)
{
if (it->scan_secret.size() != ec_secret_size)
continue; // stealth address is not owned
//printf("it->Encoded() %s\n", it->Encoded().c_str());
memcpy(&sScan.e[0], &it->scan_secret[0], ec_secret_size);
if (StealthSecret(sScan, vchEphemPK, it->spend_pubkey, sShared, pkExtracted) != 0)
{
printf("StealthSecret failed.\n");
continue;
};
//printf("pkExtracted %"PRIszu": %s\n", pkExtracted.size(), HexStr(pkExtracted).c_str());
CPubKey cpkE(pkExtracted);
if (!cpkE.IsValid())
continue;
CKeyID ckidE = cpkE.GetID();
if (ckidMatch != ckidE)
continue;
if (fDebug)
printf("Found stealth txn to address %s\n", it->Encoded().c_str());
if (IsLocked())
{
if (fDebug)
printf("Wallet is locked, adding key without secret.\n");
// -- add key without secret
std::vector<uint8_t> vchEmpty;
AddCryptedKey(cpkE, vchEmpty);
CKeyID keyId = cpkE.GetID();
CBitcoinAddress coinAddress(keyId);
std::string sLabel = it->Encoded();
SetAddressBookName(keyId, sLabel);
CPubKey cpkEphem(vchEphemPK);
CPubKey cpkScan(it->scan_pubkey);
CStealthKeyMetadata lockedSkMeta(cpkEphem, cpkScan);
if (!CWalletDB(strWalletFile).WriteStealthKeyMeta(keyId, lockedSkMeta))
printf("WriteStealthKeyMeta failed for %s\n", coinAddress.ToString().c_str());
mapStealthKeyMeta[keyId] = lockedSkMeta;
nFoundStealth++;
} else
{
if (it->spend_secret.size() != ec_secret_size)
continue;
memcpy(&sSpend.e[0], &it->spend_secret[0], ec_secret_size);
if (StealthSharedToSecretSpend(sShared, sSpend, sSpendR) != 0)
{
printf("StealthSharedToSecretSpend() failed.\n");
continue;
}
ec_point pkTestSpendR;
if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0)
{
printf("SecretToPublicKey() failed.\n");
continue;
}
CSecret vchSecret;
vchSecret.resize(ec_secret_size);
memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size);
CKey ckey;
try {
ckey.SetSecret(vchSecret, true);
} catch (std::exception& e) {
printf("ckey.SetSecret() threw: %s.\n", e.what());
continue;
}
CPubKey cpkT = ckey.GetPubKey();
if (!cpkT.IsValid())
{
printf("cpkT is invalid.\n");
continue;
}
if (!ckey.IsValid())
{
printf("Reconstructed key is invalid.\n");
continue;
}
CKeyID keyID = cpkT.GetID();
if (fDebug)
{
CBitcoinAddress coinAddress(keyID);
printf("Adding key %s.\n", coinAddress.ToString().c_str());
}
if (!AddKey(ckey))
{
printf("AddKey failed.\n");
continue;
}
std::string sLabel = it->Encoded();
SetAddressBookName(keyID, sLabel);
nFoundStealth++;
}
if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr)
&& opCode == OP_RETURN
&& txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr)
&& vchENarr.size() > 0)
{
SecMsgCrypter crypter;
crypter.SetKey(&sShared.e[0], &vchEphemPK[0]);
std::vector<uint8_t> vchNarr;
if (!crypter.Decrypt(&vchENarr[0], vchENarr.size(), vchNarr))
{
printf("Decrypt narration failed.\n");
continue;
}
std::string sNarr = std::string(vchNarr.begin(), vchNarr.end());
snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputId);
mapNarr[cbuf] = sNarr;
}
txnMatch = true;
break;
}
if (txnMatch)
break;
}
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment