private ConcurrentDictionary<COutPoint, uint> mapStakeSeen = new ConcurrentDictionary<COutPoint, uint>();
- private ConcurrentDictionary<COutPoint, uint> mapStakeSeenOrphan = new ConcurrentDictionary<COutPoint, uint>();
+ private ConcurrentDictionary<Tuple<COutPoint, uint>, uint256> mapStakeSeenOrphan = new ConcurrentDictionary<Tuple<COutPoint, uint>, uint256>();
/// <summary>
// Init list of block items
foreach (var item in blockTreeItems)
{
+ item.nStakeModifierChecksum = StakeModifier.GetModifierChecksum(item);
+
blockMap.TryAdd(item.Hash, item);
if (item.IsProofOfStake)
// 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;
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.GetModifierChecksum(itemTemplate);
- // TODO: verify stake modifier checkpoints
+ if (!ModifierCheckpoints.Verify(itemTemplate.nHeight, itemTemplate.nStakeModifierChecksum))
+ {
+ return false; // Stake modifier checkpoints mismatch
+ }
// Add to index
if (block.IsProofOfStake)
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 fStreamReadWrite, ref block))
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)
}
// Add to current best branch
- cursor.prev.next = cursor;
+ var prevCursor = cursor.prev;
+ prevCursor.next = cursor;
+
+ if (!UpdateDBCursor(ref prevCursor))
+ {
+ return false; // unable to update
+ }
// Delete redundant memory transactions
foreach (var tx in block.vtx)
return false; // Invalid block found.
}
- bool fScriptChecks = cursor.nHeight >= Checkpoints.TotalBlocksEstimate;
+ bool fScriptChecks = cursor.nHeight >= HashCheckpoints.TotalBlocksEstimate;
var scriptFlags = scriptflag.SCRIPT_VERIFY_NOCACHE | scriptflag.SCRIPT_VERIFY_P2SH;
- ulong nFees = 0;
- ulong nValueIn = 0;
- ulong nValueOut = 0;
+ long nFees = 0;
+ long nValueIn = 0;
+ long nValueOut = 0;
uint nSigOps = 0;
var queuedMerkleNodes = new Dictionary<uint256, CMerkleNode>();
else
{
bool Invalid;
- if (!FetchInputs(tx, ref queuedOutputs, ref inputs, true, out Invalid))
+ if (!FetchInputs(ref tx, ref queuedOutputs, ref inputs, true, out Invalid))
{
return false; // Unable to fetch some inputs.
}
return false; // too many sigops
}
- ulong nTxValueIn = tx.GetValueIn(ref inputs);
- ulong nTxValueOut = tx.nValueOut;
+ long nTxValueIn = tx.GetValueIn(ref inputs);
+ long nTxValueOut = tx.nValueOut;
nValueIn += nTxValueIn;
nValueOut += nTxValueOut;
nFees += nTxValueIn - nTxValueOut;
}
- if (!ConnectInputs(tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags))
+ if (!ConnectInputs(ref tx, ref inputs, ref queuedOutputs, ref cursor, true, fScriptChecks, scriptFlags))
{
return false;
}
if (!block.IsProofOfStake)
{
- ulong nBlockReward = CBlock.GetProofOfWorkReward(cursor.nBits, nFees);
+ long nBlockReward = CBlock.GetProofOfWorkReward(cursor.nBits, nFees);
// Check coinbase reward
if (block.vtx[0].nValueOut > nBlockReward)
}
}
- cursor.nMint = (long)(nValueOut - nValueIn + nFees);
- cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + (long)nValueOut - (long)nValueIn;
+ cursor.nMint = nValueOut - nValueIn + nFees;
+ cursor.nMoneySupply = (cursor.prev != null ? cursor.prev.nMoneySupply : 0) + nValueOut - nValueIn;
if (!UpdateDBCursor(ref cursor))
{
return true;
}
- private bool ConnectInputs(CTransaction tx, ref Dictionary<COutPoint, TxOutItem> inputs, ref Dictionary<COutPoint, TxOutItem> queued, ref CBlockStoreItem cursorBlock, bool fBlock, 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)
{
// 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;
+ long nValueIn = 0;
+ long nFees = 0;
for (uint i = 0; i < tx.vin.Length; i++)
{
var prevout = tx.vin[i].prevout;
if (tx.IsCoinStake)
{
- // ppcoin: coin stake tx earns reward instead of paying fee
- ulong nCoinAge;
+ // Coin stake tx earns reward instead of paying fee
+ long nCoinAge;
if (!tx.GetCoinAge(ref inputs, out nCoinAge))
{
return false; // unable to get coin age for coinstake
}
- ulong nReward = tx.nValueOut - nValueIn;
+ long nReward = tx.nValueOut - nValueIn;
- ulong nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - tx.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK) + CTransaction.nCent;
+ long nCalculatedReward = CBlock.GetProofOfStakeReward(nCoinAge, cursorBlock.nBits, tx.nTime) - tx.GetMinFee(1, false, CTransaction.MinFeeMode.GMF_BLOCK) + CTransaction.nCent;
if (nReward > nCalculatedReward)
{
}
// Tally transaction fees
- ulong nTxFee = nValueIn - tx.nValueOut;
+ long nTxFee = nValueIn - tx.nValueOut;
if (nTxFee < 0)
{
return false; // nTxFee < 0
/// <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.
var prevBlockHeader = prevBlockCursor.BlockHeader;
- // TODO: proof-of-work/proof-of-stake verification
uint nHeight = prevBlockCursor.nHeight + 1;
// Check timestamp against prev
}
}
- // TODO: Enforce rule that the coinbase starts with serialized block height
+ // Check that the block chain matches the known block chain up to a checkpoint
+ if (!HashCheckpoints.Verify(nHeight, nHash))
+ {
+ return false; // rejected by checkpoint lock-in
+ }
+
+ // Enforce rule that the coinbase starts with serialized block height
+ var expect = new CScript();
+ expect.AddNumber((int)nHeight);
+
+ byte[] expectBytes = expect;
+ byte[] scriptSig = block.vtx[0].vin[0].scriptSig;
+
+ if (!expectBytes.SequenceEqual(scriptSig.Take(expectBytes.Length)))
+ {
+ return false; // coinbase doesn't start with serialized height.
+ }
// Write block to file.
var itemTemplate = new CBlockStoreItem()
}
/// <summary>
- /// GEt block by hash.
+ /// Get block by hash.
/// </summary>
/// <param name="blockHash">Block hash</param>
/// <param name="block">Block object reference</param>
/// <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);
}
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
}
{
if (block.IsProofOfStake)
{
- // TODO: limit duplicity on stake
+ var proof = block.ProofOfStake;
+
+ // Limited duplicity on stake: prevents block flood attack
+ // Duplicate stake allowed only when there is orphan child block
+ if (mapStakeSeenOrphan.ContainsKey(proof) && !orphanMapByPrev.ContainsKey(blockHash))
+ {
+ return false; // duplicate proof-of-stake
+ }
+ else
+ {
+ mapStakeSeenOrphan.TryAdd(proof, blockHash);
+ }
}
- var block2 = new CBlock(block);
- orphanMap.TryAdd(blockHash, block2);
- orphanMapByPrev.TryAdd(blockHash, block2);
+ orphanMap.TryAdd(blockHash, block);
+ orphanMapByPrev.TryAdd(blockHash, block);
return true;
}
return false;
}
- // Recursively process any orphan blocks that depended on this one
- var orphansQueue = new List<uint256>();
- orphansQueue.Add(blockHash);
-
- for (int i = 0; i < orphansQueue.Count; i++)
+ if (orphanMapByPrev.Count > 0)
{
- var hashPrev = orphansQueue[i];
+ // Recursively process any orphan blocks that depended on this one
+
+ var orphansQueue = new List<uint256>();
+ orphansQueue.Add(blockHash);
- foreach (var pair in orphanMap)
+ for (int i = 0; i < orphansQueue.Count; i++)
{
- var orphanBlock = pair.Value;
+ var hashPrev = orphansQueue[i];
- if (orphanBlock.header.prevHash == blockHash)
+ foreach (var pair in orphanMapByPrev)
{
- if (AcceptBlock(ref orphanBlock))
+ var orphanBlock = pair.Value;
+
+ if (orphanBlock.header.prevHash == blockHash)
{
- orphansQueue.Add(pair.Key);
- }
+ if (AcceptBlock(ref orphanBlock))
+ {
+ orphansQueue.Add(pair.Key);
+ }
+
+ CBlock dummy1;
+ orphanMap.TryRemove(pair.Key, out dummy1);
- CBlock dummy1;
- orphanMap.TryRemove(pair.Key, out dummy1);
+ uint256 dummyHash;
+ mapStakeSeenOrphan.TryRemove(orphanBlock.ProofOfStake, out dummyHash);
+ }
}
- }
- CBlock dummy2;
- orphanMap.TryRemove(hashPrev, out dummy2);
+ CBlock dummy2;
+ orphanMapByPrev.TryRemove(hashPrev, out dummy2);
+ }
}
return true;
}
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);
}
return true;