Initial implementation of stake modifier calculation.
[NovacoinLibrary.git] / Novacoin / CBlockStore.cs
index 279edc7..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;
@@ -16,7 +35,7 @@ namespace Novacoin
     /// Block headers table
     /// </summary>
     [Table("BlockStorage")]
-    class CBlockStoreItem
+    public class CBlockStoreItem
     {
         /// <summary>
         /// Item ID in the database
@@ -61,6 +80,11 @@ namespace Novacoin
         public uint nNonce { get; set; }
 
         /// <summary>
+        /// Next block hash.
+        /// </summary>
+        public byte[] nextHash { get; set; }
+
+        /// <summary>
         /// Block type flags
         /// </summary>
         public BlockType BlockTypeFlag { get; set; }
@@ -71,9 +95,19 @@ namespace Novacoin
         public long nStakeModifier { get; set; }
 
         /// <summary>
-        /// Stake entropy bit
+        /// Stake modifier checksum.
+        /// </summary>
+        public uint nStakeModifierChecksum { get; set; }
+
+        /// <summary>
+        /// Chain trust score
         /// </summary>
-        public byte nEntropyBit { get; set; }
+        public byte[] ChainTrust { get; set; }
+
+        /// <summary>
+        /// Proof-of-Stake hash
+        /// </summary>
+        public byte[] hashProofOfStake { get; set; }
 
         /// <summary>
         /// Block height
@@ -207,6 +241,222 @@ namespace Novacoin
                 return false;
             }
         }
+
+        /// <summary>
+        /// Previous block cursor
+        /// </summary>
+        [Ignore]
+        public CBlockStoreItem prev {
+            get { return CBlockStore.Instance.GetCursor(prevHash); }
+        }
+
+        /// <summary>
+        /// Next block cursor
+        /// </summary>
+        [Ignore]
+        public CBlockStoreItem next
+        {
+            get { return CBlockStore.Instance.GetCursor(nextHash); }
+        }
+
+        [Ignore]
+        bool IsInMainChain
+        {
+            get { return (next != null); }
+        }
+
+        /// <summary>
+        /// STake modifier generation flag
+        /// </summary>
+        [Ignore]
+        public bool GeneratedStakeModifier
+        {
+            get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
+        }
+
+        /// <summary>
+        /// Stake entropy bit
+        /// </summary>
+        [Ignore]
+        public uint StakeEntropyBit
+        {
+            get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
+        }
+
+        /// <summary>
+        /// Sets stake modifier and flag.
+        /// </summary>
+        /// <param name="nModifier">New stake modifier.</param>
+        /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
+        public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
+        {
+            nStakeModifier = nModifier;
+            if (fGeneratedStakeModifier)
+                BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
+        }
+
+        /// <summary>
+        /// Set entropy bit.
+        /// </summary>
+        /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
+        /// <returns>False if value is our of range.</returns>
+        public bool SetStakeEntropyBit(byte nEntropyBit)
+        {
+            if (nEntropyBit > 1)
+                return false;
+            BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
+            return true;
+        }
+
+        /// <summary>
+        /// Set proof-of-stake flag.
+        /// </summary>
+        public void SetProofOfStake()
+        {
+            BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
+        }
+
+        /// <summary>
+        /// Block has no proof-of-stake flag.
+        /// </summary>
+        [Ignore]
+        public bool IsProofOfWork
+        {
+            get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
+        }
+
+        /// <summary>
+        /// Block has proof-of-stake flag set.
+        /// </summary>
+        [Ignore]
+        public bool IsProofOfStake 
+        {
+            get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
+        }
+
+        /// <summary>
+        /// Chain trust score.
+        /// </summary>
+        [Ignore]
+        public uint256 nChainTrust {
+            get
+            {
+                if (ChainTrust.Length != 32)
+                {
+                    byte[] tmp = ChainTrust;
+                    Array.Resize(ref tmp, 32);
+                    ChainTrust = tmp;
+                }
+
+                return ChainTrust;
+            }
+            set { ChainTrust = Interop.TrimArray(value); }
+        }
+
+        /// <summary>
+        /// Block trust score.
+        /// </summary>
+        [Ignore]
+        public uint256 nBlockTrust
+        {
+            get
+            {
+                uint256 nTarget = 0;
+                nTarget.Compact = nBits;
+
+                /* Old protocol */
+                if (nTime < NetUtils.nChainChecksSwitchTime)
+                {
+                    return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1;
+                }
+
+                /* New protocol */
+
+                // Calculate work amount for block
+                var nPoWTrust = NetUtils.nPoWBase / (nTarget + 1);
+
+                // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low
+                nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust;
+
+                // Return nPoWTrust for the first 12 blocks
+                if (prev == null || prev.nHeight < 12)
+                    return nPoWTrust;
+
+                CBlockStoreItem currentIndex = prev;
+
+                if (IsProofOfStake)
+                {
+                    var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
+
+                    // Return 1/3 of score if parent block is not the PoW block
+                    if (!prev.IsProofOfWork)
+                    {
+                        return nNewTrust / 3;
+                    }
+
+                    int nPoWCount = 0;
+
+                    // Check last 12 blocks type
+                    while (prev.nHeight - currentIndex.nHeight < 12)
+                    {
+                        if (currentIndex.IsProofOfWork)
+                        {
+                            nPoWCount++;
+                        }
+                        currentIndex = currentIndex.prev;
+                    }
+
+                    // Return 1/3 of score if less than 3 PoW blocks found
+                    if (nPoWCount < 3)
+                    {
+                        return nNewTrust / 3;
+                    }
+
+                    return nNewTrust;
+                }
+                else
+                {
+                    var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust;
+
+                    // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks
+                    if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake)
+                    {
+                        return nPoWTrust + (2 * nLastBlockTrust / 3);
+                    }
+
+                    int nPoSCount = 0;
+
+                    // Check last 12 blocks type
+                    while (prev.nHeight - currentIndex.nHeight < 12)
+                    {
+                        if (currentIndex.IsProofOfStake)
+                        {
+                            nPoSCount++;
+                        }
+                        currentIndex = currentIndex.prev;
+                    }
+
+                    // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found
+                    if (nPoSCount < 7)
+                    {
+                        return nPoWTrust + (2 * nLastBlockTrust / 3);
+                    }
+
+                    nTarget.Compact = prev.nBits;
+
+                    if (!nTarget)
+                    {
+                        return 0;
+                    }
+
+                    var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
+
+                    // Return nPoWTrust + full trust score for previous block nBits
+                    return nPoWTrust + nNewTrust;
+                }
+            }
+        }
+
     }
 
     /// <summary>
