X-Git-Url: https://git.novaco.in/?a=blobdiff_plain;f=Novacoin%2FCBlockStore.cs;h=c6361218e6bb0ff4b2809d92c5ecd7aef440d685;hb=514b68fc3103fca11d6d06ddb65ed8d2a14507e1;hp=935a4abf127881f5fc5b32ad5931b8a4808e0c73;hpb=217d4232e9feeb787b9163b2ca1e701840d5b65b;p=NovacoinLibrary.git
diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs
index 935a4ab..c636121 100644
--- a/Novacoin/CBlockStore.cs
+++ b/Novacoin/CBlockStore.cs
@@ -28,20 +28,20 @@ using SQLite.Net.Interop;
using SQLite.Net.Platform.Generic;
using SQLiteNetExtensions.Attributes;
using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text;
namespace Novacoin
{
- ///
- /// Block headers table
- ///
[Table("BlockStorage")]
- public class CBlockStoreItem
+ public class CBlockStoreItem : IBlockStorageItem
{
+ #region IBlockStorageItem
///
/// Item ID in the database
///
[PrimaryKey, AutoIncrement]
- public int ItemID { get; set; }
+ public long ItemID { get; set; }
///
/// PBKDF2+Salsa20 of block hash
@@ -52,77 +52,124 @@ namespace Novacoin
///
/// 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; }
///
- /// Stake modifier checksum.
+ /// Proof-of-Stake hash
///
- public uint nStakeModifierChecksum { get; set; }
+ [Column("hashProofOfStake")]
+ public byte[] hashProofOfStake { get; set; }
///
- /// Chain trust score
+ /// Stake generation outpoint.
///
- public byte[] ChainTrust { get; set; }
+ [Column("prevoutStake")]
+ public byte[] prevoutStake { get; set; }
///
- /// Proof-of-Stake hash
+ /// Stake generation time.
///
- public byte[] hashProofOfStake { get; set; }
+ [Column("nStakeTime")]
+ public uint nStakeTime { get; set; }
///
- /// Block height
+ /// Block height, encoded in VarInt format
///
- public uint nHeight { get; set; }
+ [Column("Height")]
+ public byte[] Height { get; set; }
///
- /// Block position in file
+ /// Block position in file, encoded in VarInt format
///
- public long nBlockPos { get; set; }
+ [Column("BlockPos")]
+ public byte[] BlockPos { get; set; }
///
- /// Block size in bytes
+ /// Block size in bytes, encoded in VarInt format
///
- public int nBlockSize { get; set; }
+ [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); }
+ }
+
+ ///
+ /// Accessor and mutator for Height value.
+ ///
+ [Ignore]
+ public uint nHeight
+ {
+ get { return (uint)VarInt.DecodeVarInt(Height); }
+ set { Height = VarInt.EncodeVarInt(value); }
+ }
+
///
/// Fill database item with data from given block header.
@@ -131,8 +178,9 @@ namespace Novacoin
/// Header hash
public uint256 FillHeader(CBlockHeader header)
{
- uint256 _hash;
- Hash = _hash = header.Hash;
+ uint256 _hash = header.Hash;
+
+ Hash = _hash;
nVersion = header.nVersion;
prevHash = header.prevHash;
@@ -256,7 +304,22 @@ namespace Novacoin
[Ignore]
public CBlockStoreItem next
{
- get { return CBlockStore.Instance.GetCursor(nextHash); }
+ get
+ {
+ if (nextHash == null)
+ {
+ return null;
+ }
+
+ return CBlockStore.Instance.GetCursor(nextHash);
+ }
+ set
+ {
+ CBlockStoreItem newCursor = this;
+ newCursor.nextHash = value.Hash;
+
+ CBlockStore.Instance.UpdateCursor(this, ref newCursor);
+ }
}
[Ignore]
@@ -335,25 +398,6 @@ namespace Novacoin
}
///
- /// Chain trust score.
- ///
- [Ignore]
- public uint256 nChainTrust {
- get
- {
- if (ChainTrust.Length != 32)
- {
- byte[] tmp = ChainTrust;
- Array.Resize(ref tmp, 32);
- ChainTrust = tmp;
- }
-
- return ChainTrust;
- }
- set { ChainTrust = Interop.TrimArray(value); }
- }
-
- ///
/// Block trust score.
///
[Ignore]
@@ -457,6 +501,15 @@ namespace Novacoin
}
}
+ ///
+ /// Stake modifier checksum.
+ ///
+ public uint nStakeModifierChecksum;
+
+ ///
+ /// Chain trust score
+ ///
+ public uint256 nChainTrust;
}
///
@@ -472,42 +525,62 @@ namespace Novacoin
///
/// Transaction type.
///
- public enum TxType
+ public enum TxFlags : byte
{
TX_COINBASE,
TX_COINSTAKE,
TX_USER
}
- [Table("TransactionStorage")]
- public class CTransactionStoreItem
+ ///
+ /// Output flags.
+ ///
+ public enum OutputFlags : byte
{
+ AVAILABLE, // Unspent output
+ SPENT // Spent output
+ }
+
+ [Table("MerkleNodes")]
+ public class CMerkleNode : IMerkleNode
+ {
+ #region IMerkleNode
///
- /// Transaction hash
+ /// Node identifier
///
- [PrimaryKey]
- public byte[] TransactionHash { get; set; }
+ [PrimaryKey, AutoIncrement]
+ public long nMerkleNodeID { get; set; }
///
- /// Block hash
+ /// Reference to parent block database item.
///
- [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")]
- public byte[] BlockHash { get; set; }
+ [ForeignKey(typeof(CBlockStoreItem), Name = "ItemId")]
+ public long nParentBlockID { get; set; }
///
/// Transaction type flag
///
- public TxType txType { get; set; }
+ [Column("TransactionFlags")]
+ public TxFlags TransactionFlags { get; set; }
+
+ ///
+ /// Transaction hash
+ ///
+ [Column("TransactionHash")]
+ public byte[] TransactionHash { get; set; }
///
- /// Tx position in file
+ /// Transaction offset from the beginning of block header, encoded in VarInt format.
///
- public long nTxPos { get; set; }
+ [Column("TxOffset")]
+ public byte[] TxOffset { get; set; }
///
- /// Transaction size
+ /// Transaction size, encoded in VarInt format.
///
- public int nTxSize { get; set; }
+ [Column("TxSize")]
+ public byte[] TxSize { get; set; }
+ #endregion
///
/// Read transaction from file.
@@ -515,14 +588,15 @@ namespace Novacoin
/// Stream with read access.
/// CTransaction reference.
/// Result
- public bool ReadFromFile(ref Stream reader, out CTransaction tx)
+ public bool ReadFromFile(ref Stream reader, long nBlockPos, out CTransaction tx)
{
var buffer = new byte[CTransaction.nMaxTxSize];
+
tx = null;
try
{
- reader.Seek(nTxPos, SeekOrigin.Begin); // Seek to transaction offset
+ reader.Seek(nBlockPos + nTxOffset, SeekOrigin.Begin); // Seek to transaction offset
if (nTxSize != reader.Read(buffer, 0, nTxSize))
{
@@ -544,6 +618,80 @@ namespace Novacoin
return false;
}
}
+
+ ///
+ /// Transaction offset accessor
+ ///
+ [Ignore]
+ public long nTxOffset
+ {
+ get { return (long) VarInt.DecodeVarInt(TxOffset); }
+ }
+
+ ///
+ /// Transaction size accessor
+ ///
+ [Ignore]
+ public int nTxSize
+ {
+ get { return (int)VarInt.DecodeVarInt(TxSize); }
+ }
+
+ }
+
+ [Table("Outputs")]
+ public class TxOutItem : ITxOutItem
+ {
+ ///
+ /// 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.
+ ///
+ public uint nOut
+ {
+ get { return (uint)VarInt.DecodeVarInt(OutputNumber); }
+ }
+
+ ///
+ /// Getter for output value.
+ ///
+ public ulong nValue
+ {
+ get { return VarInt.DecodeVarInt(OutputValue); }
+ }
+
+ ///
+ /// Getter ans setter for IsSpent flag.
+ ///
+ public bool IsSpent
+ {
+ get { return (outputFlags & OutputFlags.SPENT) != 0; }
+ set { outputFlags |= value ? OutputFlags.SPENT : OutputFlags.AVAILABLE; }
+ }
}
public class CBlockStore : IDisposable
@@ -570,6 +718,8 @@ namespace Novacoin
///
/// Map of block tree nodes.
+ ///
+ /// blockHash => CBlockStoreItem
///
private ConcurrentDictionary blockMap = new ConcurrentDictionary();
@@ -580,16 +730,48 @@ namespace Novacoin
private ConcurrentDictionary orphanMapByPrev = new ConcurrentDictionary();
///
- /// Map of unspent items.
+ /// Unconfirmed transactions.
+ ///
+ /// TxID => Transaction
///
- private ConcurrentDictionary txMap = new ConcurrentDictionary();
+ private ConcurrentDictionary mapUnconfirmedTx = new ConcurrentDictionary();
+ ///
+ /// Map of the proof-of-stake hashes. This is necessary for stake duplication checks.
+ ///
private ConcurrentDictionary mapProofOfStake = new ConcurrentDictionary();
- public static CBlockStore Instance;
+
+ private ConcurrentDictionary mapStakeSeen = new ConcurrentDictionary();
+ private ConcurrentDictionary mapStakeSeenOrphan = new ConcurrentDictionary();
+
+ ///
+ /// Trust score for the longest chain.
+ ///
+ private uint256 nBestChainTrust = 0;
+
+ ///
+ /// Top block of the best chain.
+ ///
+ private uint256 nHashBestChain = 0;
+
+ ///
+ /// Cursor which is pointing us to the end of best chain.
+ ///
+ private CBlockStoreItem bestBlockCursor = null;
+
+ ///
+ /// Cursor which is always pointing us to genesis block.
+ ///
+ private CBlockStoreItem genesisBlockCursor = null;
+
+ ///
+ /// Current and the only instance of block storage manager. Should be a property with private setter though it's enough for the beginning.
+ ///
+ public static CBlockStore Instance = null;
///
- /// Block file stream with read access
+ /// Block file stream with read/write access
///
private Stream fStreamReadWrite;
@@ -616,7 +798,8 @@ namespace Novacoin
{
// Create tables
dbConn.CreateTable(CreateFlags.AutoIncPK);
- dbConn.CreateTable(CreateFlags.ImplicitPK);
+ dbConn.CreateTable(CreateFlags.AutoIncPK);
+ dbConn.CreateTable(CreateFlags.ImplicitPK);
var genesisBlock = new CBlock(
Interop.HexToArray(
@@ -664,18 +847,25 @@ namespace Novacoin
foreach (var item in blockTreeItems)
{
blockMap.TryAdd(item.Hash, item);
+
+ if (item.IsProofOfStake)
+ {
+ // build mapStakeSeen
+ mapStakeSeen.TryAdd(item.prevoutStake, item.nStakeTime);
+ }
}
}
}
- public bool GetTransaction(uint256 TxID, ref CTransaction tx)
+ public bool GetTxOutCursor(COutPoint outpoint, ref TxOutItem txOutCursor)
{
- var reader = new BinaryReader(fStreamReadWrite).BaseStream;
- var QueryTx = dbConn.Query("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
+ var queryResults = dbConn.Query("select o.* from [Outputs] o left join [MerkleNodes] m on (m.nMerkleNodeID = o.nMerkleNodeID) where m.[TransactionHash] = ?", (byte[])outpoint.hash);
- if (QueryTx.Count == 1)
+ if (queryResults.Count == 1)
{
- return QueryTx[0].ReadFromFile(ref reader, out tx);
+ txOutCursor = queryResults[0];
+
+ return true;
}
// Tx not found
@@ -683,6 +873,47 @@ namespace Novacoin
return false;
}
+ public bool FetchInputs(ref CTransaction tx, ref Dictionary queued, out TxOutItem[] inputs, bool IsBlock, out bool Invalid)
+ {
+ Invalid = true;
+ inputs = null;
+
+ StringBuilder queryBuilder = new StringBuilder();
+
+ queryBuilder.Append("select o.* from [Outputs] o left join [MerkleNodes] m on (m.[nMerkleNodeID] = o.[nMerkleNodeID]) where ");
+
+ for (var i = 0; i < tx.vin.Length; i++)
+ {
+ queryBuilder.AppendFormat(" {0} (m.[TransactionHash] = x'{1}' and o.[OutputNumber] = x'{2}')",
+ (i > 0 ? "or" : string.Empty), Interop.ToHex(tx.vin[i].prevout.hash),
+ Interop.ToHex(VarInt.EncodeVarInt(tx.vin[i].prevout.n)
+ ));
+ }
+
+ var queryResults = dbConn.Query(queryBuilder.ToString());
+
+ if (queryResults.Count < tx.vin.Length)
+ {
+ // It seems than some transactions are being spent in the same block.
+
+ if (IsBlock)
+ {
+
+ }
+ else
+ {
+ // TODO: use mapUnconfirmed
+
+ return false;
+ }
+ }
+
+ inputs = queryResults.ToArray();
+ Invalid = false;
+
+ return true;
+ }
+
private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
{
var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
@@ -706,16 +937,14 @@ namespace Novacoin
if (itemTemplate.IsProofOfStake)
{
uint256 hashProofOfStake;
- if (!CBlockStore.Instance.GetProofOfStakeHash(blockHash, out hashProofOfStake))
+ if (!GetProofOfStakeHash(blockHash, out hashProofOfStake))
{
return false; // hashProofOfStake not found
}
itemTemplate.hashProofOfStake = hashProofOfStake;
}
- // TODO: compute stake modifier
-
- // ppcoin: compute stake modifier
+ // compute stake modifier
long nStakeModifier = 0;
bool fGeneratedStakeModifier = false;
if (!StakeModifier.ComputeNextStakeModifier(itemTemplate, ref nStakeModifier, ref fGeneratedStakeModifier))
@@ -732,6 +961,9 @@ namespace Novacoin
if (block.IsProofOfStake)
{
itemTemplate.SetProofOfStake();
+
+ itemTemplate.prevoutStake = block.vtx[1].vin[0].prevout;
+ itemTemplate.nStakeTime = block.vtx[1].nTime;
}
if (!itemTemplate.WriteToFile(ref writer, ref block))
@@ -739,43 +971,136 @@ namespace Novacoin
return false;
}
- dbConn.Insert(itemTemplate);
+ if (dbConn.Insert(itemTemplate) == 0 || !blockMap.TryAdd(blockHash, itemTemplate))
+ {
+ return false;
+ }
- // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually.
- for (int i = 0; i < block.vtx.Length; i++)
+ if (itemTemplate.nChainTrust > nBestChainTrust)
{
- // Handle trasactions
+ // New best chain
+
+ // TODO: SetBestChain implementation
- if (!block.vtx[i].VerifyScripts())
+ /*
+ if (!SetBestChain(ref itemTemplate))
{
- return false;
+ return false; // SetBestChain failed.
}
+ */
+ }
- var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i);
- TxType txnType = TxType.TX_USER;
+ return true;
+ }
- if (block.vtx[i].IsCoinBase)
+ private bool SetBestChain(ref CBlockStoreItem cursor)
+ {
+ dbConn.BeginTransaction();
+
+ uint256 hashBlock = cursor.Hash;
+
+ if (genesisBlockCursor == null && hashBlock == NetUtils.nHashGenesisBlock)
+ {
+ genesisBlockCursor = cursor;
+ }
+ else if (nHashBestChain == (uint256)cursor.prevHash)
+ {
+ if (!SetBestChainInner(cursor))
{
- txnType = TxType.TX_COINBASE;
+ return false;
}
- else if (block.vtx[i].IsCoinStake)
+ }
+ else
+ {
+ // the first block in the new chain that will cause it to become the new best chain
+ CBlockStoreItem cursorIntermediate = cursor;
+
+ // list of blocks that need to be connected afterwards
+ List secondary = new List();
+
+ // Reorganize is costly in terms of db load, as it works in a single db transaction.
+ // Try to limit how much needs to be done inside
+ while (cursorIntermediate.prev != null && cursorIntermediate.prev.nChainTrust > bestBlockCursor.nChainTrust)
{
- txnType = TxType.TX_COINSTAKE;
+ secondary.Add(cursorIntermediate);
+ cursorIntermediate = cursorIntermediate.prev;
}
- var NewTxItem = new CTransactionStoreItem()
+ // Switch to new best branch
+ if (!Reorganize(cursorIntermediate))
{
- TransactionHash = block.vtx[i].Hash,
- BlockHash = blockHash,
- nTxPos = nTxOffset,
- nTxSize = block.vtx[i].Size,
- txType = txnType
- };
-
- dbConn.Insert(NewTxItem);
+ dbConn.Rollback();
+ InvalidChainFound(cursor);
+ return false; // reorganize failed
+ }
+
+
}
- return blockMap.TryAdd(blockHash, itemTemplate);
+
+ throw new NotImplementedException();
+ }
+
+ private void InvalidChainFound(CBlockStoreItem cursor)
+ {
+ throw new NotImplementedException();
+ }
+
+ private bool Reorganize(CBlockStoreItem cursorIntermediate)
+ {
+ throw new NotImplementedException();
+ }
+
+ private bool SetBestChainInner(CBlockStoreItem cursor)
+ {
+ uint256 hash = cursor.Hash;
+ CBlock block;
+
+ // Adding to current best branch
+ if (!ConnectBlock(cursor, false, out block) || !WriteHashBestChain(hash))
+ {
+ dbConn.Rollback();
+ InvalidChainFound(cursor);
+ return false;
+ }
+
+ // Add to current best branch
+ cursor.prev.next = cursor;
+
+ dbConn.Commit();
+
+ // Delete redundant memory transactions
+ foreach (var tx in block.vtx)
+ {
+ CTransaction dummy;
+ mapUnconfirmedTx.TryRemove(tx.Hash, out dummy);
+ }
+
+ return true;
+ }
+
+ private bool ConnectBlock(CBlockStoreItem cursor, bool fJustCheck, out CBlock block)
+ {
+ var reader = new BinaryReader(fStreamReadWrite).BaseStream;
+ if (cursor.ReadFromFile(ref reader, out block))
+ {
+ return false; // Unable to read block from file.
+ }
+
+ // Check it again in case a previous version let a bad block in, but skip BlockSig checking
+ if (!block.CheckBlock(!fJustCheck, !fJustCheck, false))
+ {
+ return false; // Invalid block found.
+ }
+
+ // TODO: the remaining stuff lol :D
+
+ throw new NotImplementedException();
+ }
+
+ private bool WriteHashBestChain(uint256 hash)
+ {
+ throw new NotImplementedException();
}
///
@@ -862,14 +1187,40 @@ namespace Novacoin
return false;
}
- public bool GetByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos)
+
+ ///
+ /// Interface for join
+ ///
+ interface IBlockJoinMerkle : IBlockStorageItem, IMerkleNode
+ {
+ }
+
+ ///
+ /// Get block and transaction by transaction hash.
+ ///
+ /// Transaction hash
+ /// Block reference
+ /// Transaction reference
+ /// Block position reference
+ /// Transaction position reference
+ /// Result of operation
+ public bool GetBlockByTransactionID(uint256 TxID, ref CBlock block, ref CTransaction tx, ref long nBlockPos, ref long nTxPos)
{
- var QueryTx = dbConn.Query("select * from [TransactionStorage] where [TransactionHash] = ?", (byte[])TxID);
+ var queryResult = dbConn.Query("select *, from [BlockStorage] b left join [MerkleNodes] m on (b.[ItemID] = m.[nParentBlockID]) where m.[TransactionHash] = ?", (byte[])TxID);
- if (QueryTx.Count == 1)
+ if (queryResult.Count == 1)
{
- nTxPos = QueryTx[0].nTxPos;
- return GetBlock(QueryTx[0].BlockHash, ref block, ref nBlockPos);
+ CBlockStoreItem blockCursor = (CBlockStoreItem) queryResult[0];
+ CMerkleNode txCursor = (CMerkleNode)queryResult[0];
+
+ var reader = new BinaryReader(fStreamReadWrite).BaseStream;
+
+ if (!txCursor.ReadFromFile(ref reader, blockCursor.nBlockPos, out tx))
+ {
+ return false; // Unable to read transaction
+ }
+
+ return blockCursor.ReadFromFile(ref reader, out block);
}
// Tx not found
@@ -902,6 +1253,8 @@ namespace Novacoin
if (QueryBlockCursor.Count == 1)
{
+ blockMap.TryAdd(blockHash, QueryBlockCursor[0]);
+
return QueryBlockCursor[0];
}
@@ -909,6 +1262,22 @@ namespace Novacoin
return null;
}
+ ///
+ /// Update cursor in memory and on disk.
+ ///
+ /// Original cursor
+ /// New cursor
+ ///
+ public bool UpdateCursor(CBlockStoreItem originalItem, ref CBlockStoreItem newItem)
+ {
+ if (blockMap.TryUpdate(originalItem.Hash, newItem, originalItem))
+ {
+ return dbConn.Update(newItem) != 0;
+ }
+
+ return false;
+ }
+
public bool ProcessBlock(ref CBlock block)
{
var blockHash = block.header.Hash;
@@ -935,7 +1304,7 @@ namespace Novacoin
if (block.IsProofOfStake)
{
- if (!block.SignatureOK || !block.vtx[1].VerifyScripts())
+ if (!block.SignatureOK)
{
// Proof-of-Stake signature validation failure.
return false;