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