From: alex Date: Mon, 20 Jan 2014 21:14:40 +0000 (+0400) Subject: Some proof-of-stake checkings refactoring X-Git-Tag: v0.4.4.7-nvc-next-testing~15 X-Git-Url: https://git.novaco.in/?p=novacoin.git;a=commitdiff_plain;h=ae33b3e73443f841738bdfb4a8057dc519951bc3 Some proof-of-stake checkings refactoring * Implement CBlock::CheckSignature() method for signatures and proofhash checking; * Move PoW header signature checking to new CBlock::CheckLegacySignature() method; * Remove some redundant messages from debug.log output; * Make some DoS checkings harder. Another changes: * getblock and getblockbynumber RPC calls are now providing generator public key for proof-of-stake blocks. --- diff --git a/src/kernel.cpp b/src/kernel.cpp index 6ff8e60..5cf4b34 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -270,14 +270,20 @@ static bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64& nStakeModifier // quantities so as to generate blocks faster, degrading the system back into // a proof-of-work situation. // -bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake) +bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fPrintProofOfStake) { if (nTimeTx < txPrev.nTime) // Transaction timestamp violation + { + fFatal = true; return error("CheckStakeKernelHash() : nTime violation"); + } unsigned int nTimeBlockFrom = blockFrom.GetBlockTime(); if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement + { + fFatal = true; return error("CheckStakeKernelHash() : min age violation"); + } CBigNum bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(nBits); @@ -316,7 +322,11 @@ bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned // Now check if proof-of-stake hash meets target protocol if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay) - return false; + { + fFatal = true; + return error("CheckStakeKernelHash() : proof-of-stake for block %s not meeting target", hashBlockFrom.ToString().c_str()); + } + if (fDebug && !fPrintProofOfStake) { printf("CheckStakeKernelHash() : using modifier 0x%016"PRI64x" at height=%d timestamp=%s for block from height=%d timestamp=%s\n", @@ -333,10 +343,13 @@ bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned } // Check kernel hash target and coinstake signature -bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake) +bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal) { if (!tx.IsCoinStake()) + { + fFatal = true; return error("CheckProofOfStake() : called on non-coinstake %s", tx.GetHash().ToString().c_str()); + } // Kernel (input 0) must match the stake hash target per coin age (nBits) const CTxIn& txin = tx.vin[0]; @@ -348,7 +361,7 @@ bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hash CCoinsViewCache &view = *pcoinsTip; if (!view.GetCoins(txin.prevout.hash, coins)) - return tx.DoS(1, error("CheckProofOfStake() : INFO: read coins for txPrev failed")); // previous transaction not in main chain, may occur during initial download + return fDebug? error("CheckProofOfStake() : INFO: read coins for txPrev failed") : false; // previous transaction not in main chain, may occur during initial download CBlockIndex* pindex = FindBlockByHeight(coins.nHeight); @@ -369,10 +382,18 @@ bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hash // Verify signature if (!VerifySignature(coins, tx, 0, true, false, 0)) - return tx.DoS(100, error("CheckProofOfStake() : VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str())); + { + fFatal = true; + return error("CheckProofOfStake() : VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str()); + } - if (!CheckStakeKernelHash(nBits, block, nTxPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, targetProofOfStake, fDebug)) - return tx.DoS(1, error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str())); // may occur during initial download or if behind on block chain sync + if (!CheckStakeKernelHash(nBits, block, nTxPos, txPrev, txin.prevout, tx.nTime, hashProofOfStake, targetProofOfStake, fFatal, fDebug)) + { + if (fFatal) + return false; + // may occur during initial download or if behind on block chain sync + return fDebug? error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s", tx.GetHash().ToString().c_str(), hashProofOfStake.ToString().c_str()) : false; + } return true; } diff --git a/src/kernel.h b/src/kernel.h index dc2fbb0..246feb2 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -17,12 +17,12 @@ static const int MODIFIER_INTERVAL_RATIO = 3; bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64& nStakeModifier, bool& fGeneratedStakeModifier); // Check whether stake kernel meets hash target -// Sets hashProofOfStake on success return -bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool fPrintProofOfStake=false); +// Sets hashProofOfStake and targetProofOfStake on success return +bool CheckStakeKernelHash(unsigned int nBits, const CBlock& blockFrom, unsigned int nTxPrevOffset, const CTransaction& txPrev, const COutPoint& prevout, unsigned int nTimeTx, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal, bool fPrintProofOfStake=false); // Check kernel hash target and coinstake signature -// Sets hashProofOfStake on success return -bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake); +// Sets hashProofOfStake and targetProofOfStake on success return +bool CheckProofOfStake(const CTransaction& tx, unsigned int nBits, uint256& hashProofOfStake, uint256& targetProofOfStake, bool& fFatal); // Check whether the coinstake timestamp meets protocol bool CheckCoinStakeTimestamp(int64 nTimeBlock, int64 nTimeTx); diff --git a/src/main.cpp b/src/main.cpp index 0543508..ce8748b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2165,10 +2165,6 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) c // Check coinstake timestamp if (!CheckCoinStakeTimestamp(GetBlockTime(), (int64)vtx[1].nTime)) return DoS(50, error("CheckBlock() : coinstake timestamp violation nTimeBlock=%"PRI64d" nTimeTx=%u", GetBlockTime(), vtx[1].nTime)); - - // NovaCoin: check proof-of-stake block signature - if (fCheckSig && !CheckBlockSignature(true)) - return DoS(100, error("CheckBlock() : bad proof-of-stake block signature")); } else { @@ -2189,8 +2185,8 @@ bool CBlock::CheckBlock(bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) c { bool checkEntropySig = (GetBlockTime() < ENTROPY_SWITCH_TIME); - // NovaCoin: check proof-of-work block signature - if (checkEntropySig && !CheckBlockSignature(false)) + // check legacy proof-of-work block signature + if (checkEntropySig && !CheckLegacySignature()) return DoS(100, error("CheckBlock() : bad proof-of-work block signature")); } } @@ -2249,7 +2245,7 @@ bool CBlock::AcceptBlock() // Check proof-of-work or proof-of-stake if (nBits != GetNextTargetRequired(pindexPrev, IsProofOfStake())) - return DoS(100, error("AcceptBlock() : incorrect %s", IsProofOfWork() ? "proof-of-work" : "proof-of-stake")); + return DoS(100, error("AcceptBlock() : incorrect proof-of-%s amount", IsProofOfWork() ? "work" : "stake")); // Check timestamp against prev if (GetBlockTime() <= pindexPrev->GetMedianTimePast() || FutureDrift(GetBlockTime()) < pindexPrev->GetBlockTime()) @@ -2415,25 +2411,44 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) 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()) { - uint256 hashProofOfStake = 0, targetProofOfStake = 0; - if (!CheckProofOfStake(pblock->vtx[1], pblock->nBits, hashProofOfStake, targetProofOfStake)) + // Limited duplicity on stake: prevents block flood attack + // Duplicate stake allowed only when there is orphan child block + if (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()); + + bool fFatal = false; + uint256 hashProofOfStake; + + // Verify proof-of-stake hash target and signatures + if (!pblock->CheckSignature(fFatal, hashProofOfStake)) { - 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 + if (fFatal) + { + // Invalid blockhash/coinstake signature or no generator defined, nothing to do here + // This also may occur when supplied proof-of-stake doesn't satisfy required target + if (pfrom) + pfrom->Misbehaving(100); + return error("ProcessBlock() : invalid signature in proof-of-stake block %s", hash.ToString().c_str()); + } + else + { + // Blockhash and coinstake signatures are OK but target checkings failed + // This may occur during initial block download + + if (pfrom) + pfrom->Misbehaving(1); // Small DoS penalty + + printf("WARNING: ProcessBlock(): proof-of-stake target checkings failed for block %s, we'll try again later\n", hash.ToString().c_str()); + return false; + } } + if (!mapProofOfStake.count(hash)) // add to mapProofOfStake mapProofOfStake.insert(make_pair(hash, hashProofOfStake)); } @@ -2456,11 +2471,11 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) { if (pfrom) pfrom->Misbehaving(100); - return error("ProcessBlock() : block with too little %s", pblock->IsProofOfStake()? "proof-of-stake" : "proof-of-work"); + return error("ProcessBlock() : block with too little proof-of-%s", pblock->IsProofOfStake() ? "stake" : "work"); } } - // ppcoin: ask for pending sync-checkpoint if any + // Ask for pending sync-checkpoint if any if (!IsInitialBlockDownload()) Checkpoints::AskForPendingSyncCheckpoint(pfrom); @@ -2469,7 +2484,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) { 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 @@ -2479,6 +2494,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) else setStakeSeenOrphan.insert(pblock2->GetProofOfStake()); } + mapOrphanBlocks.insert(make_pair(hash, pblock2)); mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); @@ -2527,7 +2543,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) return true; } -// novacoin: attempt to generate suitable proof-of-stake +// attempt to generate suitable proof-of-stake bool CBlock::SignBlock(CWallet& wallet) { // if we are trying to sign @@ -2577,57 +2593,78 @@ bool CBlock::SignBlock(CWallet& wallet) return false; } -// ppcoin: check block signature -bool CBlock::CheckBlockSignature(bool fProofOfStake) const +// get generation key +bool CBlock::GetGenerator(CKey& GeneratorKey) const { - if (GetHash() == (!fTestNet ? hashGenesisBlock : hashGenesisBlockTestNet)) - return vchBlockSig.empty(); + if(!IsProofOfStake()) + return false; vector vSolutions; txnouttype whichType; - if(fProofOfStake) + const CTxOut& txout = vtx[1].vout[1]; + + if (!Solver(txout.scriptPubKey, whichType, vSolutions)) + return false; + if (whichType == TX_PUBKEY) { - const CTxOut& txout = vtx[1].vout[1]; + valtype& vchPubKey = vSolutions[0]; + CKey key; + return GeneratorKey.SetPubKey(vchPubKey); + } + + return false; +} + +// verify proof-of-stake signatures +bool CBlock::CheckSignature(bool& fFatal, uint256& hashProofOfStake) const +{ + CKey key; + + // no generator or invalid hash signature means fatal error + fFatal = !GetGenerator(key) || !key.Verify(GetHash(), vchBlockSig); + + if (fFatal) + return false; + + uint256 hashTarget = 0; + if (!CheckProofOfStake(vtx[1], nBits, hashProofOfStake, hashTarget, fFatal)) + return false; // hash target mismatch or invalid coinstake signature + + return true; +} + +// verify legacy proof-of-work signature +bool CBlock::CheckLegacySignature() const +{ + if (IsProofOfStake()) + return false; + + vector vSolutions; + txnouttype whichType; + + for(unsigned int i = 0; i < vtx[0].vout.size(); i++) + { + const CTxOut& txout = vtx[0].vout[i]; if (!Solver(txout.scriptPubKey, whichType, vSolutions)) return false; + if (whichType == TX_PUBKEY) { + // Verify valtype& vchPubKey = vSolutions[0]; CKey key; if (!key.SetPubKey(vchPubKey)) - return false; + continue; if (vchBlockSig.empty()) - return false; - return key.Verify(GetHash(), vchBlockSig); + continue; + if(!key.Verify(GetHash(), vchBlockSig)) + continue; + return true; } } - else - { - for(unsigned int i = 0; i < vtx[0].vout.size(); i++) - { - const CTxOut& txout = vtx[0].vout[i]; - - if (!Solver(txout.scriptPubKey, whichType, vSolutions)) - return false; - - if (whichType == TX_PUBKEY) - { - // Verify - valtype& vchPubKey = vSolutions[0]; - CKey key; - if (!key.SetPubKey(vchPubKey)) - continue; - if (vchBlockSig.empty()) - continue; - if(!key.Verify(GetHash(), vchBlockSig)) - continue; - return true; - } - } - } return false; } diff --git a/src/main.h b/src/main.h index b5c770a..ace3a8b 100644 --- a/src/main.h +++ b/src/main.h @@ -1396,8 +1396,14 @@ public: // Generate proof-of-stake block signature bool SignBlock(CWallet& keystore); - // Validate header signature - bool CheckBlockSignature(bool fProofOfStake) const; + // Get generator key + bool GetGenerator(CKey& GeneratorKey) const; + + // Validate proof-of-stake block signature + bool CheckSignature(bool& fFatal, uint256& hashProofOfStake) const; + + // Legacy proof-of-work signature + bool CheckLegacySignature() const; }; diff --git a/src/wallet.cpp b/src/wallet.cpp index 7ee94f1..b3c1b25 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -15,7 +15,6 @@ using namespace std; extern int nStakeMaxAge; -extern unsigned int nTransactionsUpdated; ////////////////////////////////////////////////////////////////////////////// // @@ -1557,14 +1556,15 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int } + bool fFatal = false; bool fKernelFound = false; - for (unsigned int n=0; nGetHash(), pcoin.second); - if (CheckStakeKernelHash(nBits, block, nTxPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake)) + if (CheckStakeKernelHash(nBits, block, nTxPos, *pcoin.first, prevoutStake, txNew.nTime - n, hashProofOfStake, targetProofOfStake, fFatal)) { // Found a kernel if (fDebug && GetBoolArg("-printcoinstake"))