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