// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include "txdb.h"
#include "miner.h"
#include "kernel.h"
+#include "kernel_worker.h"
using namespace std;
// BitcoinMiner
//
-string strMintMessage = "Info: Minting suspended due to locked wallet.";
-string strMintWarning;
+int64_t nReserveBalance = 0;
+static unsigned int nMaxStakeSearchInterval = 60;
+uint64_t nStakeInputsMapSize = 0;
int static FormatHashBlocks(void* pbuffer, unsigned int len)
{
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());
- }
};
-uint64 nLastBlockTx = 0;
-uint64 nLastBlockSize = 0;
-int64 nLastCoinStakeSearchInterval = 0;
+uint64_t nLastBlockTx = 0;
+uint64_t nLastBlockSize = 0;
+uint32_t nLastCoinStakeSearchInterval = 0;
// We want to sort transactions by priority and fee, so:
typedef boost::tuple<double, double, CTransaction*> TxPriority;
}
};
-// CreateNewBlock:
-// fProofOfStake: try (best effort) to make a proof-of-stake block
-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 << reservekey.GetReservedKey() << OP_CHECKSIG;
+ 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"
// a transaction spammer can cheaply fill blocks using
// 1-satoshi-fee transactions. It should be set above the real
// cost to you of processing a transaction.
- int64 nMinTxFee = MIN_TX_FEE;
+ int64_t nMinTxFee = MIN_TX_FEE;
if (mapArgs.count("-mintxfee"))
ParseMoney(mapArgs["-mintxfee"], nMinTxFee);
- // ppcoin: if coinstake available add coinstake tx
- static int64 nLastCoinStakeSearchTime = GetAdjustedTime(); // only initialized at startup
CBlockIndex* pindexPrev = pindexBest;
- if (fProofOfStake) // attempt to find a coinstake
- {
- pblock->nBits = GetNextTargetRequired(pindexPrev, true);
- CTransaction txCoinStake;
- int64 nSearchTime = txCoinStake.nTime; // search to current time
- if (nSearchTime > nLastCoinStakeSearchTime)
- {
- if (pwallet->CreateCoinStake(*pwallet, pblock->nBits, nSearchTime-nLastCoinStakeSearchTime, txCoinStake))
- {
- if (txCoinStake.nTime >= max(pindexPrev->GetMedianTimePast()+1, pindexPrev->GetBlockTime() - nMaxClockDrift))
- { // make sure coinstake would meet timestamp protocol
- // as it would be the same as the block timestamp
- pblock->vtx[0].nTime = txCoinStake.nTime;
- pblock->vtx.push_back(txCoinStake);
- }
- }
- nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
- nLastCoinStakeSearchTime = nSearchTime;
- }
- }
-
- pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake());
+ pblock->nBits = GetNextTargetRequired(pindexPrev, fProofOfStake);
// Collect memory pool transactions into the block
- int64 nFees = 0;
+ int64_t nFees = 0;
{
LOCK2(cs_main, mempool.cs);
CBlockIndex* pindexPrev = pindexBest;
COrphan* porphan = NULL;
double dPriority = 0;
- int64 nTotalIn = 0;
+ int64_t nTotalIn = 0;
bool fMissingInputs = false;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue;
continue;
}
- int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
+ int64_t nValueIn = txPrev.vout[txin.prevout.n].nValue;
nTotalIn += nValueIn;
int nConf = txindex.GetDepthInMainChain();
// Collect transactions into block
map<uint256, CTxIndex> mapTestPool;
- uint64 nBlockSize = 1000;
- uint64 nBlockTx = 0;
+ uint64_t nBlockSize = 1000;
+ uint64_t nBlockTx = 0;
int nBlockSigOps = 100;
bool fSortedByFee = (nBlockPrioritySize <= 0);
continue;
// Timestamp limit
- if (tx.nTime > GetAdjustedTime() || (pblock->IsProofOfStake() && tx.nTime > pblock->vtx[1].nTime))
+ if (tx.nTime > GetAdjustedTime() || (fProofOfStake && tx.nTime > txCoinStake->nTime))
continue;
- // ppcoin: simplify transaction fee - allow free = false
- int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK);
-
// Skip free transactions if we're past the minimum block size:
if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
continue;
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
continue;
- int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
+ // 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;
- if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
+ if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true, true, MANDATORY_SCRIPT_VERIFY_FLAGS))
continue;
mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
swap(mapTestPool, mapTestPoolTmp);
nLastBlockTx = nBlockTx;
nLastBlockSize = nBlockSize;
- if (fDebug && GetBoolArg("-printpriority"))
- printf("CreateNewBlock(): total size %"PRI64u"\n", nBlockSize);
+ if (!fProofOfStake)
+ {
+ pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits, nFees);
+
+ if (fDebug)
+ printf("CreateNewBlock(): PoW reward %" PRIu64 "\n", pblock->vtx[0].vout[0].nValue);
+ }
- if (pblock->IsProofOfWork())
- pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);
+ if (fDebug && GetBoolArg("-printpriority"))
+ printf("CreateNewBlock(): total size %" PRIu64 "\n", nBlockSize);
// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
- if (pblock->IsProofOfStake())
- pblock->nTime = pblock->vtx[1].nTime; //same as coinstake timestamp
- pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
- pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
- if (pblock->IsProofOfWork())
+ if (!fProofOfStake)
+ {
+ pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
+ pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime()));
pblock->UpdateTime(pindexPrev);
+ }
pblock->nNonce = 0;
}
hashPrevBlock = pblock->hashPrevBlock;
}
++nExtraNonce;
+
unsigned int nHeight = pindexPrev->nHeight+1; // Height first in coinbase required for block.version=2
pblock->vtx[0].vin[0].scriptSig = (CScript() << nHeight << CBigNum(nExtraNonce)) + COINBASE_FLAGS;
assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);
return true;
}
-void StakeMiner(CWallet *pwallet)
+// Precalculated SHA256 contexts and metadata
+// (txid, vout.n) => (kernel, (tx.nTime, nAmount))
+typedef std::map<std::pair<uint256, unsigned int>, std::pair<std::vector<unsigned char>, std::pair<uint32_t, uint64_t> > > MidstateMap;
+
+// Fill the inputs map with precalculated contexts and metadata
+bool FillMap(CWallet *pwallet, uint32_t nUpperTime, MidstateMap &inputsMap)
{
- SetThreadPriority(THREAD_PRIORITY_LOWEST);
+ // Choose coins to use
+ int64_t nBalance = pwallet->GetBalance();
- // Make this thread recognisable as the mining thread
- RenameThread("novacoin-miner");
+ if (nBalance <= nReserveBalance)
+ return false;
- // Each thread has its own counter
- unsigned int nExtraNonce = 0;
+ uint32_t nTime = GetAdjustedTime();
- while (true)
+ CTxDB txdb("r");
{
- if (fShutdown)
- return;
- while (vNodes.empty() || IsInitialBlockDownload())
+ 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++)
{
- Sleep(1000);
- if (fShutdown)
- return;
+ 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;
+
+ // 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<unsigned char>(ssKernel.begin(), ssKernel.end()), make_pair(pcoin->first->nTime, pcoin->first->vout[pcoin->second].nValue));
}
- while (pwallet->IsLocked())
+ 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<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) => (kernel, (tx.nTime, nAmount))
+ for(MidstateMap::const_iterator input = inputsMap.begin(); input != inputsMap.end(); input++)
{
- strMintWarning = strMintMessage;
- Sleep(1000);
+ 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;
+ }
}
- strMintWarning = "";
- //
- // Create new block
- //
- CBlockIndex* pindexPrev = pindexBest;
+ // 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;
- auto_ptr<CBlock> pblock(CreateNewBlock(pwallet, true));
- if (!pblock.get())
- return;
- IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
+ bool fTrySync = true;
+
+ CBlockIndex* pindexPrev = pindexBest;
+ uint32_t nBits = GetNextTargetRequired(pindexPrev, true);
- if(pblock->IsProofOfStake())
+ printf("ThreadStakeMinter started\n");
+
+ try
+ {
+ vnThreadsRunning[THREAD_MINTER]++;
+
+ MidstateMap::key_type LuckyInput;
+ std::pair<uint256, uint32_t> solution;
+
+ // Main miner loop
+ do
{
- // Trying to sign a block
- if (!pblock->SignBlock(*pwallet))
+ if (fShutdown)
+ goto _endloop;
+
+ while (pwallet->IsLocked())
{
- strMintWarning = strMintMessage;
- continue;
+ Sleep(1000);
+ if (fShutdown)
+ goto _endloop; // Don't be afraid to use a goto if that's the best option.
}
- strMintWarning = "";
- SetThreadPriority(THREAD_PRIORITY_NORMAL);
- CheckStake(pblock.get(), *pwallet);
- SetThreadPriority(THREAD_PRIORITY_LOWEST);
+ while (vNodes.empty() || IsInitialBlockDownload())
+ {
+ fTrySync = true;
+
+ 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();
+ }
+ }
+
+ Sleep(500);
+
+ _endloop:
+ (void)0; // do nothing
}
+ while(!fShutdown);
- Sleep(500);
- 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]);
}