Some proof-of-stake checkings refactoring
authoralex <alex@alex-VirtualBox.(none)>
Mon, 20 Jan 2014 21:14:40 +0000 (01:14 +0400)
committeralex <alex@alex-VirtualBox.(none)>
Mon, 20 Jan 2014 21:14:40 +0000 (01:14 +0400)
* 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.

src/kernel.cpp
src/kernel.h
src/main.cpp
src/main.h
src/wallet.cpp

index 6ff8e60..5cf4b34 100644 (file)
@@ -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;
 }
index dc2fbb0..246feb2 100644 (file)
@@ -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);
index 0543508..ce8748b 100644 (file)
@@ -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<valtype> 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<valtype> 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;
 }
 
index b5c770a..ace3a8b 100644 (file)
@@ -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;
 };
 
 
index 7ee94f1..b3c1b25 100644 (file)
@@ -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; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown && pindexPrev == pindexBest; n++)
+        for (unsigned int n=0; n<min(nSearchInterval,(int64)nMaxStakeSearchInterval) && !fKernelFound && !fShutdown && pindexPrev == pindexBest && !fFatal; n++)
         {
             // Search backward in time from the given txNew timestamp 
             // Search nSearchInterval seconds back up to nMaxStakeSearchInterval
             uint256 hashProofOfStake = 0, targetProofOfStake = 0;
             COutPoint prevoutStake = COutPoint(pcoin.first->GetHash(), 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"))