using SQLite.Net.Platform.Generic;
using System.Collections.Generic;
using System.Text;
+using System.Diagnostics.Contracts;
+using System.Linq;
namespace Novacoin
{
// Load data about the top node.
ChainParams = dbConn.Table<ChainState>().First();
+
+ genesisBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", (byte[])NetInfo.nHashGenesisBlock).First();
+ bestBlockCursor = dbConn.Query<CBlockStoreItem>("select * from [BlockStorage] where [Hash] = ?", ChainParams.HashBestChain).First();
}
}
- public bool GetTxOutCursor(COutPoint outpoint, ref TxOutItem txOutCursor)
+ public bool GetTxOutCursor(COutPoint outpoint, out TxOutItem txOutCursor)
{
var queryResults = dbConn.Query<TxOutItem>("select o.* from [Outputs] o left join [MerkleNodes] m on (m.nMerkleNodeID = o.nMerkleNodeID) where m.[TransactionHash] = ?", (byte[])outpoint.hash);
// Tx not found
+ txOutCursor = null;
+
return false;
}
-
-
- public bool FetchInputs(CTransaction tx, ref Dictionary<COutPoint, TxOutItem> queued, ref Dictionary<COutPoint, TxOutItem> inputs, bool IsBlock, out bool Invalid)
+
+ public bool FetchInputs(ref CTransaction tx, ref Dictionary<COutPoint, TxOutItem> queued, ref Dictionary<COutPoint, TxOutItem> inputs, bool IsBlock, out bool Invalid)
{
Invalid = false;
if (tx.IsCoinBase)
{
// Coinbase transactions have no inputs to fetch.
- return true;
+ return true;
}
StringBuilder queryBuilder = new StringBuilder();
-
+
queryBuilder.Append("select o.*, m.[TransactionHash] 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),
+ 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)
));
}
return false; // Already spent
}
- var inputsKey = new COutPoint(item.TransactionHash, item.nOut);
-
- item.IsSpent = true;
+ var inputsKey = new COutPoint(item.TransactionHash, item.nOut);
// Add output data to dictionary
- inputs.Add(inputsKey, (TxOutItem) item);
+ inputs.Add(inputsKey, item.getTxOutItem());
}
if (queryResults.Count < tx.vin.Length)
{
var outPoint = txin.prevout;
+ if (inputs.ContainsKey(outPoint))
+ {
+ continue; // We have already seen this input.
+ }
+
if (!queued.ContainsKey(outPoint))
{
return false; // No such transaction
inputs.Add(outPoint, queued[outPoint]);
// Mark output as spent
- queued[outPoint].IsSpent = true;
+ // queued[outPoint].IsSpent = true;
}
}
else
return false; // nOut is out of range
}
-
+
// TODO: return inputs from map
throw new NotImplementedException();
private bool AddItemToIndex(ref CBlockStoreItem itemTemplate, ref CBlock block)
{
- var writer = new BinaryWriter(fStreamReadWrite).BaseStream;
uint256 blockHash = itemTemplate.Hash;
if (blockMap.ContainsKey(blockHash))
return false;
}
+ // Begin transaction
+ dbConn.BeginTransaction();
+
// Compute chain trust score
itemTemplate.nChainTrust = (itemTemplate.prev != null ? itemTemplate.prev.nChainTrust : 0) + itemTemplate.nBlockTrust;
return false; // SetStakeEntropyBit() failed
}
- // Save proof-of-stake hash value
- if (itemTemplate.IsProofOfStake)
- {
- uint256 hashProofOfStake;
- if (!GetProofOfStakeHash(blockHash, out hashProofOfStake))
- {
- return false; // hashProofOfStake not found
- }
- itemTemplate.hashProofOfStake = hashProofOfStake;
- }
-
// compute stake modifier
long nStakeModifier = 0;
bool fGeneratedStakeModifier = false;
}
itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier);
- itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(itemTemplate);
+ itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(ref itemTemplate);
// TODO: verify stake modifier checkpoints
itemTemplate.prevoutStake = block.vtx[1].vin[0].prevout;
itemTemplate.nStakeTime = block.vtx[1].nTime;
+
+ // Save proof-of-stake hash value
+ uint256 hashProofOfStake;
+ if (!GetProofOfStakeHash(ref blockHash, out hashProofOfStake))
+ {
+ return false; // hashProofOfStake not found
+ }
+ itemTemplate.hashProofOfStake = hashProofOfStake;
}
- if (!itemTemplate.WriteToFile(ref writer, ref block))
+ if (!itemTemplate.WriteToFile(ref fStreamReadWrite, ref block))
{
return false;
}
// Get last RowID.
itemTemplate.ItemID = dbPlatform.SQLiteApi.LastInsertRowid(dbConn.Handle);
-
+
if (!blockMap.TryAdd(blockHash, itemTemplate))
{
return false; // blockMap add failed
}
}
+ // Commit transaction
+ dbConn.Commit();
+
return true;
}
nTimeBestReceived = Interop.GetTime();
nTransactionsUpdated++;
+ if (!UpdateTopChain(cursor))
+ {
+ return false; // unable to set top chain node.
+ }
+
return true;
}
}
}
-
// Connect longer branch
var txDelete = new List<CTransaction>();
foreach (var cursor in connect)
return false; // UpdateTopChain failed
}
- // Make sure it's successfully written to disk
- dbConn.Commit();
-
// Resurrect memory transactions that were in the disconnected branch
foreach (var tx in txResurrect)
{
}
// Add to current best branch
- cursor.prev.next = cursor;
+ var prevCursor = cursor.prev;
+ prevCursor.next = cursor;
- dbConn.Commit();
+ if (!UpdateDBCursor(ref prevCursor))
+ {
+ return false; // unable to update
+ }
// Delete redundant memory transactions
foreach (var tx in block.vtx)
return true;
}
- private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck=false)
+ private bool ConnectBlock(CBlockStoreItem cursor, ref CBlock block, bool fJustCheck = false)
{
// Check it again in case a previous version let a bad block in, but skip BlockSig checking
if (!block.CheckBlock(!fJustCheck, !fJustCheck, false))
uint nSigOps = 0;
var queuedMerkleNodes = new Dictionary<uint256, CMerkleNode>();
- var queued = new Dictionary<COutPoint, TxOutItem>();
+ var queuedOutputs = new Dictionary<COutPoint, TxOutItem>();
for (var nTx = 0; nTx < block.vtx.Length; nTx++)
{
var tx = block.vtx[nTx];
var hashTx = tx.Hash;
- var nTxPos = cursor.nBlockPos + block.GetTxOffset(nTx);
+
+ if (!queuedMerkleNodes.ContainsKey(hashTx))
+ {
+ var nTxPos = cursor.nBlockPos + block.GetTxOffset(nTx);
+ var mNode = new CMerkleNode(cursor.ItemID, nTxPos, tx);
+
+ queuedMerkleNodes.Add(hashTx, mNode);
+ }
Dictionary<COutPoint, TxOutItem> txouts;
if (GetOutputs(hashTx, out txouts))
else
{
bool Invalid;
- if (!FetchInputs(tx, ref queued, ref inputs, true, out Invalid))
+ if (!FetchInputs(ref tx, ref queuedOutputs, ref inputs, true, out Invalid))
{
return false; // Unable to fetch some inputs.
}
// Add in sigops done by pay-to-script-hash inputs;
// this is to prevent a "rogue miner" from creating
// an incredibly-expensive-to-validate block.
- nSigOps += tx.GetP2SHSigOpCount(inputs);
+ nSigOps += tx.GetP2SHSigOpCount(ref inputs);
if (nSigOps > CBlock.nMaxSigOps)
{
return false; // too many sigops
}
- ulong nTxValueIn = tx.GetValueIn(inputs);
+ ulong nTxValueIn = tx.GetValueIn(ref inputs);
ulong nTxValueOut = tx.nValueOut;
nValueIn += nTxValueIn;
nFees += nTxValueIn - nTxValueOut;
}
- if (!ConnectInputs(tx, inputs, queued, cursor, fScriptChecks, scriptFlags))
+ if (!ConnectInputs(ref tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags))
{
return false;
}
for (var i = 0u; i < tx.vout.Length; i++)
{
- var mNode = new CMerkleNode(cursor.ItemID, nTxPos, tx);
- queuedMerkleNodes.Add(hashTx, mNode);
-
var outKey = new COutPoint(hashTx, i);
- var outData = new TxOutItem();
-
- outData.nValue = tx.vout[i].nValue;
- outData.scriptPubKey = tx.vout[i].scriptPubKey;
- outData.nOut = i;
-
-
- outData.IsSpent = false;
+ var outData = new TxOutItem()
+ {
+ nMerkleNodeID = -1,
+ nValue = tx.vout[i].nValue,
+ scriptPubKey = tx.vout[i].scriptPubKey,
+ IsSpent = false,
+ nOut = i
+ };
- queued.Add(outKey, outData);
+ queuedOutputs.Add(outKey, outData);
}
}
}
}
- cursor.nMint = (long) (nValueOut - nValueIn + nFees);
+ cursor.nMint = (long)(nValueOut - nValueIn + nFees);
cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn;
if (!UpdateDBCursor(ref cursor))
return true;
}
+ // Flush merkle nodes.
+ var savedMerkleNodes = new Dictionary<uint256, CMerkleNode>();
+ foreach (var merklePair in queuedMerkleNodes)
+ {
+ var merkleNode = merklePair.Value;
+
+ if (!SaveMerkleNode(ref merkleNode))
+ {
+ // Unable to save merkle tree cursor.
+ return false;
+ }
+
+ savedMerkleNodes.Add(merklePair.Key, merkleNode);
+ }
+
// Write queued transaction changes
- var actualMerkleNodes = new Dictionary<uint256, CMerkleNode>();
- var queuedOutpointItems = new List<TxOutItem>();
- foreach(KeyValuePair<COutPoint, TxOutItem> outPair in queued)
+ var newOutpointItems = new List<TxOutItem>();
+ var updatedOutpointItems = new List<TxOutItem>();
+ foreach (var outPair in queuedOutputs)
{
- uint256 txID = outPair.Key.hash;
- CMerkleNode merkleNode;
+ var outItem = outPair.Value;
- if (actualMerkleNodes.ContainsKey(txID))
+ if (outItem.nMerkleNodeID == -1)
{
- merkleNode = actualMerkleNodes[txID];
+ // This outpoint doesn't exist yet, adding to insert list.
+
+ outItem.nMerkleNodeID = savedMerkleNodes[outPair.Key.hash].nMerkleNodeID;
+ newOutpointItems.Add(outItem);
}
else
{
- merkleNode = queuedMerkleNodes[txID];
- if (!SaveMerkleNode(ref merkleNode))
- {
- // Unable to save merkle tree cursor.
- return false;
- }
- actualMerkleNodes.Add(txID, merkleNode);
- }
+ // This outpount already exists, adding to update list.
- var outItem = outPair.Value;
- outItem.nMerkleNodeID = merkleNode.nMerkleNodeID;
+ updatedOutpointItems.Add(outItem);
+ }
+ }
- queuedOutpointItems.Add(outItem);
+ if (updatedOutpointItems.Count != 0 && !UpdateOutpoints(ref updatedOutpointItems))
+ {
+ return false; // Unable to update outpoints
}
- if (!SaveOutpoints(ref queuedOutpointItems))
+ if (newOutpointItems.Count != 0 && !InsertOutpoints(ref newOutpointItems))
{
- return false; // Unable to save outpoints
+ return false; // Unable to insert outpoints
}
return true;
}
/// <summary>
- /// Insert set of outpoints
+ /// Insert set of new outpoints
+ /// </summary>
+ /// <param name="newOutpointItems">List of TxOutItem objects.</param>
+ /// <returns>Result</returns>
+ private bool InsertOutpoints(ref List<TxOutItem> newOutpointItems)
+ {
+ return (dbConn.InsertAll(newOutpointItems, false) != 0);
+ }
+
+
+ /// <summary>
+ /// Update set of outpoints
/// </summary>
/// <param name="queuedOutpointItems">List of TxOutItem objects.</param>
/// <returns>Result</returns>
- private bool SaveOutpoints(ref List<TxOutItem> queuedOutpointItems)
+ private bool UpdateOutpoints(ref List<TxOutItem> updatedOutpointItems)
{
- return dbConn.InsertAll(queuedOutpointItems, false) != 0;
+ return (dbConn.UpdateAll(updatedOutpointItems, false) != 0);
}
/// <summary>
return true;
}
- private bool ConnectInputs(CTransaction tx, Dictionary<COutPoint, TxOutItem> inputs, Dictionary<COutPoint, TxOutItem> queued, CBlockStoreItem cursor, bool fScriptChecks, scriptflag scriptFlags)
+ private bool ConnectInputs(ref CTransaction tx, ref Dictionary<COutPoint, TxOutItem> inputs, ref Dictionary<COutPoint, TxOutItem> queued, ref CBlockStoreItem cursorBlock, bool fBlock, bool fScriptChecks, scriptflag scriptFlags)
{
- throw new NotImplementedException();
+ // Take over previous transactions' spent items
+ // fBlock is true when this is called from AcceptBlock when a new best-block is added to the blockchain
+
+ if (!tx.IsCoinBase)
+ {
+ ulong nValueIn = 0;
+ ulong nFees = 0;
+ for (uint i = 0; i < tx.vin.Length; i++)
+ {
+ var prevout = tx.vin[i].prevout;
+ Contract.Assert(inputs.ContainsKey(prevout));
+ var input = inputs[prevout];
+
+ CBlockStoreItem parentBlockCursor;
+
+ if (input.nMerkleNodeID == -1)
+ {
+ // This input seems as is confirmed by the same block.
+
+ if (!queued.ContainsKey(prevout))
+ {
+ return false; // No such output has been queued by this block.
+ }
+
+ // TODO: Ensure that neither coinbase nor coinstake outputs are
+ // available for spending in the generation block.
+ }
+ else
+ {
+ // This input has been confirmed by one of the earlier accepted blocks.
+
+ var merkleItem = GetMerkleCursor(input, out parentBlockCursor);
+
+ if (merkleItem == null)
+ {
+ return false; // Unable to find merkle node
+ }
+
+ // If prev is coinbase or coinstake, check that it's matured
+ if (merkleItem.IsCoinBase || merkleItem.IsCoinStake)
+ {
+ if (cursorBlock.nHeight - parentBlockCursor.nHeight < NetInfo.nGeneratedMaturity)
+ {
+ return false; // tried to spend non-matured generation input.
+ }
+ }
+
+ // check transaction timestamp
+ if (merkleItem.nTime > tx.nTime)
+ {
+ return false; // transaction timestamp earlier than input transaction
+ }
+ }
+
+ // Check for negative or overflow input values
+ nValueIn += input.nValue;
+ if (!CTransaction.MoneyRange(input.nValue) || !CTransaction.MoneyRange(nValueIn))
+ {
+ return false; // txin values out of range
+ }
+
+ }
+
+ // The first loop above does all the inexpensive checks.
+ // Only if ALL inputs pass do we perform expensive ECDSA signature checks.
+ // Helps prevent CPU exhaustion attacks.
+ for (int i = 0; i < tx.vin.Length; i++)
+ {
+ var prevout = tx.vin[i].prevout;
+ Contract.Assert(inputs.ContainsKey(prevout));
+ var input = inputs[prevout];
+
+ // Check for conflicts (double-spend)
+ if (input.IsSpent)
+ {
+ return false;
+ }
+
+ // Skip ECDSA signature verification when connecting blocks (fBlock=true)
+ // before the last blockchain checkpoint. This is safe because block merkle hashes are
+ // still computed and checked, and any change will be caught at the next checkpoint.
+ if (fScriptChecks)
+ {
+ // Verify signature
+ if (!ScriptCode.VerifyScript(tx.vin[i].scriptSig, input.scriptPubKey, tx, i, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0))
+ {
+ return false; // VerifyScript failed.
+ }
+ }
+
+ // Mark outpoint as spent
+ input.IsSpent = true;
+ inputs[prevout] = input;
+
+ // Write back
+ if (fBlock)
+ {
+ if (input.nMerkleNodeID != -1)
+ {
+ // Input has been confirmed earlier.
+ queued.Add(prevout, input);
+ }
+ else
+ {
+ // Input has been confirmed by current block.
+ queued[prevout] = input;
+ }
+ }
+ }
+
+ if (tx.IsCoinStake)
+ {
+ // ppcoin: coin stake tx earns reward instead of paying fee
+ ulong nCoinAge;
+ if (!tx.GetCoinAge(ref inputs, out nCoinAge))
+ {
+ return false; // unable to get coin age for coinstake
+ }
+
+ ulong nReward = tx.nValueOut - nValueIn;
+
+ ulong nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - tx.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK) + CTransaction.nCent;
+
+ if (nReward > nCalculatedReward)
+ {
+ return false; // coinstake pays too much
+ }
+ }
+ else
+ {
+ if (nValueIn < tx.nValueOut)
+ {
+ return false; // value in < value out
+ }
+
+ // Tally transaction fees
+ ulong nTxFee = nValueIn - tx.nValueOut;
+ if (nTxFee < 0)
+ {
+ return false; // nTxFee < 0
+ }
+
+ nFees += nTxFee;
+
+ if (!CTransaction.MoneyRange(nFees))
+ {
+ return false; // nFees out of range
+ }
+ }
+
+ }
+
+ return true;
}
+
/// <summary>
/// Set new top node or current best chain.
/// </summary>
/// <param name="blockHash">Block hash</param>
/// <param name="hashProofOfStake">Proof-of-stake hash</param>
/// <returns>Proof-of-Stake hash value</returns>
- private bool GetProofOfStakeHash(uint256 blockHash, out uint256 hashProofOfStake)
+ private bool GetProofOfStakeHash(ref uint256 blockHash, out uint256 hashProofOfStake)
{
return mapProofOfStake.TryGetValue(blockHash, out hashProofOfStake);
}
return false;
}
- CBlockStoreItem prevBlockCursor = null;
+ CBlockStoreItem prevBlockCursor;
if (!blockMap.TryGetValue(block.header.prevHash, out prevBlockCursor))
{
// Unable to get the cursor.
if (!AddItemToIndex(ref itemTemplate, ref block))
{
+ dbConn.Rollback();
+
return false;
}
/// <param name="block">Block reference</param>
/// <param name="nBlockPos">Block position reference</param>
/// <returns>Result of operation</returns>
- public bool GetBlockByTransactionID(uint256 TxID, ref CBlock block, ref long nBlockPos)
+ public bool GetBlockByTransactionID(uint256 TxID, out CBlock block, out long nBlockPos)
{
+ block = null;
+ nBlockPos = -1;
+
var queryResult = dbConn.Query<CBlockStoreItem>("select b.* from [BlockStorage] b left join [MerkleNodes] m on (b.[ItemID] = m.[nParentBlockID]) where m.[TransactionHash] = ?", (byte[])TxID);
if (queryResult.Count == 1)
{
CBlockStoreItem blockCursor = queryResult[0];
+ nBlockPos = blockCursor.nBlockPos;
+
return blockCursor.ReadFromFile(ref fStreamReadWrite, out block);
}
return false;
}
- public bool WriteNodes(ref CMerkleNode[] merkleNodes)
- {
-
-
- return true;
- }
-
/// <summary>
/// Get block cursor from map.
/// </summary>
}
/// <summary>
+ /// Get merkle node cursor by output metadata.
+ /// </summary>
+ /// <param name="item">Output metadata object</param>
+ /// <returns>Merkle node cursor or null</returns>
+ public CMerkleNode GetMerkleCursor(TxOutItem item, out CBlockStoreItem blockCursor)
+ {
+ blockCursor = null;
+
+ // Trying to get cursor from the database.
+ var QueryMerkleCursor = dbConn.Query<CMerkleNode>("select * from [MerkleNodes] where [nMerkleNodeID] = ?", item.nMerkleNodeID);
+
+ if (QueryMerkleCursor.Count == 1)
+ {
+ var merkleNode = QueryMerkleCursor[0];
+
+ // Search for block
+ var results = blockMap.Where(x => x.Value.ItemID == merkleNode.nParentBlockID).Select(x => x.Value).ToArray();
+
+ blockCursor = results[0];
+
+ return merkleNode;
+ }
+
+ // Nothing found.
+ return null;
+ }
+
+ /// <summary>
/// Load cursor from database.
/// </summary>
/// <param name="blockHash">Block hash</param>
if (block.IsProofOfStake)
{
- if (!block.SignatureOK)
- {
- // Proof-of-Stake signature validation failure.
- return false;
- }
-
// TODO: proof-of-stake validation
uint256 hashProofOfStake = 0, targetProofOfStake = 0;
- if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, ref hashProofOfStake, ref targetProofOfStake))
+ if (!StakeModifier.CheckProofOfStake(block.vtx[1], block.header.nBits, out hashProofOfStake, out targetProofOfStake))
{
return false; // do not error here as we expect this during initial block download
}
var intBuffer = new byte[4];
var fStream2 = File.OpenRead(BlockFile);
- var readerForBlocks = new BinaryReader(fStream2).BaseStream;
- readerForBlocks.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
+ fStream2.Seek(nOffset, SeekOrigin.Begin); // Seek to previous offset + previous block length
- while (readerForBlocks.Read(buffer, 0, 4) == 4) // Read magic number
+ while (fStream2.Read(buffer, 0, 4) == 4) // Read magic number
{
var nMagic = BitConverter.ToUInt32(buffer, 0);
if (nMagic != 0xe5e9e8e4)
throw new Exception("Incorrect magic number.");
}
- var nBytesRead = readerForBlocks.Read(buffer, 0, 4);
+ var nBytesRead = fStream2.Read(buffer, 0, 4);
if (nBytesRead != 4)
{
throw new Exception("BLKSZ EOF");
var nBlockSize = BitConverter.ToInt32(buffer, 0);
- nOffset = readerForBlocks.Position;
+ nOffset = fStream2.Position;
- nBytesRead = readerForBlocks.Read(buffer, 0, nBlockSize);
+ nBytesRead = fStream2.Read(buffer, 0, nBlockSize);
if (nBytesRead == 0 || nBytesRead != nBlockSize)
{
}
int nCount = blockMap.Count;
- Console.WriteLine("nCount={0}, Hash={1}, Time={2}", nCount, block.header.Hash, DateTime.Now); // Commit on each 100th block
-
- /*
- if (nCount % 100 == 0 && nCount != 0)
- {
- Console.WriteLine("Commit...");
- dbConn.Commit();
- dbConn.BeginTransaction();
- }*/
+ Console.WriteLine("nCount={0}, Hash={1}, NumTx={2}, Time={3}", nCount, block.header.Hash, block.vtx.Length, DateTime.Now);
}
- dbConn.Commit();
-
return true;
}