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;
11 using System.Collections.Generic;
16 /// Block headers table
18 [Table("BlockStorage")]
19 public class CBlockStoreItem
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 byte[] nextHash { get; set; }
71 public BlockType BlockTypeFlag { get; set; }
76 public long nStakeModifier { get; set; }
81 public byte[] ChainTrust { get; set; }
84 /// Proof-of-Stake hash
86 public byte[] hashProofOfStake { get; set; }
91 public uint nHeight { get; set; }
94 /// Block position in file
96 public long nBlockPos { get; set; }
99 /// Block size in bytes
101 public int nBlockSize { get; set; }
104 /// Fill database item with data from given block header.
106 /// <param name="header">Block header</param>
107 /// <returns>Header hash</returns>
108 public uint256 FillHeader(CBlockHeader header)
111 Hash = _hash = header.Hash;
113 nVersion = header.nVersion;
114 prevHash = header.prevHash;
115 merkleRoot = header.merkleRoot;
116 nTime = header.nTime;
117 nBits = header.nBits;
118 nNonce = header.nNonce;
124 /// Reconstruct block header from item data.
126 public CBlockHeader BlockHeader
130 CBlockHeader header = new CBlockHeader();
132 header.nVersion = nVersion;
133 header.prevHash = prevHash;
134 header.merkleRoot = merkleRoot;
135 header.nTime = nTime;
136 header.nBits = nBits;
137 header.nNonce = nNonce;
144 /// Read block from file.
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)
151 var buffer = new byte[nBlockSize];
156 reader.Seek(nBlockPos, SeekOrigin.Begin);
158 if (nBlockSize != reader.Read(buffer, 0, nBlockSize))
163 block = new CBlock(buffer);
172 catch (BlockException)
174 // Constructor exception
180 /// Writes given block to file and prepares cursor object for insertion into the database.
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)
189 byte[] blockBytes = block;
191 var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber);
192 var blkLenBytes = BitConverter.GetBytes(blockBytes.Length);
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);
199 // Save block size and current position in the block cursor fields.
200 nBlockPos = writer.Position;
201 nBlockSize = blockBytes.Length;
203 // Write block and flush the stream.
204 writer.Write(blockBytes, 0, blockBytes.Length);
216 // Some serialization error
222 /// Previous block cursor
225 public CBlockStoreItem prev {
226 get { return CBlockStore.Instance.GetCursor(prevHash); }
230 /// Next block cursor
233 public CBlockStoreItem next
235 get { return CBlockStore.Instance.GetCursor(nextHash); }
241 get { return (next != null); }
245 /// STake modifier generation flag
248 public bool GeneratedStakeModifier
250 get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; }
254 /// Stake entropy bit
257 public uint StakeEntropyBit
259 get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); }
263 /// Sets stake modifier and flag.
265 /// <param name="nModifier">New stake modifier.</param>
266 /// <param name="fGeneratedStakeModifier">Set generation flag?</param>
267 public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier)
269 nStakeModifier = nModifier;
270 if (fGeneratedStakeModifier)
271 BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER;
277 /// <param name="nEntropyBit">Entropy bit value (0 or 1).</param>
278 /// <returns>False if value is our of range.</returns>
279 public bool SetStakeEntropyBit(byte nEntropyBit)
283 BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0);
288 /// Set proof-of-stake flag.
290 public void SetProofOfStake()
292 BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE;
296 /// Block has no proof-of-stake flag.
299 public bool IsProofOfWork
301 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; }
305 /// Block has proof-of-stake flag set.
308 public bool IsProofOfStake
310 get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; }
314 /// Chain trust score.
317 public uint256 nChainTrust {
320 if (ChainTrust.Length != 32)
322 byte[] tmp = ChainTrust;
323 Array.Resize(ref tmp, 32);
329 set { ChainTrust = Interop.TrimArray(value); }
333 /// Block trust score.
336 public uint256 nBlockTrust
341 nTarget.Compact = nBits;
344 if (nTime < NetUtils.nChainChecksSwitchTime)
346 return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1;
351 // Calculate work amount for block
352 var nPoWTrust = NetUtils.nPoWBase / (nTarget + 1);
354 // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low
355 nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust;
357 // Return nPoWTrust for the first 12 blocks
358 if (prev == null || prev.nHeight < 12)
361 CBlockStoreItem currentIndex = prev;
365 var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
367 // Return 1/3 of score if parent block is not the PoW block
368 if (!prev.IsProofOfWork)
370 return nNewTrust / 3;
375 // Check last 12 blocks type
376 while (prev.nHeight - currentIndex.nHeight < 12)
378 if (currentIndex.IsProofOfWork)
382 currentIndex = currentIndex.prev;
385 // Return 1/3 of score if less than 3 PoW blocks found
388 return nNewTrust / 3;
395 var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust;
397 // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks
398 if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake)
400 return nPoWTrust + (2 * nLastBlockTrust / 3);
405 // Check last 12 blocks type
406 while (prev.nHeight - currentIndex.nHeight < 12)
408 if (currentIndex.IsProofOfStake)
412 currentIndex = currentIndex.prev;
415 // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found
418 return nPoWTrust + (2 * nLastBlockTrust / 3);
421 nTarget.Compact = prev.nBits;
428 var nNewTrust = (new uint256(1) << 256) / (nTarget + 1);
430 // Return nPoWTrust + full trust score for previous block nBits
431 return nPoWTrust + nNewTrust;
441 public enum BlockType
443 BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block
444 BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier
445 BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier
449 /// Transaction type.
458 [Table("TransactionStorage")]
459 public class CTransactionStoreItem
465 public byte[] TransactionHash { get; set; }
470 [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
471 public byte[] BlockHash { get; set; }
474 /// Transaction type flag
476 public TxType txType { get; set; }
479 /// Tx position in file
481 public long nTxPos { get; set; }
486 public int nTxSize { get; set; }
489 /// Read transaction from file.
491 /// <param name="reader">Stream with read access.</param>
492 /// <param name="tx">CTransaction reference.</param>
493 /// <returns>Result</returns>
494 public bool ReadFromFile(ref Stream reader, out CTransaction tx)
496 var buffer = new byte[CTransaction.nMaxTxSize];
501 reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
503 if (nTxSize != reader.Read(buffer, 0, nTxSize))
508 tx = new CTransaction(buffer);
517 catch (TransactionConstructorException)
525 public class CBlockStore : IDisposable
527 public const uint nMagicNumber = 0xe5e9e8e4;
529 private bool disposed = false;
530 private object LockObj = new object();
533 /// SQLite connection object.
535 private SQLiteConnection dbConn;
540 private string strBlockFile;
543 /// Index database file.
545 private string strDbFile;
548 /// Map of block tree nodes.
550 private ConcurrentDictionary<uint256, CBlockStoreItem> blockMap = new ConcurrentDictionary<uint256, CBlockStoreItem>();
553 /// Orphaned blocks map.
555 private ConcurrentDictionary<uint256, CBlock> orphanMap = new ConcurrentDictionary<uint256, CBlock>();
556 private ConcurrentDictionary<uint256, CBlock> orphanMapByPrev = new ConcurrentDictionary<uint256, CBlock>();
559 /// Map of unspent items.
561 private ConcurrentDictionary<uint256, CTransactionStoreItem> txMap = new ConcurrentDictionary<uint256, CTransactionStoreItem>();
563 public static CBlockStore Instance;
566 /// Block file stream with read access
568 private Stream fStreamReadWrite;
571 /// Init the block storage manager.
573 /// <param name="IndexDB">Path to index database</param>
574 /// <param name="BlockFile">Path to block file</param>
575 public CBlockStore(string IndexDB = "blockstore.dat", string BlockFile = "blk0001.dat")
578 strBlockFile = BlockFile;
580 bool firstInit = !File.Exists(strDbFile);
581 dbConn = new SQLiteConnection(new SQLitePlatformGeneric(), strDbFile);
583 fStreamReadWrite = File.Open(strBlockFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
592 dbConn.CreateTable<CBlockStoreItem>(CreateFlags.AutoIncPK);
593 dbConn.CreateTable<CTransactionStoreItem>(CreateFlags.ImplicitPK);
595 var genesisBlock = new CBlock(
597 "01000000" + // nVersion=1
598 "0000000000000000000000000000000000000000000000000000000000000000" + // prevhash is zero
599 "7b0502ad2f9f675528183f83d6385794fbcaa914e6d385c6cb1d866a3b3bb34c" + // merkle root
600 "398e1151" + // nTime=1360105017
601 "ffff0f1e" + // nBits=0x1e0fffff
602 "d3091800" + // nNonce=1575379
604 "01000000" + // nVersion=1
605 "398e1151" + // nTime=1360105017
607 "0000000000000000000000000000000000000000000000000000000000000000" + // input txid is zero
608 "ffffffff" + // n=uint.maxValue
609 "4d" + // scriptSigLen=77
610 "04ffff001d020f274468747470733a2f2f626974636f696e74616c6b2e6f72672f696e6465782e7068703f746f7069633d3133343137392e6d736731353032313936236d736731353032313936" + // scriptSig
611 "ffffffff" + // nSequence=uint.maxValue
613 "0000000000000000" + // nValue=0
614 "00" + // scriptPubkeyLen=0
615 "00000000" + // nLockTime=0
619 // Write block to file.
620 var itemTemplate = new CBlockStoreItem()
625 itemTemplate.FillHeader(genesisBlock.header);
627 if (!AddItemToIndex(ref itemTemplate, ref genesisBlock))
629 throw new Exception("Unable to write genesis block");
635 var blockTreeItems = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] order by [ItemId] asc");
637 // Init list of block items
638 foreach (var item in blockTreeItems)
640 blockMap.TryAdd(item.Hash, item);
645 public bool GetTransaction(uint256 TxID, ref CTransaction tx)
647 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
648 var QueryTx = dbConn.Query<CTransactionStoreItem>("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
650 if (QueryTx.Count == 1)
652 return QueryTx[0].ReadFromFile(ref reader, out tx);
660 private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
662 var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
663 uint256 blockHash = itemTemplate.Hash;
665 if (blockMap.ContainsKey(blockHash))
667 // Already have this block.
671 // Compute chain trust score
672 itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
674 if (!itemTemplate.SetStakeEntropyBit(Entropy.GetStakeEntropyBit(itemTemplate.nHeight, blockHash)))
676 return false; // SetStakeEntropyBit() failed
679 // TODO: set stake entropy bit, record proof-of-stake hash value
681 // TODO: compute stake modifier
684 if (block.IsProofOfStake)
686 itemTemplate.SetProofOfStake();
689 if (!itemTemplate.WriteToFile(ref writer, ref block))
694 dbConn.Insert(itemTemplate);
696 // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
697 for (int i = 0; i < block.vtx.Length; i++)
699 // Handle trasactions
701 if (!block.vtx[i].VerifyScripts())
706 var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
707 TxType txnType = TxType.TX_USER;
709 if (block.vtx[i].IsCoinBase)
711 txnType = TxType.TX_COINBASE;
713 else if (block.vtx[i].IsCoinStake)
715 txnType = TxType.TX_COINSTAKE;
718 var NewTxItem = new CTransactionStoreItem()
720 TransactionHash = block.vtx[i].Hash,
721 BlockHash = blockHash,
723 nTxSize = block.vtx[i].Size,
727 dbConn.Insert(NewTxItem);
730 return blockMap.TryAdd(blockHash, itemTemplate);
733 public bool AcceptBlock(ref CBlock block)
735 uint256 nHash = block.header.Hash;
737 if (blockMap.ContainsKey(nHash))
739 // Already have this block.
743 CBlockStoreItem prevBlockCursor = null;
744 if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
746 // Unable to get the cursor.
750 var prevBlockHeader = prevBlockCursor.BlockHeader;
752 // TODO: proof-of-work/proof-of-stake verification
753 uint nHeight = prevBlockCursor.nHeight + 1;
755 // Check timestamp against prev
756 if (NetUtils.FutureDrift(block.header.nTime) < prevBlockHeader.nTime)
758 // block's timestamp is too early
762 // Check that all transactions are finalized
763 foreach (var tx in block.vtx)
765 if (!tx.IsFinal(nHeight, block.header.nTime))
771 // TODO: Enforce rule that the coinbase starts with serialized block height
773 // Write block to file.
774 var itemTemplate = new CBlockStoreItem()
779 itemTemplate.FillHeader(block.header);
781 if (!AddItemToIndex(ref itemTemplate, ref block))
789 public bool GetBlock(uint256 blockHash, ref CBlock block)
791 var reader = new BinaryReader(fStreamReadWrite).BaseStream;
793 var QueryBlock = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
795 if (QueryBlock.Count == 1)
797 return QueryBlock[0].ReadFromFile(ref reader, out block);
806 /// Get block cursor from map.
808 /// <param name="blockHash">block hash</param>
809 /// <returns>Cursor or null</returns>
810 public CBlockStoreItem GetCursor(uint256 blockHash)
814 // Genesis block has zero prevHash and no parent.
818 // First, check our block map.
819 CBlockStoreItem item = null;
820 if (blockMap.TryGetValue(blockHash, out item))
825 // Trying to get cursor from the database.
826 var QueryBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])blockHash);
828 if (QueryBlockCursor.Count == 1)
830 return QueryBlockCursor[0];
837 public bool ProcessBlock(ref CBlock block)
839 var blockHash = block.header.Hash;
841 if (blockMap.ContainsKey(blockHash))
843 // We already have this block.
847 if (orphanMap.ContainsKey(blockHash))
849 // We already have block in the list of orphans.
853 // TODO: Limited duplicity on stake and reserialization of block signature
855 if (!block.CheckBlock(true, true, true))
857 // Preliminary checks failure.
861 if (block.IsProofOfStake)
863 if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
865 // Proof-of-Stake signature validation failure.
869 // TODO: proof-of-stake validation
872 // TODO: difficulty verification
874 // If don't already have its previous block, shunt it off to holding area until we get it
875 if (!blockMap.ContainsKey(block.header.prevHash))
877 if (block.IsProofOfStake)
879 // TODO: limit duplicity on stake
882 var block2 = new CBlock(block);
883 orphanMap.TryAdd(blockHash, block2);
884 orphanMapByPrev.TryAdd(blockHash, block2);
889 // Store block to disk
890 if (!AcceptBlock(ref block))
896 // Recursively process any orphan blocks that depended on this one
897 var orphansQueue = new List<uint256>();
898 orphansQueue.Add(blockHash);
900 for (int i = 0; i < orphansQueue.Count; i++)
902 var hashPrev = orphansQueue[i];
904 foreach (var pair in orphanMap)
906 var orphanBlock = pair.Value;
908 if (orphanBlock.header.prevHash == blockHash)
910 if (AcceptBlock(ref orphanBlock))
912 orphansQueue.Add(pair.Key);
916 orphanMap.TryRemove(pair.Key, out dummy1);
921 orphanMap.TryRemove(hashPrev, out dummy2);
927 public bool ParseBlockFile(string BlockFile = "bootstrap.dat")
929 // TODO: Rewrite completely.
933 var buffer = new byte[CBlock.nMaxBlockSize]; // Max block size is 1Mb
934 var intBuffer = new byte[4];
936 var fStream2 = File.OpenRead(BlockFile);
937 var readerForBlocks = new BinaryReader(fStream2).BaseStream;
939 readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
941 dbConn.BeginTransaction();
943 while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
945 var nMagic = BitConverter.ToUInt32(buffer, 0);
946 if (nMagic != 0xe5e9e8e4)
948 throw new Exception("Incorrect magic number.");
951 var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
954 throw new Exception("BLKSZ EOF");
957 var nBlockSize = BitConverter.ToInt32(buffer, 0);
959 nOffset = readerForBlocks.Position;
961 nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
963 if (nBytesRead == 0 || nBytesRead != nBlockSize)
965 throw new Exception("BLK EOF");
968 var block = new CBlock(buffer);
969 var hash = block.header.Hash;
971 if (blockMap.ContainsKey(hash))
976 if (!ProcessBlock(ref block))
978 throw new Exception("Invalid block: " + block.header.Hash);
981 int nCount = blockMap.Count;
982 Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
984 if (nCount % 100 == 0 && nCount != 0)
986 Console.WriteLine("Commit...");
988 dbConn.BeginTransaction();
1002 public void Dispose()
1005 GC.SuppressFinalize(this);
1008 protected virtual void Dispose(bool disposing)
1014 // Free other state (managed objects).
1016 fStreamReadWrite.Dispose();