From 514b68fc3103fca11d6d06ddb65ed8d2a14507e1 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Sun, 6 Sep 2015 03:49:33 +0300 Subject: [PATCH] Beginning of FetchInputs implementation. --- Novacoin/CBlockStore.cs | 453 ++++++++++++++++++++++++---------------------- Novacoin/CTransaction.cs | 6 +- Novacoin/Novacoin.csproj | 1 + 3 files changed, 238 insertions(+), 222 deletions(-) diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index 7d24209..c636121 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -29,15 +29,14 @@ 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 /// @@ -53,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; } /// /// 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("Height")] + public byte[] Height { get; set; } + /// - /// Block height + /// Block position in file, encoded in VarInt format /// - public uint nHeight { get; set; } + [Column("BlockPos")] + public byte[] BlockPos { get; set; } /// - /// Block position in file + /// Block size in bytes, encoded in VarInt format /// - public long nBlockPos { get; set; } + [Column("BlockSize")] + public byte[] BlockSize { get; set; } + #endregion /// - /// Block size in bytes + /// Accessor and mutator for BlockPos value. /// - public int nBlockSize { get; set; } + [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. @@ -132,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; @@ -257,7 +304,15 @@ 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; @@ -470,7 +525,7 @@ namespace Novacoin /// /// Transaction type. /// - public enum TxType + public enum TxFlags : byte { TX_COINBASE, TX_COINSTAKE, @@ -478,20 +533,21 @@ namespace Novacoin } /// - /// Transaction type. + /// Output flags. /// - public enum OutputType + public enum OutputFlags : byte { - TX_USER = (1 << 0), // User output - TX_COINBASE = (1 << 1), // Coinbase output - TX_COINSTAKE = (1 << 2), // Coinstake output - TX_AVAILABLE = (2 << 0), // Unspent output - TX_SPENT = (2 << 1) // Spent output + AVAILABLE, // Unspent output + SPENT // Spent output } [Table("MerkleNodes")] - public class MerkleNode + public class CMerkleNode : IMerkleNode { + #region IMerkleNode + /// + /// Node identifier + /// [PrimaryKey, AutoIncrement] public long nMerkleNodeID { get; set; } @@ -502,214 +558,139 @@ namespace Novacoin public long nParentBlockID { get; set; } /// - /// Transaction hash - /// - public byte[] TransactionHash { get; set; } - - public static bool QueryParentBlockCursor(uint256 transactionHash, out CBlockStoreItem cursor) - { - throw new NotImplementedException(); - } - } - - [Table("Outputs")] - public class TxOutItem - { - /// - /// Link the transaction hash with database item identifier. - /// - private static ConcurrentDictionary outMap = new ConcurrentDictionary(); - - /// - /// Reference to transaction item. - /// - [ForeignKey(typeof(MerkleNode), Name = "nMerkleNodeID")] - public long nMerkleNodeID { get; set; } - - /// - /// Output flags + /// Transaction type flag /// - public OutputType outputFlags { get; set; } + [Column("TransactionFlags")] + public TxFlags TransactionFlags { get; set; } /// - /// Output number in VarInt format. + /// Transaction hash /// - public byte[] OutputNumber { get; set; } + [Column("TransactionHash")] + public byte[] TransactionHash { get; set; } /// - /// Output value in VarInt format. + /// Transaction offset from the beginning of block header, encoded in VarInt format. /// - public byte[] OutputValue { get; set; } + [Column("TxOffset")] + public byte[] TxOffset { get; set; } /// - /// Second half of script which contains spending instructions. + /// Transaction size, encoded in VarInt format. /// - public byte[] scriptPubKey { get; set; } + [Column("TxSize")] + public byte[] TxSize { get; set; } + #endregion /// - /// Construct new item from provided transaction data. + /// Read transaction from file. /// - /// - public TxOutItem(CTransaction tx, uint nOut) + /// Stream with read access. + /// CTransaction reference. + /// Result + public bool ReadFromFile(ref Stream reader, long nBlockPos, out CTransaction tx) { - Contract.Requires(nOut < tx.vout.Length); + var buffer = new byte[CTransaction.nMaxTxSize]; + + tx = null; - long nMerkleId = 0; - if (!outMap.TryGetValue(tx.Hash, out nMerkleId)) + try { - // Not in the blockchain - nMerkleNodeID = -1; - } + reader.Seek(nBlockPos + nTxOffset, SeekOrigin.Begin); // Seek to transaction offset - OutputNumber = VarInt.EncodeVarInt(nOut); - OutputValue = VarInt.EncodeVarInt(tx.vout[nOut].nValue); - scriptPubKey = tx.vout[nOut].scriptPubKey; + if (nTxSize != reader.Read(buffer, 0, nTxSize)) + { + return false; + } + + tx = new CTransaction(buffer); - if (tx.IsCoinBase) + return true; + } + catch (IOException) { - outputFlags |= OutputType.TX_COINBASE; + // I/O error + return false; } - else if (tx.IsCoinStake) + catch (TransactionConstructorException) { - outputFlags |= OutputType.TX_COINSTAKE; + // Constructor error + return false; } } /// - /// Getter for output number. - /// - [Ignore] - public uint nOut - { - get { return (uint)VarInt.DecodeVarInt(OutputNumber); } - } - - /// - /// Getter for output value. + /// Transaction offset accessor /// [Ignore] - public ulong nValue + public long nTxOffset { - get { return VarInt.DecodeVarInt(OutputValue); } + get { return (long) VarInt.DecodeVarInt(TxOffset); } } /// - /// Is this a user transaction output? + /// Transaction size accessor /// [Ignore] - public bool IsUser + public int nTxSize { - get { return (outputFlags & OutputType.TX_USER) != 0; } + get { return (int)VarInt.DecodeVarInt(TxSize); } } - /// - /// Is this a coinbase transaction output? - /// - [Ignore] - public bool IsCoinBase - { - get { return (outputFlags & OutputType.TX_COINBASE) != 0; } - } - - /// - /// Is this a coinstake transaction output? - /// - [Ignore] - public bool IsCoinStake - { - get { return (outputFlags & OutputType.TX_COINSTAKE) != 0; } - } - - /// - /// Getter ans setter for IsSpent flag. - /// - [Ignore] - public bool IsSpent - { - get { return (outputFlags & OutputType.TX_SPENT) != 0; } - set { outputFlags |= value ? OutputType.TX_SPENT : OutputType.TX_AVAILABLE; } - } } - - [Table("TransactionStorage")] - public class CTransactionStoreItem + [Table("Outputs")] + public class TxOutItem : ITxOutItem { /// - /// Transaction hash + /// Reference to transaction item. /// - [PrimaryKey] - public byte[] TransactionHash { get; set; } + [ForeignKey(typeof(CMerkleNode), Name = "nMerkleNodeID")] + public long nMerkleNodeID { get; set; } /// - /// Block hash + /// Output flags /// - [ForeignKey(typeof(CBlockStoreItem), Name = "Hash")] - public byte[] BlockHash { get; set; } + public OutputFlags outputFlags { get; set; } /// - /// Transaction type flag + /// Output number in VarInt format. /// - public TxType txType { get; set; } + public byte[] OutputNumber { get; set; } /// - /// Tx position in file + /// Output value in VarInt format. /// - public long nTxPos { get; set; } + public byte[] OutputValue { get; set; } /// - /// Transaction size + /// Second half of script which contains spending instructions. /// - public int nTxSize { get; set; } + public byte[] scriptPubKey { get; set; } /// - /// Serialized output array + /// Getter for output number. /// - public byte[] vOut { get; set; } + public uint nOut + { + get { return (uint)VarInt.DecodeVarInt(OutputNumber); } + } /// - /// Read transaction from file. + /// Getter for output value. /// - /// Stream with read access. - /// CTransaction reference. - /// Result - public bool ReadFromFile(ref Stream reader, out CTransaction tx) + public ulong nValue { - var buffer = new byte[CTransaction.nMaxTxSize]; - tx = null; - - try - { - reader.Seek(nTxPos, 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; - } + get { return VarInt.DecodeVarInt(OutputValue); } } /// - /// Outputs array access + /// Getter ans setter for IsSpent flag. /// - [Ignore] - public CTxOut[] Outputs { - get { return CTxOut.DeserializeOutputsArray(vOut); } - set { vOut = CTxOut.SerializeOutputsArray(value); } + public bool IsSpent + { + get { return (outputFlags & OutputFlags.SPENT) != 0; } + set { outputFlags |= value ? OutputFlags.SPENT : OutputFlags.AVAILABLE; } } } @@ -737,6 +718,8 @@ namespace Novacoin /// /// Map of block tree nodes. + /// + /// blockHash => CBlockStoreItem /// private ConcurrentDictionary blockMap = new ConcurrentDictionary(); @@ -747,10 +730,12 @@ 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. /// @@ -761,11 +746,6 @@ namespace Novacoin private ConcurrentDictionary mapStakeSeenOrphan = new ConcurrentDictionary(); /// - /// Unconfirmed transactions. - /// - private ConcurrentDictionary mapUnconfirmedTx = new ConcurrentDictionary(); - - /// /// Trust score for the longest chain. /// private uint256 nBestChainTrust = 0; @@ -818,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( @@ -876,14 +857,15 @@ namespace Novacoin } } - 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 @@ -891,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; @@ -967,40 +990,6 @@ namespace Novacoin */ } - // We have no SetBestChain and ConnectBlock/Disconnect block yet, so adding these transactions manually. - for (int i = 0; i < block.vtx.Length; i++) - { - // Handle trasactions using our temporary stub algo - - if (!block.vtx[i].VerifyScripts()) - { - return false; - } - - var nTxOffset = itemTemplate.nBlockPos + block.GetTxOffset(i); - TxType txnType = TxType.TX_USER; - - if (block.vtx[i].IsCoinBase) - { - txnType = TxType.TX_COINBASE; - } - else if (block.vtx[i].IsCoinStake) - { - txnType = TxType.TX_COINSTAKE; - } - - var NewTxItem = new CTransactionStoreItem() - { - TransactionHash = block.vtx[i].Hash, - BlockHash = blockHash, - nTxPos = nTxOffset, - nTxSize = block.vtx[i].Size, - txType = txnType - }; - - dbConn.Insert(NewTxItem); - } - return true; } @@ -1198,14 +1187,40 @@ namespace Novacoin return false; } + + /// + /// 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 diff --git a/Novacoin/CTransaction.cs b/Novacoin/CTransaction.cs index 8402b12..8a5f6a7 100644 --- a/Novacoin/CTransaction.cs +++ b/Novacoin/CTransaction.cs @@ -138,15 +138,15 @@ namespace Novacoin return true; } - CTransaction txPrev = null; + TxOutItem txOutCursor = null; for (int i = 0; i < vin.Length; i++) { var outpoint = vin[i].prevout; - if (!CBlockStore.Instance.GetTransaction(outpoint.hash, ref txPrev)) + if (!CBlockStore.Instance.GetTxOutCursor(outpoint, ref txOutCursor)) return false; - if (!ScriptCode.VerifyScript(vin[i].scriptSig, txPrev.vout[outpoint.n].scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) + if (!ScriptCode.VerifyScript(vin[i].scriptSig, txOutCursor.scriptPubKey, this, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) return false; } diff --git a/Novacoin/Novacoin.csproj b/Novacoin/Novacoin.csproj index ce2b90b..ef9956e 100644 --- a/Novacoin/Novacoin.csproj +++ b/Novacoin/Novacoin.csproj @@ -110,6 +110,7 @@ + -- 1.7.1