3dfecf5cb7
Strip getwork, getworkex, getblocktemplate, submitblock RPC commands and their helper functions (SHA256Transform, FormatHashBlocks, FormatHashBuffers, IncrementExtraNonce, CheckWork) which have been dead code since PoW ended at block 9000. AV engines pattern-match these nonce-incrementing loops and mining pool interfaces as cryptominer signatures. Block validation (CheckProofOfWork) and Hash9 algorithm files are preserved - only block *creation* for PoW mining is removed. PoS staking code is untouched. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
457 lines
16 KiB
C++
457 lines
16 KiB
C++
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2012 The Bitcoin developers
|
|
// Copyright (c) 2014-2015 Triangles team
|
|
// 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"
|
|
|
|
using namespace std;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TrianglesMiner
|
|
//
|
|
|
|
//
|
|
|
|
// PoW mining helpers (SHA256Transform, FormatHashBlocks, FormatHashBuffers,
|
|
// IncrementExtraNonce, CheckWork) removed - PoW ended at block 9000.
|
|
|
|
// Some explaining would be appreciated
|
|
class COrphan
|
|
{
|
|
public:
|
|
CTransaction* ptx;
|
|
set<uint256> setDependsOn;
|
|
double dPriority;
|
|
double dFeePerKb;
|
|
|
|
COrphan(CTransaction* ptxIn)
|
|
{
|
|
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);
|
|
for (uint256 hash : setDependsOn)
|
|
printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str());
|
|
}
|
|
};
|
|
|
|
|
|
uint64_t nLastBlockTx = 0;
|
|
uint64_t nLastBlockSize = 0;
|
|
int64_t nLastCoinStakeSearchInterval = 0;
|
|
|
|
// We want to sort transactions by priority and fee, so:
|
|
typedef std::tuple<double, double, CTransaction*> TxPriority;
|
|
class TxPriorityCompare
|
|
{
|
|
bool byFee;
|
|
public:
|
|
TxPriorityCompare(bool _byFee) : byFee(_byFee) { }
|
|
bool operator()(const TxPriority& a, const TxPriority& b)
|
|
{
|
|
if (byFee)
|
|
{
|
|
if (std::get<1>(a) == std::get<1>(b))
|
|
return std::get<0>(a) < std::get<0>(b);
|
|
return std::get<1>(a) < std::get<1>(b);
|
|
}
|
|
else
|
|
{
|
|
if (std::get<0>(a) == std::get<0>(b))
|
|
return std::get<1>(a) < std::get<1>(b);
|
|
return std::get<0>(a) < std::get<0>(b);
|
|
}
|
|
}
|
|
};
|
|
|
|
// CreateNewBlock: fProofOfStake: try (best effort) to make a proof-of-stake block
|
|
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake, int64_t* pFees)
|
|
{
|
|
// Create new block
|
|
unique_ptr<CBlock> pblock(new CBlock());
|
|
if (!pblock.get())
|
|
return NULL;
|
|
|
|
CBlockIndex* pindexPrev = pindexBest;
|
|
|
|
// Create coinbase tx
|
|
CTransaction txNew;
|
|
txNew.vin.resize(1);
|
|
txNew.vin[0].prevout.SetNull();
|
|
txNew.vout.resize(1);
|
|
|
|
if (!fProofOfStake)
|
|
{
|
|
CReserveKey reservekey(pwallet);
|
|
txNew.vout[0].scriptPubKey.SetDestination(reservekey.GetReservedKey().GetID());
|
|
}
|
|
else
|
|
{
|
|
// Height first in coinbase required for block.version=2
|
|
txNew.vin[0].scriptSig = (CScript() << pindexPrev->nHeight+1) + COINBASE_FLAGS;
|
|
assert(txNew.vin[0].scriptSig.size() <= 100);
|
|
|
|
txNew.vout[0].SetEmpty();
|
|
}
|
|
|
|
// Add our coinbase tx as first transaction
|
|
pblock->vtx.push_back(txNew);
|
|
|
|
// Largest block you're willing to create:
|
|
unsigned int nBlockMaxSize = GetArg("-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));
|
|
|
|
// 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);
|
|
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);
|
|
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
|
|
|
|
// Fee-per-kilobyte amount considered the same as "free"
|
|
// Be careful setting this: if you set it to zero then
|
|
// 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_t nMinTxFee = MIN_TX_FEE;
|
|
if (mapArgs.count("-mintxfee"))
|
|
ParseMoney(mapArgs["-mintxfee"], nMinTxFee);
|
|
|
|
pblock->nBits = GetNextTargetRequired(pindexPrev, fProofOfStake);
|
|
|
|
// Collect memory pool transactions into the block
|
|
int64_t nFees = 0;
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
CTxDB txdb("r");
|
|
|
|
// Priority order to process transactions
|
|
list<COrphan> vOrphan; // list memory doesn't move
|
|
map<uint256, vector<COrphan*> > mapDependers;
|
|
|
|
// This vector will be sorted into a priority queue:
|
|
vector<TxPriority> vecPriority;
|
|
vecPriority.reserve(mempool.mapTx.size());
|
|
for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi)
|
|
{
|
|
CTransaction& tx = (*mi).second;
|
|
if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal())
|
|
continue;
|
|
|
|
COrphan* porphan = NULL;
|
|
double dPriority = 0;
|
|
int64_t nTotalIn = 0;
|
|
bool fMissingInputs = false;
|
|
for (const CTxIn& txin : tx.vin)
|
|
{
|
|
// Read prev transaction
|
|
CTransaction txPrev;
|
|
CTxIndex txindex;
|
|
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
|
|
{
|
|
// This should never happen; all transactions in the memory
|
|
// pool should connect to either transactions in the chain
|
|
// or other transactions in the memory pool.
|
|
if (!mempool.mapTx.count(txin.prevout.hash))
|
|
{
|
|
printf("ERROR: mempool transaction missing input\n");
|
|
if (fDebug) assert("mempool transaction missing input" == 0);
|
|
fMissingInputs = true;
|
|
if (porphan)
|
|
vOrphan.pop_back();
|
|
break;
|
|
}
|
|
|
|
// Has to wait for dependencies
|
|
if (!porphan)
|
|
{
|
|
// Use list for automatic deletion
|
|
vOrphan.push_back(COrphan(&tx));
|
|
porphan = &vOrphan.back();
|
|
}
|
|
mapDependers[txin.prevout.hash].push_back(porphan);
|
|
porphan->setDependsOn.insert(txin.prevout.hash);
|
|
nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue;
|
|
continue;
|
|
}
|
|
int64_t nValueIn = txPrev.vout[txin.prevout.n].nValue;
|
|
nTotalIn += nValueIn;
|
|
|
|
int nConf = txindex.GetDepthInMainChain();
|
|
dPriority += (double)nValueIn * nConf;
|
|
}
|
|
if (fMissingInputs) continue;
|
|
|
|
// Priority is sum(valuein * age) / txsize
|
|
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
|
|
dPriority /= nTxSize;
|
|
|
|
// This is a more accurate fee-per-kilobyte than is used by the client code, because the
|
|
// client code rounds up the size to the nearest 1K. That's good, because it gives an
|
|
// incentive to create smaller transactions.
|
|
double dFeePerKb = double(nTotalIn-tx.GetValueOut()) / (double(nTxSize)/1000.0);
|
|
|
|
if (porphan)
|
|
{
|
|
porphan->dPriority = dPriority;
|
|
porphan->dFeePerKb = dFeePerKb;
|
|
}
|
|
else
|
|
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second));
|
|
}
|
|
|
|
// Collect transactions into block
|
|
map<uint256, CTxIndex> mapTestPool;
|
|
uint64_t nBlockSize = 1000;
|
|
uint64_t nBlockTx = 0;
|
|
int nBlockSigOps = 100;
|
|
bool fSortedByFee = (nBlockPrioritySize <= 0);
|
|
|
|
TxPriorityCompare comparer(fSortedByFee);
|
|
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
|
|
while (!vecPriority.empty())
|
|
{
|
|
// Take highest priority transaction off the priority queue:
|
|
double dPriority = std::get<0>(vecPriority.front());
|
|
double dFeePerKb = std::get<1>(vecPriority.front());
|
|
CTransaction& tx = *(std::get<2>(vecPriority.front()));
|
|
|
|
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
vecPriority.pop_back();
|
|
|
|
// Size limits
|
|
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
|
|
if (nBlockSize + nTxSize >= nBlockMaxSize)
|
|
continue;
|
|
|
|
// Legacy limits on sigOps:
|
|
unsigned int nTxSigOps = tx.GetLegacySigOpCount();
|
|
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
|
|
continue;
|
|
|
|
// Timestamp limit
|
|
if (tx.nTime > GetAdjustedTime() || (fProofOfStake && tx.nTime > pblock->vtx[0].nTime))
|
|
continue;
|
|
|
|
// Transaction fee
|
|
int64_t nMinFee = tx.GetMinFee(nBlockSize, GMF_BLOCK);
|
|
|
|
// Skip free transactions if we're past the minimum block size:
|
|
if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
|
|
continue;
|
|
|
|
// Prioritize by fee once past the priority size or we run out of high-priority
|
|
// transactions:
|
|
if (!fSortedByFee &&
|
|
((nBlockSize + nTxSize >= nBlockPrioritySize) || (dPriority < COIN * 144 / 250)))
|
|
{
|
|
fSortedByFee = true;
|
|
comparer = TxPriorityCompare(fSortedByFee);
|
|
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
}
|
|
|
|
// Connecting shouldn't fail due to dependency on other memory pool transactions
|
|
// because we're already processing them in order of dependency
|
|
map<uint256, CTxIndex> mapTestPoolTmp(mapTestPool);
|
|
MapPrevTx mapInputs;
|
|
bool fInvalid;
|
|
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
|
|
continue;
|
|
|
|
int64_t nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
|
|
if (nTxFees < nMinFee)
|
|
continue;
|
|
|
|
nTxSigOps += tx.GetP2SHSigOpCount(mapInputs);
|
|
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
|
|
continue;
|
|
|
|
if (!tx.ConnectInputs(txdb, mapInputs, mapTestPoolTmp, CDiskTxPos(1,1,1), pindexPrev, false, true))
|
|
continue;
|
|
mapTestPoolTmp[tx.GetHash()] = CTxIndex(CDiskTxPos(1,1,1), tx.vout.size());
|
|
swap(mapTestPool, mapTestPoolTmp);
|
|
|
|
// Added
|
|
pblock->vtx.push_back(tx);
|
|
nBlockSize += nTxSize;
|
|
++nBlockTx;
|
|
nBlockSigOps += nTxSigOps;
|
|
nFees += nTxFees;
|
|
|
|
if (fDebug && GetBoolArg("-printpriority"))
|
|
{
|
|
printf("priority %.1f feeperkb %.1f txid %s\n",
|
|
dPriority, dFeePerKb, tx.GetHash().ToString().c_str());
|
|
}
|
|
|
|
// Add transactions that depend on this one to the priority queue
|
|
uint256 hash = tx.GetHash();
|
|
if (mapDependers.count(hash))
|
|
{
|
|
for (COrphan* porphan : mapDependers[hash])
|
|
{
|
|
if (!porphan->setDependsOn.empty())
|
|
{
|
|
porphan->setDependsOn.erase(hash);
|
|
if (porphan->setDependsOn.empty())
|
|
{
|
|
vecPriority.push_back(TxPriority(porphan->dPriority, porphan->dFeePerKb, porphan->ptx));
|
|
std::push_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nLastBlockTx = nBlockTx;
|
|
nLastBlockSize = nBlockSize;
|
|
|
|
if (fDebug && GetBoolArg("-printpriority"))
|
|
printf("CreateNewBlock(): total size %" PRIu64 "\n", nBlockSize);
|
|
|
|
if (!fProofOfStake)
|
|
pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(nFees);
|
|
|
|
if (pFees)
|
|
*pFees = nFees;
|
|
|
|
// Fill in header
|
|
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
|
|
pblock->nTime = max(pindexPrev->GetPastTimeLimit()+1, pblock->GetMaxTransactionTime());
|
|
pblock->nTime = max(pblock->GetBlockTime(), PastDrift(pindexPrev->GetBlockTime(), pindexPrev->nHeight + 1));
|
|
if (!fProofOfStake)
|
|
pblock->UpdateTime(pindexPrev);
|
|
pblock->nNonce = 0;
|
|
}
|
|
|
|
return pblock.release();
|
|
}
|
|
|
|
|
|
bool CheckStake(CBlock* pblock, CWallet& wallet)
|
|
{
|
|
uint256 proofHash = 0, hashTarget = 0;
|
|
uint256 hash = pblock->GetHash();
|
|
|
|
if(!pblock->IsProofOfStake())
|
|
return error("CheckStake() : %s is not a proof-of-stake block", hash.GetHex().c_str());
|
|
|
|
// verify hash target and signature of coinstake tx
|
|
//bool fIsInitialDownload = IsInitialBlockDownload();
|
|
if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, proofHash, hashTarget))
|
|
return error("CheckStake() : proof-of-stake checking failed");
|
|
|
|
//// debug print
|
|
printf("CheckStake() : new proof-of-stake block found \n hash: %s \nproofhash: %s \ntarget: %s\n", hash.GetHex().c_str(), proofHash.GetHex().c_str(), hashTarget.GetHex().c_str());
|
|
pblock->print();
|
|
printf("out %s\n", FormatMoney(pblock->vtx[1].GetValueOut()).c_str());
|
|
|
|
// Found a solution
|
|
{
|
|
LOCK(cs_main);
|
|
if (pblock->hashPrevBlock != hashBestChain)
|
|
return error("CheckStake() : generated block is stale");
|
|
|
|
// Track how many getdata requests this block gets
|
|
{
|
|
LOCK(wallet.cs_wallet);
|
|
wallet.mapRequestCount[hash] = 0;
|
|
}
|
|
|
|
// Process this block the same as if we had received it from another node
|
|
if (!ProcessBlock(NULL, pblock))
|
|
return error("CheckStake() : ProcessBlock, block not accepted");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void StakeMiner(CWallet *pwallet)
|
|
{
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
|
|
// Make this thread recognisable as the mining thread
|
|
RenameThread("Triangles-miner");
|
|
|
|
bool fTryToSync = true;
|
|
|
|
while (true)
|
|
{
|
|
if (fShutdown)
|
|
return;
|
|
|
|
while (pwallet->IsLocked())
|
|
{
|
|
nLastCoinStakeSearchInterval = 0;
|
|
MilliSleep(1000);
|
|
if (fShutdown)
|
|
return;
|
|
}
|
|
|
|
while (vNodes.empty() || IsInitialBlockDownload())
|
|
{
|
|
nLastCoinStakeSearchInterval = 0;
|
|
fTryToSync = true;
|
|
MilliSleep(1000);
|
|
if (fShutdown)
|
|
return;
|
|
}
|
|
|
|
if (fTryToSync)
|
|
{
|
|
fTryToSync = false;
|
|
if (vNodes.size() < 1 || nBestHeight < GetNumBlocksOfPeers())
|
|
{
|
|
MilliSleep(60000);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update cached stake weight for UI display (avoids heavy work on UI thread)
|
|
//
|
|
{
|
|
uint64_t nMinWeight = 0, nMaxWeight = 0, nWeight = 0;
|
|
pwallet->GetStakeWeight(*pwallet, nMinWeight, nMaxWeight, nWeight);
|
|
pwallet->nCachedStakeWeight = nWeight;
|
|
pwallet->nCachedStakeWeightTime = GetTime();
|
|
}
|
|
|
|
//
|
|
// Create new block
|
|
//
|
|
int64_t nFees;
|
|
unique_ptr<CBlock> pblock(CreateNewBlock(pwallet, true, &nFees));
|
|
if (!pblock.get())
|
|
return;
|
|
|
|
// Try to sign the block
|
|
if (pblock->SignBlock(*pwallet, nFees))
|
|
{
|
|
printf("StakeMiner(): A proof-of-stake block has been found! %s\n", pblock->GetHash().ToString().c_str());
|
|
SetThreadPriority(THREAD_PRIORITY_NORMAL);
|
|
CheckStake(pblock.get(), *pwallet);
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
MilliSleep(500);
|
|
}
|
|
else
|
|
MilliSleep(500);
|
|
}
|
|
}
|
|
|
|
|