@@ -214,10 +464,9 @@ namespace Novacoin
     /// </summary>
     public enum BlockType
     {
-        PROOF_OF_WORK,
-        PROOF_OF_WORK_MODIFIER,
-        PROOF_OF_STAKE,
-        PROOF_OF_STAKE_MODIFIER
+        BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
+        BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
+        BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
     };
 
     /// <summary>
@@ -335,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>
@@ -357,6 +608,8 @@ namespace Novacoin
 
             fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
 
+            Instance = this;
+
             if (firstInit)
             {
                 lock (LockObj)
@@ -413,8 +666,6 @@ namespace Novacoin
                     blockMap.TryAdd(item.Hash, item);
                 }
             }
-
-            Instance = this;
         }
 
         public bool GetTransaction(uint256 TxID, ref CTransaction tx)
@@ -443,12 +694,45 @@ namespace Novacoin
                 return false;
             }
 
-            // TODO: compute chain trust, set stake entropy bit, record proof-of-stake hash value
+            // Compute chain trust score
+            itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
+
+            if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.nHeight, blockHash)))
+            {
+                return false; // SetStakeEntropyBit() failed
+            }
+
+            // 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
-            itemTemplate.BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK;
+            if (block.IsProofOfStake)
+            {
+                itemTemplate.SetProofOfStake();
+            }
 
             if (!itemTemplate.WriteToFile(ref writer, ref block))
             {
@@ -494,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;
@@ -538,7 +833,6 @@ namespace Novacoin
             var itemTemplate = new CBlockStoreItem()
             {
                 nHeight = nHeight,
-                nEntropyBit = Entropy.GetStakeEntropyBit(nHeight, nHash)
             };
 
             itemTemplate.FillHeader(block.header);
@@ -551,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;
 
@@ -559,6 +853,7 @@ namespace Novacoin
 
             if (QueryBlock.Count == 1)
             {
+                nBlockPos = QueryBlock[0].nBlockPos;
                 return QueryBlock[0].ReadFromFile(ref reader, out block);
             }
 
@@ -567,6 +862,53 @@ 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>
+        /// <param name="blockHash">block hash</param>
+        /// <returns>Cursor or null</returns>
+        public CBlockStoreItem GetCursor(uint256 blockHash)
+        {
+            if (blockHash == 0)
+            {
+                // Genesis block has zero prevHash and no parent.
+                return null;
+            }
+
+            // First, check our block map.
+            CBlockStoreItem item = null;
+            if (blockMap.TryGetValue(blockHash, out item))
+            {
+                return item;
+            }
+
+            // Trying to get cursor from the database.
+            var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
+
+            if (QueryBlockCursor.Count == 1)
+            {
+                return QueryBlockCursor[0];
+            }
+
+            // Nothing found.
+            return null;
+        }
+
         public bool ProcessBlock(ref CBlock block)
         {
             var blockHash = block.header.Hash;
@@ -600,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