Duplicate checking.
[NovacoinLibrary.git] / Novacoin / CBlockStore.cs
1 \feffusing System;
2 using System.IO;
3 using System.Linq;
4 using System.Collections.Generic;
5
6 using SQLite.Net;
7 using SQLite.Net.Attributes;
8 using SQLite.Net.Interop;
9 using SQLite.Net.Platform.Generic;
10
11 namespace Novacoin
12 {
13     public enum BlockType
14     {
15         PROOF_OF_WORK,
16         PROOF_OF_WORK_MODIFIER,
17         PROOF_OF_STAKE,
18         PROOF_OF_STAKE_MODIFIER
19     };
20
21     [Table("BlockStorage")]
22     class CBlockStoreItem
23     {
24         /// <summary>
25         /// Item ID in the database
26         /// </summary>
27         [PrimaryKey, AutoIncrement]
28         public int ItemID { get; set; }
29
30         /// <summary>
31         /// PBKDF2+Salsa20 of block hash
32         /// </summary>
33         [Unique]
34         public byte[] Hash { get; set; }
35
36         /// <summary>
37         /// Next block hash
38         /// </summary>
39         public byte[] NextHash { get; set; }
40
41         /// <summary>
42         /// Serialized representation of block header
43         /// </summary>
44         public byte[] BlockHeader { get; set; }
45
46         /// <summary>
47         /// Block type flags
48         /// </summary>
49         public BlockType BlockTypeFlag { get; set; }
50
51         /// <summary>
52         /// Block position in file
53         /// </summary>
54         public long nBlockPos { get; set; }
55
56         /// <summary>
57         /// Block size in bytes
58         /// </summary>
59         public int nBlockSize { get; set; }
60     }
61
62     /// <summary>
63     /// Block chain node
64     /// </summary>
65     public class CChainNode
66     {
67         /// <summary>
68         /// Block number
69         /// </summary>
70         public int nDepth;
71
72         /// <summary>
73         /// Block header
74         /// </summary>
75         public CBlockHeader blockHeader;
76
77         /// <summary>
78         /// Block type flag
79         /// </summary>
80         public BlockType blockType;
81
82         /// <summary>
83         /// Next block hash
84         /// </summary>
85         public ScryptHash256 hashNextBlock;
86     }
87
88     public class CBlockStore : IDisposable
89     {
90         private bool disposed = false;
91         private object LockObj = new object();
92         private SQLiteConnection dbConn = null;
93
94         private Dictionary<ScryptHash256, CChainNode> blockMap = new Dictionary<ScryptHash256, CChainNode>();
95         
96         /// <summary>
97         /// Init the block storage manager.
98         /// </summary>
99         /// <param name="IndexDB">Path to index database</param>
100         /// <param name="BlockFile">Path to block file</param>
101         public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
102         {
103             bool firstInit = !File.Exists(IndexDB);
104             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), IndexDB);
105
106             if (firstInit)
107             {
108                 lock (LockObj)
109                 {
110                     dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
111                 }
112             }
113             else
114             {
115                 var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
116
117                 foreach (CBlockStoreItem si in QueryGet)
118                 {
119                     blockMap.Add(
120                         new ScryptHash256(si.Hash),
121                         new CChainNode()
122                         {
123                             blockHeader = new CBlockHeader(si.BlockHeader),
124                             blockType = si.BlockTypeFlag
125                         });
126                 }
127             }
128         }
129
130         public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
131         {
132             // TODO: Rewrite completely.
133
134             var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] desc limit 1");
135
136             var nOffset = 0L;
137
138             if (QueryGet.Count() == 1)
139             {
140                 var res = QueryGet.First();
141                 nOffset = res.nBlockPos + res.nBlockSize;
142             }
143
144             var fileReader = new BinaryReader(File.OpenRead(BlockFile));
145             var fileStream = fileReader.BaseStream;
146
147             var buffer = new byte[1000000]; // Max block size is 1Mb
148             var intBuffer = new byte[4];
149
150             fileStream.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
151
152             dbConn.BeginTransaction();
153
154             while (fileStream.Read(buffer, 0, 4) == 4) // Read magic number
155             {
156                 var nMagic = BitConverter.ToUInt32(buffer, 0);
157                 if (nMagic != 0xe5e9e8e4)
158                 {
159                     Console.WriteLine("Incorrect magic number.");
160                     break;
161                 }
162
163                 var nBytesRead = fileStream.Read(buffer, 0, 4);
164                 if (nBytesRead != 4)
165                 {
166                     Console.WriteLine("BLKSZ EOF");
167                     break;
168                 }
169
170                 var nBlockSize = BitConverter.ToInt32(buffer, 0);
171
172                 nOffset = fileStream.Position;
173
174                 nBytesRead = fileStream.Read(buffer, 0, nBlockSize);
175
176                 if (nBytesRead == 0 || nBytesRead != nBlockSize)
177                 {
178                     Console.WriteLine("BLK EOF");
179                     break;
180                 }
181
182                 var block = new CBlock(buffer);
183                 var headerHash = block.header.Hash;
184
185                 if (nOffset % 1000 == 0)  // Commit on each 1000th block
186                 {
187                     Console.WriteLine("Offset={0}, Hash: {1}", nOffset, headerHash);
188                     dbConn.Commit();
189                     dbConn.BeginTransaction();
190                 }
191
192                 if (blockMap.ContainsKey(headerHash))
193                 {
194                     Console.WriteLine("Duplicate block {0}", headerHash);
195                     continue;
196                 }
197
198                 blockMap.Add(
199                     headerHash,
200                     new CChainNode() {
201                         blockHeader = block.header,
202                         blockType = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK
203                     });
204
205                 var result = dbConn.Insert(new CBlockStoreItem()
206                 {
207                     Hash = headerHash,
208                     BlockHeader = block.header,
209                     BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK,
210                     nBlockPos = nOffset,
211                     nBlockSize = nBlockSize
212                 });
213             }
214
215             dbConn.Commit();
216             
217             fileReader.Dispose();
218
219             return true;
220         }
221
222         ~CBlockStore()
223         {
224             Dispose(false);
225         }
226
227         public void Dispose()
228         {
229             Dispose(true);
230             GC.SuppressFinalize(this);
231         }
232
233         protected virtual void Dispose(bool disposing)
234         {
235             if (!disposed)
236             {
237                 if (disposing)
238                 {
239                     // Free other state (managed objects).
240                 }
241
242                 if (dbConn != null)
243                 {
244                     dbConn.Close();
245                     dbConn = null;
246                 }
247
248                 disposed = true;
249             }
250         }
251
252     }
253 }