X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=blobdiff_plain;f=src%2Fwallet.cpp;h=3aed377357666038b61874eec33b0d14f4d4d57e;hp=898ba674e594ee8a8020a8e8561e47d7f1e8a0d1;hb=d81967aac0d13a39c13c985d5f1230bebad6756a;hpb=2c8f88fc67af7916670ce0086e6a9078df8182f0 diff --git a/src/wallet.cpp b/src/wallet.cpp index 898ba67..3aed377 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -13,10 +13,13 @@ #include "coincontrol.h" #include +#include "main.h" + using namespace std; -extern int nStakeMaxAge; +bool fCoinsDataActual; + ////////////////////////////////////////////////////////////////////////////// // // mapWallet @@ -103,6 +106,21 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } +bool CWallet::AddWatchOnly(const CScript &dest) +{ + if (!CCryptoKeyStore::AddWatchOnly(dest)) + return false; + nTimeFirstKey = 1; // No birthday information for watch-only keys. + if (!fFileBacked) + return true; + return CWalletDB(strWalletFile).WriteWatchOnly(dest); +} + +bool CWallet::LoadWatchOnly(const CScript &dest) +{ + return CCryptoKeyStore::AddWatchOnly(dest); +} + // ppcoin: optional setting to unlock wallet for block minting only; // serves to disable the trivial sendmoney when OS account compromised bool fWalletUnlockMintOnly = false; @@ -354,7 +372,7 @@ CWallet::TxItems CWallet::OrderedTxItems(std::list& acentries, return txOrdered; } -void CWallet::WalletUpdateSpent(const CTransaction &tx) +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 @@ -378,6 +396,24 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) } } } + + if (fBlock) + { + uint256 hash = tx.GetHash(); + map::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); + } + } + } + } } @@ -500,7 +536,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) } #endif // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins - WalletUpdateSpent(wtx); + WalletUpdateSpent(wtx, (wtxIn.hashBlock != 0)); // Notify UI of new or updated transaction NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); @@ -555,7 +591,7 @@ bool CWallet::EraseFromWallet(uint256 hash) } -bool CWallet::IsMine(const CTxIn &txin) const +isminetype CWallet::IsMine(const CTxIn &txin) const { { LOCK(cs_wallet); @@ -564,14 +600,13 @@ bool CWallet::IsMine(const CTxIn &txin) const { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.vout.size()) - if (IsMine(prev.vout[txin.prevout.n])) - return true; + return IsMine(prev.vout[txin.prevout.n]); } } - return false; + return MINE_NO; } -int64 CWallet::GetDebit(const CTxIn &txin) const +int64 CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const { { LOCK(cs_wallet); @@ -580,7 +615,7 @@ int64 CWallet::GetDebit(const CTxIn &txin) const { const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.vout.size()) - if (IsMine(prev.vout[txin.prevout.n])) + if (IsMine(prev.vout[txin.prevout.n]) & filter) return prev.vout[txin.prevout.n].nValue; } } @@ -589,17 +624,19 @@ int64 CWallet::GetDebit(const CTxIn &txin) const 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 + // payment to a script that is ours, 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)) + if (::IsMine(*this, txout.scriptPubKey)) { + CTxDestination address; + if (!ExtractDestination(txout.scriptPubKey, address)) + return true; + LOCK(cs_wallet); if (!mapAddressBook.count(address)) return true; @@ -653,7 +690,7 @@ int CWalletTx::GetRequestCount() const } void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, list >& listReceived, - list >& listSent, int64& nFee, string& strSentAccount) const + list >& listSent, int64& nFee, string& strSentAccount, const isminefilter& filter) const { nGeneratedImmature = nGeneratedMature = nFee = 0; listReceived.clear(); @@ -663,14 +700,14 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l if (IsCoinBase() || IsCoinStake()) { if (GetBlocksToMaturity() > 0) - nGeneratedImmature = pwallet->GetCredit(*this); + nGeneratedImmature = pwallet->GetCredit(*this, filter); else - nGeneratedMature = GetCredit(); + nGeneratedMature = GetCredit(filter); return; } // Compute fee: - int64 nDebit = GetDebit(); + int64 nDebit = GetDebit(filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { int64 nValueOut = GetValueOut(); @@ -680,29 +717,41 @@ void CWalletTx::GetAmounts(int64& nGeneratedImmature, int64& nGeneratedMature, l // Sent/received. BOOST_FOREACH(const CTxOut& txout, vout) { + isminetype fIsMine = pwallet->IsMine(txout); + // 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; + } + else if (!(fIsMine & filter)) + continue; + + // In either case, we need to get the destination address CTxDestination address; - vector vchPubKey; if (!ExtractDestination(txout.scriptPubKey, address)) { printf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", this->GetHash().ToString().c_str()); + address = CNoDestination(); } - // Don't report 'change' txouts - if (nDebit > 0 && pwallet->IsChange(txout)) - continue; - + // 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 (pwallet->IsMine(txout)) + // If we are receiving the output, add it as a "received" entry + if (fIsMine & filter) listReceived.push_back(make_pair(address, txout.nValue)); } } void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, int64& nReceived, - int64& nSent, int64& nFee) const + int64& nSent, int64& nFee, const isminefilter& filter) const { nGenerated = nReceived = nSent = nFee = 0; @@ -711,7 +760,7 @@ void CWalletTx::GetAccountAmounts(const string& strAccount, int64& nGenerated, i string strSentAccount; list > listReceived; list > listSent; - GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount); + GetAmounts(allGeneratedImmature, allGeneratedMature, listReceived, listSent, allFee, strSentAccount, filter); if (strAccount == "") nGenerated = allGeneratedMature; @@ -988,7 +1037,7 @@ int64 CWallet::GetBalance() const for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (pcoin->IsConfirmed()) + if (pcoin->IsTrusted()) nTotal += pcoin->GetAvailableCredit(); } } @@ -996,6 +1045,22 @@ int64 CWallet::GetBalance() const return nTotal; } +int64 CWallet::GetWatchOnlyBalance() const +{ + int64 nTotal = 0; + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->IsTrusted()) + nTotal += pcoin->GetAvailableWatchCredit(); + } + } + + return nTotal; +} + int64 CWallet::GetUnconfirmedBalance() const { int64 nTotal = 0; @@ -1004,13 +1069,28 @@ int64 CWallet::GetUnconfirmedBalance() const for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { const CWalletTx* pcoin = &(*it).second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + if (!pcoin->IsFinal() || !pcoin->IsTrusted()) nTotal += pcoin->GetAvailableCredit(); } } return nTotal; } +int64 CWallet::GetUnconfirmedWatchOnlyBalance() const +{ + int64 nTotal = 0; + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + if (!pcoin->IsFinal() || !pcoin->IsTrusted()) + nTotal += pcoin->GetAvailableWatchCredit(); + } + } + return nTotal; +} + int64 CWallet::GetImmatureBalance() const { int64 nTotal = 0; @@ -1018,9 +1098,22 @@ int64 CWallet::GetImmatureBalance() const LOCK(cs_wallet); for (map::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); + const CWalletTx* pcoin = &(*it).second; + nTotal += pcoin->GetImmatureCredit(); + } + } + return nTotal; +} + +int64 CWallet::GetImmatureWatchOnlyBalance() const +{ + int64 nTotal = 0; + { + LOCK(cs_wallet); + for (map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx* pcoin = &(*it).second; + nTotal += pcoin->GetImmatureWatchOnlyCredit(); } } return nTotal; @@ -1040,7 +1133,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const if (!pcoin->IsFinal()) continue; - if (fOnlyConfirmed && !pcoin->IsConfirmed()) + if (fOnlyConfirmed && !pcoin->IsTrusted()) continue; if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0) @@ -1049,16 +1142,20 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const if(pcoin->IsCoinStake() && pcoin->GetBlocksToMaturity() > 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, pcoin->GetDepthInMainChain())); - + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + isminetype mine = IsMine(pcoin->vout[i]); + if (!(pcoin->IsSpent(i)) && mine != MINE_NO && + pcoin->vout[i].nValue >= nMinimumInputValue && + (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + { + vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain(), mine == MINE_SPENDABLE)); + } + } } } } -void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf) const +void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf, int64 nMinValue, int64 nMaxValue) const { vCoins.clear(); @@ -1074,9 +1171,17 @@ void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf) const 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())); + for (unsigned int i = 0; i < pcoin->vout.size(); i++) { + isminetype mine = IsMine(pcoin->vout[i]); + + // ignore coin if it was already spent or we don't own it + if (pcoin->IsSpent(i) || mine == MINE_NO) + continue; + + // if coin value is between required limits then add new item to vector + if (pcoin->vout[i].nValue >= nMinValue && pcoin->vout[i].nValue < nMaxValue) + vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain(), mine == MINE_SPENDABLE)); + } } } } @@ -1119,7 +1224,6 @@ static void ApproximateBestSubset(vectorIsCoinStake() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) - nTotal += CWallet::GetCredit(*pcoin); + nTotal += CWallet::GetCredit(*pcoin, MINE_ALL); + } + return nTotal; +} + +int64 CWallet::GetWatchOnlyStake() const +{ + int64 nTotal = 0; + LOCK(cs_wallet); + for (map::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, MINE_WATCH_ONLY); } return nTotal; } @@ -1141,7 +1258,20 @@ int64 CWallet::GetNewMint() const { const CWalletTx* pcoin = &(*it).second; if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0 && pcoin->GetDepthInMainChain() > 0) - nTotal += CWallet::GetCredit(*pcoin); + nTotal += CWallet::GetCredit(*pcoin, MINE_ALL); + } + return nTotal; +} + +int64 CWallet::GetWatchOnlyNewMint() const +{ + int64 nTotal = 0; + LOCK(cs_wallet); + for (map::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, MINE_WATCH_ONLY); } return nTotal; } @@ -1160,11 +1290,14 @@ bool CWallet::SelectCoinsMinConf(int64 nTargetValue, unsigned int nSpendTime, in random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt); - BOOST_FOREACH(COutput output, vCoins) + BOOST_FOREACH(const COutput &output, vCoins) { + if (!output.fSpendable) + continue; + const CWalletTx *pcoin = output.tx; - if (output.nDepth < (pcoin->IsFromMe() ? nConfMine : nConfTheirs)) + if (output.nDepth < (pcoin->IsFromMe(MINE_ALL) ? nConfMine : nConfTheirs)) continue; int i = output.i; @@ -1262,6 +1395,8 @@ bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, setvout[out.i].nValue; setCoinsRet.insert(make_pair(out.tx, out.i)); } @@ -1274,19 +1409,25 @@ bool CWallet::SelectCoins(int64 nTargetValue, unsigned int nSpendTime, set >& setCoinsRet, int64& nValueRet) const +bool CWallet::SelectCoinsSimple(int64 nTargetValue, int64 nMinValue, int64 nMaxValue, unsigned int nSpendTime, int nMinConf, set >& setCoinsRet, int64& nValueRet) const { vector vCoins; - AvailableCoinsMinConf(vCoins, nMinConf); + AvailableCoinsMinConf(vCoins, nMinConf, nMinValue, nMaxValue); setCoinsRet.clear(); nValueRet = 0; BOOST_FOREACH(COutput output, vCoins) { + if(!output.fSpendable) + continue; const CWalletTx *pcoin = output.tx; int i = output.i; + // Ignore immature coins + if (pcoin->GetBlocksToMaturity() > 0) + continue; + // Stop if we've chosen enough inputs if (nValueRet >= nTargetValue) break; @@ -1364,18 +1505,21 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // 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 < MIN_TX_FEE && nChange > 0 && nChange < CENT) + if (wtxNew.nTime < FEE_SWITCH_TIME && !fTestNet) { - int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); - nChange -= nMoveToFee; - nFeeRet += nMoveToFee; - } + if (nFeeRet < MIN_TX_FEE && nChange > 0 && nChange < CENT) + { + int64 nMoveToFee = min(nChange, MIN_TX_FEE - nFeeRet); + nChange -= nMoveToFee; + nFeeRet += nMoveToFee; + } - // ppcoin: sub-cent change is moved to fee - if (nChange > 0 && nChange < MIN_TXOUT_AMOUNT) - { - nFeeRet += nChange; - nChange = 0; + // sub-cent change is moved to fee + if (nChange > 0 && nChange < CENT) + { + nFeeRet += nChange; + nChange = 0; + } } if (nChange > 0) @@ -1430,7 +1574,15 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); - int64 nMinFee = wtxNew.GetMinFee(1, false, GMF_SEND, nBytes); + bool fAllowFree = CTransaction::AllowFree(dPriority); + + // Disable free transactions until 1 July 2014 + if (!fTestNet && wtxNew.nTime < FEE_SWITCH_TIME) + { + fAllowFree = false; + } + + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); if (nFeeRet < max(nPayFee, nMinFee)) { @@ -1456,6 +1608,22 @@ bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& w return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, coinControl); } +void CWallet::GetStakeWeightFromValue(const int64& nTime, const int64& nValue, uint64& nWeight) +{ + int64 nTimeWeight = GetWeight(nTime, (int64)GetTime()); + + // If time weight is lower or equal to zero then weight is zero. + if (nTimeWeight <= 0) + { + nWeight = 0; + return; + } + + CBigNum bnCoinDayWeight = CBigNum(nValue) * nTimeWeight / COIN / (24 * 60 * 60); + nWeight = bnCoinDayWeight.getuint64(); +} + + // NovaCoin: get current stake weight bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint64& nMaxWeight, uint64& nWeight) { @@ -1464,88 +1632,219 @@ bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint int64 nReserveBalance = 0; if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) - { - error("GetStakeWeight : invalid reserve balance amount"); - return false; - } + return error("CreateCoinStake : invalid reserve balance amount"); if (nBalance <= nReserveBalance) return false; - vector vwtxPrev; + CTxDB txdb("r"); + { + LOCK2(cs_main, cs_wallet); + // Cache outputs unless best block or wallet transaction set changed + if (!fCoinsDataActual) + { + mapMeta.clear(); + int64 nValueIn = 0; + CoinsSet setCoins; + if (!SelectCoinsSimple(nBalance - nReserveBalance, MIN_TX_FEE, MAX_MONEY, GetAdjustedTime(), nCoinbaseMaturity * 10, setCoins, nValueIn)) + return false; -/* - * TODO: performance comparison + if (setCoins.empty()) + return false; - static set > setCoins; - static uint256 hashPrevBlock; - static int64 nValueIn = 0; + { + CTxIndex txindex; + CBlock block; + for(CoinsSet::iterator pcoin = setCoins.begin(); pcoin != setCoins.end(); pcoin++) + { + // Load transaction index item + if (!txdb.ReadTxIndex(pcoin->first->GetHash(), txindex)) + continue; + + // Read block header + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + continue; - // Cache outputs unless best block changed - if (hashPrevBlock != pindexBest->GetBlockHash()) + uint64 nStakeModifier = 0; + if (!GetKernelStakeModifier(block.GetHash(), nStakeModifier)) + continue; + + // Add meta record + // (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier)) + mapMeta[make_pair(pcoin->first->GetHash(), pcoin->second)] = make_pair(make_pair(txindex, *pcoin), make_pair(block, nStakeModifier)); + + if (fDebug) + printf("Load coin: %s\n", pcoin->first->GetHash().GetHex().c_str()); + } + } + + if (fDebug) + printf("Get stake weight: %zu meta items loaded for %zu coins\n", mapMeta.size(), setCoins.size()); + + fCoinsDataActual = true; + } + } + + + // (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier)) + for(MetaMap::const_iterator meta_item = mapMeta.begin(); meta_item != mapMeta.end(); meta_item++) { - if (!SelectCoinsSimple(nBalance - nReserveBalance, GetAdjustedTime(), nCoinbaseMaturity * 10, setCoins, nValueIn)) - return false; + // Get coin + CoinsSet::value_type pcoin = meta_item->second.first.second; - if (setCoins.empty()) - return false; + int64 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)GetTime()); + CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60); - hashPrevBlock == pindexBest->GetBlockHash(); + // 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(); + } } -*/ - set > setCoins; + return true; +} + +bool CWallet::MergeCoins(const int64& nAmount, const int64& nMinValue, const int64& nOutputValue, list& listMerged) +{ + int64 nBalance = GetBalance(); + + if (nAmount > nBalance) + return false; + + listMerged.clear(); int64 nValueIn = 0; + set > setCoins; - if (!SelectCoinsSimple(nBalance - nReserveBalance, GetTime(), nCoinbaseMaturity * 10, setCoins, nValueIn)) + // Simple coins selection - no randomization + if (!SelectCoinsSimple(nAmount, nMinValue, nOutputValue, GetTime(), 1, setCoins, nValueIn)) return false; if (setCoins.empty()) return false; - CTxDB txdb("r"); + CWalletTx wtxNew; + vector vwtxPrev; + + // Reserve a new key pair from key pool + CReserveKey reservekey(this); + CPubKey vchPubKey = reservekey.GetReservedKey(); + + // Output script + CScript scriptOutput; + scriptOutput.SetDestination(vchPubKey.GetID()); + + // Insert output + wtxNew.vout.push_back(CTxOut(0, scriptOutput)); + + double dWeight = 0; 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 nCredit = pcoin.first->vout[pcoin.second].nValue; + + // Add current coin to inputs list and add its credit to transaction output + wtxNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second)); + wtxNew.vout[0].nValue += nCredit; + vwtxPrev.push_back(pcoin.first); + +/* + // Replaced with estimation for performance purposes + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign scripts to get actual transaction size for fee calculation + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; } +*/ - unsigned int nTime = pcoin.first->nTime; + // Assuming that average scriptsig size is 110 bytes + int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION) + wtxNew.vin.size() * 110; + dWeight += (double)nCredit * pcoin.first->GetDepthInMainChain(); - // Kernel hash weight starts from 0 at the 30-day min age - // this change increases active coins participating the hash and helps - // to secure the network when proof-of-stake difficulty is low - // - // Maximum TimeWeight is 90 days. - int64 nTimeWeight = min((int64)GetTime() - nTime - nStakeMinAge, (int64)nStakeMaxAge); - CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60); + double dFinalPriority = dWeight /= nBytes; + bool fAllowFree = CTransaction::AllowFree(dFinalPriority); + + // Get actual transaction fee according to its estimated size and priority + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); - // Count input that is more than 90 days old - if (nTime + nStakeMaxAge < GetTime()) + // Prepare transaction for commit if sum is enough ot its size is too big + if (nBytes >= MAX_BLOCK_SIZE_GEN/6 || wtxNew.vout[0].nValue >= nOutputValue) { - nMaxWeight += bnCoinDayWeight.getuint64(); - nWeight += bnCoinDayWeight.getuint64(); + wtxNew.vout[0].nValue -= nMinFee; // Set actual fee + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign all scripts + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; + } + + // Try to commit, return false on failure + if (!CommitTransaction(wtxNew, reservekey)) + return false; + + listMerged.push_back(wtxNew.GetHash()); // Add to hashes list + + dWeight = 0; // Reset all temporary values + vwtxPrev.clear(); + wtxNew.SetNull(); + wtxNew.vout.push_back(CTxOut(0, scriptOutput)); } + } - // Count input that is more than 30 days old and less than 90 days old - if (nTime + nStakeMinAge < GetTime() && nTime + nStakeMaxAge > GetTime()) - { - nMinWeight += bnCoinDayWeight.getuint64(); - nWeight += bnCoinDayWeight.getuint64(); + // Create transactions if there are some unhandled coins left + if (wtxNew.vout[0].nValue > 0) { + int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION) + wtxNew.vin.size() * 110; + + double dFinalPriority = dWeight /= nBytes; + bool fAllowFree = CTransaction::AllowFree(dFinalPriority); + + // Get actual transaction fee according to its size and priority + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); + + wtxNew.vout[0].nValue -= nMinFee; // Set actual fee + + if (wtxNew.vout[0].nValue <= 0) + return false; + + for (unsigned int i = 0; i < wtxNew.vin.size(); i++) { + const CWalletTx *txin = vwtxPrev[i]; + + // Sign all scripts again + if (!SignSignature(*this, *txin, wtxNew, i)) + return false; } + + // Try to commit, return false on failure + if (!CommitTransaction(wtxNew, reservekey)) + return false; + + listMerged.push_back(wtxNew.GetHash()); // Add to hashes list } return true; } + 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; CBigNum bnTargetPerCoinDay; @@ -1571,149 +1870,149 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int vector vwtxPrev; -/* - * TODO: performance comparison + CTxDB txdb("r"); + { + LOCK2(cs_main, cs_wallet); + // Cache outputs unless best block or wallet transaction set changed + if (!fCoinsDataActual) + { + mapMeta.clear(); + int64 nValueIn = 0; + CoinsSet setCoins; + if (!SelectCoinsSimple(nBalance - nReserveBalance, MIN_TX_FEE, MAX_MONEY, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn)) + return false; - static set > setCoins; - static uint256 hashPrevBlock; - static int64 nValueIn = 0; + if (setCoins.empty()) + return false; - // Cache outputs unless best block changed - if (hashPrevBlock != pindexBest->GetBlockHash()) - { - if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn)) - return false; + { + CTxIndex txindex; + CBlock block; + for(CoinsSet::iterator pcoin = setCoins.begin(); pcoin != setCoins.end(); pcoin++) + { + // Load transaction index item + if (!txdb.ReadTxIndex(pcoin->first->GetHash(), txindex)) + continue; - if (setCoins.empty()) - return false; + // Read block header + if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + continue; - hashPrevBlock == pindexBest->GetBlockHash(); - } -*/ + uint64 nStakeModifier = 0; + if (!GetKernelStakeModifier(block.GetHash(), nStakeModifier)) + continue; - set > setCoins; - int64 nValueIn = 0; + // Add meta record + // txid => ((txindex, (tx, vout.n)), (block, modifier)) + mapMeta[make_pair(pcoin->first->GetHash(), pcoin->second)] = make_pair(make_pair(txindex, *pcoin), make_pair(block, nStakeModifier)); - // Select coins with suitable depth - if (!SelectCoinsSimple(nBalance - nReserveBalance, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn)) - return false; + if (fDebug) + printf("Load coin: %s\n", pcoin->first->GetHash().GetHex().c_str()); + } + } - if (setCoins.empty()) - return false; + if (fDebug) + printf("Stake miner: %zu meta items loaded for %zu coins\n", mapMeta.size(), setCoins.size()); + + fCoinsDataActual = true; + } + } int64 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; + KernelSearchSettings settings; + settings.nBits = nBits; + settings.nTime = txNew.nTime; + settings.nOffset = 0; + settings.nLimit = mapMeta.size(); + settings.nSearchInterval = nSearchInterval; + + unsigned int nTimeTx, nBlockTime; + COutPoint prevoutStake; + CoinsSet::value_type kernelcoin; + + if (ScanForStakeKernelHash(mapMeta, settings, kernelcoin, nTimeTx, nBlockTime)) + { + // Found a kernel + if (fDebug && GetBoolArg("-printcoinstake")) + printf("CreateCoinStake : kernel found\n"); + vector vSolutions; + txnouttype whichType; + CScript scriptPubKeyOut; + scriptPubKeyKernel = kernelcoin.first->vout[kernelcoin.second].scriptPubKey; + if (!Solver(scriptPubKeyKernel, whichType, vSolutions)) + { + if (fDebug && GetBoolArg("-printcoinstake")) + printf("CreateCoinStake : failed to parse kernel\n"); + return false; + } + if (fDebug && GetBoolArg("-printcoinstake")) + printf("CreateCoinStake : parsed kernel type=%d\n", whichType); + if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH) { - LOCK2(cs_main, cs_wallet); - if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) - continue; + if (fDebug && GetBoolArg("-printcoinstake")) + printf("CreateCoinStake : no support for kernel type=%d\n", whichType); + return false; // only support pay to public key and pay to address } - - 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; nGetHash(), pcoin.second); - if (CheckStakeKernelHash(nBits, block, txindex.pos.nTxPos - txindex.pos.nBlockPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake)) + // convert to pay to public key type + if (!keystore.GetKey(uint160(vSolutions[0]), key)) { - // Found a kernel if (fDebug && GetBoolArg("-printcoinstake")) - printf("CreateCoinStake : kernel found\n"); - vector 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; - } + printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); + return false; // 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 : 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 (block.GetBlockTime() + nStakeSplitAge > txNew.nTime) - txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake + printf("CreateCoinStake : failed to get key for kernel type=%d\n", whichType); + return false; // unable to find corresponding public key + } + if (key.GetPubKey() != vchPubKey) + { if (fDebug && GetBoolArg("-printcoinstake")) - printf("CreateCoinStake : added kernel type=%d\n", whichType); - fKernelFound = true; - break; + printf("CreateCoinStake : invalid key for kernel type=%d\n", whichType); + return false; // keys mismatch } + + scriptPubKeyOut = scriptPubKeyKernel; } - if (fKernelFound || fShutdown) - break; // if kernel is found stop searching + txNew.nTime = nTimeTx; + txNew.vin.push_back(CTxIn(kernelcoin.first->GetHash(), kernelcoin.second)); + nCredit += kernelcoin.first->vout[kernelcoin.second].nValue; + vwtxPrev.push_back(kernelcoin.first); + txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); + + if (GetWeight((int64)nBlockTime, (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); } if (nCredit == 0 || nCredit > nBalance - nReserveBalance) return false; - BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) + // (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier)) + for(MetaMap::const_iterator meta_item = mapMeta.begin(); meta_item != mapMeta.end(); meta_item++) { + // Get coin + CoinsSet::value_type pcoin = meta_item->second.first.second; + // 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 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)txNew.nTime); + // Stop adding more inputs if already too many inputs if (txNew.vin.size() >= 100) break; @@ -1727,8 +2026,9 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int 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); @@ -1770,9 +2070,9 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int return error("CreateCoinStake : exceeded coinstake size limit"); // Check enough fee is paid - if (nMinFee < txNew.GetMinFee() - MIN_TX_FEE) + if (nMinFee < txNew.GetMinFee(1, false, GMF_BLOCK, nBytes) - CENT) { - nMinFee = txNew.GetMinFee() - MIN_TX_FEE; + nMinFee = txNew.GetMinFee(1, false, GMF_BLOCK, nBytes) - CENT; continue; // try signing again } else @@ -2165,14 +2465,14 @@ std::map CWallet::GetAddressBalances() { CWalletTx *pcoin = &walletEntry.second; - if (!pcoin->IsFinal() || !pcoin->IsConfirmed()) + 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)) + if (nDepth < (pcoin->IsFromMe(MINE_ALL) ? 0 : 1)) continue; for (unsigned int i = 0; i < pcoin->vout.size(); i++) @@ -2324,6 +2624,16 @@ void CWallet::FixSpentCoins(int& nMismatchFound, int64& nBalanceInQuestion, bool pcoin->WriteToDisk(); } } + + } + + if(IsMine((CTransaction)*pcoin) && (pcoin->IsCoinBase() || pcoin->IsCoinStake()) && pcoin->GetDepthInMainChain() == 0) + { + printf("FixSpentCoins %s tx %s\n", fCheckOnly ? "found" : "removed", pcoin->GetHash().ToString().c_str()); + if (!fCheckOnly) + { + EraseFromWallet(pcoin->GetHash()); + } } } } @@ -2412,6 +2722,7 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx) map::const_iterator mi = mapWallet.find(hashTx); if (mi != mapWallet.end()) NotifyTransactionChanged(this, hashTx, CT_UPDATED); + vMintingWalletUpdated.push_back(hashTx); } }