X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=src%2Fwallet.cpp;h=f9ce658161998079e567aee99f55d883bea96e70;hb=dedcc04171679c3deaad10b88f99c72c8a9dac5d;hp=e1dc9eb3facacf8f666a58b65f9aae79df81b5db;hpb=4ffa25aa74b459ff189a74b8cb40f1c07e34e36c;p=novacoin.git diff --git a/src/wallet.cpp b/src/wallet.cpp index e1dc9eb..f9ce658 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -13,8 +13,12 @@ #include "coincontrol.h" #include +#include "main.h" + using namespace std; -extern int nStakeMaxAge; + + +bool fCoinsDataActual; ////////////////////////////////////////////////////////////////////////////// // @@ -389,6 +393,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock) wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); + vMintingWalletUpdated.push_back(txin.prevout.hash); } } } @@ -406,6 +411,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx, bool fBlock) wtx.MarkUnspent(&txout - &tx.vout[0]); wtx.WriteToDisk(); NotifyTransactionChanged(this, hash, CT_UPDATED); + vMintingWalletUpdated.push_back(hash); } } } @@ -536,7 +542,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) // Notify UI of new or updated transaction NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); - + vMintingWalletUpdated.push_back(hash); // notify an external script when a wallet transaction comes in or is updated std::string strCmd = GetArg("-walletnotify", ""); @@ -642,8 +648,7 @@ bool CWallet::IsChange(const CTxOut& txout) const int64 CWalletTx::GetTxTime() const { - int64 n = nTimeSmart; - return n ? n : nTimeReceived; + return nTime; } int CWalletTx::GetRequestCount() const @@ -1151,7 +1156,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const } } -void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf) const +void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf, int64 nMinValue, int64 nMaxValue) const { vCoins.clear(); @@ -1170,7 +1175,12 @@ void CWallet::AvailableCoinsMinConf(vector& vCoins, int nConf) const 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) + // 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)); } } @@ -1400,10 +1410,10 @@ 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; @@ -1415,6 +1425,10 @@ bool CWallet::SelectCoinsSimple(int64 nTargetValue, unsigned int nSpendTime, int 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; @@ -1611,88 +1625,163 @@ void CWallet::GetStakeWeightFromValue(const int64& nTime, const int64& nValue, u } -// NovaCoin: get current stake weight -bool CWallet::GetStakeWeight(const CKeyStore& keystore, uint64& nMinWeight, uint64& nMaxWeight, uint64& nWeight) +// NovaCoin: get current stake miner statistics +void CWallet::GetStakeStats(float &nKernelsRate, float &nCoinDaysRate) { - // Choose coins to use - int64 nBalance = GetBalance(); - int64 nReserveBalance = 0; + static uint64 nLastKernels = 0, nLastCoinDays = 0; + static float nLastKernelsRate = 0, nLastCoinDaysRate = 0; + static int64 nLastTime = GetTime(); - if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) + if (nKernelsTried < nLastKernels) { - error("GetStakeWeight : invalid reserve balance amount"); - return false; - } - - if (nBalance <= nReserveBalance) - return false; + nLastKernels = 0; + nLastCoinDays = 0; - vector vwtxPrev; - -/* - * TODO: performance comparison + nLastTime = GetTime(); + } - static set > setCoins; - static uint256 hashPrevBlock; - static int64 nValueIn = 0; + int64 nInterval = GetTime() - nLastTime; + //if (nKernelsTried > 1000 && nInterval > 5) + if (nInterval > 10) + { + nKernelsRate = nLastKernelsRate = ( nKernelsTried - nLastKernels ) / (float) nInterval; + nCoinDaysRate = nLastCoinDaysRate = ( nCoinDaysTried - nLastCoinDays ) / (float) nInterval; - // Cache outputs unless best block changed - if (hashPrevBlock != pindexBest->GetBlockHash()) + nLastKernels = nKernelsTried; + nLastCoinDays = nCoinDaysTried; + nLastTime = GetTime(); + } + else { - if (!SelectCoinsSimple(nBalance - nReserveBalance, GetAdjustedTime(), nCoinbaseMaturity * 10, setCoins, nValueIn)) - return false; + nKernelsRate = nLastKernelsRate; + nCoinDaysRate = nLastCoinDaysRate; + } +} - if (setCoins.empty()) - return false; +bool CWallet::MergeCoins(const int64& nAmount, const int64& nMinValue, const int64& nOutputValue, list& listMerged) +{ + int64 nBalance = GetBalance(); - hashPrevBlock == pindexBest->GetBlockHash(); - } -*/ + if (nAmount > nBalance) + return false; - set > setCoins; + 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; - int64 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)GetTime()); - CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60); + // 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); - // Weight is greater than zero - if (nTimeWeight > 0) - { - nWeight += bnCoinDayWeight.getuint64(); +/* + // 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; } +*/ + + // 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(); + + double dFinalPriority = dWeight /= nBytes; + bool fAllowFree = CTransaction::AllowFree(dFinalPriority); - // Weight is greater than zero, but the maximum value isn't reached yet - if (nTimeWeight > 0 && nTimeWeight < nStakeMaxAge) + // Get actual transaction fee according to its estimated size and priority + int64 nMinFee = wtxNew.GetMinFee(1, fAllowFree, GMF_SEND, nBytes); + + // 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) { - nMinWeight += 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)); } + } - // Maximum weight was reached - if (nTimeWeight == nStakeMaxAge) - { - nMaxWeight += 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 combine threshold is important to security @@ -1722,145 +1811,144 @@ 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: %"PRIszu" meta items loaded for %"PRIszu" coins\n", mapMeta.size(), setCoins.size()); + + fCoinsDataActual = true; + nKernelsTried = 0; + nCoinDaysTried = 0; + } + } 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, nKernelsTried, nCoinDaysTried)) + { + // 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 (GetWeight(block.GetBlockTime(), (int64)txNew.nTime) < nStakeMaxAge) - 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)) @@ -1896,7 +1984,13 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int CTxDB txdb("r"); if (!txNew.GetCoinAge(txdb, nCoinAge)) return error("CreateCoinStake : failed to calculate coin age"); - nCredit += GetProofOfStakeReward(nCoinAge, nBits, txNew.nTime); + + int64 nReward = GetProofOfStakeReward(nCoinAge, nBits, txNew.nTime); + // Refuse to create mint that has zero or negative reward + if(nReward <= 0) + return false; + + nCredit += nReward; } int64 nMinFee = 0; @@ -1971,6 +2065,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) coin.MarkSpent(txin.prevout.n); coin.WriteToDisk(); NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); + vMintingWalletUpdated.push_back(coin.GetHash()); } if (fFileBacked) @@ -2576,7 +2671,10 @@ void CWallet::UpdatedTransaction(const uint256 &hashTx) // Only notify UI if this transaction is in this wallet map::const_iterator mi = mapWallet.find(hashTx); if (mi != mapWallet.end()) + { NotifyTransactionChanged(this, hashTx, CT_UPDATED); + vMintingWalletUpdated.push_back(hashTx); + } } } @@ -2630,3 +2728,21 @@ void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { for (std::map::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off } + +void CWallet::ClearOrphans() +{ + list orphans; + + LOCK(cs_wallet); + for(map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) + { + const CWalletTx *wtx = &(*it).second; + if((wtx->IsCoinBase() || wtx->IsCoinStake()) && !wtx->IsInMainChain()) + { + orphans.push_back(wtx->GetHash()); + } + } + + for(list::const_iterator it = orphans.begin(); it != orphans.end(); ++it) + EraseFromWallet(*it); +}