// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include "db.h"
#include "wallet.h"
#include "walletdb.h"
#include "crypter.h"
#include "ui_interface.h"
#include "base58.h"
#include "kernel.h"
+#include "coincontrol.h"
+#include <boost/algorithm/string/replace.hpp>
using namespace std;
extern int nStakeMaxAge;
-
//////////////////////////////////////////////////////////////////////////////
//
// mapWallet
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
+ CPubKey pubkey = key.GetPubKey();
+
+ // Create new metadata
+ int64 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(key.GetPubKey(), key.GetPrivKey());
+ return CWalletDB(strWalletFile).WriteKey(pubkey, key.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]);
return true;
}
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
- return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret);
+ return pwalletdbEncryption->WriteCryptedKey(vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
else
- return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey, vchCryptedSecret);
+ 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::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
- CMasterKey kMasterKey;
+ CMasterKey kMasterKey(nDerivationMethodIndex);
RandAddSeedPerfmon();
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
// 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)
+bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& tx, const CBlock* pblock, bool fUpdate, bool fFindBlock)
{
- uint256 hash = tx.GetHash();
{
LOCK(cs_wallet);
bool fExisted = mapWallet.count(hash);
void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived,
int64& nSent, int64& nFee) const
{
- nReceived = nSent = nFee = 0;
+ nGenerated = nReceived = nSent = nFee = 0;
int64 allGeneratedImmature, allGeneratedMature, allFee;
+ allGeneratedImmature = allGeneratedMature = allFee = 0;
string strSentAccount;
list<pair<CTxDestination, int64> > listReceived;
list<pair<CTxDestination, int64> > listSent;
}
}
-void CWalletTx::AddSupportingTransactions(CTxDB& txdb)
+void CWalletTx::AddSupportingTransactions()
{
vtxPrev.clear();
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;
{
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);
block.ReadFromDisk(pindex, true);
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
- if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
+ if (AddToWalletIfInvolvingMe(tx.GetHash(), 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;
+ bool fMissing = false;
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;
+ CCoins coins;
bool fUpdated = false;
- if (txdb.ReadTxIndex(wtx.GetHash(), txindex))
+ bool fNotFound = pcoinsTip->GetCoins(wtx.GetHash(), coins);
+ if (!fNotFound || wtx.GetDepthInMainChain() > 0)
{
// 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++)
+ for (unsigned int i = 0; i < wtx.vout.size(); i++)
{
if (wtx.IsSpent(i))
continue;
- if (!txindex.vSpent[i].IsNull() && IsMine(wtx.vout[i]))
+ if ((i >= coins.vout.size() || coins.vout[i].IsNull()) && IsMine(wtx.vout[i]))
{
wtx.MarkSpent(i);
fUpdated = true;
- vMissingTx.push_back(txindex.vSpent[i]);
+ fMissing = true;
}
}
if (fUpdated)
{
// Re-accept any txes of ours that aren't already in a block
if (!(wtx.IsCoinBase() || wtx.IsCoinStake()))
- wtx.AcceptWalletTransaction(txdb, false);
+ wtx.AcceptWalletTransaction(false);
}
}
- if (!vMissingTx.empty())
+ if (fMissing)
{
// TODO: optimize this to scan just part of the block chain?
if (ScanForWalletTransactions(pindexGenesisBlock))
}
}
-void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
+void CWalletTx::RelayWalletTransaction()
{
+ CCoinsViewCache& coins = *pcoinsTip;
BOOST_FOREACH(const CMerkleTx& tx, vtxPrev)
{
if (!(tx.IsCoinBase() || tx.IsCoinStake()))
{
uint256 hash = tx.GetHash();
- if (!txdb.ContainsTx(hash))
- RelayMessage(CInv(MSG_TX, hash), (CTransaction)tx);
+ if (!coins.HaveCoins(hash))
+ RelayTransaction((CTransaction)tx, hash);
}
}
if (!(IsCoinBase() || IsCoinStake()))
{
uint256 hash = GetHash();
- if (!txdb.ContainsTx(hash))
+ if (!coins.HaveCoins(hash))
{
printf("Relaying wtx %s\n", hash.ToString().substr(0,10).c_str());
- RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this);
+ RelayTransaction((CTransaction)*this, hash);
}
}
}
-void CWalletTx::RelayWalletTransaction()
-{
- CTxDB txdb("r");
- RelayWalletTransaction(txdb);
-}
-
void CWallet::ResendWalletTransactions()
{
// Do this infrequently and randomly to avoid giving away
// 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
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());
+ wtx.RelayWalletTransaction();
}
}
}
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
- if (pcoin->IsFinal() && pcoin->IsConfirmed())
+ if (pcoin->IsConfirmed())
nTotal += pcoin->GetAvailableCredit();
}
}
}
// populate vCoins with vector of spendable COutputs
-void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const
+void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const
{
vCoins.clear();
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
- if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && pcoin->vout[i].nValue > 0)
+ 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, pcoin->GetDepthInMainChain()));
+
+ }
+ }
+}
+
+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()));
}
}
int i = output.i;
+ // Follow the timestamp rules
if (pcoin->nTime > nSpendTime)
- continue; // ppcoin: timestamp must not exceed spend time
+ continue;
int64 n = pcoin->vout[i].nValue;
return true;
}
-bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const
+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);
+ 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 nTargetValue, unsigned int nSpendTime, int nMinConf, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& 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;
-bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet)
+ // Stop if we've chosen enough inputs
+ if (nValueRet >= nTargetValue)
+ break;
+
+ // 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)
+ {
+ // 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> >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl)
{
int64 nValue = 0;
BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend)
{
LOCK2(cs_main, cs_wallet);
- // txdb must be opened before the mapWallet lock
- CTxDB txdb("r");
{
nFeeRet = nTransactionFee;
- loop
+ while (true)
{
wtxNew.vin.clear();
wtxNew.vout.clear();
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
- if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn))
+ if (!SelectCoins(nTotalValue, wtxNew.nTime, setCoins, nValueIn, coinControl))
return false;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
nFeeRet += nMoveToFee;
}
- // ppcoin: sub-cent change is moved to fee
+ // sub-cent change is moved to fee
if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT)
{
nFeeRet += nChange;
if (nChange > 0)
{
- // 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();
- // assert(mapKeys.count(vchPubKey));
-
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
- scriptChange.SetDestination(vchPubKey.GetID());
+
+ // 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());
// Check that enough fee is included
int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000);
- int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND);
-
+ bool fAllowFree = CTransaction::AllowFree(dPriority);
+ int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND);
if (nFeeRet < max(nPayFee, nMinFee))
{
nFeeRet = max(nPayFee, nMinFee);
}
// Fill vtxPrev by copying from previous transactions vtxPrev
- wtxNew.AddSupportingTransactions(txdb);
+ wtxNew.AddSupportingTransactions();
wtxNew.fTimeReceivedIsTxTime = true;
break;
return true;
}
-bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet)
+bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, const CCoinControl* coinControl)
{
vector< pair<CScript, int64> > vecSend;
vecSend.push_back(make_pair(scriptPubKey, nValue));
- return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet);
+ return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, coinControl);
+}
+
+// NovaCoin: get current stake weight
+bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint64& nMaxWeight, uint64& nWeight)
+{
+ // Choose coins to use
+ int64 nBalance = GetBalance();
+ int64 nReserveBalance = 0;
+
+ if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance))
+ {
+ error("GetStakeWeight : invalid reserve balance amount");
+ return false;
+ }
+
+ if (nBalance <= nReserveBalance)
+ return false;
+
+ vector<const CWalletTx*> vwtxPrev;
+ set<pair<const CWalletTx*,unsigned int> > setCoins;
+ int64 nValueIn = 0;
+
+ if (!SelectCoinsSimple(nBalance - nReserveBalance, GetTime(), nCoinbaseMaturity * 10, setCoins, nValueIn))
+ return false;
+
+ if (setCoins.empty())
+ return false;
+
+ CCoinsViewCache &view = *pcoinsTip;
+ BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
+ {
+ CCoins coins;
+ {
+ LOCK2(cs_main, cs_wallet);
+ if (!view.GetCoinsReadOnly(pcoin.first->GetHash(), coins))
+ continue;
+ }
+
+ int64 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)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;
}
-// ppcoin: create coin stake transaction
-bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew)
+bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int64 nSearchInterval, CTransaction& txNew, CKey& key)
{
- // The following split & combine thresholds are important to security
+ // The following combine threshold is important to security
// Should not be adjusted if you don't understand the consequences
- static unsigned int nStakeSplitAge = (60 * 60 * 24 * 90);
int64 nCombineThreshold = GetProofOfWorkReward(GetLastBlockIndex(pindexBest, false)->nBits) / 3;
-
+ CBlockIndex* pindexPrev = pindexBest;
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
- LOCK2(cs_main, cs_wallet);
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 nBalance = GetBalance();
int64 nReserveBalance = 0;
+
if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance))
return error("CreateCoinStake : invalid reserve balance amount");
+
if (nBalance <= nReserveBalance)
return false;
- set<pair<const CWalletTx*,unsigned int> > setCoins;
+
vector<const CWalletTx*> vwtxPrev;
+ set<pair<const CWalletTx*,unsigned int> > setCoins;
int64 nValueIn = 0;
- if (!SelectCoins(nBalance - nReserveBalance, txNew.nTime, setCoins, nValueIn))
+
+ // Select coins with suitable depth
+ if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn))
return false;
+
if (setCoins.empty())
return false;
+
int64 nCredit = 0;
CScript scriptPubKeyKernel;
+
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
- CTxDB txdb("r");
- CTxIndex txindex;
- if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
- continue;
+ CCoins coins;
+ {
+ LOCK2(cs_main, cs_wallet);
+ if (!view.GetCoinsReadOnly(pcoin.first->GetHash(), coins))
+ continue;
+ }
- // Read block header
- CBlock block;
- if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
- continue;
static int nMaxStakeSearchInterval = 60;
- if (block.GetBlockTime() + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
+ if (coins.nBlockTime + nStakeMinAge > txNew.nTime - nMaxStakeSearchInterval)
continue; // only count coins meeting min age requirement
+ // Read block header
+ CBlock block;
+ unsigned int nTxPos = 0;
+ {
+ LOCK2(cs_main, cs_wallet);
+ CBlockIndex *pindex = FindBlockByHeight(coins.nHeight);
+
+ if (!block.ReadFromDisk(pindex))
+ continue;
+
+ BOOST_FOREACH(const CTransaction &tx, block.vtx) {
+ if (tx.GetHash() == pcoin.first->GetHash()) {
+ break;
+ }
+ nTxPos += tx.GetSerializeSize(SER_DISK, CLIENT_VERSION);
+ }
+ nTxPos += GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(block.vtx.size());
+ }
+
+
+ bool fFatal = false;
bool fKernelFound = false;
- for (unsigned int n=0; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown; n++)
+ for (unsigned int n=0; n<min(nSearchInterval,(int64)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;
+ 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))
+ if (CheckStakeKernelHash(nBits, block, nTxPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake, fFatal, true))
{
// Found a kernel
if (fDebug && GetBoolArg("-printcoinstake"))
if (whichType == TX_PUBKEYHASH) // pay to address type
{
// convert to pay to public key type
- CKey key;
if (!keystore.GetKey(uint160(vSolutions[0]), key))
{
if (fDebug && GetBoolArg("-printcoinstake"))
}
scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG;
}
- else
+ 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.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 (block.GetBlockTime() + nStakeSplitAge > txNew.nTime)
+
+ if (GetWeight(block.GetBlockTime(), (int64)txNew.nTime) < nStakeMaxAge)
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
if (fDebug && GetBoolArg("-printcoinstake"))
printf("CreateCoinStake : added kernel type=%d\n", whichType);
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
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 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)txNew.nTime);
+
// Stop adding more inputs if already too many inputs
if (txNew.vin.size() >= 100)
break;
if (pcoin.first->vout[pcoin.second].nValue > nCombineThreshold)
continue;
// Do not add input that is still too young
- if (pcoin.first->nTime + nStakeMaxAge > txNew.nTime)
+ if (nTimeWeight < nStakeMaxAge)
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 nCoinAge;
- CTxDB txdb("r");
- if (!txNew.GetCoinAge(txdb, nCoinAge))
+
+ if (!txNew.GetCoinAge(nCoinAge))
return error("CreateCoinStake : failed to calculate coin age");
nCredit += GetProofOfStakeReward(nCoinAge, nBits, txNew.nTime);
}
int64 nMinFee = 0;
- loop
+ while (true)
{
// Set output amount
if (txNew.vout.size() == 3)
return true;
}
-bool CWallet::TopUpKeyPool()
+bool CWallet::TopUpKeyPool(unsigned int nSize)
{
{
LOCK(cs_wallet);
CWalletDB walletdb(strWalletFile);
// Top up key pool
- unsigned int nTargetSize = max(GetArg("-keypool", 100), 0LL);
+ unsigned int nTargetSize;
+ if (nSize > 0)
+ nTargetSize = nSize;
+ else
+ nTargetSize = max(GetArg("-keypool", 100), 0LL);
+
while (setKeyPool.size() < (nTargetSize + 1))
{
int64 nEnd = 1;
return ret;
}
-// ppcoin: check 'spent' consistency between wallet and txindex
-// ppcoin: fix wallet spent state according to txindex
+// 1. check 'spent' consistency between wallet and coins database
+// 2. fix wallet spent state according to coins database
+// 3. remove orphaned coinstakes and coinbases from wallet
void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion, bool fCheckOnly)
{
nMismatchFound = 0;
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
vCoins.push_back(&(*it).second);
- CTxDB txdb("r");
+ CCoinsViewCache &view = *pcoinsTip;
BOOST_FOREACH(CWalletTx* pcoin, vCoins)
{
- // Find the corresponding transaction index
- CTxIndex txindex;
- if (!txdb.ReadTxIndex(pcoin->GetHash(), txindex))
+ uint256 hash = pcoin->GetHash();
+ if(!view.HaveCoins(hash))
continue;
+
+ // Find the corresponding transaction index
+ CCoins &coins = view.GetCoins(hash);
+
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()))
+ if (IsMine(pcoin->vout[n]))
{
- printf("FixSpentCoins found lost coin %sppc %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)
+ if (pcoin->IsSpent(n) && coins.IsAvailable(n))
{
- pcoin->MarkUnspent(n);
- pcoin->WriteToDisk();
+ printf("FixSpentCoins found lost coin %snvc %s[%d], %s\n",
+ FormatMoney(pcoin->vout[n].nValue).c_str(), hash.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 %sppc %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)
+ else if (!pcoin->IsSpent(n) && !coins.IsAvailable(n))
{
- pcoin->MarkSpent(n);
- pcoin->WriteToDisk();
+ printf("FixSpentCoins found spent coin %snvc %s[%d], %s\n",
+ FormatMoney(pcoin->vout[n].nValue).c_str(), hash.ToString().c_str(), n, fCheckOnly? "repair not attempted" : "repairing");
+ nMismatchFound++;
+ nBalanceInQuestion += pcoin->vout[n].nValue;
+ if (!fCheckOnly)
+ {
+ pcoin->MarkSpent(n);
+ pcoin->WriteToDisk();
+ }
}
+
+ NotifyTransactionChanged(this, hash, CT_UPDATED);
+ }
+ }
+
+ if((pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetDepthInMainChain() == 0)
+ {
+ printf("FixSpentCoins %s orphaned generation tx %s\n", fCheckOnly ? "found" : "removed", hash.ToString().c_str());
+ if (!fCheckOnly)
+ {
+ EraseFromWallet(hash);
}
}
}
vchPubKey = CPubKey();
}
-void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress)
+void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const
{
setAddress.clear();
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
}
}
+
+void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64> &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
+}