Checkpoints valudation, PoW reward calculation and continue working on block index.
[NovacoinLibrary.git] / Novacoin / CKeyStore.cs
index e9e3603..8eb9e9d 100644 (file)
@@ -21,6 +21,7 @@ using SQLite.Net;
 using SQLite.Net.Attributes;
 using SQLite.Net.Interop;
 using SQLite.Net.Platform.Generic;
+using System;
 using System.IO;
 using System.Linq;
 
@@ -68,7 +69,7 @@ namespace Novacoin
         /// Item creation time
         /// </summary>
         [Indexed]
-        public int nTime { get; set; }
+        public uint nTime { get; set; }
     }
 
     /// <summary>
@@ -96,61 +97,107 @@ namespace Novacoin
 
     /// <summary>
     /// select count(...) as Count from ... where ...
+    /// select number from ... where ...
     /// </summary>
-    class CountQuery
+    class NumQuery
     {
-        public int Count { get; set; }
+        public int Num { get; set; }
     }
 
-    public class CKeyStore
+    /// <summary>
+    /// Key storage
+    /// </summary>
+    public class CKeyStore : IDisposable
     {
+        private bool disposed = false;
         private object LockObj = new object();
         private SQLiteConnection dbConn = null;
+        private int nKeyPoolSize = 100;
 
-        public CKeyStore(string strDatabasePath="KeyStore.db")
+        /// <summary>
+        /// Initialize new instance of key store.
+        /// </summary>
+        /// <param name="strDatabasePath">Path to database file.</param>
+        /// <param name="KeyPoolSize">Number of reserved keys.</param>
+        public CKeyStore(string strDatabasePath="KeyStore.db", int KeyPoolSize = 100)
         {
-            bool firstInit = File.Exists(strDatabasePath);
+            bool firstInit = !File.Exists(strDatabasePath);
 
             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDatabasePath);
+            nKeyPoolSize = KeyPoolSize;
 
-            if (!firstInit)
+            if (firstInit)
             {
                 lock(LockObj)
                 {
                     dbConn.CreateTable<KeyStorageItem>(CreateFlags.AutoIncPK);
                     dbConn.CreateTable<ScriptStorageItem>(CreateFlags.AutoIncPK);
 
-                    dbConn.BeginTransaction();
-
-                    // Generate keys
-                    for (int i = 0; i < 1000; i++)
-                    {
-                        var keyPair = new CKeyPair();
-
-                        var res = dbConn.Insert(new KeyStorageItem()
-                        {
-                            KeyID = keyPair.KeyID.hashBytes,
-                            PublicKey = keyPair.PublicBytes,
-                            PrivateKey = keyPair.SecretBytes,
-                            IsCompressed = keyPair.IsCompressed,
-                            IsUsed = false,
-                            nTime = Interop.GetTime()
-                        });
-
-                        // TODO: Additional initialization
-                    }
-
-                    dbConn.Commit();
+                    GenerateKeys(nKeyPoolSize);
                 }
             }
         }
 
         ~CKeyStore()
         {
-            if (dbConn != null)
+            Dispose(false);
+        }
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!disposed)
+            {
+                if (disposing)
+                {
+                    // Free other state (managed objects).
+                }
+
+                if (dbConn != null)
+                {
+                    dbConn.Close();
+                    dbConn = null;
+                }
+
+                disposed = true;
+            }
+        }
+
+
+        /// <summary>
+        /// Generate keys and insert them to key store.
+        /// </summary>
+        /// <param name="n"></param>
+        private void GenerateKeys(int n)
+        {
+            lock (LockObj)
             {
-                dbConn.Close();
-                dbConn = null;
+                dbConn.BeginTransaction();
+
+                // Generate keys
+                for (int i = 0; i < n; i++)
+                {
+                    var keyPair = new CKeyPair();
+
+                    var res = dbConn.Insert(new KeyStorageItem()
+                    {
+                        KeyID = keyPair.KeyID,
+                        PublicKey = keyPair.PubKey,
+                        PrivateKey = keyPair,
+                        IsCompressed = keyPair.IsCompressed,
+                        IsUsed = false,
+                        nTime = Interop.GetTime()
+                    });
+
+                    // TODO: Additional initialization
+                }
+
+                dbConn.Commit();
             }
         }
 
