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 timestamp
///
[Column("nTime")]
public uint nTime { 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, (int)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 uint nTxSize
{
get { return (uint)VarInt.DecodeVarInt(TxSize); }
private set { TxSize = VarInt.EncodeVarInt(value); }
}
[Ignore]
public bool IsCoinBase
{
get { return TransactionFlags == TxFlags.TX_COINBASE; }
}
[Ignore]
public bool IsCoinStake
{
get { return TransactionFlags == TxFlags.TX_COINSTAKE; }
}
public CMerkleNode()
{
}
public CMerkleNode(CTransaction tx)
{
nTime = tx.nTime;
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)
{
nTime = tx.nTime;
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
{
///
/// Outpoint identifier.
///
[PrimaryKey, AutoIncrement]
public long nOutpointID { get; set; }
///
/// 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; }
///
/// To avoid awkwardness of sqlite wrapper.
///
///
public TxOutItem getTxOutItem()
{
return new TxOutItem()
{
nOutpointID = nOutpointID,
nMerkleNodeID = nMerkleNodeID,
outputFlags = outputFlags,
OutputNumber = OutputNumber,
OutputValue = OutputValue,
scriptPubKey = scriptPubKey
};
}
}
}