540012b540f510f9588cc214b67ee1f6174e8078
[NovacoinLibrary.git] / Novacoin / CBlockStore.cs
1 \feffusing System;
2 using System.IO;
3 using System.Linq;
4 using System.Collections.Concurrent;
5
6 using SQLite.Net;
7 using SQLite.Net.Attributes;
8 using SQLite.Net.Interop;
9 using SQLite.Net.Platform.Generic;
10 using SQLiteNetExtensions.Attributes;
11
12
13 namespace Novacoin
14 {
15     /// <summary>
16     /// Block headers table
17     /// </summary>
18     [Table("BlockStorage")]
19     class CBlockStoreItem
20     {
21         /// <summary>
22         /// Item ID in the database
23         /// </summary>
24         [PrimaryKey, AutoIncrement]
25         public int ItemID { get; set; }
26
27         /// <summary>
28         /// PBKDF2+Salsa20 of block hash
29         /// </summary>
30         [Unique]
31         public byte[] Hash { get; set; }
32
33         /// <summary>
34         /// Version of block schema
35         /// </summary>
36         public uint nVersion { get; set; }
37
38         /// <summary>
39         /// Previous block hash.
40         /// </summary>
41         public byte[] prevHash { get; set; }
42
43         /// <summary>
44         /// Merkle root hash.
45         /// </summary>
46         public byte[] merkleRoot { get; set; }
47
48         /// <summary>
49         /// Block timestamp.
50         /// </summary>
51         public uint nTime { get; set; }
52
53         /// <summary>
54         /// Compressed difficulty representation.
55         /// </summary>
56         public uint nBits { get; set; }
57
58         /// <summary>
59         /// Nonce counter.
60         /// </summary>
61         public uint nNonce { get; set; }
62
63         /// <summary>
64         /// Block type flags
65         /// </summary>
66         public BlockType BlockTypeFlag { get; set; }
67
68         /// <summary>
69         /// Next block hash
70         /// </summary>
71         public byte[] NextHash { get; set; }
72
73         /// <summary>
74         /// Block position in file
75         /// </summary>
76         public long nBlockPos { get; set; }
77
78         /// <summary>
79         /// Block size in bytes
80         /// </summary>
81         public int nBlockSize { get; set; }
82
83         /// <summary>
84         /// Fill database item with data from given block header.
85         /// </summary>
86         /// <param name="header">Block header</param>
87         /// <returns>Header hash</returns>
88         public ScryptHash256 FillHeader(CBlockHeader header)
89         {
90             ScryptHash256 _hash;
91             Hash = _hash = header.Hash;
92
93             nVersion = header.nVersion;
94             prevHash = header.prevHash;
95             merkleRoot = header.merkleRoot;
96             nTime = header.nTime;
97             nBits = header.nBits;
98             nNonce = header.nNonce;
99
100             return _hash;
101         }
102
103         /// <summary>
104         /// Reconstruct block header from item data.
105         /// </summary>
106         public CBlockHeader BlockHeader
107         {
108             get
109             {
110                 CBlockHeader header = new CBlockHeader();
111
112                 header.nVersion = nVersion;
113                 header.prevHash = new Hash256(prevHash);
114                 header.merkleRoot = new Hash256(merkleRoot);
115                 header.nTime = nTime;
116                 header.nBits = nBits;
117                 header.nNonce = nNonce;
118
119                 return header;
120             }
121         }
122
123         /// <summary>
124         /// Read block from file.
125         /// </summary>
126         /// <param name="reader">Stream with read access.</param>
127         /// <param name="reader">CBlock reference.</param>
128         /// <returns>Result</returns>
129         public bool ReadFromFile(ref Stream reader, out CBlock block)
130         {
131             var buffer = new byte[nBlockSize];
132             block = null;
133
134             try
135             {
136                 reader.Seek(nBlockPos, SeekOrigin.Begin);
137
138                 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
139                 {
140                     return false;
141                 }
142
143                 block = new CBlock(buffer);
144
145                 return true;
146             }
147             catch (IOException)
148             {
149                 // I/O error
150                 return false;
151             }
152             catch (BlockConstructorException)
153             {
154                 // Constructor exception
155                 return false;
156             }
157         }
158     }
159
160     /// <summary>
161     /// Block type.
162     /// </summary>
163     public enum BlockType
164     {
165         PROOF_OF_WORK,
166         PROOF_OF_WORK_MODIFIER,
167         PROOF_OF_STAKE,
168         PROOF_OF_STAKE_MODIFIER
169     };
170
171     /// <summary>
172     /// Transaction type.
173     /// </summary>
174     public enum TxType
175     {
176         TX_COINBASE,
177         TX_COINSTAKE,
178         TX_USER
179     }
180
181     [Table("TransactionStorage")]
182     public class CTransactionStoreItem
183     {
184         /// <summary>
185         /// Transaction hash
186         /// </summary>
187         [PrimaryKey]
188         public byte[] TransactionHash { get; set; }
189
190         /// <summary>
191         /// Block hash
192         /// </summary>
193         [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
194         public byte[] BlockHash { get; set; }
195
196         /// <summary>
197         /// Transaction type flag
198         /// </summary>
199         public TxType txType { get; set; }
200
201         /// <summary>
202         /// Tx position in file
203         /// </summary>
204         public long nTxPos { get; set; }
205
206         /// <summary>
207         /// Transaction size
208         /// </summary>
209         public int nTxSize { get; set; }
210
211         /// <summary>
212         /// Read transaction from file.
213         /// </summary>
214         /// <param name="reader">Stream with read access.</param>
215         /// <param name="tx">CTransaction reference.</param>
216         /// <returns>Result</returns>
217         public bool ReadFromFile(ref Stream reader, out CTransaction tx)
218         {
219             var buffer = new byte[250000]; // Max transaction size is 250kB
220             tx = null;
221
222             try
223             {
224                 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
225
226                 if (nTxSize != reader.Read(buffer, 0, nTxSize))
227                 {
228                     return false;
229                 }
230
231                 tx = new CTransaction(buffer);
232
233                 return true;
234             }
235             catch (IOException)
236             {
237                 // I/O error
238                 return false;
239             }
240             catch (TransactionConstructorException)
241             {
242                 // Constructor error
243                 return false;
244             }
245         }
246     }
247
248     /// <summary>
249     /// Block chain node
250     /// </summary>
251     public class CChainNode
252     {
253         /// <summary>
254         /// Block number
255         /// </summary>
256         public int nDepth;
257
258         /// <summary>
259         /// Block header
260         /// </summary>
261         public CBlockHeader blockHeader;
262
263         /// <summary>
264         /// Block type flag
265         /// </summary>
266         public BlockType blockType;
267
268         /// <summary>
269         /// Next block hash
270         /// </summary>
271         public ScryptHash256 hashNextBlock;
272     }
273
274     public class CBlockStore : IDisposable
275     {
276         private bool disposed = false;
277         private object LockObj = new object();
278         private SQLiteConnection dbConn = null;
279         private string strBlockFile;
280
281         private ConcurrentDictionary<ScryptHash256, CChainNode> blockMap = new ConcurrentDictionary<ScryptHash256, CChainNode>();
282         private ConcurrentDictionary<Hash256, CTransactionStoreItem> txMap = new ConcurrentDictionary<Hash256, CTransactionStoreItem>();
283         private CBlock genesisBlock = new CBlock(Interop.HexToArray("0100000000000000000000000000000000000000000000000000000000000000000000007b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c398e1151ffff0f1ed30918000101000000398e1151010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936ffffffff010000000000000000000000000000"));
284
285         public static CBlockStore Instance;
286
287         /// <summary>
288         /// Block file stream
289         /// </summary>
290         private Stream reader;
291
292         /// <summary>
293         /// Init the block storage manager.
294         /// </summary>
295         /// <param name="IndexDB">Path to index database</param>
296         /// <param name="BlockFile">Path to block file</param>
297         public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "bootstrap.dat")
298         {
299             strBlockFile = BlockFile;
300
301             bool firstInit = !File.Exists(IndexDB);
302             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), IndexDB);
303
304             if (firstInit)
305             {
306                 lock (LockObj)
307                 {
308                     // Create tables
309                     dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
310                     dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
311
312                     // Init store with genesis block
313
314                     var NewBlockItem = new CBlockStoreItem()
315                     {
316                         BlockTypeFlag = genesisBlock.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK,
317                         nBlockPos = 8,
318                         nBlockSize = ((byte[])genesisBlock).Length
319                     };
320
321                     var HeaderHash = NewBlockItem.FillHeader(genesisBlock.header);
322                     var NewNode = new CChainNode() { blockHeader = genesisBlock.header, blockType = BlockType.PROOF_OF_WORK };
323
324                     blockMap.TryAdd(HeaderHash, NewNode);
325                     dbConn.Insert(NewBlockItem);
326
327                     var NewTxItem = new CTransactionStoreItem()
328                     {
329                         TransactionHash = genesisBlock.vtx[0].Hash,
330                         BlockHash = HeaderHash,
331                         txType = TxType.TX_COINBASE,
332                         nTxPos = 8 + genesisBlock.GetTxOffset(0),
333                         nTxSize = genesisBlock.vtx[0].Size
334                     };
335
336                     dbConn.Insert(NewTxItem);
337                 }
338             }
339             else
340             {
341                 var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
342
343                 // Init list of block items
344                 foreach (var storeItem in QueryGet)
345                 {
346                     var currentNode = new CChainNode() { blockHeader = new CBlockHeader(storeItem.BlockHeader), blockType = storeItem.BlockTypeFlag };
347                     blockMap.TryAdd(new ScryptHash256(storeItem.Hash), currentNode);
348                 }
349             }
350
351             var fStream1 = File.OpenRead(strBlockFile);
352             reader = new BinaryReader(fStream1).BaseStream;
353
354             Instance = this;
355
356         }
357
358         public bool GetTransaction(Hash256 TxID, ref CTransaction tx)
359         {
360             var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
361
362             if (QueryTx.Count == 1)
363             {
364                 return QueryTx[0].ReadFromFile(ref reader, out tx);
365             }
366
367             // Tx not found
368
369             return false;
370         }
371
372         public bool GetBlock(ScryptHash256 blockHash, ref CBlock block)
373         {
374             var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
375
376             if (QueryBlock.Count == 1)
377             {
378                 return QueryBlock[0].ReadFromFile(ref reader, out block);
379             }
380
381             // Block not found
382
383             return false;
384         }
385
386         public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
387         {
388             strBlockFile = BlockFile;
389
390             // TODO: Rewrite completely.
391
392             var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] desc limit 1");
393
394             var nOffset = 0L;
395
396             if (QueryGet.Count() == 1)
397             {
398                 var res = QueryGet.First();
399                 nOffset = res.nBlockPos + res.nBlockSize;
400             }
401
402             var buffer = new byte[1000000]; // Max block size is 1Mb
403             var intBuffer = new byte[4];
404
405             var fStream2 = File.OpenRead(strBlockFile);
406             var readerForBlocks = new BinaryReader(fStream2).BaseStream;
407
408             readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
409
410             dbConn.BeginTransaction();
411
412             while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
413             {
414                 var nMagic = BitConverter.ToUInt32(buffer, 0);
415                 if (nMagic != 0xe5e9e8e4)
416                 {
417                     throw new Exception("Incorrect magic number.");
418                 }
419
420                 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
421                 if (nBytesRead != 4)
422                 {
423                     throw new Exception("BLKSZ EOF");
424                 }
425
426                 var nBlockSize = BitConverter.ToInt32(buffer, 0);
427
428                 nOffset = readerForBlocks.Position;
429
430                 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
431
432                 if (nBytesRead == 0 || nBytesRead != nBlockSize)
433                 {
434                     throw new Exception("BLK EOF");
435                 }
436
437                 var block = new CBlock(buffer);
438
439                 if (block.header.merkleRoot != block.hashMerkleRoot)
440                 {
441                     Console.WriteLine("MerkleRoot mismatch: {0} vs. {1} in block {2}.", block.header.merkleRoot, block.hashMerkleRoot, block.header.Hash);
442                     continue;
443                 }
444
445                 if (block.IsProofOfStake && !block.SignatureOK)
446                 {
447                     Console.WriteLine("Proof-of-Stake signature is invalid for block {0}.", block.header.Hash);
448                     continue;
449                 }
450
451                 var NewStoreItem = new CBlockStoreItem()
452                 {
453                     BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK,
454                     nBlockPos = nOffset,
455                     nBlockSize = nBlockSize
456                 };
457
458                 var NewChainNode = new CChainNode()
459                 {
460                     blockHeader = block.header,
461                     blockType = NewStoreItem.BlockTypeFlag
462                 };
463
464                 var HeaderHash = NewStoreItem.FillHeader(block.header);
465
466                 int nCount = blockMap.Count;
467                 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, HeaderHash, DateTime.Now); // Commit on each 100th block
468
469                 if (nCount % 100 == 0)
470                 {
471                     Console.WriteLine("Commit...");
472                     dbConn.Commit();
473                     dbConn.BeginTransaction();
474                 }
475
476                 if (!blockMap.TryAdd(HeaderHash, NewChainNode))
477                 {
478                     Console.WriteLine("Duplicate block: {0}", HeaderHash);
479                     continue;
480                 }
481
482                 // Verify transactions
483
484                 foreach (var tx in block.vtx)
485                 {
486                     if (!tx.VerifyScripts())
487                     {
488                         Console.WriteLine("Error checking tx {0}", tx.Hash);
489                         continue;
490                     }
491                 }
492
493                 dbConn.Insert(NewStoreItem);
494
495                 for (int i = 0; i < block.vtx.Length; i++)
496                 {
497                     // Handle trasactions
498
499                     var nTxOffset = nOffset + block.GetTxOffset(i);
500                     TxType txnType = TxType.TX_USER;
501
502                     if (block.vtx[i].IsCoinBase)
503                     {
504                         txnType = TxType.TX_COINBASE;
505                     }
506                     else if (block.vtx[i].IsCoinStake)
507                     {
508                         txnType = TxType.TX_COINSTAKE;
509                     }
510
511                     var NewTxItem = new CTransactionStoreItem()
512                     {
513                         TransactionHash = block.vtx[i].Hash,
514                         BlockHash = HeaderHash,
515                         nTxPos = nTxOffset,
516                         nTxSize = block.vtx[i].Size,
517                         txType = txnType
518                     };
519
520                     dbConn.Insert(NewTxItem);
521                 }
522             }
523
524             dbConn.Commit();
525
526             return true;
527         }
528
529         ~CBlockStore()
530         {
531             Dispose(false);
532         }
533
534         public void Dispose()
535         {
536             Dispose(true);
537             GC.SuppressFinalize(this);
538         }
539
540         protected virtual void Dispose(bool disposing)
541         {
542             if (!disposed)
543             {
544                 if (disposing)
545                 {
546                     // Free other state (managed objects).
547
548                     reader.Dispose();
549                 }
550
551                 if (dbConn != null)
552                 {
553                     dbConn.Close();
554                     dbConn = null;
555                 }
556
557                 disposed = true;
558             }
559         }
560
561     }
562 }