Relevant code changes to main.cpp
// ppcoin: miner's coin stake is rewarded based on coin age spent (coin-days)
int64 GetProofOfStakeReward(int64 nCoinAge)
{
static int64 nRewardCoinYear = CENT; // creation amount per coin-year
int64 nSubsidy = nCoinAge * 33 / (365 * 33 + 8) * nRewardCoinYear;
if (fDebug && GetBoolArg("-printcreation"))
printf("GetProofOfStakeReward(): create=%s nCoinAge=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nCoinAge);
return nSubsidy;
}
static const int64 nTargetTimespan = 7 * 24 * 60 * 60; // one week
static const int64 nTargetSpacingStake = 10 * 60; // ten minutes
static const int64 nTargetSpacingWorkMax = 2 * 60 * 60; // two hours
unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake)
{
if (pindexLast == NULL)
return bnProofOfWorkLimit.GetCompact(); // genesis block
const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
if (pindexPrev->pprev == NULL)
return bnInitialHashTarget.GetCompact(); // first block
const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
if (pindexPrevPrev->pprev == NULL)
return bnInitialHashTarget.GetCompact(); // second block
int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();
// ppcoin: target change every block
// ppcoin: retarget with exponential moving toward target spacing
CBigNum bnNew;
bnNew.SetCompact(pindexPrev->nBits);
int64 nTargetSpacing = fProofOfStake? nTargetSpacingStake : min(nTargetSpacingWorkMax, nTargetSpacingStake * (1 + pindexLast->nHeight - pindexPrev->nHeight));
int64 nInterval = nTargetTimespan / nTargetSpacing;
bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing);
bnNew /= ((nInterval + 1) * nTargetSpacing);
if (bnNew > bnProofOfWorkLimit)
bnNew = bnProofOfWorkLimit;
return bnNew.GetCompact();
}
bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs,
map& mapTestPool, const CDiskTxPos& posThisTx,
const CBlockIndex* pindexBlock, bool fBlock, bool fMiner, bool fStrictPayToScriptHash)
{
...
if (IsCoinStake())
{
// ppcoin: coin stake tx earns reward instead of paying fee
uint64 nCoinAge;
if (!GetCoinAge(txdb, nCoinAge))
return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str());
int64 nStakeReward = GetValueOut() - nValueIn;
if (nStakeReward > GetProofOfStakeReward(nCoinAge) - GetMinFee() + MIN_TX_FEE)
return DoS(100, error("ConnectInputs() : %s stake reward exceeded", GetHash().ToString().substr(0,10).c_str()));
}
else
{
if (nValueIn < GetValueOut())
return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
// Tally transaction fees
int64 nTxFee = nValueIn - GetValueOut();
if (nTxFee < 0)
return DoS(100, error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,10).c_str()));
// ppcoin: enforce transaction fees for every block
if (nTxFee < GetMinFee())
return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(GetMinFee()).c_str(), FormatMoney(nTxFee).c_str())) : false;
nFees += nTxFee;
if (!MoneyRange(nFees))
return DoS(100, error("ConnectInputs() : nFees out of range"));
}
}
return true;
}
// ppcoin: coinstake must meet hash target according to the protocol:
// kernel (input 0) must meet the formula
// hash(nBits + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDay
// this ensures that the chance of getting a coinstake is proportional to the
// amount of coin age one owns.
// The reason this hash is chosen is the following:
// nBits: encodes all past block timestamps, making computing hash in advance
// more difficult
// txPrev.block.nTime: prevent nodes from guessing a good timestamp to
// generate transaction for future advantage
// txPrev.offset: offset of txPrev inside block, to reduce the chance of
// nodes generating coinstake at the same time
// txPrev.nTime: reduce the chance of nodes generating coinstake at the same
// time
// txPrev.vout.n: output number of txPrev, to reduce the chance of nodes
// generating coinstake at the same time
// block/tx hash should not be used here as they can be generated in vast
// quantities so as to generate blocks faster, degrading the system back into
// a proof-of-work situation.
//
bool CTransaction::CheckProofOfStake(unsigned int nBits) const
{
CBigNum bnTargetPerCoinDay;
bnTargetPerCoinDay.SetCompact(nBits);
if (!IsCoinStake())
return true;
// Kernel (input 0) must match the stake hash target per coin age (nBits)
const CTxIn& txin = vin[0];
// First try finding the previous transaction in database
CTxDB txdb("r");
CTransaction txPrev;
CTxIndex txindex;
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
return false; // previous transaction not in main chain
txdb.Close();
if (nTime < txPrev.nTime)
return false; // Transaction timestamp violation
// Verify signature
if (!VerifySignature(txPrev, *this, 0, true, 0))
return DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", GetHash().ToString().c_str()));
// Read block header
CBlock block;
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
return false; // unable to read block of previous transaction
if (block.GetBlockTime() + nStakeMinAge > nTime)
return false; // only count coins meeting min age requirement
int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
CBigNum bnCoinDay = CBigNum(nValueIn) * min(nTime-txPrev.nTime, (unsigned int)STAKE_MAX_AGE) / COIN / (24 * 60 * 60);
// Calculate hash
CDataStream ss(SER_GETHASH, 0);
ss << nBits << block.nTime << (txindex.pos.nTxPos - txindex.pos.nBlockPos) << txPrev.nTime << txin.prevout.n << nTime;
if (CBigNum(Hash(ss.begin(), ss.end())) <= bnCoinDay * bnTargetPerCoinDay)
return true;
else
return DoS(100, error("CheckProofOfStake() : check target failed on coinstake %s", GetHash().ToString().c_str()));
}
// ppcoin: total coin age spent in transaction, in the unit of coin-days.
// Only those coins meeting minimum age requirement counts. As those
// transactions not in main chain are not currently indexed so we
// might not find out about their coin age. Older transactions are
// guaranteed to be in main chain by sync-checkpoint. This rule is
// introduced to help nodes establish a consistent view of the coin
// age (trust score) of competing branches.
bool CTransaction::GetCoinAge(CTxDB& txdb, uint64& nCoinAge) const
{
CBigNum bnCentSecond = 0; // coin age in the unit of cent-seconds
nCoinAge = 0;
if (IsCoinBase())
return true;
BOOST_FOREACH(const CTxIn& txin, vin)
{
// First try finding the previous transaction in database
CTransaction txPrev;
CTxIndex txindex;
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
continue; // previous transaction not in main chain
if (nTime < txPrev.nTime)
return false; // Transaction timestamp violation
// Read block header
CBlock block;
if (!block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false))
return false; // unable to read block of previous transaction
if (block.GetBlockTime() + nStakeMinAge > nTime)
continue; // only count coins meeting min age requirement
int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
bnCentSecond += CBigNum(nValueIn) * (nTime-txPrev.nTime) / CENT;
if (fDebug && GetBoolArg("-printcoinage"))
printf("coin age nValueIn=%-12I64d nTimeDiff=%d bnCentSecond=%s\n", nValueIn, nTime - txPrev.nTime, bnCentSecond.ToString().c_str());
}
CBigNum bnCoinDay = bnCentSecond * CENT / COIN / (24 * 60 * 60);
if (fDebug && GetBoolArg("-printcoinage"))
printf("coin age bnCoinDay=%s\n", bnCoinDay.ToString().c_str());
nCoinAge = bnCoinDay.getuint64();
return true;
}
// ppcoin: total coin age spent in block, in the unit of coin-days.
bool CBlock::GetCoinAge(uint64& nCoinAge) const
{
nCoinAge = 0;
CTxDB txdb("r");
BOOST_FOREACH(const CTransaction& tx, vtx)
{
uint64 nTxCoinAge;
if (tx.GetCoinAge(txdb, nTxCoinAge))
nCoinAge += nTxCoinAge;
else
return false;
}
if (nCoinAge == 0) // block coin age minimum 1 coin-day
nCoinAge = 1;
if (fDebug && GetBoolArg("-printcoinage"))
printf("block coin age total nCoinDays=%"PRI64d"\n", nCoinAge);
return true;
}
bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos)
{
// Check for duplicate
uint256 hash = GetHash();
if (mapBlockIndex.count(hash))
return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,20).c_str());
// Construct new block index object
CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this);
if (!pindexNew)
return error("AddToBlockIndex() : new CBlockIndex failed");
map::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
if (pindexNew->fProofOfStake)
setStakeSeen.insert(make_pair(pindexNew->prevoutStake, pindexNew->nStakeTime));
pindexNew->phashBlock = &((*mi).first);
map::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{
pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
}
// ppcoin: compute chain trust score
pindexNew->bnChainTrust = (pindexNew->pprev ? pindexNew->pprev->bnChainTrust : 0) + pindexNew->GetBlockTrust();
CTxDB txdb;
if (!txdb.TxnBegin())
return false;
txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
if (!txdb.TxnCommit())
return false;
// New best
if (pindexNew->bnChainTrust > bnBestChainTrust)
if (!SetBestChain(txdb, pindexNew))
return false;
txdb.Close();
if (pindexNew == pindexBest)
{
// Notify UI to display prev block's coinbase if it was ours
static uint256 hashPrevBestCoinBase;
UpdatedTransaction(hashPrevBestCoinBase);
hashPrevBestCoinBase = vtx[0].GetHash();
}
MainFrameRepaint();
return true;
}
bool CBlock::CheckBlock() const
{
// These are checks that are independent of context
// that can be verified before saving an orphan block.
// Size limits
if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
return DoS(100, error("CheckBlock() : size limits failed"));
// Check proof of work matches claimed amount
if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp
if (GetBlockTime() > GetAdjustedTime() + nMaxClockDrift)
return error("CheckBlock() : block timestamp too far in the future");
// First transaction must be coinbase, the rest must not be
if (vtx.empty() || !vtx[0].IsCoinBase())
return DoS(100, error("CheckBlock() : first tx is not coinbase"));
for (unsigned int i = 1; i < vtx.size(); i++)
if (vtx[i].IsCoinBase())
return DoS(100, error("CheckBlock() : more than one coinbase"));
// ppcoin: only the second transaction can be the optional coinstake
for (int i = 2; i < vtx.size(); i++)
if (vtx[i].IsCoinStake())
return DoS(100, error("CheckBlock() : coinstake in wrong position"));
// ppcoin: coinbase output should be empty if proof-of-stake block
if (IsProofOfStake() && (vtx[0].vout.size() != 1 || !vtx[0].vout[0].IsEmpty()))
return error("CheckBlock() : coinbase output not empty for proof-of-stake block");
// Check coinbase timestamp
if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift)
return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));
// Check coinstake timestamp
if (IsProofOfStake() && GetBlockTime() > (int64)vtx[1].nTime + nMaxClockDrift)
return DoS(50, error("CheckBlock() : coinstake timestamp is too early"));
// Check coinbase reward
if (vtx[0].GetValueOut() > (IsProofOfWork()? (GetProofOfWorkReward(nBits) - vtx[0].GetMinFee() + MIN_TX_FEE) : 0))
return DoS(50, error("CheckBlock() : coinbase reward exceeded %s > %s",
FormatMoney(vtx[0].GetValueOut()).c_str(),
FormatMoney(IsProofOfWork()? GetProofOfWorkReward(nBits) : 0).c_str()));
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
{
if (!tx.CheckTransaction())
return DoS(tx.nDoS, error("CheckBlock() : CheckTransaction failed"));
// ppcoin: check transaction timestamp
if (GetBlockTime() < (int64)tx.nTime)
return DoS(50, error("CheckBlock() : block timestamp earlier than transaction timestamp"));
}
// Check for duplicate txids. This is caught by ConnectInputs(),
// but catching it earlier avoids a potential DoS attack:
set uniqueTx;
BOOST_FOREACH(const CTransaction& tx, vtx)
{
uniqueTx.insert(tx.GetHash());
}
if (uniqueTx.size() != vtx.size())
return DoS(100, error("CheckBlock() : duplicate transaction"));
unsigned int nSigOps = 0;
BOOST_FOREACH(const CTransaction& tx, vtx)
{
nSigOps += tx.GetLegacySigOpCount();
}
if (nSigOps > MAX_BLOCK_SIGOPS)
return DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"));
// Check merkleroot
if (hashMerkleRoot != BuildMerkleTree())
return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
// ppcoin: check block signature
if (!CheckBlockSignature())
return DoS(100, error("CheckBlock() : bad block signature"));
return true;
}
bool CBlock::AcceptBlock()
{
// Check for duplicate
uint256 hash = GetHash();
if (mapBlockIndex.count(hash))
return error("AcceptBlock() : block already in mapBlockIndex");
// Get prev block index
map::iterator mi = mapBlockIndex.find(hashPrevBlock);
if (mi == mapBlockIndex.end())
return DoS(10, error("AcceptBlock() : prev block not found"));
CBlockIndex* pindexPrev = (*mi).second;
int nHeight = pindexPrev->nHeight+1;
// Check proof-of-work or proof-of-stake
if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake()))
return DoS(100, error("AcceptBlock() : incorrect proof-of-work/proof-of-stake"));
// Check timestamp against prev
if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
return error("AcceptBlock() : block's timestamp is too early");
// Check that all transactions are finalized
BOOST_FOREACH(const CTransaction& tx, vtx)
if (!tx.IsFinal(nHeight, GetBlockTime()))
return DoS(10, error("AcceptBlock() : contains a non-final transaction"));
// Check that the block chain matches the known block chain up to a hardened checkpoint
if (!Checkpoints::CheckHardened(nHeight, hash))
return DoS(100, error("AcceptBlock() : rejected by hardened checkpoint lockin at %d", nHeight));
// ppcoin: check that the block satisfies synchronized checkpoint
if (!Checkpoints::CheckSync(hash, pindexPrev))
return error("AcceptBlock() : rejected by synchronized checkpoint");
// Write block to history file
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK, CLIENT_VERSION)))
return error("AcceptBlock() : out of disk space");
unsigned int nFile = -1;
unsigned int nBlockPos = 0;
if (!WriteToDisk(nFile, nBlockPos))
return error("AcceptBlock() : WriteToDisk failed");
if (!AddToBlockIndex(nFile, nBlockPos))
return error("AcceptBlock() : AddToBlockIndex failed");
// Relay inventory, but don't relay old inventory during initial block download
int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate();
if (hashBestChain == hash)
{
LOCK(cs_vNodes);
BOOST_FOREACH(CNode* pnode, vNodes)
if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate))
pnode->PushInventory(CInv(MSG_BLOCK, hash));
}
// ppcoin: check pending sync-checkpoint
Checkpoints::AcceptPendingSyncCheckpoint();
return true;
}
bool ProcessBlock(CNode* pfrom, CBlock* pblock)
{
// Check for duplicate
uint256 hash = pblock->GetHash();
if (mapBlockIndex.count(hash))
return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,20).c_str());
if (mapOrphanBlocks.count(hash))
return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,20).c_str());
// ppcoin: check proof-of-stake
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (pblock->IsProofOfStake() && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
// Preliminary checks
if (!pblock->CheckBlock())
return error("ProcessBlock() : CheckBlock FAILED");
// ppcoin: verify hash target and signature of coinstake tx
if (pblock->IsProofOfStake() && !pblock->vtx[1].CheckProofOfStake(pblock->nBits))
{
printf("WARNING: ProcessBlock(): check proof-of-stake failed for block %s\n", hash.ToString().c_str());
return false; // do not error here as we expect this during initial block download
}
CBlockIndex* pcheckpoint = Checkpoints::GetLastSyncCheckpoint();
if (pcheckpoint && pblock->hashPrevBlock != hashBestChain && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
{
// Extra checks to prevent "fill up memory by spamming with bogus blocks"
int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime;
CBigNum bnNewBlock;
bnNewBlock.SetCompact(pblock->nBits);
CBigNum bnRequired;
bnRequired.SetCompact(ComputeMinWork(GetLastBlockIndex(pcheckpoint, pblock->IsProofOfStake())->nBits, deltaTime));
if (bnNewBlock > bnRequired)
{
if (pfrom)
pfrom->Misbehaving(100);
return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work");
}
}
// ppcoin: ask for pending sync-checkpoint if any
if (!IsInitialBlockDownload())
Checkpoints::AskForPendingSyncCheckpoint(pfrom);
// If don't already have its previous block, shunt it off to holding area until we get it
if (!mapBlockIndex.count(pblock->hashPrevBlock))
{
printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,20).c_str());
CBlock* pblock2 = new CBlock(*pblock);
// ppcoin: check proof-of-stake
if (pblock2->IsProofOfStake())
{
// Limited duplicity on stake: prevents block flood attack
// Duplicate stake allowed only when there is orphan child block
if (setStakeSeenOrphan.count(pblock2->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash) && !Checkpoints::WantedByPendingSyncCheckpoint(hash))
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for orphan block %s", pblock2->GetProofOfStake().first.ToString().c_str(), pblock2->GetProofOfStake().second, hash.ToString().c_str());
else
setStakeSeenOrphan.insert(pblock2->GetProofOfStake());
}
mapOrphanBlocks.insert(make_pair(hash, pblock2));
mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2));
// Ask this guy to fill in what we're missing
if (pfrom)
{
pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(pblock2));
// ppcoin: getblocks may not obtain the ancestor block rejected
// earlier by duplicate-stake check so we ask for it again directly
if (!IsInitialBlockDownload())
pfrom->AskFor(CInv(MSG_BLOCK, WantedByOrphan(pblock2)));
}
return true;
}
// Store to disk
if (!pblock->AcceptBlock())
return error("ProcessBlock() : AcceptBlock FAILED");
// Recursively process any orphan blocks that depended on this one
vector vWorkQueue;
vWorkQueue.push_back(hash);
for (unsigned int i = 0; i < vWorkQueue.size(); i++)
{
uint256 hashPrev = vWorkQueue[i];
for (multimap::iterator mi = mapOrphanBlocksByPrev.lower_bound(hashPrev);
mi != mapOrphanBlocksByPrev.upper_bound(hashPrev);
++mi)
{
CBlock* pblockOrphan = (*mi).second;
if (pblockOrphan->AcceptBlock())
vWorkQueue.push_back(pblockOrphan->GetHash());
mapOrphanBlocks.erase(pblockOrphan->GetHash());
setStakeSeenOrphan.erase(pblockOrphan->GetProofOfStake());
delete pblockOrphan;
}
mapOrphanBlocksByPrev.erase(hashPrev);
}
printf("ProcessBlock: ACCEPTED\n");
// ppcoin: if responsible for sync-checkpoint send it
if (pfrom && !CSyncCheckpoint::strMasterPrivKey.empty())
Checkpoints::SendSyncCheckpoint(Checkpoints::AutoSelectSyncCheckpoint());
return true;
}
// ppcoin: sign block
bool CBlock::SignBlock(const CKeyStore& keystore)
{
vector vSolutions;
txnouttype whichType;
const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0];
if (!Solver(txout.scriptPubKey, whichType, vSolutions))
return false;
if (whichType == TX_PUBKEY)
{
// Sign
const valtype& vchPubKey = vSolutions[0];
CKey key;
if (!keystore.GetKey(Hash160(vchPubKey), key))
return false;
if (key.GetPubKey() != vchPubKey)
return false;
return key.Sign(GetHash(), vchBlockSig);
}
return false;
}
// ppcoin: check block signature
bool CBlock::CheckBlockSignature() const
{
if (GetHash() == hashGenesisBlock)
return vchBlockSig.empty();
vector vSolutions;
txnouttype whichType;
const CTxOut& txout = IsProofOfStake()? vtx[1].vout[1] : vtx[0].vout[0];
if (!Solver(txout.scriptPubKey, whichType, vSolutions))
return false;
if (whichType == TX_PUBKEY)
{
const valtype& vchPubKey = vSolutions[0];
CKey key;
if (!key.SetPubKey(vchPubKey))
return false;
if (vchBlockSig.empty())
return false;
return key.Verify(GetHash(), vchBlockSig);
}
return false;
}
// CreateNewBlock:
// fProofOfStake: try (best effort) to make a proof-of-stake block
CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake)
{
CReserveKey reservekey(pwallet);
// 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);
txNew.vout[0].scriptPubKey << reservekey.GetReservedKey() << OP_CHECKSIG;
// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
// ppcoin: if coinstake available add coinstake tx
static int64 nLastCoinStakeSearchTime = GetAdjustedTime(); // only initialized at startup
CBlockIndex* pindexPrev = pindexBest;
if (fProofOfStake) // attemp to find a coinstake
{
pblock->nBits = GetNextTargetRequired(pindexPrev, true);
CTransaction txCoinStake;
int64 nSearchTime = GetAdjustedTime();
if (nSearchTime > nLastCoinStakeSearchTime)
{
if (pwallet->CreateCoinStake(*pwallet, pblock->nBits, nSearchTime-nLastCoinStakeSearchTime, txCoinStake))
{
pblock->vtx.push_back(txCoinStake);
pblock->vtx[0].vout[0].SetEmpty();
}
nLastCoinStakeSearchInterval = nSearchTime - nLastCoinStakeSearchTime;
nLastCoinStakeSearchTime = nSearchTime;
}
}
pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake());
// Collect memory pool transactions into the block
int64 nFees = 0;
{
LOCK2(cs_main, mempool.cs);
CTxDB txdb("r");
// Priority order to process transactions
list vOrphan; // list memory doesn't move
map > mapDependers;
multimap mapPriority;
for (map::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;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
// Read prev transaction
CTransaction txPrev;
CTxIndex txindex;
if (!txPrev.ReadFromDisk(txdb, txin.prevout, txindex))
{
// 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);
continue;
}
int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
// Read block header
int nConf = txindex.GetDepthInMainChain();
dPriority += (double)nValueIn * nConf;
if (fDebug && GetBoolArg("-printpriority"))
printf("priority nValueIn=%-12"PRI64d" nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority);
}
// Priority is sum(valuein * age) / txsize
dPriority /= ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
if (porphan)
porphan->dPriority = dPriority;
else
mapPriority.insert(make_pair(-dPriority, &(*mi).second));
if (fDebug && GetBoolArg("-printpriority"))
{
printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str());
if (porphan)
porphan->print();
printf("\n");
}
}
// Collect transactions into block
map mapTestPool;
uint64 nBlockSize = 1000;
uint64 nBlockTx = 0;
int nBlockSigOps = 100;
while (!mapPriority.empty())
{
// Take highest priority transaction off priority queue
CTransaction& tx = *(*mapPriority.begin()).second;
mapPriority.erase(mapPriority.begin());
// Size limits
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN)
continue;
// Legacy limits on sigOps:
unsigned int nTxSigOps = tx.GetLegacySigOpCount();
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue;
// Timestamp limit
if (tx.nTime > GetAdjustedTime())
continue;
// ppcoin: simplify transaction fee - allow free = false
int64 nMinFee = tx.GetMinFee(nBlockSize, false, GMF_BLOCK);
// Connecting shouldn't fail due to dependency on other memory pool transactions
// because we're already processing them in order of dependency
map mapTestPoolTmp(mapTestPool);
MapPrevTx mapInputs;
bool fInvalid;
if (!tx.FetchInputs(txdb, mapTestPoolTmp, false, true, mapInputs, fInvalid))
continue;
int64 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;
// Add transactions that depend on this one to the priority queue
uint256 hash = tx.GetHash();
if (mapDependers.count(hash))
{
BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
{
if (!porphan->setDependsOn.empty())
{
porphan->setDependsOn.erase(hash);
if (porphan->setDependsOn.empty())
mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx));
}
}
}
}
nLastBlockTx = nBlockTx;
nLastBlockSize = nBlockSize;
if (fDebug && GetBoolArg("-printpriority"))
printf("CreateNewBlock(): total size %lu\n", nBlockSize);
}
if (pblock->IsProofOfWork())
pblock->vtx[0].vout[0].nValue = GetProofOfWorkReward(pblock->nBits);
// Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
pblock->UpdateTime(pindexPrev);
pblock->nNonce = 0;
return pblock.release();
}
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce)
{
// Update nExtraNonce
static uint256 hashPrevBlock;
if (hashPrevBlock != pblock->hashPrevBlock)
{
nExtraNonce = 0;
hashPrevBlock = pblock->hashPrevBlock;
}
++nExtraNonce;
pblock->vtx[0].vin[0].scriptSig = (CScript() << pblock->nTime << CBigNum(nExtraNonce)) + COINBASE_FLAGS;
assert(pblock->vtx[0].vin[0].scriptSig.size() <= 100);
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
}
void BitcoinMiner(CWallet *pwallet, bool fProofOfStake)
{
printf("CPUMiner started for proof-of-%s\n", fProofOfStake? "stake" : "work");
SetThreadPriority(THREAD_PRIORITY_LOWEST);
// Each thread has its own key and counter
CReserveKey reservekey(pwallet);
unsigned int nExtraNonce = 0;
while (fGenerateBitcoins || fProofOfStake)
{
if (fShutdown)
return;
while (vNodes.empty() || IsInitialBlockDownload())
{
Sleep(1000);
if (fShutdown)
return;
if ((!fGenerateBitcoins) && !fProofOfStake)
return;
}
while (pwallet->IsLocked())
{
strMintWarning = strMintMessage;
Sleep(1000);
}
strMintWarning = "";
//
// Create new block
//
unsigned int nTransactionsUpdatedLast = nTransactionsUpdated;
CBlockIndex* pindexPrev = pindexBest;
auto_ptr pblock(CreateNewBlock(pwallet, fProofOfStake));
if (!pblock.get())
return;
IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
if (fProofOfStake)
{
// ppcoin: if proof-of-stake block found then process block
if (pblock->IsProofOfStake())
{
if (!pblock->SignBlock(*pwalletMain))
{
strMintWarning = strMintMessage;
continue;
}
strMintWarning = "";
printf("CPUMiner : proof-of-stake block found %s\n", pblock->GetHash().ToString().c_str());
SetThreadPriority(THREAD_PRIORITY_NORMAL);
CheckWork(pblock.get(), *pwalletMain, reservekey);
SetThreadPriority(THREAD_PRIORITY_LOWEST);
}
Sleep(500);
continue;
}
printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size());
//
// Prebuild hash buffers
//
char pmidstatebuf[32+16]; char* pmidstate = alignup<16>(pmidstatebuf);
char pdatabuf[128+16]; char* pdata = alignup<16>(pdatabuf);
char phash1buf[64+16]; char* phash1 = alignup<16>(phash1buf);
FormatHashBuffers(pblock.get(), pmidstate, pdata, phash1);
unsigned int& nBlockTime = *(unsigned int*)(pdata + 64 + 4);
unsigned int& nBlockNonce = *(unsigned int*)(pdata + 64 + 12);
//
// Search
//
int64 nStart = GetTime();
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
uint256 hashbuf[2];
uint256& hash = *alignup<16>(hashbuf);
loop
{
unsigned int nHashesDone = 0;
unsigned int nNonceFound;
// Crypto++ SHA-256
nNonceFound = ScanHash_CryptoPP(pmidstate, pdata + 64, phash1,
(char*)&hash, nHashesDone);
// Check if something found
if (nNonceFound != (unsigned int) -1)
{
for (unsigned int i = 0; i < sizeof(hash)/4; i++)
((unsigned int*)&hash)[i] = ByteReverse(((unsigned int*)&hash)[i]);
if (hash <= hashTarget)
{
// Found a solution
pblock->nNonce = ByteReverse(nNonceFound);
assert(hash == pblock->GetHash());
if (!pblock->SignBlock(*pwalletMain))
{
strMintWarning = strMintMessage;
break;
}
strMintWarning = "";
SetThreadPriority(THREAD_PRIORITY_NORMAL);
CheckWork(pblock.get(), *pwalletMain, reservekey);
SetThreadPriority(THREAD_PRIORITY_LOWEST);
break;
}
}
// Meter hashes/sec
static int64 nHashCounter;
if (nHPSTimerStart == 0)
{
nHPSTimerStart = GetTimeMillis();
nHashCounter = 0;
}
else
nHashCounter += nHashesDone;
if (GetTimeMillis() - nHPSTimerStart > 4000)
{
static CCriticalSection cs;
{
LOCK(cs);
if (GetTimeMillis() - nHPSTimerStart > 4000)
{
dHashesPerSec = 1000.0 * nHashCounter / (GetTimeMillis() - nHPSTimerStart);
nHPSTimerStart = GetTimeMillis();
nHashCounter = 0;
static int64 nLogTime;
if (GetTime() - nLogTime > 30 * 60)
{
nLogTime = GetTime();
printf("%s ", DateTimeStrFormat(GetTime()).c_str());
printf("hashmeter %3d CPUs %6.0f khash/s\n", vnThreadsRunning[THREAD_MINER], dHashesPerSec/1000.0);
}
}
}
}
// Check for stop or if block needs to be rebuilt
if (fShutdown)
return;
if (!fGenerateBitcoins)
return;
if (fLimitProcessors && vnThreadsRunning[THREAD_MINER] > nLimitProcessors)
return;
if (vNodes.empty())
break;
if (nBlockNonce >= 0xffff0000)
break;
if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)
break;
if (pindexPrev != pindexBest)
break;
// Update nTime every few seconds
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, pblock->GetMaxTransactionTime());
pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
pblock->UpdateTime(pindexPrev);
nBlockTime = ByteReverse(pblock->nTime);
if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift)
break; // need to update coinbase timestamp
}
}
}
There are also lots of curious "stake connection tests," which may or may not indicate the aforementioned centralized nature of this PoS method.
Stake blocks are always difficulty 1 and nonce zero. Apparently the blockchain is programmed to always accept them as valid as long as a certain quantity of network time has passed and the quantity for which they are valid is predestined by the code included.
For whatever reason Sunny King decided not to bother publishing the pseudocode implementation of his algorithms in favour of the epic LaTeX narrative of his implementation, but if there are vulnerabilities you should be able to see them in the above code.