4 using System.Collections.Concurrent;
7 using SQLite.Net.Attributes;
8 using SQLite.Net.Interop;
9 using SQLite.Net.Platform.Generic;
10 using SQLiteNetExtensions.Attributes;
16 /// Block headers table
18 [Table("BlockStorage")]
22 /// Item ID in the database
24 [PrimaryKey, AutoIncrement]
25 public int ItemID { get; set; }
28 /// PBKDF2+Salsa20 of block hash
31 public byte[] Hash { get; set; }
34 /// Version of block schema
36 public uint nVersion { get; set; }
39 /// Previous block hash.
41 public byte[] prevHash { get; set; }
46 public byte[] merkleRoot { get; set; }
51 public uint nTime { get; set; }
54 /// Compressed difficulty representation.
56 public uint nBits { get; set; }
61 public uint nNonce { get; set; }
66 public BlockType BlockTypeFlag { get; set; }
71 public byte[] NextHash { get; set; }
74 /// Block position in file
76 public long nBlockPos { get; set; }
79 /// Block size in bytes
81 public int nBlockSize { get; set; }
84 /// Fill database item with data from given block header.
86 /// <param name="header">Block header</param>
87 /// <returns>Header hash</returns>
88 public ScryptHash256 FillHeader(CBlockHeader header)
91 Hash = _hash = header.Hash;
93 nVersion = header.nVersion;
94 prevHash = header.prevHash;
95 merkleRoot = header.merkleRoot;
98 nNonce = header.nNonce;
104 /// Reconstruct block header from item data.
106 public CBlockHeader BlockHeader
110 CBlockHeader header = new CBlockHeader();
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;
124 /// Read block from file.
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)
131 var buffer = new byte[nBlockSize];
136 reader.Seek(nBlockPos, SeekOrigin.Begin);
138 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
143 block = new CBlock(buffer);
152 catch (BlockConstructorException)
154 // Constructor exception
163 public enum BlockType
166 PROOF_OF_WORK_MODIFIER,
168 PROOF_OF_STAKE_MODIFIER
172 /// Transaction type.
181 [Table("TransactionStorage")]
182 public class CTransactionStoreItem
188 public byte[] TransactionHash { get; set; }
193 [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
194 public byte[] BlockHash { get; set; }
197 /// Transaction type flag
199 public TxType txType { get; set; }
202 /// Tx position in file
204 public long nTxPos { get; set; }
209 public int nTxSize { get; set; }
212 /// Read transaction from file.
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)
219 var buffer = new byte[250000]; // Max transaction size is 250kB
224 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
226 if (nTxSize != reader.Read(buffer, 0, nTxSize))
231 tx = new CTransaction(buffer);
240 catch (TransactionConstructorException)
251 public class CChainNode
261 public CBlockHeader blockHeader;
266 public BlockType blockType;
271 public ScryptHash256 hashNextBlock;
274 public class CBlockStore : IDisposable
276 private bool disposed = false;
277 private object LockObj = new object();
278 private SQLiteConnection dbConn = null;
279 private string strBlockFile;
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"));
285 public static CBlockStore Instance;
288 /// Block file stream
290 private Stream reader;
293 /// Init the block storage manager.
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")
299 strBlockFile = BlockFile;
301 bool firstInit = !File.Exists(IndexDB);
302 dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), IndexDB);
309 dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
310 dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
312 // Init store with genesis block
314 var NewBlockItem = new CBlockStoreItem()
316 BlockTypeFlag = genesisBlock.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK,
318 nBlockSize = ((byte[])genesisBlock).Length
321 var HeaderHash = NewBlockItem.FillHeader(genesisBlock.header);
322 var NewNode = new CChainNode() { blockHeader = genesisBlock.header, blockType = BlockType.PROOF_OF_WORK };
324 blockMap.TryAdd(HeaderHash, NewNode);
325 dbConn.Insert(NewBlockItem);
327 var NewTxItem = new CTransactionStoreItem()
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
336 dbConn.Insert(NewTxItem);
341 var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
343 // Init list of block items
344 foreach (var storeItem in QueryGet)
346 var currentNode = new CChainNode() { blockHeader = new CBlockHeader(storeItem.BlockHeader), blockType = storeItem.BlockTypeFlag };
347 blockMap.TryAdd(new ScryptHash256(storeItem.Hash), currentNode);
351 var fStream1 = File.OpenRead(strBlockFile);
352 reader = new BinaryReader(fStream1).BaseStream;
358 public bool GetTransaction(Hash256 TxID, ref CTransaction tx)
360 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
362 if (QueryTx.Count == 1)
364 return QueryTx[0].ReadFromFile(ref reader, out tx);
372 public bool GetBlock(ScryptHash256 blockHash, ref CBlock block)
374 var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
376 if (QueryBlock.Count == 1)
378 return QueryBlock[0].ReadFromFile(ref reader, out block);
386 public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
388 strBlockFile = BlockFile;
390 // TODO: Rewrite completely.
392 var QueryGet = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] desc limit 1");
396 if (QueryGet.Count() == 1)
398 var res = QueryGet.First();
399 nOffset = res.nBlockPos + res.nBlockSize;
402 var buffer = new byte[1000000]; // Max block size is 1Mb
403 var intBuffer = new byte[4];
405 var fStream2 = File.OpenRead(strBlockFile);
406 var readerForBlocks = new BinaryReader(fStream2).BaseStream;
408 readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
410 dbConn.BeginTransaction();
412 while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
414 var nMagic = BitConverter.ToUInt32(buffer, 0);
415 if (nMagic != 0xe5e9e8e4)
417 throw new Exception("Incorrect magic number.");
420 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
423 throw new Exception("BLKSZ EOF");
426 var nBlockSize = BitConverter.ToInt32(buffer, 0);
428 nOffset = readerForBlocks.Position;
430 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
432 if (nBytesRead == 0 || nBytesRead != nBlockSize)
434 throw new Exception("BLK EOF");
437 var block = new CBlock(buffer);
439 if (block.header.merkleRoot != block.hashMerkleRoot)
441 Console.WriteLine("MerkleRoot mismatch: {0} vs. {1} in block {2}.", block.header.merkleRoot, block.hashMerkleRoot, block.header.Hash);
445 if (block.IsProofOfStake && !block.SignatureOK)
447 Console.WriteLine("Proof-of-Stake signature is invalid for block {0}.", block.header.Hash);
451 var NewStoreItem = new CBlockStoreItem()
453 BlockTypeFlag = block.IsProofOfStake ? BlockType.PROOF_OF_STAKE : BlockType.PROOF_OF_WORK,
455 nBlockSize = nBlockSize
458 var NewChainNode = new CChainNode()
460 blockHeader = block.header,
461 blockType = NewStoreItem.BlockTypeFlag
464 var HeaderHash = NewStoreItem.FillHeader(block.header);
466 int nCount = blockMap.Count;
467 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, HeaderHash, DateTime.Now); // Commit on each 100th block
469 if (nCount % 100 == 0)
471 Console.WriteLine("Commit...");
473 dbConn.BeginTransaction();
476 if (!blockMap.TryAdd(HeaderHash, NewChainNode))
478 Console.WriteLine("Duplicate block: {0}", HeaderHash);
482 // Verify transactions
484 foreach (var tx in block.vtx)
486 if (!tx.VerifyScripts())
488 Console.WriteLine("Error checking tx {0}", tx.Hash);
493 dbConn.Insert(NewStoreItem);
495 for (int i = 0; i < block.vtx.Length; i++)
497 // Handle trasactions
499 var nTxOffset = nOffset + block.GetTxOffset(i);
500 TxType txnType = TxType.TX_USER;
502 if (block.vtx[i].IsCoinBase)
504 txnType = TxType.TX_COINBASE;
506 else if (block.vtx[i].IsCoinStake)
508 txnType = TxType.TX_COINSTAKE;
511 var NewTxItem = new CTransactionStoreItem()
513 TransactionHash = block.vtx[i].Hash,
514 BlockHash = HeaderHash,
516 nTxSize = block.vtx[i].Size,
520 dbConn.Insert(NewTxItem);
534 public void Dispose()
537 GC.SuppressFinalize(this);
540 protected virtual void Dispose(bool disposing)
546 // Free other state (managed objects).