Initial implementation of stake modifier calculation.
authorCryptoManiac <balthazar@yandex.ru>
Thu, 3 Sep 2015 22:06:46 +0000 (01:06 +0300)
committerCryptoManiac <balthazar@yandex.ru>
Thu, 3 Sep 2015 22:06:46 +0000 (01:06 +0300)
Note that this functionality doesn't work properly yet.

Novacoin/CBlockStore.cs
Novacoin/NetInfo.cs
Novacoin/StakeModifier.cs

index 94fc2b6..935a4ab 100644 (file)
@@ -1,4 +1,23 @@
-\feffusing System;
+\feff/**
+*  Novacoin classes library
+*  Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
+
+*  This program is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU Affero General Public License as
+*  published by the Free Software Foundation, either version 3 of the
+*  License, or (at your option) any later version.
+
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU Affero General Public License for more details.
+
+*  You should have received a copy of the GNU Affero General Public License
+*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+using System;
 using System.IO;
 using System.Linq;
 using System.Collections.Concurrent;
@@ -76,6 +95,11 @@ namespace Novacoin
         public long nStakeModifier { get; set; }
 
         /// <summary>
+        /// Stake modifier checksum.
+        /// </summary>
+        public uint nStakeModifierChecksum { get; set; }
+
+        /// <summary>
         /// Chain trust score
         /// </summary>
         public byte[] ChainTrust { get; set; }
@@ -560,6 +584,8 @@ namespace Novacoin
         /// </summary>
         private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
 
+        private ConcurrentDictionary<uint256, uint256> mapProofOfStake = new ConcurrentDictionary<uint256, uint256>();
+
         public static CBlockStore Instance;
 
         /// <summary>
@@ -676,10 +702,32 @@ namespace Novacoin
                 return false; // SetStakeEntropyBit() failed
             }
 
-            // TODO: set stake entropy bit, record proof-of-stake hash value
+            // Save proof-of-stake hash value
+            if (itemTemplate.IsProofOfStake)
+            {
+                uint256 hashProofOfStake;
+                if (!CBlockStore.Instance.GetProofOfStakeHash(blockHash, out hashProofOfStake))
+                {
+                    return false;  // hashProofOfStake not found 
+                }
+                itemTemplate.hashProofOfStake = hashProofOfStake;
+            }
 
             // TODO: compute stake modifier
 
+            // ppcoin: compute stake modifier
+            long nStakeModifier = 0;
+            bool fGeneratedStakeModifier = false;
+            if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier))
+            {
+                return false;  // ComputeNextStakeModifier() failed
+            }
+
+            itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
+            itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate);
+
+            // TODO: verify stake modifier checkpoints
+
             // Add to index
             if (block.IsProofOfStake)
             {
@@ -730,6 +778,17 @@ namespace Novacoin
             return blockMap.TryAdd(blockHash, itemTemplate);
         }
 
+        /// <summary>
+        /// Try to find proof-of-stake hash in the map.
+        /// </summary>
+        /// <param name="blockHash">Block hash</param>
+        /// <param name="hashProofOfStake">Proof-of-stake hash</param>
+        /// <returns>Proof-of-Stake hash value</returns>
+        private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake)
+        {
+            return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake);
+        }
+
         public bool AcceptBlock(ref CBlock block)
         {
             uint256 nHash = block.header.Hash;
@@ -786,7 +845,7 @@ namespace Novacoin
             return true;
         }
 
-        public bool GetBlock(uint256 blockHash, ref CBlock block)
+        public bool GetBlock(uint256 blockHash, ref CBlock block, ref long nBlockPos)
         {
             var reader = new BinaryReader(fStreamReadWrite).BaseStream;
 
@@ -794,6 +853,7 @@ namespace Novacoin
 
             if (QueryBlock.Count == 1)
             {
+                nBlockPos = QueryBlock[0].nBlockPos;
                 return QueryBlock[0].ReadFromFile(ref reader, out block);
             }
 
@@ -802,6 +862,21 @@ namespace Novacoin
             return false;
         }
 
+        public bool GetByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos)
+        {
+            var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
+
+            if (QueryTx.Count == 1)
+            {
+                nTxPos = QueryTx[0].nTxPos;
+                return GetBlock(QueryTx[0].BlockHash, ref block, ref nBlockPos);
+            }
+
+            // Tx not found
+
+            return false;
+        }
+
         /// <summary>
         /// Get block cursor from map.
         /// </summary>
@@ -867,6 +942,18 @@ namespace Novacoin
                 }
 
                 // TODO: proof-of-stake validation
+
+                uint256 hashProofOfStake = 0, targetProofOfStake = 0;
+                if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, ref hashProofOfStake, ref targetProofOfStake))
+                {
+                    return false; // do not error here as we expect this during initial block download
+                }
+                if (!mapProofOfStake.ContainsKey(blockHash)) 
+                {
+                    // add to mapProofOfStake
+                    mapProofOfStake.TryAdd(blockHash, hashProofOfStake);
+                }
+
             }
 
             // TODO: difficulty verification
index d8ac0cf..1cf6f54 100644 (file)
@@ -14,6 +14,8 @@ namespace Novacoin
 
         public static uint nChainChecksSwitchTime = 1379635200; // Fri, 20 Sep 2013 00:00:00 GMT
 
+        public static uint256 nHashGenesisBlock = new uint256("00000a060336cbb72fe969666d337b87198b1add2abaa59cca226820b32933a4");
+
         public static readonly uint nLockTimeThreshold = 500000000;
         private static readonly uint nDrift = 7200;
 
index e19a8f0..7f45eb8 100644 (file)
@@ -1,4 +1,23 @@
-\feffusing System;
+\feff/**
+*  Novacoin classes library
+*  Copyright (C) 2015 Alex D. (balthazar.ad@gmail.com)
+
+*  This program is free software: you can redistribute it and/or modify
+*  it under the terms of the GNU Affero General Public License as
+*  published by the Free Software Foundation, either version 3 of the
+*  License, or (at your option) any later version.
+
+*  This program is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU Affero General Public License for more details.
+
+*  You should have received a copy of the GNU Affero General Public License
+*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+using System;
 using System.Collections.Generic;
 using System.Diagnostics.Contracts;
 using System.IO;
@@ -6,6 +25,9 @@ using System.Linq;
 
 namespace Novacoin
 {
+    /// <summary>
+    /// Stake modifier calculation. Doesn't work properly, for now.
+    /// </summary>
     public class StakeModifier
     {
         /// <summary>
@@ -95,7 +117,7 @@ namespace Novacoin
         /// Get stake modifier selection interval (in seconds)
         /// </summary>
         /// <returns></returns>
-        static long GetStakeModifierSelectionInterval()
+        internal static long GetStakeModifierSelectionInterval()
         {
             long nSelectionInterval = 0;
             for (int nSection = 0; nSection < 64; nSection++)
@@ -115,7 +137,7 @@ namespace Novacoin
         /// <param name="nStakeModifierPrev">Previous value of stake modifier.</param>
         /// <param name="selectedCursor">Selection result.</param>
         /// <returns></returns>
-        static bool SelectBlockFromCandidates(List<Tuple<uint, uint256>> sortedByTimestamp, Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor)
+        internal static bool SelectBlockFromCandidates(List<Tuple<uint, uint256>> sortedByTimestamp, Dictionary<uint256, CBlockStoreItem> mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor)
         {
             bool fSelected = false;
             uint256 hashBest = 0;
@@ -191,7 +213,7 @@ namespace Novacoin
         /// additional bits in the stake modifier, even after generating a chain of
         /// blocks.
         /// </summary>
-        bool ComputeNextStakeModifier(CBlockStoreItem cursorCurrent, ref long nStakeModifier, ref bool fGeneratedStakeModifier)
+        public static bool ComputeNextStakeModifier(CBlockStoreItem cursorCurrent, ref long nStakeModifier, ref bool fGeneratedStakeModifier)
         {
             nStakeModifier = 0;
             fGeneratedStakeModifier = false;
@@ -308,7 +330,7 @@ namespace Novacoin
             return true;
         }
 
-        bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier)
+        public static bool GetKernelStakeModifier(uint256 hashBlockFrom, ref long nStakeModifier)
         {
             uint nStakeModifierHeight = 0;
             uint nStakeModifierTime = 0;
@@ -316,14 +338,13 @@ namespace Novacoin
             return GetKernelStakeModifier(hashBlockFrom, ref nStakeModifier, ref nStakeModifierHeight, ref nStakeModifierTime);
         }
 
-        bool CheckStakeKernelHash(uint nBits, CBlock blockFrom, uint nTxPrevOffset, CTransaction txPrev, COutPoint prevout, uint nTimeTx, ref uint256 hashProofOfStake, ref uint256 targetProofOfStake)
+        public static bool CheckStakeKernelHash(uint nBits, uint256 hashBlockFrom, uint nTimeBlockFrom, uint nTxPrevOffset, CTransaction txPrev, COutPoint prevout, uint nTimeTx, ref uint256 hashProofOfStake, ref uint256 targetProofOfStake)
         {
             if (nTimeTx < txPrev.nTime)
             {
                 return false; // Transaction timestamp violation
             }
 
-            uint nTimeBlockFrom = blockFrom.header.nTime;
             if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
             {
                 return false; // Min age violation
@@ -333,7 +354,6 @@ namespace Novacoin
             nTargetPerCoinDay.Compact = nBits;
 
             ulong nValueIn = txPrev.vout[prevout.n].nValue;
-            uint256 hashBlockFrom = blockFrom.header.Hash;
             uint256 nCoinDayWeight = new uint256(nValueIn) * GetWeight(txPrev.nTime, nTimeTx) / CTransaction.nCoin / (24 * 60 * 60);
 
             targetProofOfStake = nCoinDayWeight * nTargetPerCoinDay;
@@ -378,5 +398,67 @@ namespace Novacoin
 
             return Math.Min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, nStakeMaxAge);
         }
+
+        internal static uint GetStakeModifierChecksum(CBlockStoreItem itemTemplate)
+        {
+            Contract.Assert(itemTemplate.prev != null || (uint256)itemTemplate.Hash == NetUtils.nHashGenesisBlock);
+
+            // Hash previous checksum with flags, hashProofOfStake and nStakeModifier
+            MemoryStream ss = new MemoryStream();
+            BinaryWriter writer = new BinaryWriter(ss);
+
+            if (itemTemplate.prev != null)
+            {
+                writer.Write(itemTemplate.prev.nStakeModifierChecksum);
+            }
+
+            writer.Write((uint)itemTemplate.BlockTypeFlag);
+
+            if (itemTemplate.IsProofOfStake)
+            {
+                writer.Write(itemTemplate.hashProofOfStake);
+            }
+            else
+            {
+                writer.Write(new uint256(0));
+            }
+            writer.Write(itemTemplate.nStakeModifier);
+
+            uint256 hashChecksum = CryptoUtils.ComputeHash256(ss.ToArray());
+            writer.Close();
+
+            hashChecksum >>= (256 - 32);
+
+            return (uint)hashChecksum.Low64;
+        }
+
+        internal static bool CheckProofOfStake(CTransaction tx, uint nBits, ref uint256 hashProofOfStake, ref uint256 targetProofOfStake)
+        {
+            if (!tx.IsCoinStake)
+            {
+                return false; // called on non-coinstake
+            }
+
+            // Kernel (input 0) must match the stake hash target per coin age (nBits)
+            CTxIn txin = tx.vin[0];
+
+            // Read block header
+            
+            CBlock block = null;
+            CTransaction txPrev = null;
+            long nBlockPos = 0, nTxPos = 0;
+            
+            if (!CBlockStore.Instance.GetByTransactionID(txin.prevout.hash, ref block, ref txPrev, ref nBlockPos, ref nTxPos))
+            {
+                return false; // unable to read block of previous transaction
+            }
+
+            if (!CheckStakeKernelHash(nBits, block.header.Hash, block.header.nTime, (uint)(nTxPos - nBlockPos), txPrev, txin.prevout, tx.nTime, ref hashProofOfStake, ref targetProofOfStake))
+            {
+                return false; // check kernel failed on coinstake 
+            }
+
+            return true;
+        }
     }
 }