IsInMainChain property
[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 using System.Collections.Generic;
12
13 namespace Novacoin
14 {
15     /// <summary>
16     /// Block headers table
17     /// </summary>
18     [Table("BlockStorage")]
19     public 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         /// Next block hash.
65         /// </summary>
66         public byte[] nextHash { get; set; }
67
68         /// <summary>
69         /// Block type flags
70         /// </summary>
71         public BlockType BlockTypeFlag { get; set; }
72
73         /// <summary>
74         /// Stake modifier
75         /// </summary>
76         public long nStakeModifier { get; set; }
77
78         /// <summary>
79         /// Stake entropy bit
80         /// </summary>
81         public byte nEntropyBit { get; set; }
82
83         /// <summary>
84         /// Proof-of-Stake hash
85         /// </summary>
86         public byte[] hashProofOfStake { get; set; }
87
88         /// <summary>
89         /// Block height
90         /// </summary>
91         public uint nHeight { get; set; }
92
93         /// <summary>
94         /// Block position in file
95         /// </summary>
96         public long nBlockPos { get; set; }
97
98         /// <summary>
99         /// Block size in bytes
100         /// </summary>
101         public int nBlockSize { get; set; }
102
103         /// <summary>
104         /// Fill database item with data from given block header.
105         /// </summary>
106         /// <param name="header">Block header</param>
107         /// <returns>Header hash</returns>
108         public uint256 FillHeader(CBlockHeader header)
109         {
110             uint256 _hash;
111             Hash = _hash = header.Hash;
112
113             nVersion = header.nVersion;
114             prevHash = header.prevHash;
115             merkleRoot = header.merkleRoot;
116             nTime = header.nTime;
117             nBits = header.nBits;
118             nNonce = header.nNonce;
119
120             return _hash;
121         }
122
123         /// <summary>
124         /// Reconstruct block header from item data.
125         /// </summary>
126         public CBlockHeader BlockHeader
127         {
128             get
129             {
130                 CBlockHeader header = new CBlockHeader();
131
132                 header.nVersion = nVersion;
133                 header.prevHash = prevHash;
134                 header.merkleRoot = merkleRoot;
135                 header.nTime = nTime;
136                 header.nBits = nBits;
137                 header.nNonce = nNonce;
138
139                 return header;
140             }
141         }
142
143         /// <summary>
144         /// Read block from file.
145         /// </summary>
146         /// <param name="reader">Stream with read access.</param>
147         /// <param name="reader">CBlock reference.</param>
148         /// <returns>Result</returns>
149         public bool ReadFromFile(ref Stream reader, out CBlock block)
150         {
151             var buffer = new byte[nBlockSize];
152             block = null;
153
154             try
155             {
156                 reader.Seek(nBlockPos, SeekOrigin.Begin);
157
158                 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
159                 {
160                     return false;
161                 }
162
163                 block = new CBlock(buffer);
164
165                 return true;
166             }
167             catch (IOException)
168             {
169                 // I/O error
170                 return false;
171             }
172             catch (BlockException)
173             {
174                 // Constructor exception
175                 return false;
176             }
177         }
178
179         /// <summary>
180         /// Writes given block to file and prepares cursor object for insertion into the database.
181         /// </summary>
182         /// <param name="writer">Stream with write access.</param>
183         /// <param name="block">CBlock reference.</param>
184         /// <returns>Result</returns>
185         public bool WriteToFile(ref Stream writer, ref CBlock block)
186         {
187             try
188             {
189                 byte[] blockBytes = block;
190
191                 var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber);
192                 var blkLenBytes = BitConverter.GetBytes(blockBytes.Length);
193
194                 // Seek to the end and then append magic bytes there.
195                 writer.Seek(0, SeekOrigin.End);
196                 writer.Write(magicBytes, 0, magicBytes.Length);
197                 writer.Write(blkLenBytes, 0, blkLenBytes.Length);
198
199                 // Save block size and current position in the block cursor fields.
200                 nBlockPos = writer.Position;
201                 nBlockSize = blockBytes.Length;                
202
203                 // Write block and flush the stream.
204                 writer.Write(blockBytes, 0, blockBytes.Length);
205                 writer.Flush();
206
207                 return true;
208             }
209             catch (IOException)
210             {
211                 // I/O error
212                 return false;
213             }
214             catch (Exception)
215             {
216                 // Some serialization error
217                 return false;
218             }
219         }
220
221         /// <summary>
222         /// Previous block cursor
223         /// </summary>
224         public CBlockStoreItem prev {
225             get { return CBlockStore.Instance.GetCursor(prevHash); }
226         }
227
228         /// <summary>
229         /// Next block cursor
230         /// </summary>
231         public CBlockStoreItem next
232         {
233             get { return CBlockStore.Instance.GetCursor(nextHash); }
234         }
235
236         bool IsInMainChain
237         {
238             get { return (next != null); }
239         }
240
241
242     /// <summary>
243     /// STake modifier generation flag
244     /// </summary>
245     public bool GeneratedStakeModifier
246         {
247             get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
248         }
249
250         public uint StakeEntropyBit
251         {
252             get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
253         }
254
255         /// <summary>
256         /// Sets stake modifier and flag.
257         /// </summary>
258         /// <param name="nModifier">New stake modifier.</param>
259         /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
260         public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
261         {
262             nStakeModifier = nModifier;
263             if (fGeneratedStakeModifier)
264                 BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
265         }
266
267         /// <summary>
268         /// Set entropy bit.
269         /// </summary>
270         /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
271         /// <returns>False if value is our of range.</returns>
272         public bool SetStakeEntropyBit(byte nEntropyBit)
273         {
274             if (nEntropyBit > 1)
275                 return false;
276             BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
277             return true;
278         }
279
280         /// <summary>
281         /// Set proof-of-stake flag.
282         /// </summary>
283         public void SetProofOfStake()
284         {
285             BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
286         }
287
288         /// <summary>
289         /// Block has no proof-of-stake flag.
290         /// </summary>
291         public bool IsProofOfWork
292         {
293             get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
294         }
295
296         /// <summary>
297         /// Block has proof-of-stake flag set.
298         /// </summary>
299         public bool IsProofOfStake 
300         {
301             get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
302         }
303
304
305     }
306
307     /// <summary>
308     /// Block type.
309     /// </summary>
310     public enum BlockType
311     {
312         BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
313         BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
314         BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
315     };
316
317     /// <summary>
318     /// Transaction type.
319     /// </summary>
320     public enum TxType
321     {
322         TX_COINBASE,
323         TX_COINSTAKE,
324         TX_USER
325     }
326
327     [Table("TransactionStorage")]
328     public class CTransactionStoreItem
329     {
330         /// <summary>
331         /// Transaction hash
332         /// </summary>
333         [PrimaryKey]
334         public byte[] TransactionHash { get; set; }
335
336         /// <summary>
337         /// Block hash
338         /// </summary>
339         [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
340         public byte[] BlockHash { get; set; }
341
342         /// <summary>
343         /// Transaction type flag
344         /// </summary>
345         public TxType txType { get; set; }
346
347         /// <summary>
348         /// Tx position in file
349         /// </summary>
350         public long nTxPos { get; set; }
351
352         /// <summary>
353         /// Transaction size
354         /// </summary>
355         public int nTxSize { get; set; }
356
357         /// <summary>
358         /// Read transaction from file.
359         /// </summary>
360         /// <param name="reader">Stream with read access.</param>
361         /// <param name="tx">CTransaction reference.</param>
362         /// <returns>Result</returns>
363         public bool ReadFromFile(ref Stream reader, out CTransaction tx)
364         {
365             var buffer = new byte[CTransaction.nMaxTxSize];
366             tx = null;
367
368             try
369             {
370                 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
371
372                 if (nTxSize != reader.Read(buffer, 0, nTxSize))
373                 {
374                     return false;
375                 }
376
377                 tx = new CTransaction(buffer);
378
379                 return true;
380             }
381             catch (IOException)
382             {
383                 // I/O error
384                 return false;
385             }
386             catch (TransactionConstructorException)
387             {
388                 // Constructor error
389                 return false;
390             }
391         }
392     }
393
394     public class CBlockStore : IDisposable
395     {
396         public const uint nMagicNumber = 0xe5e9e8e4;
397
398         private bool disposed = false;
399         private object LockObj = new object();
400
401         /// <summary>
402         /// SQLite connection object.
403         /// </summary>
404         private SQLiteConnection dbConn;
405
406         /// <summary>
407         /// Block file.
408         /// </summary>
409         private string strBlockFile;
410
411         /// <summary>
412         /// Index database file.
413         /// </summary>
414         private string strDbFile;
415
416         /// <summary>
417         /// Map of block tree nodes.
418         /// </summary>
419         private ConcurrentDictionary<uint256, CBlockStoreItem> blockMap = new ConcurrentDictionary<uint256, CBlockStoreItem>();
420
421         /// <summary>
422         /// Orphaned blocks map.
423         /// </summary>
424         private ConcurrentDictionary<uint256, CBlock> orphanMap = new ConcurrentDictionary<uint256, CBlock>();
425         private ConcurrentDictionary<uint256, CBlock> orphanMapByPrev = new ConcurrentDictionary<uint256, CBlock>();
426
427         /// <summary>
428         /// Map of unspent items.
429         /// </summary>
430         private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
431
432         public static CBlockStore Instance;
433
434         /// <summary>
435         /// Block file stream with read access
436         /// </summary>
437         private Stream fStreamReadWrite;
438
439         /// <summary>
440         /// Init the block storage manager.
441         /// </summary>
442         /// <param name="IndexDB">Path to index database</param>
443         /// <param name="BlockFile">Path to block file</param>
444         public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
445         {
446             strDbFile = IndexDB;
447             strBlockFile = BlockFile;
448
449             bool firstInit = !File.Exists(strDbFile);
450             dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile);
451
452             fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
453
454             if (firstInit)
455             {
456                 lock (LockObj)
457                 {
458                     // Create tables
459                     dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
460                     dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
461
462                     var genesisBlock = new CBlock(
463                         Interop.HexToArray(
464                             "01000000" + // nVersion=1
465                             "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero
466                             "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root
467                             "398e1151" + // nTime=1360105017
468                             "ffff0f1e" + // nBits=0x1e0fffff
469                             "d3091800" + // nNonce=1575379
470                             "01" +       // nTxCount=1
471                             "01000000" + // nVersion=1
472                             "398e1151" + // nTime=1360105017
473                             "01" +       // nInputs=1
474                             "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero
475                             "ffffffff" + // n=uint.maxValue
476                             "4d" +       // scriptSigLen=77
477                             "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig
478                             "ffffffff" + // nSequence=uint.maxValue
479                             "01" +       // nOutputs=1
480                             "0000000000000000" + // nValue=0
481                             "00" +       // scriptPubkeyLen=0
482                             "00000000" + // nLockTime=0
483                             "00"         // sigLen=0
484                     ));
485
486                     // Write block to file.
487                     var itemTemplate = new CBlockStoreItem()
488                     {
489                         nHeight = 0
490                     };
491
492                     itemTemplate.FillHeader(genesisBlock.header);
493
494                     if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
495                     {
496                         throw new Exception("Unable to write genesis block");
497                     }
498                 }
499             }
500             else
501             {
502                 var blockTreeItems = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
503
504                 // Init list of block items
505                 foreach (var item in blockTreeItems)
506                 {
507                     blockMap.TryAdd(item.Hash, item);
508                 }
509             }
510
511             Instance = this;
512         }
513
514         public bool GetTransaction(uint256 TxID, ref CTransaction tx)
515         {
516             var reader = new BinaryReader(fStreamReadWrite).BaseStream;
517             var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
518
519             if (QueryTx.Count == 1)
520             {
521                 return QueryTx[0].ReadFromFile(ref reader, out tx);
522             }
523
524             // Tx not found
525
526             return false;
527         }
528
529         private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
530         {
531             var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
532             uint256 blockHash = itemTemplate.Hash;
533
534             if (blockMap.ContainsKey(blockHash))
535             {
536                 // Already have this block.
537                 return false;
538             }
539
540             // TODO: compute chain trust, set stake entropy bit, record proof-of-stake hash value
541
542             // TODO: compute stake modifier
543
544             // Add to index
545             if (block.IsProofOfStake)
546             {
547                 itemTemplate.SetProofOfStake();
548             }
549
550             if (!itemTemplate.WriteToFile(ref writer, ref block))
551             {
552                 return false;
553             }
554
555             dbConn.Insert(itemTemplate);
556
557             // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
558             for (int i = 0; i < block.vtx.Length; i++)
559             {
560                 // Handle trasactions
561
562                 if (!block.vtx[i].VerifyScripts())
563                 {
564                     return false;
565                 }
566
567                 var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
568                 TxType txnType = TxType.TX_USER;
569
570                 if (block.vtx[i].IsCoinBase)
571                 {
572                     txnType = TxType.TX_COINBASE;
573                 }
574                 else if (block.vtx[i].IsCoinStake)
575                 {
576                     txnType = TxType.TX_COINSTAKE;
577                 }
578
579                 var NewTxItem = new CTransactionStoreItem()
580                 {
581                     TransactionHash = block.vtx[i].Hash,
582                     BlockHash = blockHash,
583                     nTxPos = nTxOffset,
584                     nTxSize = block.vtx[i].Size,
585                     txType = txnType
586                 };
587
588                 dbConn.Insert(NewTxItem);
589             }
590
591             return blockMap.TryAdd(blockHash, itemTemplate);
592         }
593
594         public bool AcceptBlock(ref CBlock block)
595         {
596             uint256 nHash = block.header.Hash;
597
598             if (blockMap.ContainsKey(nHash))
599             {
600                 // Already have this block.
601                 return false;
602             }
603
604             CBlockStoreItem prevBlockCursor = null;
605             if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
606             {
607                 // Unable to get the cursor.
608                 return false;
609             }
610
611             var prevBlockHeader = prevBlockCursor.BlockHeader;
612
613             // TODO: proof-of-work/proof-of-stake verification
614             uint nHeight = prevBlockCursor.nHeight + 1;
615
616             // Check timestamp against prev
617             if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime)
618             {
619                 // block's timestamp is too early
620                 return false;
621             }
622
623             // Check that all transactions are finalized
624             foreach (var tx in block.vtx)
625             {
626                 if (!tx.IsFinal(nHeight, block.header.nTime))
627                 {
628                     return false;
629                 }
630             }
631
632             // TODO: Enforce rule that the coinbase starts with serialized block height
633
634             // Write block to file.
635             var itemTemplate = new CBlockStoreItem()
636             {
637                 nHeight = nHeight,
638                 nEntropyBit = Entropy.GetStakeEntropyBit(nHeight, nHash)
639             };
640
641             itemTemplate.FillHeader(block.header);
642
643             if (!AddItemToIndex(ref itemTemplate, ref block))
644             {
645                 return false;
646             }
647
648             return true;
649         }
650
651         public bool GetBlock(uint256 blockHash, ref CBlock block)
652         {
653             var reader = new BinaryReader(fStreamReadWrite).BaseStream;
654
655             var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
656
657             if (QueryBlock.Count == 1)
658             {
659                 return QueryBlock[0].ReadFromFile(ref reader, out block);
660             }
661
662             // Block not found
663
664             return false;
665         }
666
667         /// <summary>
668         /// Get block cursor from map.
669         /// </summary>
670         /// <param name="blockHash">block hash</param>
671         /// <returns>Cursor or null</returns>
672         public CBlockStoreItem GetCursor(uint256 blockHash)
673         {
674             if (blockHash == 0)
675             {
676                 // Genesis block has zero prevHash and no parent.
677                 return null;
678             }
679
680             // First, check our block map.
681             CBlockStoreItem item = null;
682             if (blockMap.TryGetValue(blockHash, out item))
683             {
684                 return item;
685             }
686
687             // Trying to get cursor from the database.
688             var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
689
690             if (QueryBlockCursor.Count == 1)
691             {
692                 return QueryBlockCursor[0];
693             }
694
695             // Nothing found.
696             return null;
697         }
698
699         public bool ProcessBlock(ref CBlock block)
700         {
701             var blockHash = block.header.Hash;
702
703             if (blockMap.ContainsKey(blockHash))
704             {
705                 // We already have this block.
706                 return false;
707             }
708
709             if (orphanMap.ContainsKey(blockHash))
710             {
711                 // We already have block in the list of orphans.
712                 return false;
713             }
714
715             // TODO: Limited duplicity on stake and reserialization of block signature
716
717             if (!block.CheckBlock(true, true, true))
718             {
719                 // Preliminary checks failure.
720                 return false;
721             }
722
723             if (block.IsProofOfStake)
724             {
725                 if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
726                 {
727                     // Proof-of-Stake signature validation failure.
728                     return false;
729                 }
730
731                 // TODO: proof-of-stake validation
732             }
733
734             // TODO: difficulty verification
735
736             // If don't already have its previous block, shunt it off to holding area until we get it
737             if (!blockMap.ContainsKey(block.header.prevHash))
738             {
739                 if (block.IsProofOfStake)
740                 {
741                     // TODO: limit duplicity on stake
742                 }
743
744                 var block2 = new CBlock(block);
745                 orphanMap.TryAdd(blockHash, block2);
746                 orphanMapByPrev.TryAdd(blockHash, block2);
747
748                 return true;
749             }
750
751             // Store block to disk
752             if (!AcceptBlock(ref block))
753             {
754                 // Accept failed
755                 return false;
756             }
757
758             // Recursively process any orphan blocks that depended on this one
759             var orphansQueue = new List<uint256>();
760             orphansQueue.Add(blockHash);
761
762             for (int i = 0; i < orphansQueue.Count; i++)
763             {
764                 var hashPrev = orphansQueue[i];
765
766                 foreach (var pair in orphanMap)
767                 {
768                     var orphanBlock = pair.Value;
769
770                     if (orphanBlock.header.prevHash == blockHash)
771                     {
772                         if (AcceptBlock(ref orphanBlock))
773                         {
774                             orphansQueue.Add(pair.Key);
775                         }
776
777                         CBlock dummy1;
778                         orphanMap.TryRemove(pair.Key, out dummy1);
779                     }
780                 }
781
782                 CBlock dummy2;
783                 orphanMap.TryRemove(hashPrev, out dummy2);
784             }
785
786             return true;
787         }
788
789         public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
790         {
791             // TODO: Rewrite completely.
792
793             var nOffset = 0L;
794
795             var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb
796             var intBuffer = new byte[4];
797
798             var fStream2 = File.OpenRead(BlockFile);
799             var readerForBlocks = new BinaryReader(fStream2).BaseStream;
800
801             readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
802
803             dbConn.BeginTransaction();
804
805             while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
806             {
807                 var nMagic = BitConverter.ToUInt32(buffer, 0);
808                 if (nMagic != 0xe5e9e8e4)
809                 {
810                     throw new Exception("Incorrect magic number.");
811                 }
812
813                 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
814                 if (nBytesRead != 4)
815                 {
816                     throw new Exception("BLKSZ EOF");
817                 }
818
819                 var nBlockSize = BitConverter.ToInt32(buffer, 0);
820
821                 nOffset = readerForBlocks.Position;
822
823                 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
824
825                 if (nBytesRead == 0 || nBytesRead != nBlockSize)
826                 {
827                     throw new Exception("BLK EOF");
828                 }
829
830                 var block = new CBlock(buffer);
831                 var hash = block.header.Hash;
832
833                 if (blockMap.ContainsKey(hash))
834                 {
835                     continue;
836                 }
837
838                 if (!ProcessBlock(ref block))
839                 {
840                     throw new Exception("Invalid block: " + block.header.Hash);
841                 }
842
843                 int nCount = blockMap.Count;
844                 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
845
846                 if (nCount % 100 == 0 && nCount != 0)
847                 {
848                     Console.WriteLine("Commit...");
849                     dbConn.Commit();
850                     dbConn.BeginTransaction();
851                 }
852             }
853
854             dbConn.Commit();
855
856             return true;
857         }
858
859         ~CBlockStore()
860         {
861             Dispose(false);
862         }
863
864         public void Dispose()
865         {
866             Dispose(true);
867             GC.SuppressFinalize(this);
868         }
869
870         protected virtual void Dispose(bool disposing)
871         {
872             if (!disposed)
873             {
874                 if (disposing)
875                 {
876                     // Free other state (managed objects).
877
878                     fStreamReadWrite.Dispose();
879                 }
880
881                 if (dbConn != null)
882                 {
883                     dbConn.Close();
884                     dbConn = null;
885                 }
886
887                 disposed = true;
888             }
889         }
890
891     }
892 }