@@ -165,9 +212,9 @@ namespace Novacoin
             {
                 var res = dbConn.Insert(new KeyStorageItem()
                 {
-                    KeyID = keyPair.KeyID.hashBytes,
-                    PublicKey = keyPair.PublicBytes,
-                    PrivateKey = keyPair.SecretBytes,
+                    KeyID = keyPair.KeyID,
+                    PublicKey = keyPair.PubKey,
+                    PrivateKey = keyPair,
                     IsCompressed = keyPair.IsCompressed,
                     IsUsed = true,
                     nTime = Interop.GetTime()
@@ -182,11 +229,16 @@ namespace Novacoin
             return true;
         }
 
+        /// <summary>
+        /// Check existance of item with provided KeyID
+        /// </summary>
+        /// <param name="scriptID">Hash160 of public key.</param>
+        /// <returns>Checking result</returns>
         public bool HaveKey(CKeyID keyID)
         {
-            var QueryCount = dbConn.Query<CountQuery>("select count([ItemID]) as [Count] from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
+            var QueryCount = dbConn.Query<NumQuery>("select count([ItemID]) from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
 
-            return QueryCount.First().Count == 1;
+            return QueryCount.First().Num == 1;
         }
 
         /// <summary>
@@ -197,7 +249,7 @@ namespace Novacoin
         /// <returns>Result</returns>
         public bool GetKey(CKeyID keyID, out CKeyPair keyPair)
         {
-            var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", keyID.hashBytes);
+            var QueryGet = dbConn.Query<KeyStorageItem>("select * from [KeyStorage] where [KeyID] = ?", (byte[])keyID);
 
             if (QueryGet.Count() == 1)
             {
@@ -209,14 +261,19 @@ namespace Novacoin
             return false;
         }
 
+        /// <summary>
+        /// Add redeem script to script store.
+        /// </summary>
+        /// <param name="script">CScript instance</param>
+        /// <returns>Result</returns>
         public bool AddScript(CScript script)
         {
             lock (LockObj)
             {
                 var res = dbConn.Insert(new ScriptStorageItem()
                 {
-                    ScriptID = script.ScriptID.hashBytes,
-                    ScriptCode = script.Bytes
+                    ScriptID = script.ScriptID,
+                    ScriptCode = script
                 });
 
                 if (res == 0)
@@ -228,16 +285,27 @@ namespace Novacoin
             return true;
         }
 
+        /// <summary>
+        /// Check existance of item with provided ScriptID
+        /// </summary>
+        /// <param name="scriptID">Hash160 of script code.</param>
+        /// <returns>Checking result</returns>
         public bool HaveScript(CScriptID scriptID)
         {
-            var QueryGet = dbConn.Query<CountQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
+            var QueryGet = dbConn.Query<NumQuery>("select count([ItemID]) from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
 
-            return QueryGet.First().Count == 1;
+            return QueryGet.First().Num == 1;
         }
 
+        /// <summary>
+        /// Get redeem script from database.
+        /// </summary>
+        /// <param name="scriptID">Script ID, evaluated as Hash160(script code).</param>
+        /// <param name="script">Instance of CScript</param>
+        /// <returns>Result</returns>
         public bool GetScript(CScriptID scriptID, out CScript script)
         {
-            var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", scriptID.hashBytes);
+            var QueryGet = dbConn.Query<ScriptStorageItem>("select * from [ScriptStorage] where [ScriptID] = ?", (byte[])scriptID);
 
             if (QueryGet.Count() == 1)
             {
@@ -249,6 +317,90 @@ namespace Novacoin
             return false;
         }
 
+  
+        /// <summary>
+        /// SQLite return type for ReserveKey
+        /// </summary>                      
+        class ReservedKey
+        {
+            public int ItemId { get; set; }
+            public byte[] KeyID { get; set; }
+        }
+
+        /// <summary>
+        /// Reserve key from a list of unused keys.
+        /// </summary>
+        /// <param name="nKeyIndex">Internal index of key</param>
+        /// <returns>CKeyID instance</returns>
+        public CKeyID SelectKey(out int nKeyIndex)
+        {
+            var QueryGet = dbConn.Query<ReservedKey>("select ItemId, KeyID from [KeyStorage] where not [IsUsed] order by [nTime] asc limit 1");
+
+            if (QueryGet.Count() == 1)
+            {
+                var res = QueryGet.First();
+
+                nKeyIndex = res.ItemId;
+
+                return new CKeyID(res.KeyID);
+            }
+            else
+            {
+                // Generate new keys in case if keypool is exhausted.
+
+                GenerateKeys(nKeyPoolSize);
+
+                return SelectKey(out nKeyIndex);
+            }
+        }
+
+        /// <summary>
+        /// Mark key as used.
+        /// </summary>
+        /// <param name="nIndex">Internal index.</param>
+        public void MarkUsed(int nIndex)
+        {
+            lock (LockObj)
+            {
+                dbConn.Execute("update [KeyStorage] set [IsUsed] = true where [ItemId] = ?", nIndex);
+            }
+        }
+
+        /// <summary>
+        /// Mark key as unused.
+        /// </summary>
+        /// <param name="nIndex">Internal index.</param>
+        public void MarkUnused(int nIndex)
+        {
+            lock (LockObj)
+            {
+                dbConn.Execute("update [KeyStorage] set [IsUsed] = false where [ItemId] = ?", nIndex);
+            }
+        }
 
+        /// <summary>
+        /// Regenerate all unused keys. 
+        /// </summary>
+        public void ResetPool()
+        {
+            lock (LockObj)
+            {
+                dbConn.Execute("delete from [KeyStorage] where not [IsUsed]");
+                GenerateKeys(nKeyPoolSize);
+            }
+        }
+
+        /// <summary>
+        /// Timestamp of oldest item in keystore.
+        /// </summary>
+        public int OldestTime
+        {
+            get
+            {
+                var QueryTime = dbConn.Query<NumQuery>("select [nTime] from [KeyStorage] where not [IsUsed] order by [ItemId] asc limit 1");
+
+                return QueryTime.First().Num;
+            }
+        }
     }
 }