X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=blobdiff_plain;f=src%2Fminer.cpp;h=aae9589b92937585b8d001786a2fec434f909e3a;hp=aca0674e87d17e41d10eda64bb4612c5a48e4f0c;hb=a0bfbd64a8dc93eb87a452843f76dbb9dec9f30b;hpb=de7d6a3e988d664b52e1c4039e0bb7f04c9b8bac diff --git a/src/miner.cpp b/src/miner.cpp index aca0674..aae9589 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -7,6 +7,7 @@ #include "txdb.h" #include "miner.h" #include "kernel.h" +#include "kernel_worker.h" using namespace std; @@ -15,7 +16,9 @@ using namespace std; // BitcoinMiner // -extern unsigned int nMinerSleep; +int64_t nReserveBalance = 0; +static unsigned int nMaxStakeSearchInterval = 60; +uint64_t nStakeInputsMapSize = 0; int static FormatHashBlocks(void* pbuffer, unsigned int len) { @@ -67,14 +70,6 @@ public: ptx = ptxIn; dPriority = dFeePerKb = 0; } - - void print() const - { - printf("COrphan(hash=%s, dPriority=%.1f, dFeePerKb=%.1f)\n", - ptx->GetHash().ToString().substr(0,10).c_str(), dPriority, dFeePerKb); - BOOST_FOREACH(uint256 hash, setDependsOn) - printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str()); - } }; @@ -106,44 +101,56 @@ public: } }; -// 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 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); + unsigned int nBlockMaxSize = GetArgUInt("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2); // Limit to betweeen 1K and MAX_BLOCK_SIZE-1K for sanity: - nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE-1000), nBlockMaxSize)); + nBlockMaxSize = std::max(1000u, std::min(MAX_BLOCK_SIZE-1000u, nBlockMaxSize)); // How much of the block should be dedicated to high-priority transactions, // included regardless of the fees they pay - unsigned int nBlockPrioritySize = GetArg("-blockprioritysize", 27000); + unsigned int nBlockPrioritySize = GetArgUInt("-blockprioritysize", 27000); nBlockPrioritySize = std::min(nBlockMaxSize, nBlockPrioritySize); // Minimum block size you want to create; block will be filled with free transactions // until there are no more or the block reaches this size: - unsigned int nBlockMinSize = GetArg("-blockminsize", 0); + unsigned int nBlockMinSize = GetArgUInt("-blockminsize", 0); nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize); // Fee-per-kilobyte amount considered the same as "free" @@ -272,12 +279,9 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) 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 - int64_t nMinFee = tx.GetMinFee(nBlockSize, true, GMF_BLOCK, nTxSize); - // Skip free transactions if we're past the minimum block size: if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize)) continue; @@ -300,10 +304,13 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid)) continue; + // Transaction fee int64_t nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); + int64_t nMinFee = tx.GetMinFee(nBlockSize, true, GMF_BLOCK, nTxSize); if (nTxFees < nMinFee) continue; + // Sigops accumulation nTxSigOps += tx.GetP2SHSigOpCount(mapInputs); if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) continue; @@ -353,7 +360,7 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) 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")) @@ -361,10 +368,12 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) // 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; } @@ -513,70 +522,268 @@ bool CheckStake(CBlock* pblock, CWallet& wallet) return true; } -void StakeMiner(CWallet *pwallet) +// Precalculated SHA256 contexts and metadata +// (txid, vout.n) => (kernel, (tx.nTime, nAmount)) +typedef std::map, std::pair, std::pair > > MidstateMap; + +// Fill the inputs map with precalculated contexts 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 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 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; + + // Get stake modifier + uint64_t nStakeModifier = 0; + if (!GetKernelStakeModifier(block.GetHash(), nStakeModifier)) + continue; + + // Build static part of kernel + CDataStream ssKernel(SER_GETHASH, 0); + ssKernel << nStakeModifier; + ssKernel << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << pcoin->first->nTime << pcoin->second; + + // (txid, vout.n) => (kernel, (tx.nTime, nAmount)) + inputsMap[key] = make_pair(std::vector(ssKernel.begin(), ssKernel.end()), make_pair(pcoin->first->nTime, pcoin->first->vout[pcoin->second].nValue)); + } + + nStakeInputsMapSize = inputsMap.size(); + + if (fDebug) + printf("FillMap() : Map of %" PRIu64 " precalculated contexts has been created by stake miner\n", nStakeInputsMapSize); + } + + 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 &solution) +{ + static uint32_t nLastCoinStakeSearchTime = GetAdjustedTime(); // startup timestamp + uint32_t nSearchTime = GetAdjustedTime(); + + if (inputsMap.size() > 0 && nSearchTime > nLastCoinStakeSearchTime) + { + // Scanning interval (begintime, endtime) + std::pair interval; + + interval.first = nSearchTime; + interval.second = nSearchTime - min(nSearchTime-nLastCoinStakeSearchTime, nMaxStakeSearchInterval); + + // (txid, nout) => (kernel, (tx.nTime, nAmount)) + for(MidstateMap::const_iterator input = inputsMap.begin(); input != inputsMap.end(); input++) + { + unsigned char *kernel = (unsigned char *) &input->second.first[0]; + + // scan(State, Bits, Time, Amount, ...) + if (ScanKernelBackward(kernel, 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; +} + +// Stake miner thread +void ThreadStakeMiner(void* parg) { SetThreadPriority(THREAD_PRIORITY_LOWEST); // Make this thread recognisable as the mining thread RenameThread("novacoin-miner"); + CWallet* pwallet = (CWallet*)parg; + + 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) + printf("ThreadStakeMinter started\n"); + + try { - if (fShutdown) - return; + vnThreadsRunning[THREAD_MINTER]++; - while (pwallet->IsLocked()) - { - Sleep(1000); - if (fShutdown) - return; - } + MidstateMap::key_type LuckyInput; + std::pair solution; - while (vNodes.empty() || IsInitialBlockDownload()) + // Main miner loop + do { - fTrySync = true; - - Sleep(1000); if (fShutdown) - return; - } + goto _endloop; - if (fTrySync) - { - fTrySync = false; - if (vNodes.size() < 3 || nBestHeight < GetNumBlocksOfPeers()) + while (pwallet->IsLocked()) { Sleep(1000); - continue; + if (fShutdown) + goto _endloop; // Don't be afraid to use a goto if that's the best option. } - } - // - // Create new block - // - CBlockIndex* pindexPrev = pindexBest; + while (vNodes.empty() || IsInitialBlockDownload()) + { + fTrySync = true; - auto_ptr pblock(CreateNewBlock(pwallet, true)); - if (!pblock.get()) - return; - IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce); + Sleep(1000); + if (fShutdown) + goto _endloop; + } + + if (fTrySync) + { + // Don't try mine blocks unless we're at the top of chain and have at least three p2p connections. + fTrySync = false; + if (vNodes.size() < 3 || nBestHeight < GetNumBlocksOfPeers()) + { + Sleep(1000); + continue; + } + } + + if (ScanMap(inputsMap, nBits, LuckyInput, solution)) + { + SetThreadPriority(THREAD_PRIORITY_NORMAL); + + // 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, 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()); + + break; + } + + // 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()); + + break; + } + + 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()); + + break; + } + + CheckStake(pblock, *pwallet); + SetThreadPriority(THREAD_PRIORITY_LOWEST); + Sleep(500); + } + + 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(); + } + } - // Trying to sign a block - if (pblock->SignBlock(*pwallet)) - { - SetThreadPriority(THREAD_PRIORITY_NORMAL); - CheckStake(pblock.get(), *pwallet); - SetThreadPriority(THREAD_PRIORITY_LOWEST); Sleep(500); + + _endloop: + (void)0; // do nothing } - else - Sleep(nMinerSleep); + while(!fShutdown); - continue; + vnThreadsRunning[THREAD_MINTER]--; + } + catch (std::exception& e) { + vnThreadsRunning[THREAD_MINTER]--; + PrintException(&e, "ThreadStakeMinter()"); + } catch (...) { + vnThreadsRunning[THREAD_MINTER]--; + PrintException(NULL, "ThreadStakeMinter()"); } + printf("ThreadStakeMinter exiting, %d threads remaining\n", vnThreadsRunning[THREAD_MINTER]); }