using SQLite.Net.Attributes; using SQLiteNetExtensions.Attributes; using System; using System.IO; namespace Novacoin { [Table("ChainState")] public class ChainState { [PrimaryKey, AutoIncrement] public long itemId { get; set; } /// /// Hash of top block in the best chain /// public byte[] HashBestChain { get; set; } /// /// Total trust score of best chain /// public byte[] BestChainTrust { get; set; } public uint nBestHeight { get; set; } [Ignore] public uint256 nBestChainTrust { get { return BestChainTrust; } set { BestChainTrust = value; } } [Ignore] public uint256 nHashBestChain { get { return HashBestChain; } set { HashBestChain = value; } } } [Table("BlockStorage")] public class CBlockStoreItem { #region IBlockStorageItem /// /// Item ID in the database /// [PrimaryKey, AutoIncrement] public long ItemID { get; set; } /// /// PBKDF2+Salsa20 of block hash /// [Unique] public byte[] Hash { get; set; } /// /// Version of block schema /// [Column("nVersion")] public uint nVersion { get; set; } /// /// Previous block hash. /// [Column("prevHash")] public byte[] prevHash { get; set; } /// /// Merkle root hash. /// [Column("merkleRoot")] public byte[] merkleRoot { get; set; } /// /// Block timestamp. /// [Column("nTime")] public uint nTime { get; set; } /// /// Compressed difficulty representation. /// [Column("nBits")] public uint nBits { get; set; } /// /// Nonce counter. /// [Column("nNonce")] public uint nNonce { get; set; } /// /// Next block hash. /// [Column("nextHash")] public byte[] nextHash { get; set; } /// /// Block type flags /// [Column("BlockTypeFlag")] public BlockType BlockTypeFlag { get; set; } /// /// Stake modifier /// [Column("nStakeModifier")] public long nStakeModifier { get; set; } /// /// Proof-of-Stake hash /// [Column("hashProofOfStake")] public byte[] hashProofOfStake { get; set; } /// /// Stake generation outpoint. /// [Column("prevoutStake")] public byte[] prevoutStake { get; set; } /// /// Stake generation time. /// [Column("nStakeTime")] public uint nStakeTime { get; set; } /// /// Block height, encoded in VarInt format /// [Column("nHeight")] public uint nHeight { get; set; } /// /// Chain trust score, serialized and trimmed uint256 representation. /// [Column("ChainTrust")] public byte[] ChainTrust { get; set; } /// /// Block position in file, encoded in VarInt format /// [Column("BlockPos")] public byte[] BlockPos { get; set; } /// /// Block size in bytes, encoded in VarInt format /// [Column("BlockSize")] public byte[] BlockSize { get; set; } #endregion /// /// Accessor and mutator for BlockPos value. /// [Ignore] public long nBlockPos { get { return (long)VarInt.DecodeVarInt(BlockPos); } set { BlockPos = VarInt.EncodeVarInt(value); } } /// /// Accessor and mutator for BlockSize value. /// [Ignore] public int nBlockSize { get { return (int)VarInt.DecodeVarInt(BlockSize); } set { BlockSize = VarInt.EncodeVarInt(value); } } /// /// Fill database item with data from given block header. /// /// Block header /// Header hash public uint256 FillHeader(CBlockHeader header) { uint256 _hash = header.Hash; Hash = _hash; nVersion = header.nVersion; prevHash = header.prevHash; merkleRoot = header.merkleRoot; nTime = header.nTime; nBits = header.nBits; nNonce = header.nNonce; return _hash; } /// /// Reconstruct block header from item data. /// public CBlockHeader BlockHeader { get { CBlockHeader header = new CBlockHeader(); header.nVersion = nVersion; header.prevHash = prevHash; header.merkleRoot = merkleRoot; header.nTime = nTime; header.nBits = nBits; header.nNonce = nNonce; return header; } } /// /// Read block from file. /// /// Stream with read access. /// CBlock reference. /// Result public bool ReadFromFile(ref Stream reader, out CBlock block) { var buffer = new byte[nBlockSize]; block = null; try { reader.Seek(nBlockPos, SeekOrigin.Begin); if (nBlockSize != reader.Read(buffer, 0, nBlockSize)) { return false; } block = new CBlock(buffer); return true; } catch (IOException) { // I/O error return false; } catch (BlockException) { // Constructor exception return false; } } /// /// Writes given block to file and prepares cursor object for insertion into the database. /// /// Stream with write access. /// CBlock reference. /// Result public bool WriteToFile(ref Stream writer, ref CBlock block) { try { byte[] blockBytes = block; var magicBytes = BitConverter.GetBytes(CBlockStore.nMagicNumber); var blkLenBytes = BitConverter.GetBytes(blockBytes.Length); // Seek to the end and then append magic bytes there. writer.Seek(0, SeekOrigin.End); writer.Write(magicBytes, 0, magicBytes.Length); writer.Write(blkLenBytes, 0, blkLenBytes.Length); // Save block size and current position in the block cursor fields. nBlockPos = writer.Position; nBlockSize = blockBytes.Length; // Write block and flush the stream. writer.Write(blockBytes, 0, blockBytes.Length); writer.Flush(); return true; } catch (IOException) { // I/O error return false; } catch (Exception) { // Some serialization error return false; } } /// /// Previous block cursor /// [Ignore] public CBlockStoreItem prev { get { return CBlockStore.Instance.GetMapCursor(prevHash); } } /// /// Next block cursor /// [Ignore] public CBlockStoreItem next { get { if (nextHash == null) { return null; } return CBlockStore.Instance.GetMapCursor(nextHash); } set { nextHash = value.Hash; CBlockStore.Instance.UpdateMapCursor(this); } } [Ignore] bool IsInMainChain { get { return (next != null); } } /// /// STake modifier generation flag /// [Ignore] public bool GeneratedStakeModifier { get { return (BlockTypeFlag & BlockType.BLOCK_STAKE_MODIFIER) != 0; } } /// /// Stake entropy bit /// [Ignore] public uint StakeEntropyBit { get { return ((uint)(BlockTypeFlag & BlockType.BLOCK_STAKE_ENTROPY) >> 1); } } /// /// Sets stake modifier and flag. /// /// New stake modifier. /// Set generation flag? public void SetStakeModifier(long nModifier, bool fGeneratedStakeModifier) { nStakeModifier = nModifier; if (fGeneratedStakeModifier) BlockTypeFlag |= BlockType.BLOCK_STAKE_MODIFIER; } /// /// Set entropy bit. /// /// Entropy bit value (0 or 1). /// False if value is our of range. public bool SetStakeEntropyBit(byte nEntropyBit) { if (nEntropyBit > 1) return false; BlockTypeFlag |= (nEntropyBit != 0 ? BlockType.BLOCK_STAKE_ENTROPY : 0); return true; } /// /// Set proof-of-stake flag. /// public void SetProofOfStake() { BlockTypeFlag |= BlockType.BLOCK_PROOF_OF_STAKE; } /// /// Block has no proof-of-stake flag. /// [Ignore] public bool IsProofOfWork { get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) == 0; } } /// /// Block has proof-of-stake flag set. /// [Ignore] public bool IsProofOfStake { get { return (BlockTypeFlag & BlockType.BLOCK_PROOF_OF_STAKE) != 0; } } /// /// Block trust score. /// [Ignore] public uint256 nBlockTrust { get { uint256 nTarget = 0; nTarget.Compact = nBits; /* Old protocol */ if (nTime < NetInfo.nChainChecksSwitchTime) { return IsProofOfStake ? (new uint256(1) << 256) / (nTarget + 1) : 1; } /* New protocol */ // Calculate work amount for block var nPoWTrust = NetInfo.nPoWBase / (nTarget + 1); // Set nPowTrust to 1 if we are checking PoS block or PoW difficulty is too low nPoWTrust = (IsProofOfStake || !nPoWTrust) ? 1 : nPoWTrust; // Return nPoWTrust for the first 12 blocks if (prev == null || prev.nHeight < 12) return nPoWTrust; CBlockStoreItem currentIndex = prev; if (IsProofOfStake) { var nNewTrust = (new uint256(1) << 256) / (nTarget + 1); // Return 1/3 of score if parent block is not the PoW block if (!prev.IsProofOfWork) { return nNewTrust / 3; } int nPoWCount = 0; // Check last 12 blocks type while (prev.nHeight - currentIndex.nHeight < 12) { if (currentIndex.IsProofOfWork) { nPoWCount++; } currentIndex = currentIndex.prev; } // Return 1/3 of score if less than 3 PoW blocks found if (nPoWCount < 3) { return nNewTrust / 3; } return nNewTrust; } else { var nLastBlockTrust = prev.nChainTrust - prev.prev.nChainTrust; // Return nPoWTrust + 2/3 of previous block score if two parent blocks are not PoS blocks if (!prev.IsProofOfStake || !prev.prev.IsProofOfStake) { return nPoWTrust + (2 * nLastBlockTrust / 3); } int nPoSCount = 0; // Check last 12 blocks type while (prev.nHeight - currentIndex.nHeight < 12) { if (currentIndex.IsProofOfStake) { nPoSCount++; } currentIndex = currentIndex.prev; } // Return nPoWTrust + 2/3 of previous block score if less than 7 PoS blocks found if (nPoSCount < 7) { return nPoWTrust + (2 * nLastBlockTrust / 3); } nTarget.Compact = prev.nBits; if (!nTarget) { return 0; } var nNewTrust = (new uint256(1) << 256) / (nTarget + 1); // Return nPoWTrust + full trust score for previous block nBits return nPoWTrust + nNewTrust; } } } /// /// Stake modifier checksum. /// public uint nStakeModifierChecksum; /// /// Chain trust score /// [Ignore] public uint256 nChainTrust { get { return Interop.AppendWithZeros(ChainTrust); } set { ChainTrust = Interop.TrimArray(value); } } public long nMint { get; internal set; } public long nMoneySupply { get; internal set; } } /// /// Block type. /// public enum BlockType { BLOCK_PROOF_OF_STAKE = (1 << 0), // is proof-of-stake block BLOCK_STAKE_ENTROPY = (1 << 1), // entropy bit for stake modifier BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier }; /// /// Transaction type. /// public enum TxFlags : byte { TX_COINBASE, TX_COINSTAKE, TX_USER } /// /// Output flags. /// public enum OutputFlags : byte { AVAILABLE, // Unspent output SPENT // Spent output } [Table("MerkleNodes")] public class CMerkleNode { #region IMerkleNode /// /// Node identifier /// [PrimaryKey, AutoIncrement] public long nMerkleNodeID { get; set; } /// /// Reference to parent block database item. /// [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")] public long nParentBlockID { get; set; } /// /// Transaction type flag /// [Column("TransactionFlags")] public TxFlags TransactionFlags { get; set; } /// /// Transaction hash /// [Column("TransactionHash")] public byte[] TransactionHash { get; set; } /// /// Transaction offset from the beginning of block header, encoded in VarInt format. /// [Column("TxOffset")] public byte[] TxOffset { get; set; } /// /// Transaction size, encoded in VarInt format. /// [Column("TxSize")] public byte[] TxSize { get; set; } #endregion /// /// Read transaction from file. /// /// Stream with read access. /// CTransaction reference. /// Result public bool ReadFromFile(ref Stream reader, long nBlockPos, out CTransaction tx) { var buffer = new byte[CTransaction.nMaxTxSize]; tx = null; try { reader.Seek(nBlockPos + nTxOffset, SeekOrigin.Begin); // Seek to transaction offset if (nTxSize != reader.Read(buffer, 0, nTxSize)) { return false; } tx = new CTransaction(buffer); return true; } catch (IOException) { // I/O error return false; } catch (TransactionConstructorException) { // Constructor error return false; } } /// /// Transaction offset accessor /// [Ignore] public long nTxOffset { get { return (long)VarInt.DecodeVarInt(TxOffset); } private set { TxOffset = VarInt.EncodeVarInt(value); } } /// /// Transaction size accessor /// [Ignore] public int nTxSize { get { return (int)VarInt.DecodeVarInt(TxSize); } private set { TxSize = VarInt.EncodeVarInt(value); } } public CMerkleNode(CTransaction tx) { nTxOffset = -1; nParentBlockID = -1; nTxSize = tx.Size; TransactionHash = tx.Hash; if (tx.IsCoinBase) { TransactionFlags |= TxFlags.TX_COINBASE; } else if (tx.IsCoinStake) { TransactionFlags |= TxFlags.TX_COINSTAKE; } else { TransactionFlags |= TxFlags.TX_USER; } } public CMerkleNode(long nBlockId, long nOffset, CTransaction tx) { nParentBlockID = nBlockId; nTxOffset = nOffset; nTxSize = tx.Size; TransactionHash = tx.Hash; if (tx.IsCoinBase) { TransactionFlags |= TxFlags.TX_COINBASE; } else if (tx.IsCoinStake) { TransactionFlags |= TxFlags.TX_COINSTAKE; } else { TransactionFlags |= TxFlags.TX_USER; } } } [Table("Outputs")] public class TxOutItem { /// /// Reference to transaction item. /// [ForeignKey(typeof(CMerkleNode), Name = "nMerkleNodeID")] public long nMerkleNodeID { get; set; } /// /// Output flags /// public OutputFlags outputFlags { get; set; } /// /// Output number in VarInt format. /// public byte[] OutputNumber { get; set; } /// /// Output value in VarInt format. /// public byte[] OutputValue { get; set; } /// /// Second half of script which contains spending instructions. /// public byte[] scriptPubKey { get; set; } /// /// Getter for output number. /// [Ignore] public uint nOut { get { return (uint)VarInt.DecodeVarInt(OutputNumber); } set { OutputNumber = VarInt.EncodeVarInt(value); } } /// /// Getter for output value. /// [Ignore] public ulong nValue { get { return VarInt.DecodeVarInt(OutputValue); } set { OutputValue = VarInt.EncodeVarInt(value); } } /// /// Getter ans setter for IsSpent flag. /// [Ignore] public bool IsSpent { get { return (outputFlags & OutputFlags.SPENT) != 0; } set { outputFlags |= value ? OutputFlags.SPENT : OutputFlags.AVAILABLE; } } } /// /// TxOut + transaction hash /// public class InputsJoin : TxOutItem { public byte[] TransactionHash { get; set; } } }