#include "coincontrol.h"
#include <boost/algorithm/string/replace.hpp>
+#include "main.h"
+
using namespace std;
-extern int nStakeMaxAge;
+
+
+bool fCoinsDataActual;
//////////////////////////////////////////////////////////////////////////////
//
}
}
-void CWallet::AvailableCoinsMinConf(vector<COutput>& vCoins, int nConf) const
+void CWallet::AvailableCoinsMinConf(vector<COutput>& vCoins, int nConf, int64 nMinValue, int64 nMaxValue) const
{
vCoins.clear();
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));
}
}
}
// 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
+bool CWallet::SelectCoinsSimple(int64 nTargetValue, int64 nMinValue, int64 nMaxValue, unsigned int nSpendTime, int nMinConf, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64& nValueRet) const
{
vector<COutput> vCoins;
- AvailableCoinsMinConf(vCoins, nMinConf);
+ AvailableCoinsMinConf(vCoins, nMinConf, nMinValue, nMaxValue);
setCoinsRet.clear();
nValueRet = 0;
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;
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<const CWalletTx*> 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<pair<const CWalletTx*,unsigned int> > 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;
- // Cache outputs unless best block changed
- if (hashPrevBlock != pindexBest->GetBlockHash())
- {
- if (!SelectCoinsSimple(nBalance - nReserveBalance, GetAdjustedTime(), nCoinbaseMaturity * 10, setCoins, nValueIn))
- return false;
+ // Read block header
+ if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
+ continue;
- if (setCoins.empty())
- return false;
+ uint64 nStakeModifier = 0;
+ if (!GetKernelStakeModifier(block.GetHash(), nStakeModifier))
+ continue;
- hashPrevBlock == pindexBest->GetBlockHash();
- }
-*/
+ // Add meta record
+ // txid => ((txindex, (tx, vout.n)), (block, modifier))
+ mapMeta[pcoin->first->GetHash()] = make_pair(make_pair(txindex, *pcoin), make_pair(block, nStakeModifier));
- set<pair<const CWalletTx*,unsigned int> > setCoins;
- int64 nValueIn = 0;
+ if (fDebug)
+ printf("Load coin: %s\n", pcoin->first->GetHash().GetHex().c_str());
+ }
+ }
- if (!SelectCoinsSimple(nBalance - nReserveBalance, GetTime(), nCoinbaseMaturity * 10, setCoins, nValueIn))
- return false;
+ if (fDebug)
+ printf("Get stake weight: %zu meta items loaded for %zu coins\n", mapMeta.size(), setCoins.size());
- if (setCoins.empty())
- return false;
+ fCoinsDataActual = true;
+ }
+ }
- CTxDB txdb("r");
- BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
+
+ // txid => ((txindex, (tx, vout.n)), (block, modifier))
+ for(MetaMap::const_iterator meta_item = mapMeta.begin(); meta_item != mapMeta.end(); meta_item++)
{
- CTxIndex txindex;
- {
- LOCK2(cs_main, cs_wallet);
- if (!txdb.ReadTxIndex(pcoin.first->GetHash(), txindex))
- continue;
- }
+ // Get coin
+ CoinsSet::value_type pcoin = meta_item->second.first.second;
int64 nTimeWeight = GetWeight((int64)pcoin.first->nTime, (int64)GetTime());
CBigNum bnCoinDayWeight = CBigNum(pcoin.first->vout[pcoin.second].nValue) * nTimeWeight / COIN / (24 * 60 * 60);
return true;
}
-bool CWallet::MergeCoins(const int64& nAmount, const int64& nMaxValue, const int64& nOutputValue, list<uint256>& listMerged)
+bool CWallet::MergeCoins(const int64& nAmount, const int64& nMinValue, const int64& nOutputValue, list<uint256>& listMerged)
{
int64 nBalance = GetBalance();
set<pair<const CWalletTx*,unsigned int> > setCoins;
// Simple coins selection - no randomization
- if (!SelectCoinsSimple(nAmount, GetTime(), 1, setCoins, nValueIn))
+ if (!SelectCoinsSimple(nAmount, nMinValue, nOutputValue, GetTime(), 1, setCoins, nValueIn))
return false;
if (setCoins.empty())
wtxNew.vout.push_back(CTxOut(0, scriptOutput));
double dWeight = 0;
-
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
int64 nCredit = pcoin.first->vout[pcoin.second].nValue;
- // Ignore coin if credit is too high
- if (nCredit >= nMaxValue)
- continue;
-
- // Ignore immature coins
- if (pcoin.first->GetBlocksToMaturity() > 0)
- continue;
-
// 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];
if (!SignSignature(*this, *txin, wtxNew, i))
return false;
}
+*/
- int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
+ // 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);
- // Get actual transaction fee according to its size and priority
+ // 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 && wtxNew.vout.size() > 1))
+ if (nBytes >= MAX_BLOCK_SIZE_GEN/6 || wtxNew.vout[0].nValue >= nOutputValue)
{
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 again
+ // Sign all scripts
if (!SignSignature(*this, *txin, wtxNew, i))
return false;
}
// Create transactions if there are some unhandled coins left
if (wtxNew.vout[0].nValue > 0) {
- int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
+ int64 nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION) + wtxNew.vin.size() * 110;
double dFinalPriority = dWeight /= nBytes;
bool fAllowFree = CTransaction::AllowFree(dFinalPriority);
vector<const CWalletTx*> 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<pair<const CWalletTx*,unsigned int> > 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<pair<const CWalletTx*,unsigned int> > setCoins;
- int64 nValueIn = 0;
+ // Add meta record
+ // txid => ((txindex, (tx, vout.n)), (block, modifier))
+ mapMeta[pcoin->first->GetHash()] = 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<valtype> 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; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown; n++)
+ if (whichType == TX_PUBKEYHASH) // pay to address type
{
- // 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))
+ // 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<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;
- }
+ 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 => ((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))