From b0714a7418f2a8ff09904d1dc014c0ad46ef64c3 Mon Sep 17 00:00:00 2001 From: CryptoManiac Date: Sat, 12 Sep 2015 16:49:30 +0300 Subject: [PATCH] Add stake modifier checkpoints & do some cleanup --- Novacoin/CBlockStore.cs | 15 +++-- Novacoin/Checkpoints.cs | 33 ++++++++++- Novacoin/DatabaseObjects.cs | 5 -- Novacoin/StakeModifier.cs | 133 ++++++++++++++++++++++--------------------- Novacoin/base_uint.cs | 6 ++ Novacoin/uint256.cs | 4 +- 6 files changed, 116 insertions(+), 80 deletions(-) diff --git a/Novacoin/CBlockStore.cs b/Novacoin/CBlockStore.cs index cca056e..7439235 100644 --- a/Novacoin/CBlockStore.cs +++ b/Novacoin/CBlockStore.cs @@ -359,9 +359,12 @@ namespace Novacoin } itemTemplate.SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); - itemTemplate.nStakeModifierChecksum = StakeModifier.GetStakeModifierChecksum(ref itemTemplate); - // TODO: verify stake modifier checkpoints + var nChecksum = StakeModifier.GetModifierChecksum(ref itemTemplate); + if (!ModifierCheckpoints.Verify(itemTemplate.nHeight, nChecksum)) + { + return false; // Stake modifier checkpoints mismatch + } // Add to index if (block.IsProofOfStake) @@ -645,7 +648,7 @@ namespace Novacoin 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; long nFees = 0; @@ -965,7 +968,7 @@ namespace Novacoin if (tx.IsCoinStake) { - // ppcoin: coin stake tx earns reward instead of paying fee + // Coin stake tx earns reward instead of paying fee long nCoinAge; if (!tx.GetCoinAge(ref inputs, out nCoinAge)) { @@ -1073,7 +1076,7 @@ namespace Novacoin } // Check that the block chain matches the known block chain up to a checkpoint - if (!Checkpoints.Verify(nHeight, nHash)) + if (!HashCheckpoints.Verify(nHeight, nHash)) { return false; // rejected by checkpoint lock-in } @@ -1099,7 +1102,7 @@ namespace Novacoin } /// - /// GEt block by hash. + /// Get block by hash. /// /// Block hash /// Block object reference diff --git a/Novacoin/Checkpoints.cs b/Novacoin/Checkpoints.cs index 73854b7..5b874ea 100644 --- a/Novacoin/Checkpoints.cs +++ b/Novacoin/Checkpoints.cs @@ -2,7 +2,7 @@ using System; namespace Novacoin { - public class Checkpoints + public static class HashCheckpoints { private static Tuple[] checkpoints = new Tuple[] { @@ -39,4 +39,35 @@ namespace Novacoin return true; } } + + public static class ModifierCheckpoints + { + /// + /// Stake modifier checkpoints + /// + private static Tuple[] modifierCheckpoints = new Tuple[] + { + new Tuple( 0, 0x0e00670bu ), + new Tuple(200000, 0x01ec1503u ) + }; + + /// + /// Check stake modifier checkpoints. + /// + /// Block height. + /// Modifier checksum value. + /// Result + public static bool Verify(uint nHeight, uint nStakeModifierChecksum) + { + foreach (var checkpoint in modifierCheckpoints) + { + if (checkpoint.Item1 == nHeight) + { + return checkpoint.Item2 == nStakeModifierChecksum; + } + } + + return true; + } + } } \ No newline at end of file diff --git a/Novacoin/DatabaseObjects.cs b/Novacoin/DatabaseObjects.cs index df58f35..cb93d2d 100644 --- a/Novacoin/DatabaseObjects.cs +++ b/Novacoin/DatabaseObjects.cs @@ -502,11 +502,6 @@ namespace Novacoin } /// - /// Stake modifier checksum. - /// - public uint nStakeModifierChecksum; - - /// /// Chain trust score /// [Ignore] diff --git a/Novacoin/StakeModifier.cs b/Novacoin/StakeModifier.cs index 5d5f819..f706cf3 100644 --- a/Novacoin/StakeModifier.cs +++ b/Novacoin/StakeModifier.cs @@ -28,7 +28,7 @@ namespace Novacoin /// /// Stake modifier calculation. Doesn't work properly, for now. /// - public class StakeModifier + public static class StakeModifier { /// /// 30 days as zero time weight @@ -60,14 +60,14 @@ namespace Novacoin /// /// Mon, 20 Oct 2014 00:00:00 GMT /// - internal const uint nModifierSwitchTime = 1413763200; + public const uint nModifierSwitchTime = 1413763200; /// /// Whether the given block is subject to new modifier protocol /// /// Block timestamp /// Result - internal static bool IsFixedModifierInterval(uint nTimeBlock) + private static bool IsFixedModifierInterval(uint nTimeBlock) { return (nTimeBlock >= nModifierSwitchTime); } @@ -79,7 +79,7 @@ namespace Novacoin /// Stake modifier (ref) /// Stake modifier generation time (ref) /// - internal static bool GetLastStakeModifier(CBlockStoreItem cursor, ref long nStakeModifier, ref uint nModifierTime) + private static bool GetLastStakeModifier(CBlockStoreItem cursor, ref long nStakeModifier, ref uint nModifierTime) { if (cursor == null) { @@ -107,7 +107,7 @@ namespace Novacoin /// /// /// - internal static long GetStakeModifierSelectionIntervalSection(int nSection) + private static long GetStakeModifierSelectionIntervalSection(int nSection) { Contract.Assert(nSection >= 0 && nSection < 64); return (nModifierInterval * 63 / (63 + ((63 - nSection) * (nModifierIntervalRatio - 1)))); @@ -117,7 +117,7 @@ namespace Novacoin /// Get stake modifier selection interval (in seconds) /// /// - internal static long GetStakeModifierSelectionInterval() + private static long GetStakeModifierSelectionInterval() { long nSelectionInterval = 0; for (int nSection = 0; nSection < 64; nSection++) @@ -137,7 +137,7 @@ namespace Novacoin /// Previous value of stake modifier. /// Selection result. /// - internal static bool SelectBlockFromCandidates(List> sortedByTimestamp, Dictionary mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor) + private static bool SelectBlockFromCandidates(List> sortedByTimestamp, Dictionary mapSelectedBlocks, long nSelectionIntervalStop, long nStakeModifierPrev, ref CBlockStoreItem selectedCursor) { bool fSelected = false; uint256 hashBest = 0; @@ -169,13 +169,13 @@ namespace Novacoin var hashProof = cursor.IsProofOfStake ? (uint256)cursor.hashProofOfStake : selectedBlockHash; uint256 hashSelection; - var s = new MemoryStream(); - var writer = new BinaryWriter(s); + var stream = new MemoryStream(); + var bw = new BinaryWriter(stream); - writer.Write(hashProof); - writer.Write(nStakeModifierPrev); - hashSelection = CryptoUtils.ComputeHash256(s.ToArray()); - writer.Close(); + bw.Write(hashProof); + bw.Write(nStakeModifierPrev); + hashSelection = CryptoUtils.ComputeHash256(stream.ToArray()); + bw.Close(); // the selection hash is divided by 2**32 so that proof-of-stake block // is always favored over proof-of-work block. this is to preserve @@ -295,7 +295,7 @@ namespace Novacoin /// The stake modifier used to hash for a stake kernel is chosen as the stake /// modifier about a selection interval later than the coin generating the kernel /// - static bool GetKernelStakeModifier(uint256 hashBlockFrom, out long nStakeModifier, out uint nStakeModifierHeight, out uint nStakeModifierTime) + private static bool GetKernelStakeModifier(ref uint256 hashBlockFrom, out long nStakeModifier, out uint nStakeModifierHeight, out uint nStakeModifierTime) { nStakeModifier = 0; nStakeModifierTime = 0; @@ -333,12 +333,12 @@ namespace Novacoin return true; } - public static bool GetKernelStakeModifier(uint256 hashBlockFrom, out long nStakeModifier) + private static bool GetKernelStakeModifier(ref uint256 hashBlockFrom, out long nStakeModifier) { uint nStakeModifierHeight = 0; uint nStakeModifierTime = 0; - return GetKernelStakeModifier(hashBlockFrom, out nStakeModifier, out nStakeModifierHeight, out nStakeModifierTime); + return GetKernelStakeModifier(ref hashBlockFrom, out nStakeModifier, out nStakeModifierHeight, out nStakeModifierTime); } public static bool CheckStakeKernelHash(uint nBits, uint256 hashBlockFrom, uint nTimeBlockFrom, uint nTxPrevOffset, CTransaction txPrev, COutPoint prevout, uint nTimeTx, out uint256 hashProofOfStake, out uint256 targetProofOfStake) @@ -365,31 +365,37 @@ namespace Novacoin // Calculate hash long nStakeModifier; - uint nStakeModifierTime; - uint nStakeModifierHeight; - if (!GetKernelStakeModifier(hashBlockFrom, out nStakeModifier, out nStakeModifierHeight, out nStakeModifierTime)) + if (!GetKernelStakeModifier(ref hashBlockFrom, out nStakeModifier)) { return false; } - MemoryStream s = new MemoryStream(); - BinaryWriter w = new BinaryWriter(s); + var stream = new MemoryStream(); + var bw = new BinaryWriter(stream); - w.Write(nStakeModifier); - w.Write(nTimeBlockFrom); - w.Write(nTxPrevOffset); - w.Write(txPrev.nTime); - w.Write(prevout.n); - w.Write(nTimeTx); + // Coinstake kernel (input 0) must meet the formula + // + // hash(nStakeModifier + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime) < bnTarget * nCoinDayWeight + // + // This ensures that the chance of getting a coinstake is proportional to the + // amount of coin age one owns. + // + // Note that "+" is not arithmetic operation here, this means concatenation of byte arrays. + // + // Check https://github.com/novacoin-project/novacoin/wiki/Kernel for additional information. - hashProofOfStake = CryptoUtils.ComputeHash256(s.ToArray()); - w.Close(); + bw.Write(nStakeModifier); + bw.Write(nTimeBlockFrom); + bw.Write(nTxPrevOffset); + bw.Write(txPrev.nTime); + bw.Write(prevout.n); + bw.Write(nTimeTx); - // Now check if proof-of-stake hash meets target protocol - if (hashProofOfStake > targetProofOfStake) - return false; + hashProofOfStake = CryptoUtils.ComputeHash256(stream.ToArray()); + bw.Close(); - return true; + // Now check if proof-of-stake hash meets target protocol + return targetProofOfStake >= hashProofOfStake; } // Get time weight using supplied timestamps @@ -404,37 +410,38 @@ namespace Novacoin return Math.Min(nIntervalEnd - nIntervalBeginning - nStakeMinAge, nStakeMaxAge); } - internal static uint GetStakeModifierChecksum(ref CBlockStoreItem itemTemplate) + /// + /// Calculate stake modifier checksum. + /// + /// Block cursor. + /// Checksum value. + public static uint GetModifierChecksum(ref CBlockStoreItem cursorBlock) { - Contract.Assert(itemTemplate.prev != null || (uint256)itemTemplate.Hash == NetInfo.nHashGenesisBlock); - - // Hash previous checksum with flags, hashProofOfStake and nStakeModifier - MemoryStream ss = new MemoryStream(); - BinaryWriter writer = new BinaryWriter(ss); + Contract.Assert(cursorBlock.prev != null || (uint256)cursorBlock.Hash == NetInfo.nHashGenesisBlock); - if (itemTemplate.prev != null) - { - writer.Write(itemTemplate.prev.nStakeModifierChecksum); - } + var stream = new MemoryStream(); + var bw = new BinaryWriter(stream); - writer.Write((uint)itemTemplate.BlockTypeFlag); + // Kernel hash for proof-of-stake or zero bytes array for proof-of-work + byte[] proofBytes = cursorBlock.IsProofOfStake ? cursorBlock.hashProofOfStake : new byte[32]; - if (itemTemplate.IsProofOfStake) - { - writer.Write(itemTemplate.hashProofOfStake); - } - else + // Hash previous checksum with flags, hashProofOfStake and nStakeModifier + if (cursorBlock.prev != null) { - writer.Write(new uint256(0)); + var prevCursor = cursorBlock.prev; + bw.Write(GetModifierChecksum(ref prevCursor)); } - writer.Write(itemTemplate.nStakeModifier); - uint256 hashChecksum = CryptoUtils.ComputeHash256(ss.ToArray()); - writer.Close(); + bw.Write((uint)cursorBlock.BlockTypeFlag); + bw.Write(proofBytes); + bw.Write(cursorBlock.nStakeModifier); + + uint256 hashChecksum = CryptoUtils.ComputeHash256(stream.ToArray()); + bw.Close(); hashChecksum >>= (256 - 32); - return (uint)hashChecksum.Low64; + return hashChecksum.Low32; } public static bool CheckProofOfStake(CTransaction tx, uint nBits, out uint256 hashProofOfStake, out uint256 targetProofOfStake) @@ -451,8 +458,8 @@ namespace Novacoin // Read block header - long nBlockPos; CBlock block; + long nBlockPos; if (!CBlockStore.Instance.GetBlockByTransactionID(txin.prevout.hash, out block, out nBlockPos)) { return false; // unable to read block of previous transaction @@ -462,22 +469,16 @@ namespace Novacoin CTransaction txPrev = null; // Iterate through vtx array - for (var i = 0; i < block.vtx.Length; i++) - { - if (block.vtx[i].Hash == txin.prevout.hash) - { - txPrev = block.vtx[i]; - nTxPos = nBlockPos + block.GetTxOffset(i); - - break; - } - } + var nTxPrevIndex = Array.FindIndex(block.vtx, txItem => txItem.Hash == txin.prevout.hash); - if (txPrev == null) + if (nTxPrevIndex == -1) { return false; // No such transaction found in the block } + txPrev = block.vtx[nTxPrevIndex]; + nTxPos = nBlockPos + block.GetTxOffset(nTxPrevIndex); + if (!ScriptCode.VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, tx, 0, (int)scriptflag.SCRIPT_VERIFY_P2SH, 0)) { return false; // vin[0] signature check failed diff --git a/Novacoin/base_uint.cs b/Novacoin/base_uint.cs index d7b8f4c..a32fc6c 100644 --- a/Novacoin/base_uint.cs +++ b/Novacoin/base_uint.cs @@ -57,11 +57,17 @@ namespace Novacoin } } + /// + /// First 64 bits as ulong value. + /// public ulong Low64 { get { return pn[0] | (ulong)pn[1] << 32; } } + /// + /// First 32 bits as uint value. + /// public uint Low32 { get { return pn[0]; } diff --git a/Novacoin/uint256.cs b/Novacoin/uint256.cs index 1842c4a..856dbdc 100644 --- a/Novacoin/uint256.cs +++ b/Novacoin/uint256.cs @@ -103,11 +103,11 @@ namespace Novacoin int nSize = (bits + 7) / 8; uint nCompact = 0; if (nSize <= 3) - nCompact = ((uint)Low64) << 8 * (3 - nSize); + nCompact = Low32 << 8 * (3 - nSize); else { uint256 bn = this >> 8 * (nSize - 3); - nCompact = (uint)bn.Low64; + nCompact = bn.Low32; } if ((nCompact & 0x00800000) != 0) -- 1.7.1