std::string strWalletFileName;
bool fConfChange;
unsigned int nNodeLifespan;
-unsigned int nMinerSleep;
bool fUseFastIndex;
bool fUseMemoryLog;
enum Checkpoints::CPMode CheckpointsMode;
// Ping and address broadcast intervals
extern int64_t nPingInterval;
extern int64_t nBroadcastInterval;
+extern int64_t nReserveBalance;
//////////////////////////////////////////////////////////////////////////////
//
nNodeLifespan = (unsigned int)(GetArg("-addrlifespan", 7));
fUseFastIndex = GetBoolArg("-fastindex", true);
fUseMemoryLog = GetBoolArg("-memorylog", true);
- nMinerSleep = (unsigned int)(GetArg("-minersleep", 500));
// Ping and address broadcast intervals
nPingInterval = max<int64_t>(10 * 60, GetArg("-keepalive", 30 * 60));
if (mapArgs.count("-reservebalance")) // ppcoin: reserve balance amount
{
- int64_t nReserveBalance = 0;
if (!ParseMoney(mapArgs["-reservebalance"], nReserveBalance))
{
InitError(_("Invalid amount for -reservebalance=<amount>"));
return (nTimeBlock >= (fTestNet? nModifierTestSwitchTime : nModifierSwitchTime));
}
-// Get time weight
-int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
-{
- // 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.
-
- return min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, (int64_t)nStakeMaxAge);
-}
-
// Get the last stake modifier and its generation time from a given block
static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64_t& nStakeModifier, int64_t& nModifierTime)
{
DateTimeStrFormat(nStakeModifierTime).c_str(),
mapBlockIndex[hashBlockFrom]->nHeight,
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
- printf("CheckStakeKernelHash() : check modifier=0x%016" PRIx64 " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
+ printf("CheckStakeKernelHash() : check modifier=0x%016" PRIx64 " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashTarget=%s hashProof=%s\n",
nStakeModifier,
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
- hashProofOfStake.ToString().c_str());
+ targetProofOfStake.ToString().c_str(), hashProofOfStake.ToString().c_str());
}
// Now check if proof-of-stake hash meets target protocol
DateTimeStrFormat(nStakeModifierTime).c_str(),
mapBlockIndex[hashBlockFrom]->nHeight,
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
- printf("CheckStakeKernelHash() : pass modifier=0x%016" PRIx64 " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
+ printf("CheckStakeKernelHash() : pass modifier=0x%016" PRIx64 " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashTarget=%s hashProof=%s\n",
nStakeModifier,
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
- hashProofOfStake.ToString().c_str());
+ targetProofOfStake.ToString().c_str(), hashProofOfStake.ToString().c_str());
}
return true;
}
-// Scan given coins set for kernel solution
-bool ScanForStakeKernelHash(MetaMap &mapMeta, uint32_t nBits, uint32_t nTime, uint32_t nSearchInterval, CoinsSet::value_type &kernelcoin, uint32_t &nTimeTx, uint32_t &nBlockTime, uint64_t &nKernelsTried, uint64_t &nCoinDaysTried)
-{
- uint256 hashProofOfStake = 0;
-
- // (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier))
- for(MetaMap::const_iterator meta_item = mapMeta.begin(); meta_item != mapMeta.end(); meta_item++)
- {
- if (!fCoinsDataActual)
- break;
-
- CTxIndex txindex = (*meta_item).second.first.first;
- CBlock block = (*meta_item).second.second.first;
- uint64_t nStakeModifier = (*meta_item).second.second.second;
-
- // Get coin
- CoinsSet::value_type pcoin = meta_item->second.first.second;
-
- static unsigned int nMaxStakeSearchInterval = 60;
-
- // only count coins meeting min age requirement
- if (nStakeMinAge + block.nTime > nTime - nMaxStakeSearchInterval)
- continue;
-
- // Transaction offset inside block
- uint32_t nTxOffset = txindex.pos.nTxPos - txindex.pos.nBlockPos;
-
- // Current timestamp scanning interval
- unsigned int nCurrentSearchInterval = min(nSearchInterval, nMaxStakeSearchInterval);
-
- nBlockTime = block.nTime;
- CBigNum bnTargetPerCoinDay;
- bnTargetPerCoinDay.SetCompact(nBits);
- int64_t nValueIn = pcoin.first->vout[pcoin.second].nValue;
-
- // Search backward in time from the given timestamp
- // Search nSearchInterval seconds back up to nMaxStakeSearchInterval
- // Stopping search in case of shutting down or cache invalidation
- for (unsigned int n=0; n<nCurrentSearchInterval && fCoinsDataActual && !fShutdown; n++)
- {
- nTimeTx = nTime - n;
- CBigNum bnCoinDayWeight = CBigNum(nValueIn) * GetWeight((int64_t)pcoin.first->nTime, (int64_t)nTimeTx) / COIN / (24 * 60 * 60);
- CBigNum bnTargetProofOfStake = bnCoinDayWeight * bnTargetPerCoinDay;
-
- // Build kernel
- CDataStream ss(SER_GETHASH, 0);
- ss << nStakeModifier;
- ss << nBlockTime << nTxOffset << pcoin.first->nTime << pcoin.second << nTimeTx;
-
- // Calculate kernel hash
- hashProofOfStake = Hash(ss.begin(), ss.end());
-
- // Update statistics
- nKernelsTried += 1;
- nCoinDaysTried += bnCoinDayWeight.getuint64();
-
- if (bnTargetProofOfStake >= CBigNum(hashProofOfStake))
- {
- if (fDebug)
- printf("nStakeModifier=0x%016" PRIx64 ", nBlockTime=%u nTxOffset=%u nTxPrevTime=%u nVout=%u nTimeTx=%u hashProofOfStake=%s Success=true\n",
- nStakeModifier, nBlockTime, nTxOffset, pcoin.first->nTime, pcoin.second, nTimeTx, hashProofOfStake.GetHex().c_str());
-
- kernelcoin = pcoin;
- return true;
- }
-
- if (fDebug)
- printf("nStakeModifier=0x%016" PRIx64 ", nBlockTime=%u nTxOffset=%u nTxPrevTime=%u nTxNumber=%u nTimeTx=%u hashProofOfStake=%s Success=false\n",
- nStakeModifier, nBlockTime, nTxOffset, pcoin.first->nTime, pcoin.second, nTimeTx, hashProofOfStake.GetHex().c_str());
- }
- }
-
- return false;
-}
-
// Precompute hashing state for static part of kernel
void GetKernelMidstate(uint64_t nStakeModifier, uint32_t nBlockTime, uint32_t nTxOffset, uint32_t nInputTxTime, uint32_t nOut, SHA256_CTX &ctx)
{
return false;
}
+// Scan given midstate for solution
+bool ScanMidstateBackward(SHA256_CTX &ctx, uint32_t nBits, uint32_t nInputTxTime, int64_t nValueIn, std::pair<uint32_t, uint32_t> &SearchInterval, std::pair<uint256, uint32_t> &solution)
+{
+ CBigNum bnTargetPerCoinDay;
+ bnTargetPerCoinDay.SetCompact(nBits);
+
+ // Get maximum possible target to filter out the majority of obviously insufficient hashes
+ CBigNum bnMaxTargetPerCoinDay = bnTargetPerCoinDay * CBigNum(nValueIn) * nStakeMaxAge / COIN / (24 * 60 * 60);
+ uint256 maxTarget = bnMaxTargetPerCoinDay.getuint256();
+
+ SHA256_CTX ctxCopy = ctx;
+
+ // Search backward in time from the given timestamp
+ // Stopping search in case of shutting down
+ for (uint32_t nTimeTx=SearchInterval.first; nTimeTx>SearchInterval.second && !fShutdown; nTimeTx--)
+ {
+ // Complete first hashing iteration
+ uint256 hash1;
+ SHA256_Update(&ctxCopy, (unsigned char*)&nTimeTx, 4);
+ SHA256_Final((unsigned char*)&hash1, &ctxCopy);
+
+ // Restore context
+ ctxCopy = ctx;
+
+ // Finally, calculate kernel hash
+ uint256 hashProofOfStake;
+ SHA256((unsigned char*)&hash1, sizeof(hashProofOfStake), (unsigned char*)&hashProofOfStake);
+
+ // Skip if hash doesn't satisfy the maximum target
+ if (hashProofOfStake > maxTarget)
+ continue;
+
+ CBigNum bnCoinDayWeight = CBigNum(nValueIn) * GetWeight((int64_t)nInputTxTime, (int64_t)nTimeTx) / COIN / (24 * 60 * 60);
+ CBigNum bnTargetProofOfStake = bnCoinDayWeight * bnTargetPerCoinDay;
+
+ if (bnTargetProofOfStake >= CBigNum(hashProofOfStake))
+ {
+ solution.first = hashProofOfStake;
+ solution.second = nTimeTx;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
// Check kernel hash target and coinstake signature
bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake)
{
#include "main.h"
#include "wallet.h"
+using namespace std;
+
// ChainDB upgrade time
extern unsigned int nModifierUpgradeTime;
// MODIFIER_INTERVAL: time to elapse before new modifier is computed
extern unsigned int nModifierInterval;
-extern bool fCoinsDataActual;
-
// MODIFIER_INTERVAL_RATIO:
// ratio of group interval length between the last group and the first group
static const int MODIFIER_INTERVAL_RATIO = 3;
// Sets hashProofOfStake on success return
bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, uint32_t nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, uint32_t nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake=false);
-// Scan given coins set for kernel solution
-bool ScanForStakeKernelHash(MetaMap &mapMeta, uint32_t nBits, uint32_t nTime, uint32_t nSearchInterval, CoinsSet::value_type &kernelcoin, uint32_t &nTimeTx, uint32_t &nBlockTime, uint64_t &nKernelsTried, uint64_t &nCoinDaysTried);
-
// Precompute hashing state for static part of kernel
void GetKernelMidstate(uint64_t nStakeModifier, uint32_t nBlockTime, uint32_t nTxOffset, uint32_t nInputTxTime, uint32_t nOut, SHA256_CTX &ctx);
// Scan given midstate for kernel solutions
bool ScanMidstateForward(SHA256_CTX &ctx, uint32_t nBits, uint32_t nInputTxTime, int64_t nValueIn, std::pair<uint32_t, uint32_t> &SearchInterval, std::pair<uint256, uint32_t> &solution);
+bool ScanMidstateBackward(SHA256_CTX &ctx, uint32_t nBits, uint32_t nInputTxTime, int64_t nValueIn, std::pair<uint32_t, uint32_t> &SearchInterval, std::pair<uint256, uint32_t> &solution);
// Check kernel hash target and coinstake signature
// Sets hashProofOfStake on success return
bool CheckStakeModifierCheckpoints(int nHeight, uint32_t nStakeModifierChecksum);
// Get time weight using supplied timestamps
-int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd);
+inline int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd)
+{
+ // 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.
+
+ return min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, (int64_t)nStakeMaxAge);
+}
+
#endif // PPCOIN_KERNEL_H
BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered)
pwallet->AddToWalletIfInvolvingMe(tx, pblock, fUpdate);
- // Preloaded coins cache invalidation
- fCoinsDataActual = false;
}
// notify wallets about a new best chain
// Human readable form:
//
// nSubsidy = 100 / (diff ^ 1/6)
+ //
+ // Please note that we're using bisection to find an approximate solutuion
CBigNum bnLowerBound = CENT;
CBigNum bnUpperBound = bnSubsidyLimit;
while (bnLowerBound + CENT <= bnUpperBound)
{
CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2;
- if (fDebug && GetBoolArg("-printcreation"))
- printf("GetProofOfWorkReward() : lower=%" PRId64 " upper=%" PRId64 " mid=%" PRId64 "\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64());
if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget)
bnUpperBound = bnMidValue;
else
while (bnLowerBound + CENT <= bnUpperBound)
{
CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2;
- if (fDebug && GetBoolArg("-printcreation"))
- printf("GetProofOfStakeReward() : lower=%" PRId64 " upper=%" PRId64 " mid=%" PRId64 "\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64());
-
if(!fTestNet && nTime < STAKECURVE_SWITCH_TIME)
{
//
return true;
}
-// novacoin: attempt to generate suitable proof-of-stake
-bool CBlock::SignBlock(CWallet& wallet)
-{
- // if we are trying to sign
- // something except proof-of-stake block template
- if (!vtx[0].vout[0].IsEmpty())
- return false;
-
- // if we are trying to sign
- // a complete proof-of-stake block
- if (IsProofOfStake())
- return true;
-
- static uint32_t nLastCoinStakeSearchTime = GetAdjustedTime(); // startup timestamp
-
- CKey key;
- CTransaction txCoinStake;
- uint32_t nSearchTime = txCoinStake.nTime; // search to current time
-
- if (nSearchTime > nLastCoinStakeSearchTime)
- {
- if (wallet.CreateCoinStake(wallet, nBits, nSearchTime-nLastCoinStakeSearchTime, txCoinStake, key))
- {
- if (txCoinStake.nTime >= max(pindexBest->GetMedianTimePast()+1, PastDrift(pindexBest->GetBlockTime())))
- {
- // make sure coinstake would meet timestamp protocol
- // as it would be the same as the block timestamp
- vtx[0].nTime = nTime = txCoinStake.nTime;
- nTime = max(pindexBest->GetMedianTimePast()+1, GetMaxTransactionTime());
- nTime = max(GetBlockTime(), PastDrift(pindexBest->GetBlockTime()));
-
- // we have to make sure that we have no future timestamps in
- // our transactions set
- for (vector<CTransaction>::iterator it = vtx.begin(); it != vtx.end();)
- if (it->nTime > nTime) { it = vtx.erase(it); } else { ++it; }
-
- vtx.insert(vtx.begin() + 1, txCoinStake);
- hashMerkleRoot = BuildMerkleTree();
-
- // append a signature to our block
- return key.Sign(GetHash(), vchBlockSig);
- }
- }
- nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
- nLastCoinStakeSearchTime = nSearchTime;
- }
-
- return false;
-}
-
// ppcoin: check block signature
bool CBlock::CheckBlockSignature() const
{
bool CheckBlock(bool fCheckPOW=true, bool fCheckMerkleRoot=true, bool fCheckSig=true) const;
bool AcceptBlock();
bool GetCoinAge(uint64_t& nCoinAge) const; // ppcoin: calculate total coin age spent in block
- bool SignBlock(CWallet& keystore);
bool CheckBlockSignature() const;
private:
// BitcoinMiner
//
-extern unsigned int nMinerSleep;
+int64_t nReserveBalance = 0;
+static unsigned int nMaxStakeSearchInterval = 60;
int static FormatHashBlocks(void* pbuffer, unsigned int len)
{
}
};
-// CreateNewBlock: create new block (without proof-of-work/proof-of-stake)
-CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
+// CreateNewBlock: create new block (without proof-of-work/with provided coinstake)
+CBlock* CreateNewBlock(CWallet* pwallet, CTransaction *txCoinStake)
{
+ bool fProofOfStake = txCoinStake != NULL;
+
// Create new block
auto_ptr<CBlock> pblock(new CBlock());
if (!pblock.get())
return NULL;
// Create coinbase tx
- CTransaction txNew;
- txNew.vin.resize(1);
- txNew.vin[0].prevout.SetNull();
- txNew.vout.resize(1);
+ CTransaction txCoinBase;
+ txCoinBase.vin.resize(1);
+ txCoinBase.vin[0].prevout.SetNull();
+ txCoinBase.vout.resize(1);
if (!fProofOfStake)
{
CReserveKey reservekey(pwallet);
- txNew.vout[0].scriptPubKey.SetDestination(reservekey.GetReservedKey().GetID());
+ txCoinBase.vout[0].scriptPubKey.SetDestination(reservekey.GetReservedKey().GetID());
+
+ // Add our coinbase tx as first transaction
+ pblock->vtx.push_back(txCoinBase);
}
else
- txNew.vout[0].SetEmpty();
+ {
+ // Coinbase output must be empty for Proof-of-Stake block
+ txCoinBase.vout[0].SetEmpty();
- // Add our coinbase tx as first transaction
- pblock->vtx.push_back(txNew);
+ // Syncronize timestamps
+ pblock->nTime = txCoinBase.nTime = txCoinStake->nTime;
+
+ // Add coinbase and coinstake transactions
+ pblock->vtx.push_back(txCoinBase);
+ pblock->vtx.push_back(*txCoinStake);
+ }
// Largest block you're willing to create:
unsigned int nBlockMaxSize = GetArg("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2);
continue;
// Timestamp limit
- if (tx.nTime > GetAdjustedTime() || (fProofOfStake && tx.nTime > pblock->vtx[0].nTime))
+ if (tx.nTime > GetAdjustedTime() || (fProofOfStake && tx.nTime > txCoinStake->nTime))
continue;
// Simplify transaction fee - allow free = false
pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits, nFees);
if (fDebug)
- printf("CreateNewBlock(): reward %" PRIu64 "\n", pblock->vtx[0].vout[0].nValue);
+ printf("CreateNewBlock(): PoW reward %" PRIu64 "\n", pblock->vtx[0].vout[0].nValue);
}
if (fDebug && GetBoolArg("-printpriority"))
// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
- pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
- pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
if (!fProofOfStake)
+ {
+ pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
+ pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
pblock->UpdateTime(pindexPrev);
+ }
pblock->nNonce = 0;
}
return true;
}
+// Precalculated SHA256 contexts and metadata
+// (txid, vout.n) => (SHA256_CTX, (tx.nTime, nAmount))
+typedef std::map<std::pair<uint256, unsigned int>, std::pair<SHA256_CTX, std::pair<uint32_t, uint64_t> > > MidstateMap;
+
+// Fill the inputs map with precalculated states and metadata
+bool FillMap(CWallet *pwallet, uint32_t nUpperTime, MidstateMap &inputsMap)
+{
+ // Choose coins to use
+ int64_t nBalance = pwallet->GetBalance();
+
+ if (nBalance <= nReserveBalance)
+ return false;
+
+ uint32_t nTime = GetAdjustedTime();
+
+ CTxDB txdb("r");
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ CoinsSet setCoins;
+ int64_t nValueIn = 0;
+ if (!pwallet->SelectCoinsSimple(nBalance - nReserveBalance, MIN_TX_FEE, MAX_MONEY, nUpperTime, nCoinbaseMaturity * 10, setCoins, nValueIn))
+ return error("FillMap() : SelectCoinsSimple failed");
+
+ if (setCoins.empty())
+ return false;
+
+ CBlock block;
+ CTxIndex txindex;
+
+ for(CoinsSet::const_iterator pcoin = setCoins.begin(); pcoin != setCoins.end(); pcoin++)
+ {
+ pair<uint256, uint32_t> key = make_pair(pcoin->first->GetHash(), pcoin->second);
+
+ // Skip existent inputs
+ if (inputsMap.find(key) != inputsMap.end())
+ continue;
+
+ // Trying to parse scriptPubKey
+ txnouttype whichType;
+ vector<valtype> vSolutions;
+ if (!Solver(pcoin->first->vout[pcoin->second].scriptPubKey, whichType, vSolutions))
+ continue;
+
+ // Only support pay to public key and pay to address
+ if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH)
+ continue;
+
+ // 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;
+
+ // Only load coins meeting min age requirement
+ if (nStakeMinAge + block.nTime > nTime - nMaxStakeSearchInterval)
+ continue;
+
+ uint64_t nStakeModifier = 0;
+ if (!GetKernelStakeModifier(block.GetHash(), nStakeModifier))
+ continue;
+
+ SHA256_CTX ctx;
+ // Calculate midstate using (modifier, block time, tx offset, tx time, output number)
+ GetKernelMidstate(nStakeModifier, block.nTime, txindex.pos.nTxPos - txindex.pos.nBlockPos, pcoin->first->nTime, pcoin->second, ctx);
+
+ // (txid, vout.n) => (SHA256_CTX, (tx.nTime, nAmount))
+ inputsMap[key] = make_pair(ctx, make_pair(pcoin->first->nTime, pcoin->first->vout[pcoin->second].nValue));
+ }
+
+ if (fDebug)
+ printf("Stake miner: %" PRIszu " precalculated contexts created\n", inputsMap.size());
+ }
+
+ return true;
+}
+
+// Scan inputs map in order to find a solution
+bool ScanMap(const MidstateMap &inputsMap, uint32_t nBits, MidstateMap::key_type &LuckyInput, std::pair<uint256, uint32_t> &solution)
+{
+ static uint32_t nLastCoinStakeSearchTime = GetAdjustedTime(); // startup timestamp
+ uint32_t nSearchTime = GetAdjustedTime();
+
+ if (inputsMap.size() > 0 && nSearchTime > nLastCoinStakeSearchTime)
+ {
+ // Scanning interval (begintime, endtime)
+ std::pair<uint32_t, uint32_t> interval;
+
+ interval.first = nSearchTime;
+ interval.second = nSearchTime - min(nSearchTime-nLastCoinStakeSearchTime, nMaxStakeSearchInterval);
+
+ // (txid, nout) => (SHA256_CTX, (tx.nTime, nAmount))
+ for(MidstateMap::const_iterator input = inputsMap.begin(); input != inputsMap.end(); input++)
+ {
+ SHA256_CTX ctx = input->second.first;
+
+ // scan(State, Bits, Time, Amount, ...)
+ if (ScanMidstateBackward(ctx, nBits, input->second.second.first, input->second.second.second, interval, solution))
+ {
+ // Solution found
+ LuckyInput = input->first; // (txid, nout)
+
+ return true;
+ }
+ }
+
+ // Inputs map iteration can be big enough to consume few seconds while scanning.
+ // We're using dynamical calculation of scanning interval in order to compensate this delay.
+ nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
+ nLastCoinStakeSearchTime = nSearchTime;
+ }
+
+ // No solutions were found
+ return false;
+}
+
void StakeMiner(CWallet *pwallet)
{
SetThreadPriority(THREAD_PRIORITY_LOWEST);
// Make this thread recognisable as the mining thread
RenameThread("novacoin-miner");
+ MidstateMap inputsMap;
+ if (!FillMap(pwallet, GetAdjustedTime(), inputsMap))
+ return;
+
bool fTrySync = true;
- // Each thread has its own counter
- unsigned int nExtraNonce = 0;
+ CBlockIndex* pindexPrev = pindexBest;
+ uint32_t nBits = GetNextTargetRequired(pindexPrev, true);
while (true)
{
}
}
- //
- // Create new block
- //
- CBlockIndex* pindexPrev = pindexBest;
-
- auto_ptr<CBlock> pblock(CreateNewBlock(pwallet, true));
- if (!pblock.get())
- return;
- IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
+ MidstateMap::key_type LuckyInput;
+ std::pair<uint256, uint32_t> solution;
- // Trying to sign a block
- if (pblock->SignBlock(*pwallet))
+ if (ScanMap(inputsMap, nBits, LuckyInput, solution))
{
SetThreadPriority(THREAD_PRIORITY_NORMAL);
- CheckStake(pblock.get(), *pwallet);
+
+ // Remove lucky input from the map
+ inputsMap.erase(inputsMap.find(LuckyInput));
+
+ CKey key;
+ CTransaction txCoinStake;
+
+ // Create new coinstake transaction
+ if (!pwallet->CreateCoinStake(LuckyInput.first, LuckyInput.second, solution.second, pindexPrev->nBits, txCoinStake, key))
+ {
+ string strMessage = _("Warning: Unable to create coinstake transaction, see debug.log for the details. Mining thread has been stopped.");
+ strMiscWarning = strMessage;
+ printf("*** %s\n", strMessage.c_str());
+
+ return;
+ }
+
+ // Now we have new coinstake, it's time to create the block ...
+ CBlock* pblock;
+ pblock = CreateNewBlock(pwallet, &txCoinStake);
+ if (!pblock)
+ {
+ string strMessage = _("Warning: Unable to allocate memory for the new block object. Mining thread has been stopped.");
+ strMiscWarning = strMessage;
+ printf("*** %s\n", strMessage.c_str());
+
+ return;
+ }
+
+ unsigned int nExtraNonce = 0;
+ IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);
+
+ // ... and sign it
+ if (!key.Sign(pblock->GetHash(), pblock->vchBlockSig))
+ {
+ string strMessage = _("Warning: Proof-of-Stake miner is unable to sign the block (locked wallet?). Mining thread has been stopped.");
+ strMiscWarning = strMessage;
+ printf("*** %s\n", strMessage.c_str());
+
+ return;
+ }
+
+ CheckStake(pblock, *pwallet);
SetThreadPriority(THREAD_PRIORITY_LOWEST);
Sleep(500);
}
- else
- Sleep(nMinerSleep);
+
+ if (pindexPrev != pindexBest)
+ {
+ // The best block has been changed, we need to refill the map
+ if (FillMap(pwallet, GetAdjustedTime(), inputsMap))
+ {
+ pindexPrev = pindexBest;
+ nBits = GetNextTargetRequired(pindexPrev, true);
+ }
+ else
+ {
+ // Clear existent data if FillMap failed
+ inputsMap.clear();
+ }
+ }
+
+ Sleep(500);
continue;
}
#include "main.h"
#include "wallet.h"
-/* Generate a new block, without valid proof-of-work */
-CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake=false);
+/* Generate a new block, without valid proof-of-work/with provided proof-of-stake */
+CBlock* CreateNewBlock(CWallet* pwallet, CTransaction *txAdd=NULL);
/** Modify the extranonce in a block */
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce);
int64_t nWalletUnlockTime;
static CCriticalSection cs_nWalletUnlockTime;
+extern int64_t nReserveBalance;
extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, json_spirit::Object& entry);
std::string HelpRequiringPassphrase()
}
Object result;
- int64_t nReserveBalance = 0;
if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance))
throw runtime_error("invalid reserve balance amount\n");
result.push_back(Pair("reserve", (nReserveBalance > 0)));
#include "main.h"
using namespace std;
-
-
-bool fCoinsDataActual;
+extern int64_t nReserveBalance;
//////////////////////////////////////////////////////////////////////////////
//
return true;
}
-
-bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, uint32_t nSearchInterval, CTransaction& txNew, CKey& key)
+bool CWallet::CreateCoinStake(uint256 &hashTx, uint32_t nOut, uint32_t nGenerationTime, uint32_t nBits, CTransaction &txNew, CKey& key)
{
- // The following combine threshold is important to security
- // Should not be adjusted if you don't understand the consequences
- int64_t nCombineThreshold = GetProofOfWorkReward(GetLastBlockIndex(pindexBest, false)->nBits) / 3;
-
- CBigNum bnTargetPerCoinDay;
- bnTargetPerCoinDay.SetCompact(nBits);
-
- txNew.vin.clear();
- txNew.vout.clear();
-
- // Mark coin stake transaction
- CScript scriptEmpty;
- scriptEmpty.clear();
- txNew.vout.push_back(CTxOut(0, scriptEmpty));
+ CWalletTx wtx;
+ if (!GetTransaction(hashTx, wtx))
+ return error("Transaction %s is not found\n", hashTx.GetHex().c_str());
- // Choose coins to use
- int64_t nBalance = GetBalance();
- int64_t nReserveBalance = 0;
+ vector<valtype> vSolutions;
+ txnouttype whichType;
+ CScript scriptPubKeyOut;
+ CScript scriptPubKeyKernel = wtx.vout[nOut].scriptPubKey;
+ if (!Solver(scriptPubKeyKernel, whichType, vSolutions))
+ return error("CreateCoinStake : failed to parse kernel\n");
- if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance))
- return error("CreateCoinStake : invalid reserve balance amount");
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : parsed kernel type=%d\n", whichType);
- if (nBalance <= nReserveBalance)
- return false;
+ if (whichType != TX_PUBKEY && whichType != TX_PUBKEYHASH)
+ return error("CreateCoinStake : no support for kernel type=%d\n", whichType);
- vector<const CWalletTx*> vwtxPrev;
-
- CTxDB txdb("r");
+ if (whichType == TX_PUBKEYHASH) // pay to address type
{
- LOCK2(cs_main, cs_wallet);
- // Cache outputs unless best block or wallet transaction set changed
- if (!fCoinsDataActual)
- {
- mapMeta.clear();
- int64_t nValueIn = 0;
- CoinsSet setCoins;
- if (!SelectCoinsSimple(nBalance - nReserveBalance, MIN_TX_FEE, MAX_MONEY, txNew.nTime, nCoinbaseMaturity * 10, setCoins, nValueIn))
- return false;
-
- if (setCoins.empty())
- return false;
+ // convert to pay to public key type
+ if (!GetKey(uint160(vSolutions[0]), key))
+ return error("CreateCoinStake : failed to get key for kernel type=%d\n", whichType);
- {
- 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;
-
- uint64_t 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));
+ scriptPubKeyOut << key.GetPubKey() << OP_CHECKSIG;
+ }
+ if (whichType == TX_PUBKEY)
+ {
+ valtype& vchPubKey = vSolutions[0];
+ if (!GetKey(Hash160(vchPubKey), key))
+ return error("CreateCoinStake : failed to get key for kernel type=%d\n", whichType);
+ if (key.GetPubKey() != vchPubKey)
+ return error("CreateCoinStake : invalid key for kernel type=%d\n", whichType); // keys mismatch
+ scriptPubKeyOut = scriptPubKeyKernel;
+ }
- if (fDebug)
- printf("Load coin: %s\n", pcoin->first->GetHash().GetHex().c_str());
- }
- }
+ // The following combine threshold is important to security
+ // Should not be adjusted if you don't understand the consequences
+ int64_t nCombineThreshold = GetProofOfWorkReward(GetLastBlockIndex(pindexBest, false)->nBits) / 3;
- if (fDebug)
- printf("Stake miner: %" PRIszu " meta items loaded for %" PRIszu " coins\n", mapMeta.size(), setCoins.size());
+ int64_t nBalance = GetBalance();
+ int64_t nCredit = wtx.vout[nOut].nValue;
- fCoinsDataActual = true;
- nKernelsTried = 0;
- nCoinDaysTried = 0;
- }
- }
+ txNew.vin.clear();
+ txNew.vout.clear();
- int64_t nCredit = 0;
- CScript scriptPubKeyKernel;
+ // List of constake dependencies
+ vector<const CWalletTx*> vwtxPrev;
+ vwtxPrev.push_back(&wtx);
- unsigned int nTimeTx, nBlockTime;
- COutPoint prevoutStake;
- CoinsSet::value_type kernelcoin;
+ // Set generation time, and kernel input
+ txNew.nTime = nGenerationTime;
+ txNew.vin.push_back(CTxIn(hashTx, nOut));
- if (ScanForStakeKernelHash(mapMeta, nBits, txNew.nTime, nSearchInterval, kernelcoin, nTimeTx, nBlockTime, nKernelsTried, nCoinDaysTried))
- {
- // 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)
- {
- 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
- }
- 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);
- 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 : 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 : invalid key for kernel type=%d\n", whichType);
- return false; // keys mismatch
- }
-
- scriptPubKeyOut = scriptPubKeyKernel;
- }
+ // Mark coin stake transaction with empty vout[0]
+ CScript scriptEmpty;
+ scriptEmpty.clear();
+ txNew.vout.push_back(CTxOut(0, scriptEmpty));
- 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 (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : added kernel type=%d\n", whichType);
- if (GetWeight((int64_t)nBlockTime, (int64_t)txNew.nTime) < nStakeMaxAge)
- txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
- if (fDebug && GetBoolArg("-printcoinstake"))
- printf("CreateCoinStake : added kernel type=%d\n", whichType);
- }
+ int64_t nValueIn = 0;
+ CoinsSet setCoins;
+ if (!SelectCoinsSimple(nBalance - nReserveBalance, MIN_TX_FEE, MAX_MONEY, nGenerationTime, nCoinbaseMaturity * 10, setCoins, nValueIn))
+ return false;
- if (nCredit == 0 || nCredit > nBalance - nReserveBalance)
+ if (setCoins.empty())
return false;
- // (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier))
- for(MetaMap::const_iterator meta_item = mapMeta.begin(); meta_item != mapMeta.end(); meta_item++)
+ bool fMaxTimeWeight = false;
+ if (GetWeight((int64_t)wtx.nTime, (int64_t)nGenerationTime) == nStakeMaxAge)
{
- // Get coin
- CoinsSet::value_type pcoin = meta_item->second.first.second;
+ // Only one output for old kernel inputs
+ txNew.vout.push_back(CTxOut(0, scriptPubKeyOut));
- // 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)
+ // Iterate through set of (wtx*, nout) in order to find some additional inputs for our new coinstake transaction.
+ //
+ // * Value is higher than 0.01 NVC;
+ // * Only add inputs of the same key/address as kernel;
+ // * Input hash and kernel parent hash should be different.
+ for(CoinsSet::iterator pcoin = setCoins.begin(); pcoin != setCoins.end(); pcoin++)
{
- int64_t nTimeWeight = GetWeight((int64_t)pcoin.first->nTime, (int64_t)txNew.nTime);
-
// Stop adding more inputs if already too many inputs
if (txNew.vin.size() >= 100)
break;
if (nCredit > nCombineThreshold)
break;
// Stop adding inputs if reached reserve limit
- if (nCredit + pcoin.first->vout[pcoin.second].nValue > nBalance - nReserveBalance)
+ if (nCredit + pcoin->first->vout[pcoin->second].nValue > nBalance - nReserveBalance)
break;
- // Do not add additional significant input
- if (pcoin.first->vout[pcoin.second].nValue > nCombineThreshold)
- continue;
+
+ int64_t nTimeWeight = GetWeight((int64_t)pcoin->first->nTime, (int64_t)nGenerationTime);
+
// Do not add input that is still too young
if (nTimeWeight < nStakeMaxAge)
continue;
+ // Do not add input if key/address is not the same as kernel
+ if (pcoin->first->vout[pcoin->second].scriptPubKey != scriptPubKeyKernel && pcoin->first->vout[pcoin->second].scriptPubKey != txNew.vout[1].scriptPubKey)
+ continue;
+ // Do not add input if parents are the same
+ if (pcoin->first->GetHash() != txNew.vin[0].prevout.hash)
+ continue;
+ // Do not add additional significant input
+ if (pcoin->first->vout[pcoin->second].nValue > nCombineThreshold)
+ continue;
- txNew.vin.push_back(CTxIn(pcoin.first->GetHash(), pcoin.second));
- nCredit += pcoin.first->vout[pcoin.second].nValue;
- vwtxPrev.push_back(pcoin.first);
+ 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
+ fMaxTimeWeight = true;
+ }
+ else
{
- uint64_t nCoinAge;
- CTxDB txdb("r");
- if (!txNew.GetCoinAge(txdb, nCoinAge))
- return error("CreateCoinStake : failed to calculate coin age");
-
- int64_t nReward = GetProofOfStakeReward(nCoinAge, nBits, txNew.nTime);
- // Refuse to create mint that has zero or negative reward
- if(nReward <= 0)
- return false;
-
- nCredit += nReward;
+ // Split stake input if maximum weight isn't reached yet
+ txNew.vout.push_back(CTxOut(0, scriptPubKeyOut));
+ txNew.vout.push_back(CTxOut(0, scriptPubKeyOut));
+
+ if (fDebug && GetBoolArg("-printcoinstake"))
+ printf("CreateCoinStake : maximum time weight isn't reached, splitting coinstake\n");
}
+ // Calculate coin age reward
+ uint64_t nCoinAge;
+ CTxDB txdb("r");
+ if (!txNew.GetCoinAge(txdb, nCoinAge))
+ return error("CreateCoinStake : failed to calculate coin age\n");
+ nCredit += GetProofOfStakeReward(nCoinAge, nBits, nGenerationTime);
+
int64_t nMinFee = 0;
while (true)
{
// Set output amount
- if (txNew.vout.size() == 3)
+ if (fMaxTimeWeight)
+ txNew.vout[1].nValue = nCredit - nMinFee;
+ else
{
txNew.vout[1].nValue = ((nCredit - nMinFee) / 2 / CENT) * CENT;
txNew.vout[2].nValue = nCredit - nMinFee - txNew.vout[1].nValue;
}
- else
- txNew.vout[1].nValue = nCredit - nMinFee;
// Sign
int nIn = 0;
BOOST_FOREACH(const CWalletTx* pcoin, vwtxPrev)
{
if (!SignSignature(*this, *pcoin, txNew, nIn++))
- return error("CreateCoinStake : failed to sign coinstake");
+ return error("CreateCoinStake : failed to sign coinstake\n");
}
// Limit size
unsigned int nBytes = ::GetSerializeSize(txNew, SER_NETWORK, PROTOCOL_VERSION);
if (nBytes >= MAX_BLOCK_SIZE_GEN/5)
- return error("CreateCoinStake : exceeded coinstake size limit");
+ return error("CreateCoinStake : exceeded coinstake size limit\n");
// Check enough fee is paid
if (nMinFee < txNew.GetMinFee(1, false, GMF_BLOCK, nBytes) - CENT)
}
}
- // Successfully generated coinstake
+ // Successfully created coinstake
return true;
}
-
// Call after CreateTransaction unless you want to abort
bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
{
// Set of selected transactions
typedef std::set<std::pair<const CWalletTx*,unsigned int> > CoinsSet;
-// Preloaded coins metadata
-// (txid, vout.n) => ((txindex, (tx, vout.n)), (block, modifier))
-typedef std::map<std::pair<uint256, unsigned int>, std::pair<std::pair<CTxIndex, std::pair<const CWalletTx*,unsigned int> >, std::pair<CBlock, uint64_t> > > MetaMap;
-
-
/** (client) version numbers for particular wallet features */
enum WalletFeature
{
class CWallet : public CCryptoKeyStore
{
private:
- bool SelectCoinsSimple(int64_t nTargetValue, int64_t nMinValue, int64_t nMaxValue, unsigned int nSpendTime, int nMinConf, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const;
bool SelectCoins(int64_t nTargetValue, unsigned int nSpendTime, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl=NULL) const;
CWalletDB *pwalletdbEncryption, *pwalletdbDecryption;
// the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
int nWalletMaxVersion;
- // selected coins metadata
- std::map<std::pair<uint256, unsigned int>, std::pair<std::pair<CTxIndex, std::pair<const CWalletTx*,unsigned int> >, std::pair<CBlock, uint64_t> > > mapMeta;
-
// stake mining statistics
uint64_t nKernelsTried;
uint64_t nCoinDaysTried;
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
-
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
void AvailableCoinsMinConf(std::vector<COutput>& vCoins, int nConf, int64_t nMinValue, int64_t nMaxValue) const;
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl=NULL) const;
bool SelectCoinsMinConf(int64_t nTargetValue, unsigned int nSpendTime, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const;
+
+ // Simple select (without randomization)
+ bool SelectCoinsSimple(int64_t nTargetValue, int64_t nMinValue, int64_t nMaxValue, unsigned int nSpendTime, int nMinConf, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const;
+
// keystore implementation
// Generate a new key
CPubKey GenerateNewKey();
void GetStakeStats(float &nKernelsRate, float &nCoinDaysRate);
void GetStakeWeightFromValue(const int64_t& nTime, const int64_t& nValue, uint64_t& nWeight);
- bool CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, uint32_t nSearchInterval, CTransaction& txNew, CKey& key);
+ bool CreateCoinStake(uint256 &hashTx, uint32_t nOut, uint32_t nTime, uint32_t nBits, CTransaction &txNew, CKey& key);
bool MergeCoins(const int64_t& nAmount, const int64_t& nMinValue, const int64_t& nMaxValue, std::list<uint256>& listMerged);
std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false);