// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2011 The Bitcoin developers
-// Copyright (c) 2011 The PPCoin developers
+// Copyright (c) 2011-2012 The PPCoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file license.txt or http://www.opensource.org/licenses/mit-license.php.
#include "headers.h"
map<COutPoint, CInPoint> mapNextTx;
map<uint256, CBlockIndex*> mapBlockIndex;
-uint256 hashGenesisBlock("0x00000000b00cf820bc2b23ec0aad05da6c58af7fd71c34a6f21a747fd0f5620b");
+uint256 hashGenesisBlock("0x000000006d52486334316794cc38ffeb7ebf35a7ebd661fd39f5f46b0d001575");
static CBigNum bnProofOfWorkLimit(~uint256(0) >> 32);
const int nInitialBlockThreshold = 120; // Regard blocks up until N-threshold as "initial download"
CBlockIndex* pindexGenesisBlock = NULL;
#else
int fUseUPnP = false;
#endif
-
+int64 nBalanceReserve = 0;
//////////////////////////////////////////////////////////////////////////////
//
}
// make sure all wallets know about the given transaction, in the given block
-void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false)
+void static SyncWithWallets(const CTransaction& tx, const CBlock* pblock = NULL, bool fUpdate = false, bool fConnect = true)
{
+ if (!fConnect)
+ {
+ // ppcoin: wallets need to refund inputs when disconnecting coinstake
+ if (tx.IsCoinStake())
+ BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered)
+ if (pwallet->IsFromMe(tx))
+ pwallet->DisableTransaction(tx);
+ return;
+ }
+
BOOST_FOREACH(CWallet* pwallet, setpwalletRegistered)
pwallet->AddToWalletIfInvolvingMe(tx, pblock, fUpdate);
}
// Check for negative or overflow output values
int64 nValueOut = 0;
- BOOST_FOREACH(const CTxOut& txout, vout)
+ for (int i = (IsCoinStake()? 1 : 0); i < vout.size(); i++)
{
+ const CTxOut& txout = vout[i];
if (txout.nValue < 0)
return DoS(100, error("CTransaction::CheckTransaction() : txout.nValue negative"));
if (txout.nValue > MAX_MONEY)
// Coinbase is only valid in a block, not as a loose transaction
if (IsCoinBase())
return DoS(100, error("AcceptToMemoryPool() : coinbase as individual tx"));
+ // ppcoin: coinstake is also only valid in a block, not as a loose transaction
+ if (IsCoinStake())
+ return DoS(100, error("AcceptToMemoryPool() : coinstake as individual tx"));
// To help v0.1.5 clients who would see it as a negative number
if ((int64)nLockTime > INT_MAX)
int CMerkleTx::GetBlocksToMaturity() const
{
- if (!IsCoinBase())
+ if (!(IsCoinBase() || IsCoinStake()))
return 0;
return max(0, (COINBASE_MATURITY+20) - GetDepthInMainChain());
}
// Add previous supporting transactions first
BOOST_FOREACH(CMerkleTx& tx, vtxPrev)
{
- if (!tx.IsCoinBase())
+ if (!(tx.IsCoinBase() || tx.IsCoinStake()))
{
uint256 hash = tx.GetHash();
if (!mapTransactions.count(hash) && !txdb.ContainsTx(hash))
return pblock->GetHash();
}
-int64 static GetBlockValue(int nHeight, int64 nFees)
+int64 static GetProofOfWorkReward(unsigned int nBits)
{
- int64 nSubsidy = 50 * COIN;
+ CBigNum bnSubsidyLimit = 9999 * COIN; // subsidy amount for difficulty 1
+ CBigNum bnTarget;
+ bnTarget.SetCompact(nBits);
+ CBigNum bnTargetLimit = bnProofOfWorkLimit;
+ bnTargetLimit.SetCompact(bnTargetLimit.GetCompact());
+
+ // ppcoin: subsidy is cut in half every 16x multiply of difficulty
+ // A reasonably continuous curve is used to avoid shock to market
+ // (nSubsidyLimit / nSubsidy) ** 4 == bnProofOfWorkLimit / bnTarget
+ CBigNum bnLowerBound = CENT;
+ CBigNum bnUpperBound = bnSubsidyLimit;
+ while (bnLowerBound + CENT <= bnUpperBound)
+ {
+ CBigNum bnMidValue = (bnLowerBound + bnUpperBound) / 2;
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("GetProofOfWorkReward() : lower=%"PRI64d" upper=%"PRI64d" mid=%"PRI64d"\n", bnLowerBound.getuint64(), bnUpperBound.getuint64(), bnMidValue.getuint64());
+ if (bnMidValue * bnMidValue * bnMidValue * bnMidValue * bnTargetLimit > bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnSubsidyLimit * bnTarget)
+ bnUpperBound = bnMidValue;
+ else
+ bnLowerBound = bnMidValue;
+ }
- // Subsidy is cut in half every 4 years
- nSubsidy >>= (nHeight / 210000);
+ int64 nSubsidy = bnUpperBound.getuint64();
+ nSubsidy = (nSubsidy / CENT) * CENT;
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("GetProofOfWorkReward() : create=%s nBits=0x%08x nSubsidy=%"PRI64d"\n", FormatMoney(nSubsidy).c_str(), nBits, nSubsidy);
- return nSubsidy + nFees;
+ return nSubsidy;
+}
+
+// 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 nTargetSpacing = 10 * 60;
static const int64 nInterval = nTargetTimespan / nTargetSpacing;
+static const int64 nMaxClockDrift = 2 * 60 * 60; // 2 hours
//
// minimum amount of work that could possibly be required nTime after
return bnResult.GetCompact();
}
-unsigned int static GetNextWorkRequired(const CBlockIndex* pindexLast)
+// ppcoin: find last block index up to pindex
+const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake)
+{
+ while (pindex && (pindex->IsProofOfStake() != fProofOfStake))
+ pindex = pindex->pprev;
+ return pindex;
+}
+
+unsigned int static GetNextTargetRequired(const CBlockIndex* pindexLast, bool fProofOfStake)
{
// Genesis block and first block
if (pindexLast == NULL || pindexLast->pprev == NULL)
return bnProofOfWorkLimit.GetCompact();
- int64 nActualSpacing = pindexLast->GetBlockTime() - pindexLast->pprev->GetBlockTime();
+ const CBlockIndex* pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
+ if (pindexPrev == NULL)
+ return bnProofOfWorkLimit.GetCompact();
+ const CBlockIndex* pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
+ if (pindexPrevPrev == NULL)
+ return bnProofOfWorkLimit.GetCompact();
+ int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();
// ppcoin: target change every block
// ppcoin: retarget with exponential moving toward target spacing
if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size())
return DoS(100, error("ConnectInputs() : %s prevout.n out of range %d %d %d prev tx %s\n%s", GetHash().ToString().substr(0,10).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size(), prevout.hash.ToString().substr(0,10).c_str(), txPrev.ToString().c_str()));
- // If prev is coinbase, check that it's matured
- if (txPrev.IsCoinBase())
+ // If prev is coinbase/coinstake, check that it's matured
+ if (txPrev.IsCoinBase() || txPrev.IsCoinStake())
for (CBlockIndex* pindex = pindexBlock; pindex && pindexBlock->nHeight - pindex->nHeight < COINBASE_MATURITY; pindex = pindex->pprev)
if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile)
- return error("ConnectInputs() : tried to spend coinbase at depth %d", pindexBlock->nHeight - pindex->nHeight);
+ return error("ConnectInputs() : tried to spend coinbase/coinstake at depth %d", pindexBlock->nHeight - pindex->nHeight);
// ppcoin: check transaction timestamp
if (txPrev.nTime > nTime)
}
}
- if (nValueIn < GetValueOut())
- return DoS(100, error("ConnectInputs() : %s value in < value out", GetHash().ToString().substr(0,10).c_str()));
+ 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))
+ 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()));
- if (nTxFee < nMinFee)
- return false;
- nFees += nTxFee;
- if (!MoneyRange(nFees))
- return DoS(100, error("ConnectInputs() : nFees out of range"));
+ // 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 < nMinFee)
+ return fBlock? DoS(100, error("ConnectInputs() : %s not paying required fee=%s, paid=%s", GetHash().ToString().substr(0,10).c_str(), FormatMoney(nMinFee).c_str(), FormatMoney(nTxFee).c_str())) : false;
+ nFees += nTxFee;
+ if (!MoneyRange(nFees))
+ return DoS(100, error("ConnectInputs() : nFees out of range"));
+ }
}
if (fBlock)
return error("DisconnectBlock() : WriteBlockIndex failed");
}
+ // ppcoin: clean up wallet after disconnecting coinstake
+ BOOST_FOREACH(CTransaction& tx, vtx)
+ SyncWithWallets(tx, this, false, false);
+
return true;
}
if (!CheckBlock())
return false;
+ // ppcoin: coin stake tx must meet target protocol
+ if (IsProofOfStake() && !vtx[1].CheckProofOfStake(txdb, nBits))
+ return error("ConnectBlock() : Block %s unable to meet hash target for coinstake", GetHash().ToString().c_str());
+
//// issue here: it doesn't know the version
- unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size());
+ unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size());
map<uint256, CTxIndex> mapQueuedChanges;
int64 nFees = 0;
CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos);
nTxPos += ::GetSerializeSize(tx, SER_DISK);
- if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false))
+ if (!tx.ConnectInputs(txdb, mapQueuedChanges, posThisTx, pindex, nFees, true, false, tx.GetMinFee()))
return false;
}
// Write queued txindex changes
return error("ConnectBlock() : UpdateTxIndex failed");
}
- if (vtx[0].GetValueOut() > GetBlockValue(pindex->nHeight, nFees))
+ // ppcoin: fees are not collected by miners as in bitcoin
+ // ppcoin: fees are destroyed to compensate the entire network
+ if (vtx[0].GetValueOut() > GetProofOfWorkReward(nBits))
return false;
+ if (fDebug && GetBoolArg("-printcreation"))
+ printf("ConnectBlock() : destroy=%s nFees=%"PRI64d"\n", FormatMoney(nFees).c_str(), nFees);
// Update block index on disk without changing it in memory.
// The memory index structure will be changed after the db commits.
// Queue memory transactions to resurrect
BOOST_FOREACH(const CTransaction& tx, block.vtx)
- if (!tx.IsCoinBase())
+ if (!(tx.IsCoinBase() || tx.IsCoinStake()))
vResurrect.push_back(tx);
}
}
if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash()))
return error("Reorganize() : WriteHashBestChain failed");
+ if (!txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)))
+ return error("Reorganize() : WriteAutoCheckpoint failed");
// Make sure it's successfully written to disk before changing memory structure
if (!txdb.TxnCommit())
if (pindexGenesisBlock == NULL && hash == hashGenesisBlock)
{
txdb.WriteHashBestChain(hash);
+ txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint));
if (!txdb.TxnCommit())
return error("SetBestChain() : TxnCommit failed");
pindexGenesisBlock = pindexNew;
else if (hashPrevBlock == hashBestChain)
{
// Adding to current best branch
- if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash))
+ if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash) || !txdb.WriteAutoCheckpoint(Checkpoints::GetNextAutoCheckpoint(pindexNew->nCheckpoint)))
{
txdb.TxnAbort();
InvalidChainFound(pindexNew);
nBestChainTrust = pindexNew->nChainTrust;
nTimeBestReceived = GetTime();
nTransactionsUpdated++;
+ Checkpoints::AdvanceAutoCheckpoint(pindexBest->nCheckpoint);
printf("SetBestChain: new best=%s height=%d trust=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, CBigNum(nBestChainTrust).ToString().c_str());
return true;
}
-// ppcoin: total coin age spent in block, in the unit of coin-days.
-uint64 CBlock::GetBlockCoinAge()
+// ppcoin: coinstake must meet hash target according to the protocol:
+// at least one input 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(CTxDB& txdb, unsigned int nBits) const
{
- uint64 nCoinAge = 0;
+ CBigNum bnTargetPerCoinDay;
+ bnTargetPerCoinDay.SetCompact(nBits);
+
+ if (!IsCoinStake())
+ return true;
- BOOST_FOREACH(const CTransaction& tx, vtx)
+ BOOST_FOREACH(const CTxIn& txin, vin)
{
- if (tx.IsCoinBase())
- continue;
+ // 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
- BOOST_FOREACH(const CTxIn& txin, tx.vin)
- {
- // First try finding the previous transaction in database
- CTransaction txPrev;
- if (!txPrev.ReadFromDisk(txin.prevout))
- {
- // If database lookup fails try memory pool
- CRITICAL_BLOCK(cs_mapTransactions)
- {
- if (!mapTransactions.count(txin.prevout.hash))
- return 0; // Neither found in database nor memory pool
- txPrev = mapTransactions[txin.prevout.hash];
- }
- }
+ // 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() + AUTO_CHECKPOINT_TRUST_SPAN > nTime)
+ continue; // only count coins from at least one week ago
+
+ int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
+ CBigNum bnCoinDay = CBigNum(nValueIn) * (nTime-txPrev.nTime) / COIN / (24 * 60 * 60);
+ // Calculate hash
+ CDataStream ss(SER_GETHASH, VERSION);
+ 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;
+ }
+
+ return false;
+}
- if (tx.nTime < txPrev.nTime)
- return 0; // Transaction timestamp violation
+// ppcoin: total coin age spent in transaction, in the unit of coin-days.
+// Only those coins last spent at least a week ago count. 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 auto 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;
- int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
- CBigNum bnTxInCoinAge = CBigNum(nValueIn) * (tx.nTime - txPrev.nTime) / COIN / (24 * 60 * 60);
- nCoinAge += bnTxInCoinAge.getuint64();
+ if (IsCoinBase())
+ return true;
- if (fDebug && GetBoolArg("-printcoinage"))
- printf("coin age nValueIn=%-12I64d nTimeDiff=%d nCoinAge=%"PRI64d"\n", nValueIn, tx.nTime - txPrev.nTime, nCoinAge);
- }
+ 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() + AUTO_CHECKPOINT_TRUST_SPAN > nTime)
+ continue; // only count coins from at least one week ago
+
+ 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());
}
- if (!nCoinAge)
- nCoinAge = 1;
+ 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;
- return nCoinAge;
+ 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;
}
if (!pindexNew)
return error("AddToBlockIndex() : new CBlockIndex failed");
map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
+
pindexNew->phashBlock = &((*mi).first);
map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock);
if (miPrev != mapBlockIndex.end())
{
pindexNew->pprev = (*miPrev).second;
pindexNew->nHeight = pindexNew->pprev->nHeight + 1;
+
+ // ppcoin: compute chain checkpoint
+ pindexNew->nCheckpoint = Checkpoints::GetNextChainCheckpoint(pindexNew->pprev);
+ assert (pindexNew->nCheckpoint >= pindexNew->pprev->nCheckpoint);
}
- uint64 nCoinAge = GetBlockCoinAge();
- if (!nCoinAge)
- return error("AddToBlockIndex() : invalid or orphaned transaction in block");
+
+ // ppcoin: compute chain trust score
+ uint64 nCoinAge;
+ if (!GetCoinAge(nCoinAge))
+ return error("AddToBlockIndex() : invalid transaction in block");
pindexNew->nChainTrust = (pindexNew->pprev ? pindexNew->pprev->nChainTrust : 0) + nCoinAge;
CTxDB txdb;
return DoS(100, error("CheckBlock() : size limits failed"));
// Check proof of work matches claimed amount
- if (!CheckProofOfWork(GetHash(), nBits))
+ if (IsProofOfWork() && !CheckProofOfWork(GetHash(), nBits))
return DoS(50, error("CheckBlock() : proof of work failed"));
// Check timestamp
- if (GetBlockTime() > GetAdjustedTime() + 2 * 60 * 60)
+ 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[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"));
+
+ // Check coinbase timestamp
+ if (GetBlockTime() > (int64)vtx[0].nTime + nMaxClockDrift)
+ return DoS(50, error("CheckBlock() : coinbase timestamp is too early"));
+
// Check transactions
BOOST_FOREACH(const CTransaction& tx, vtx)
{
if (hashMerkleRoot != BuildMerkleTree())
return DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"));
+ // Coin base vout[0] scriptPubKey must be the same as coin stake vout[1]
+ // scriptPubKey
+ if (vtx.size() > 1 && vtx[1].IsCoinStake() &&
+ vtx[0].vout[0].scriptPubKey != vtx[1].vout[1].scriptPubKey)
+ return DoS(100, error("CheckBlock() : block key mismatch"));
+
+
+ // Check block signature
+ if (!CheckBlockSignature())
+ return DoS(100, error("CheckBlock() : bad block signature"));
+
return true;
}
CBlockIndex* pindexPrev = (*mi).second;
int nHeight = pindexPrev->nHeight+1;
- // Check proof of work
- if (nBits != GetNextWorkRequired(pindexPrev))
- return DoS(100, error("AcceptBlock() : incorrect proof of work"));
+ // ppcoin: check for coinstake duplicate
+ if (IsProofOfStake())
+ { // check if coinstake is already connected; that would imply the owner
+ // of the coinstake sent multiple blocks with the same coinstake
+ CTxIndex txindex;
+ if (CTxDB("r").ReadTxIndex(vtx[1].GetHash(), txindex))
+ return error("AcceptBlock() : block %s has duplicate coinstake %s", hash.ToString().c_str(), vtx[1].GetHash().ToString().c_str());
+ }
+
+ // 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())
+ if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || GetBlockTime() + nMaxClockDrift < pindexPrev->GetBlockTime())
return error("AcceptBlock() : block's timestamp is too early");
// Check that all transactions are finalized
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 checkpoint
- if (!Checkpoints::CheckBlock(nHeight, hash))
- return DoS(100, error("AcceptBlock() : rejected by checkpoint lockin at %d", nHeight));
+ // 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 automatic checkpoint
+ if (!Checkpoints::CheckAuto(pindexPrev))
+ return DoS(100, error("AcceptBlock() : rejected by automatic checkpoint at %d", Checkpoints::nAutoCheckpoint));
// Write block to history file
if (!CheckDiskSpace(::GetSerializeSize(*this, SER_DISK)))
bnNewBlock.SetCompact(pblock->nBits);
CBigNum bnRequired;
bnRequired.SetCompact(ComputeMinWork(pcheckpoint->nBits, deltaTime));
- if (bnNewBlock > bnRequired)
+ if (pblock->IsProofOfWork() && bnNewBlock > bnRequired)
{
pfrom->Misbehaving(100);
return error("ProcessBlock() : block with too little proof-of-work");
// Genesis block
const char* pszTimestamp = "MarketWatch 07/Nov/2011 Gold tops $1,790 to end at over six-week high";
CTransaction txNew;
- txNew.nTime = 1324698231;
+ txNew.nTime = 1325878371;
txNew.vin.resize(1);
txNew.vout.resize(1);
txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
- txNew.vout[0].nValue = 50 * COIN;
+ txNew.vout[0].nValue = GetProofOfWorkReward(bnProofOfWorkLimit.GetCompact());
txNew.vout[0].scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG;
CBlock block;
block.vtx.push_back(txNew);
block.hashPrevBlock = 0;
block.hashMerkleRoot = block.BuildMerkleTree();
block.nVersion = 1;
- block.nTime = 1324707839;
- block.nBits = 0x1d00ffff;
- block.nNonce = 486102291;
+ block.nTime = 1325882536;
+ block.nBits = bnProofOfWorkLimit.GetCompact();
+ block.nNonce = 2081920190;
if (fTestNet)
{
printf("%s\n", block.GetHash().ToString().c_str());
printf("%s\n", hashGenesisBlock.ToString().c_str());
printf("%s\n", block.hashMerkleRoot.ToString().c_str());
- assert(block.hashMerkleRoot == uint256("0x487e83bd2b5a5196a2a6b87998d779a162101cb02cc64bf9ac33289dd0c22352"));
+ assert(block.hashMerkleRoot == uint256("0xc7311b56de266580cca65be108ae53d7100b5c3b17da8b1106044103abd7a521"));
block.print();
assert(block.GetHash() == hashGenesisBlock);
+ assert(block.CheckBlock());
// Start new block file
unsigned int nFile;
// print item
CBlock block;
block.ReadFromDisk(pindex);
- printf("%d (%u,%u) %s %s tx %d",
+ printf("%d (%u,%u) %s %08lx %s tx %d",
pindex->nHeight,
pindex->nFile,
pindex->nBlockPos,
block.GetHash().ToString().substr(0,20).c_str(),
+ block.nBits,
DateTimeStrFormat("%x %H:%M:%S", block.GetBlockTime()).c_str(),
block.vtx.size());
return true;
}
+ // ppcoin: record my external IP reported by peer
+ if (addrFrom.IsRoutable() && addrMe.IsRoutable())
+ addrSeenByPeer = addrMe;
+
// Be shy and don't send version until we hear
if (pfrom->fInbound)
pfrom->PushVersion();
};
-CBlock* CreateNewBlock(CReserveKey& reservekey)
+CBlock* CreateNewBlock(CWallet* pwallet)
{
- CBlockIndex* pindexPrev = pindexBest;
+ CReserveKey reservekey(pwallet);
// Create new block
auto_ptr<CBlock> pblock(new CBlock());
// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
+ // ppcoin: if coinstake available add coinstake tx
+ static unsigned int nLastCoinStakeCheckTime = GetAdjustedTime() - nMaxClockDrift; // only initialized at startup
+ CBlockIndex* pindexPrev = pindexBest;
+ while (nLastCoinStakeCheckTime < GetAdjustedTime())
+ {
+ pindexPrev = pindexBest; // get best block again to avoid getting stale
+ pblock->nBits = GetNextTargetRequired(pindexPrev, true);
+ static CCriticalSection cs;
+ CTransaction txCoinStake;
+ CRITICAL_BLOCK(cs)
+ {
+ nLastCoinStakeCheckTime++;
+ txCoinStake.nTime = nLastCoinStakeCheckTime;
+ }
+ if (pwallet->CreateCoinStake(txNew.vout[0].scriptPubKey, pblock->nBits, txCoinStake))
+ {
+ pblock->vtx.push_back(txCoinStake);
+ break;
+ }
+ }
+
+ pblock->nBits = GetNextTargetRequired(pindexPrev, pblock->IsProofOfStake());
+
// Collect memory pool transactions into the block
int64 nFees = 0;
CRITICAL_BLOCK(cs_main)
for (map<uint256, CTransaction>::iterator mi = mapTransactions.begin(); mi != mapTransactions.end(); ++mi)
{
CTransaction& tx = (*mi).second;
- if (tx.IsCoinBase() || !tx.IsFinal())
+ if (tx.IsCoinBase() || tx.IsCoinStake() || !tx.IsFinal())
continue;
COrphan* porphan = NULL;
}
}
}
- pblock->vtx[0].vout[0].nValue = GetBlockValue(pindexPrev->nHeight+1, nFees);
+ 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, GetAdjustedTime());
pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime());
- pblock->nBits = GetNextWorkRequired(pindexPrev);
+ pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
pblock->nNonce = 0;
return pblock.release();
uint256 hash = pblock->GetHash();
uint256 hashTarget = CBigNum().SetCompact(pblock->nBits).getuint256();
- if (hash > hashTarget)
- return false;
+ if (hash > hashTarget && pblock->IsProofOfWork())
+ return error("BitcoinMiner : proof-of-work not meeting target");
//// debug print
printf("BitcoinMiner:\n");
- printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
+ printf("new block found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
pblock->print();
printf("%s ", DateTimeStrFormat("%x %H:%M", GetTime()).c_str());
printf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue).c_str());
unsigned int nTransactionsUpdatedLast = nTransactionsUpdated;
CBlockIndex* pindexPrev = pindexBest;
- auto_ptr<CBlock> pblock(CreateNewBlock(reservekey));
+ auto_ptr<CBlock> pblock(CreateNewBlock(pwallet));
if (!pblock.get())
return;
+
IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
+ // ppcoin: if proof-of-stake block found then process block
+ if (pblock->IsProofOfStake())
+ {
+ // should be able to sign block - assert here for now
+ assert(pblock->SignBlock(*pwalletMain));
+ printf("BitcoinMiner : 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);
+ continue;
+ }
+
printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size());
// Found a solution
pblock->nNonce = ByteReverse(nNonceFound);
assert(hash == pblock->GetHash());
+ // should be able to sign block - assert here for now
+ assert(pblock->SignBlock(*pwalletMain));
SetThreadPriority(THREAD_PRIORITY_NORMAL);
CheckWork(pblock.get(), *pwalletMain, reservekey);
// Update nTime every few seconds
pblock->nTime = max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
pblock->nTime = max(pblock->GetBlockTime(), pblock->GetMaxTransactionTime());
+ pblock->nTime = max(pblock->GetBlockTime(), pindexPrev->GetBlockTime() - nMaxClockDrift);
nBlockTime = ByteReverse(pblock->nTime);
+ if (pblock->GetBlockTime() >= (int64)pblock->vtx[0].nTime + nMaxClockDrift)
+ break; // need to update coinbase timestamp
}
}
}