diff --git a/src/chain.h b/src/chain.h index eeefa6074aa1..11e4342af3c6 100644 --- a/src/chain.h +++ b/src/chain.h @@ -7,6 +7,7 @@ #ifndef BITCOIN_CHAIN_H #define BITCOIN_CHAIN_H +#include "chainparams.h" #include "pow.h" #include "primitives/block.h" #include "tinyformat.h" @@ -155,6 +156,7 @@ class CBlockIndex BLOCK_STAKE_MODIFIER = (1 << 2), // regenerated stake modifier }; + // proof-of-stake specific fields uint256 GetBlockTrust() const; uint64_t nStakeModifier; // hash modifier for proof-of-stake @@ -164,6 +166,7 @@ class CBlockIndex uint256 hashProofOfStake; int64_t nMint; int64_t nMoneySupply; + uint256 nStakeModifierV2; //! block header int nVersion; @@ -199,6 +202,7 @@ class CBlockIndex nMoneySupply = 0; nFlags = 0; nStakeModifier = 0; + nStakeModifierV2 = uint256(); nStakeModifierChecksum = 0; prevoutStake.SetNull(); nStakeTime = 0; @@ -233,22 +237,10 @@ class CBlockIndex if(block.nVersion > 3) nAccumulatorCheckpoint = block.nAccumulatorCheckpoint; - //Proof of Stake - bnChainTrust = uint256(); - nMint = 0; - nMoneySupply = 0; - nFlags = 0; - nStakeModifier = 0; - nStakeModifierChecksum = 0; - hashProofOfStake = uint256(); - if (block.IsProofOfStake()) { SetProofOfStake(); prevoutStake = block.vtx[1].vin[0].prevout; nStakeTime = block.nTime; - } else { - prevoutStake.SetNull(); - nStakeTime = 0; } } @@ -477,7 +469,14 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(nMint); READWRITE(nMoneySupply); READWRITE(nFlags); - READWRITE(nStakeModifier); + + // v1/v2 modifier selection. + if (!Params().IsStakeModifierV2(nHeight)) { + READWRITE(nStakeModifier); + } else { + READWRITE(nStakeModifierV2); + } + if (IsProofOfStake()) { READWRITE(prevoutStake); READWRITE(nStakeTime); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ec97e6a42f3b..943a4a458f96 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -14,6 +14,7 @@ #include #include +#include struct SeedSpec6 { @@ -116,6 +117,17 @@ libzerocoin::ZerocoinParams* CChainParams::Zerocoin_Params(bool useModulusV1) co return &ZCParamsDec; } +bool CChainParams::HasStakeMinAgeOrDepth(const int contextHeight, const uint32_t contextTime, + const int utxoFromBlockHeight, const uint32_t utxoFromBlockTime) const +{ + // before stake modifier V2, the age required was 60 * 60 (1 hour) / not required on regtest + if (!IsStakeModifierV2(contextHeight)) + return (NetworkID() == CBaseChainParams::REGTEST || (utxoFromBlockTime + 3600 <= contextTime)); + + // after stake modifier V2, we require the utxo to be nStakeMinDepth deep in the chain + return (contextHeight - utxoFromBlockHeight >= nStakeMinDepth); +} + class CMainParams : public CChainParams { public: @@ -141,10 +153,11 @@ class CMainParams : public CChainParams nRejectBlockOutdatedMajority = 10260; // 95% nToCheckBlockUpgradeMajority = 10800; // Approximate expected amount of blocks in 7 days (1440*7.5) nMinerThreads = 0; - nTargetTimespan = 1 * 60; // PIVX: 1 minute - nTargetSpacing = 1 * 60; // PIVX: 1 minute + nTargetSpacing = 1 * 60; // 1 minute nMaturity = 100; - nStakeMinAge = 60 * 60; // PIVX: 1 hour + nStakeMinDepth = 600; + nFutureTimeDriftPoW = 7200; + nFutureTimeDriftPoS = 180; nMasternodeCountDrift = 20; nMaxMoneyOut = 21000000 * COIN; @@ -165,7 +178,7 @@ class CMainParams : public CChainParams nBlockDoubleAccumulated = 1050010; nEnforceNewSporkKey = 1525158000; //!> Sporks signed after (GMT): Tuesday, May 1, 2018 7:00:00 AM GMT must use the new spork key nRejectOldSporkKey = 1527811200; //!> Fully reject old spork key after (GMT): Friday, June 1, 2018 12:00:00 AM - + nBlockStakeModifierlV2 = 1967000; // Public coin spend enforcement nPublicZCSpends = 1880000; @@ -256,6 +269,7 @@ class CMainParams : public CChainParams { return data; } + }; static CMainParams mainParams; @@ -279,12 +293,12 @@ class CTestNetParams : public CMainParams nRejectBlockOutdatedMajority = 5472; // 95% nToCheckBlockUpgradeMajority = 5760; // 4 days nMinerThreads = 0; - nTargetTimespan = 1 * 60; // PIVX: 1 day nTargetSpacing = 1 * 60; // PIVX: 1 minute nLastPOWBlock = 200; nPivxBadBlockTime = 1489001494; // Skip nBit validation of Block 259201 per PR #915 nPivxBadBlocknBits = 0x1e0a20bd; // Skip nBit validation of Block 201 per PR #915 nMaturity = 15; + nStakeMinDepth = 100; nMasternodeCountDrift = 4; nModifierUpdateBlock = 51197; //approx Mon, 17 Apr 2017 04:00:00 GMT nMaxMoneyOut = 43199500 * COIN; @@ -299,7 +313,7 @@ class CTestNetParams : public CMainParams nBlockZerocoinV2 = 444020; //!> The block that zerocoin v2 becomes active nEnforceNewSporkKey = 1521604800; //!> Sporks signed after Wednesday, March 21, 2018 4:00:00 AM GMT must use the new spork key nRejectOldSporkKey = 1522454400; //!> Reject old spork key after Saturday, March 31, 2018 12:00:00 AM GMT - + nBlockStakeModifierlV2 = 1214000; // Public coin spend enforcement nPublicZCSpends = 1106100; @@ -377,12 +391,11 @@ class CRegTestParams : public CTestNetParams nRejectBlockOutdatedMajority = 950; nToCheckBlockUpgradeMajority = 1000; nMinerThreads = 1; - nTargetTimespan = 24 * 60 * 60; // PIVX: 1 day nTargetSpacing = 1 * 60; // PIVX: 1 minutes bnProofOfWorkLimit = ~uint256(0) >> 1; nLastPOWBlock = 250; nMaturity = 100; - nStakeMinAge = 0; + nStakeMinDepth = 0; nMasternodeCountDrift = 4; nModifierUpdateBlock = 0; //approx Mon, 17 Apr 2017 04:00:00 GMT nMaxMoneyOut = 43199500 * COIN; @@ -393,7 +406,7 @@ class CRegTestParams : public CTestNetParams nBlockRecalculateAccumulators = 999999999; //Trigger a recalculation of accumulators nBlockFirstFraudulent = 999999999; //First block that bad serials emerged nBlockLastGoodCheckpoint = 999999999; //Last valid accumulator checkpoint - + nBlockStakeModifierlV2 = std::numeric_limits::max(); // max integer value (never switch on regtest) // Public coin spend enforcement nPublicZCSpends = 350; @@ -470,7 +483,6 @@ class CUnitTestParams : public CMainParams, public CModifiableParams }; static CUnitTestParams unitTestParams; - static CChainParams* pCurrentParams = 0; CModifiableParams* ModifiableParams() diff --git a/src/chainparams.h b/src/chainparams.h index f03dfe03838a..f107c923cc7a 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -56,7 +56,6 @@ class CChainParams int RejectBlockOutdatedMajority() const { return nRejectBlockOutdatedMajority; } int ToCheckBlockUpgradeMajority() const { return nToCheckBlockUpgradeMajority; } int MaxReorganizationDepth() const { return nMaxReorganizationDepth; } - int StakeMinAge() const { return nStakeMinAge; } /** Used if GenerateBitcoins is called with a negative number of threads */ int DefaultMinerThreads() const { return nMinerThreads; } @@ -73,10 +72,19 @@ class CChainParams bool SkipProofOfWorkCheck() const { return fSkipProofOfWorkCheck; } /** Make standard checks */ bool RequireStandard() const { return fRequireStandard; } - int64_t TargetTimespan() const { return nTargetTimespan; } int64_t TargetSpacing() const { return nTargetSpacing; } - int64_t Interval() const { return nTargetTimespan / nTargetSpacing; } + + /** returns the coinbase maturity **/ int COINBASE_MATURITY() const { return nMaturity; } + + /** returns the coinstake maturity (min depth required) **/ + int COINSTAKE_MIN_DEPTH() const { return nStakeMinDepth; } + bool HasStakeMinAgeOrDepth(const int contextHeight, const uint32_t contextTime, const int utxoFromBlockHeight, const uint32_t utxoFromBlockTime) const; + + /** returns the max future time (and drift in seconds) allowed for a block in the future **/ + int FutureBlockTimeDrift(const bool isPoS) const { return isPoS ? nFutureTimeDriftPoS : nFutureTimeDriftPoW; } + uint32_t MaxFutureBlockTime(uint32_t time, const bool isPoS) const { return time + FutureBlockTimeDrift(isPoS); } + CAmount MaxMoneyOut() const { return nMaxMoneyOut; } /** The masternode count that we will allow the see-saw reward payments to be off by */ int MasternodeCountDrift() const { return nMasternodeCountDrift; } @@ -131,6 +139,7 @@ class CChainParams int Zerocoin_StartTime() const { return nZerocoinStartTime; } int Block_Enforce_Invalid() const { return nBlockEnforceInvalidUTXO; } int Zerocoin_Block_V2_Start() const { return nBlockZerocoinV2; } + bool IsStakeModifierV2(const int nHeight) const { return nHeight >= nBlockStakeModifierlV2; } // fake serial attack int Zerocoin_Block_EndFakeSerial() const { return nFakeSerialBlockheightEnd; } @@ -150,19 +159,21 @@ class CChainParams std::vector vAlertPubKey; int nDefaultPort; uint256 bnProofOfWorkLimit; - int nStakeMinAge; int nMaxReorganizationDepth; int nSubsidyHalvingInterval; int nEnforceBlockUpgradeMajority; int nRejectBlockOutdatedMajority; int nToCheckBlockUpgradeMajority; - int64_t nTargetTimespan; int64_t nTargetSpacing; int nLastPOWBlock; int64_t nPivxBadBlockTime; unsigned int nPivxBadBlocknBits; int nMasternodeCountDrift; int nMaturity; + int nStakeMinDepth; + int nFutureTimeDriftPoW; + int nFutureTimeDriftPoS; + int nModifierUpdateBlock; CAmount nMaxMoneyOut; int nMinerThreads; @@ -211,6 +222,7 @@ class CChainParams int nBlockZerocoinV2; int nBlockDoubleAccumulated; int nPublicZCSpends; + int nBlockStakeModifierlV2; // fake serial attack int nFakeSerialBlockheightEnd = 0; diff --git a/src/kernel.cpp b/src/kernel.cpp index 91226e764bbc..d98bbb282cf2 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -14,31 +14,13 @@ #include "utilmoneystr.h" #include "zpivchain.h" - -bool fTestNet = false; //Params().NetworkID() == CBaseChainParams::TESTNET; - -// Modifier interval: time to elapse before new modifier is computed -// Set to 3-hour for production network and 20-minute for test network -unsigned int nModifierInterval; -int nStakeTargetSpacing = 60; -unsigned int getIntervalVersion(bool fTestNet) -{ - if (fTestNet) - return MODIFIER_INTERVAL_TESTNET; - else - return MODIFIER_INTERVAL; -} +// v1 modifier interval. +static const int64_t OLD_MODIFIER_INTERVAL = 2087; // Hard checkpoints of stake modifiers to ensure they are deterministic static std::map mapStakeModifierCheckpoints = boost::assign::map_list_of(0, 0xfd11f4e7u); -// Get time weight -int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd) -{ - return nIntervalEnd - nIntervalBeginning - Params().StakeMinAge(); -} - // Get the last stake modifier and its generation time from a given block static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64_t& nStakeModifier, int64_t& nModifierTime) { @@ -57,20 +39,10 @@ static bool GetLastStakeModifier(const CBlockIndex* pindex, uint64_t& nStakeModi static int64_t GetStakeModifierSelectionIntervalSection(int nSection) { assert(nSection >= 0 && nSection < 64); - int64_t a = getIntervalVersion(fTestNet) * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1))); + int64_t a = MODIFIER_INTERVAL * 63 / (63 + ((63 - nSection) * (MODIFIER_INTERVAL_RATIO - 1))); return a; } -// Get stake modifier selection interval (in seconds) -static int64_t GetStakeModifierSelectionInterval() -{ - int64_t nSelectionInterval = 0; - for (int nSection = 0; nSection < 64; nSection++) { - nSelectionInterval += GetStakeModifierSelectionIntervalSection(nSection); - } - return nSelectionInterval; -} - // select a block from the candidate blocks in vSortedByTimestamp, excluding // already selected blocks in vSelectedBlocks, and with timestamp up to // nSelectionIntervalStop. @@ -134,6 +106,30 @@ static bool SelectBlockFromCandidates( return fSelected; } +/* NEW MODIFIER */ + +// Stake Modifier (hash modifier of proof-of-stake): +// The purpose of stake modifier is to prevent a txout (coin) owner from +// computing future proof-of-stake generated by this txout at the time +// of transaction confirmation. To meet kernel protocol, the txout +// must hash with a future stake modifier to generate the proof. +uint256 ComputeStakeModifier(const CBlockIndex* pindexPrev, const uint256& kernel) +{ + if (!pindexPrev) + return uint256(); // genesis block's modifier is 0 + + CHashWriter ss(SER_GETHASH, 0); + ss << kernel; + + // switch with old modifier on upgrade block + if (!Params().IsStakeModifierV2(pindexPrev->nHeight + 1)) + ss << pindexPrev->nStakeModifier; + else + ss << pindexPrev->nStakeModifierV2; + + return ss.GetHash(); +} + // Stake Modifier (hash modifier of proof-of-stake): // The purpose of stake modifier is to prevent a txout (coin) owner from // computing future proof-of-stake generated by this txout at the time @@ -171,14 +167,13 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeMod if (GetBoolArg("-printstakemodifier", false)) LogPrintf("%s : prev modifier= %s time=%s\n", __func__, std::to_string(nStakeModifier).c_str(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nModifierTime).c_str()); - if (nModifierTime / getIntervalVersion(fTestNet) >= pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet)) + if (nModifierTime / MODIFIER_INTERVAL >= pindexPrev->GetBlockTime() / MODIFIER_INTERVAL) return true; // Sort candidate blocks by timestamp std::vector > vSortedByTimestamp; - vSortedByTimestamp.reserve(64 * getIntervalVersion(fTestNet) / nStakeTargetSpacing); - int64_t nSelectionInterval = GetStakeModifierSelectionInterval(); - int64_t nSelectionIntervalStart = (pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet)) * getIntervalVersion(fTestNet) - nSelectionInterval; + vSortedByTimestamp.reserve(64 * MODIFIER_INTERVAL / Params().TargetSpacing()); + int64_t nSelectionIntervalStart = (pindexPrev->GetBlockTime() / MODIFIER_INTERVAL ) * MODIFIER_INTERVAL - OLD_MODIFIER_INTERVAL; const CBlockIndex* pindex = pindexPrev; while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart) { @@ -255,90 +250,134 @@ bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64_t& nStakeModifier, int nStakeModifier = pindexFrom->nStakeModifier; return true; } - int64_t nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval(); const CBlockIndex* pindex = pindexFrom; - CBlockIndex* pindexNext = chainActive[pindexFrom->nHeight + 1]; + CBlockIndex* pindexNext = chainActive[pindex->nHeight + 1];; // loop to find the stake modifier later by a selection interval - while (nStakeModifierTime < pindexFrom->GetBlockTime() + nStakeModifierSelectionInterval) { + do { if (!pindexNext) { // Should never happen - return error("Null pindexNext\n"); + return error("%s : Null pindexNext, current block %s ", __func__, pindex->phashBlock->GetHex()); } - pindex = pindexNext; - pindexNext = chainActive[pindexNext->nHeight + 1]; if (pindex->GeneratedStakeModifier()) { nStakeModifierHeight = pindex->nHeight; nStakeModifierTime = pindex->GetBlockTime(); } - } + pindexNext = chainActive[pindex->nHeight + 1]; + } while (nStakeModifierTime < pindexFrom->GetBlockTime() + OLD_MODIFIER_INTERVAL); + nStakeModifier = pindex->nStakeModifier; return true; } -//test hash vs target -bool stakeTargetHit(const uint256& hashProofOfStake, const int64_t& nValueIn, const uint256& bnTargetPerCoinDay) +bool CheckStakeKernelHash(const CBlockIndex* pindexPrev, const unsigned int nBits, CStakeInput* stake, const unsigned int nTimeTx, uint256& hashProofOfStake, const bool fVerify) { - //get the stake weight - weight is equal to coin amount - uint256 bnCoinDayWeight = uint256(nValueIn) / 100; + // Calculate the proof of stake hash + if (!GetHashProofOfStake(pindexPrev, stake, nTimeTx, fVerify, hashProofOfStake)) { + return error("%s : Failed to calculate the proof of stake hash", __func__); + } - // Now check if proof-of-stake hash meets target protocol - return hashProofOfStake < (bnCoinDayWeight * bnTargetPerCoinDay); + const CAmount& nValueIn = stake->GetValue(); + const CDataStream& ssUniqueID = stake->GetUniqueness(); + + // Base target + uint256 bnTarget; + bnTarget.SetCompact(nBits); + + // Weighted target + uint256 bnWeight = uint256(nValueIn) / 100; + bnTarget *= bnWeight; + + // Check if proof-of-stake hash meets target protocol + const bool res = (hashProofOfStake < bnTarget); + + if (fVerify || res) { + LogPrint("staking", "%s : Proof Of Stake:" + "\nssUniqueID=%s" + "\nnTimeTx=%d" + "\nhashProofOfStake=%s" + "\nnBits=%d" + "\nweight=%d" + "\nbnTarget=%s (res: %d)\n\n", + __func__, HexStr(ssUniqueID), nTimeTx, hashProofOfStake.GetHex(), + nBits, nValueIn, bnTarget.GetHex(), res); + } + return res; } -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, - unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake) -{ - CDataStream ss(SER_GETHASH, 0); - ss << nStakeModifier << nTimeBlockFrom << ssUniqueID << nTimeTx; - hashProofOfStake = Hash(ss.begin(), ss.end()); - //LogPrintf("%s : modifier:%d nTimeBlockFrom:%d nTimeTx:%d hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, hashProofOfStake.GetHex()); +bool GetHashProofOfStake(const CBlockIndex* pindexPrev, CStakeInput* stake, const unsigned int nTimeTx, const bool fVerify, uint256& hashProofOfStakeRet) { + // Grab the stake data + CBlockIndex* pindexfrom = stake->GetIndexFrom(); + if (!pindexfrom) return error("%s : Failed to find the block index for stake origin", __func__); + const CDataStream& ssUniqueID = stake->GetUniqueness(); + const unsigned int nTimeBlockFrom = pindexfrom->nTime; + CDataStream modifier_ss(SER_GETHASH, 0); + + // Hash the modifier + if (!Params().IsStakeModifierV2(pindexPrev->nHeight + 1)) { + // Modifier v1 + uint64_t nStakeModifier = 0; + if (!stake->GetModifier(nStakeModifier)) + return error("%s : Failed to get kernel stake modifier", __func__); + modifier_ss << nStakeModifier; + } else { + // Modifier v2 + modifier_ss << pindexPrev->nStakeModifierV2; + } - return stakeTargetHit(hashProofOfStake, nValueIn, bnTarget); + CDataStream ss(modifier_ss); + // Calculate hash + ss << nTimeBlockFrom << ssUniqueID << nTimeTx; + hashProofOfStakeRet = Hash(ss.begin(), ss.end()); + + if (fVerify) { + LogPrint("staking", "%s :{ nStakeModifier=%s\n" + "nStakeModifierHeight=%s\n" + "}\n", + __func__, HexStr(modifier_ss), ((stake->IsZPIV()) ? "Not available" : std::to_string(stake->getStakeModifierHeight()))); + } + return true; } -bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake) +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake) { - if(Params().NetworkID() != CBaseChainParams::REGTEST) { - if (nTimeTx < nTimeBlockFrom) - return error("%s : nTime violation", __func__); + int prevHeight = pindexPrev->nHeight; - if (nTimeBlockFrom + Params().StakeMinAge() > nTimeTx) // Min age requirement - return error("%s : min age violation - nTimeBlockFrom=%d nStakeMinAge=%d nTimeTx=%d", - __func__, nTimeBlockFrom, Params().StakeMinAge(), nTimeTx); - } + // get stake input pindex + CBlockIndex* pindexFrom = stakeInput->GetIndexFrom(); + if (!pindexFrom || pindexFrom->nHeight < 1) return error("%s : no pindexfrom", __func__); - //grab difficulty - uint256 bnTargetPerCoinDay; - bnTargetPerCoinDay.SetCompact(nBits); + const uint32_t nTimeBlockFrom = pindexFrom->nTime; + const int nHeightBlockFrom = pindexFrom->nHeight; - //grab stake modifier - uint64_t nStakeModifier = 0; - if (!stakeInput->GetModifier(nStakeModifier)) - return error("%s : failed to get kernel stake modifier", __func__); + // check for maturity (min age/depth) requirements + if (!Params().HasStakeMinAgeOrDepth(prevHeight + 1, nTimeTx, nHeightBlockFrom, nTimeBlockFrom)) + return error("%s : min age violation - height=%d - nTimeTx=%d, nTimeBlockFrom=%d, nHeightBlockFrom=%d", + __func__, prevHeight + 1, nTimeTx, nTimeBlockFrom, nHeightBlockFrom); + // iterate the hashing bool fSuccess = false; - unsigned int nTryTime = 0; - int nHeightStart = chainActive.Height(); - int nHashDrift = 60; - CDataStream ssUniqueID = stakeInput->GetUniqueness(); - CAmount nValueIn = stakeInput->GetValue(); - for (int i = 0; i < nHashDrift; i++) //iterate the hashing + const unsigned int nHashDrift = 60; + unsigned int nTryTime = nTimeTx - 1; + // iterate from nTimeTx up to nTimeTx + nHashDrift + // but not after the max allowed future blocktime drift (3 minutes for PoS) + const unsigned int maxTime = std::min(nTimeTx + nHashDrift, Params().MaxFutureBlockTime(GetAdjustedTime(), true)); + + while (nTryTime < maxTime) { //new block came in, move on - if (chainActive.Height() != nHeightStart) + if (chainActive.Height() != prevHeight) break; - //hash this iteration - nTryTime = nTimeTx + nHashDrift - i; + ++nTryTime; // if stake hash does not meet the target then continue to next iteration - if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, bnTargetPerCoinDay, nTimeBlockFrom, nTryTime, hashProofOfStake)) + if (!CheckStakeKernelHash(pindexPrev, nBits, stakeInput, nTryTime, hashProofOfStake)) continue; - fSuccess = true; // if we make it this far then we have successfully created a stake hash - //LogPrintf("%s : hashproof=%s\n", __func__, hashProofOfStake.GetHex()); + // if we made it this far, then we have successfully found a valid kernel hash + fSuccess = true; nTimeTx = nTryTime; break; } @@ -358,8 +397,9 @@ bool ContextualCheckZerocoinStake(int nPreviousBlockHeight, CStakeInput* stake) if (!pindexFrom) return error("%s : failed to get index associated with zPIV stake checksum", __func__); - if (chainActive.Height() - pindexFrom->nHeight < Params().Zerocoin_RequiredStakeDepth()) - return error("%s : zPIV stake does not have required confirmation depth. Current height %d, stakeInput height %d.", __func__, chainActive.Height(), pindexFrom->nHeight); + int depth = (nPreviousBlockHeight + 1) - pindexFrom->nHeight; + if (depth < Params().Zerocoin_RequiredStakeDepth()) + return error("%s : zPIV stake does not have required confirmation depth. Current height %d, stakeInput height %d.", __func__, nPreviousBlockHeight, pindexFrom->nHeight); //The checksum needs to be the exact checksum from 200 blocks ago uint256 nCheckpoint200 = chainActive[nPreviousBlockHeight - Params().Zerocoin_RequiredStakeDepth()]->nAccumulatorCheckpoint; @@ -373,9 +413,7 @@ bool ContextualCheckZerocoinStake(int nPreviousBlockHeight, CStakeInput* stake) return true; } -// Check kernel hash target and coinstake signature -bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::unique_ptr& stake, int nPreviousBlockHeight) -{ +bool initStakeInput(const CBlock block, std::unique_ptr& stake, int nPreviousBlockHeight) { const CTransaction tx = block.vtx[1]; if (!tx.IsCoinStake()) return error("%s : called on non-coinstake %s", __func__, tx.GetHash().ToString().c_str()); @@ -399,7 +437,7 @@ bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::uniqu CTransaction txPrev; if (!GetTransaction(txin.prevout.hash, txPrev, hashBlock, true)) return error("%s : INFO: read txPrev failed, tx id prev: %s, block id %s", - __func__, txin.prevout.hash.GetHex(), block.GetHash().GetHex()); + __func__, txin.prevout.hash.GetHex(), block.GetHash().GetHex()); //verify signature and script if (!VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx, 0))) @@ -409,41 +447,39 @@ bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::uniqu pivInput->SetInput(txPrev, txin.prevout.n); stake = std::unique_ptr(pivInput); } + return true; +} - //Get the +// Check kernel hash target and coinstake signature +bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::unique_ptr& stake, int nPreviousBlockHeight) +{ + // Initialize the stake object + if(!initStakeInput(block, stake, nPreviousBlockHeight)) + return error("%s : stake input object initialization failed", __func__); + + const CTransaction tx = block.vtx[1]; + // Kernel (input 0) must match the stake hash target per coin age (nBits) + const CTxIn& txin = tx.vin[0]; + CBlockIndex* pindexPrev = mapBlockIndex[block.hashPrevBlock]; CBlockIndex* pindexfrom = stake->GetIndexFrom(); if (!pindexfrom) return error("%s : Failed to find the block index for stake origin", __func__); - // Read block header - CBlock blockfrom; - if (!ReadBlockFromDisk(blockfrom, pindexfrom->GetBlockPos())) - return error("%s : INFO: failed to find block", __func__); - - uint256 bnTargetPerCoinDay; - bnTargetPerCoinDay.SetCompact(block.nBits); - - uint64_t nStakeModifier = 0; - if (!stake->GetModifier(nStakeModifier)) - return error("%s : failed to get modifier for stake input\n", __func__); - - unsigned int nBlockFromTime = blockfrom.nTime; + unsigned int nBlockFromTime = pindexfrom->nTime; unsigned int nTxTime = block.nTime; - if (!txin.IsZerocoinSpend() && nPreviousBlockHeight >= Params().Zerocoin_Block_Public_Spend_Enabled() - 1 - && Params().NetworkID() != CBaseChainParams::REGTEST) { - //Equivalent for zPIV is checked above in ContextualCheckZerocoinStake() - if (nTxTime < nBlockFromTime) // Transaction timestamp nTxTime - return error("%s : nTime violation - nBlockFromTime=%d nTimeTx=%d", __func__, nBlockFromTime, nTxTime); - if (nBlockFromTime + Params().StakeMinAge() > nTxTime) // Min age requirement - return error("%s : min age violation - nBlockFromTime=%d nStakeMinAge=%d nTimeTx=%d", - __func__, nBlockFromTime, Params().StakeMinAge(), nTxTime); - } - if (!CheckStake(stake->GetUniqueness(), stake->GetValue(), nStakeModifier, bnTargetPerCoinDay, nBlockFromTime, - nTxTime, hashProofOfStake)) { - return error("%s : INFO: check kernel failed on coinstake %s, hashProof=%s \n", - __func__, tx.GetHash().GetHex(), hashProofOfStake.GetHex()); + const int nBlockFromHeight = pindexfrom->nHeight; + + if (!txin.IsZerocoinSpend() && nPreviousBlockHeight >= Params().Zerocoin_Block_Public_Spend_Enabled() - 1) { + //check for maturity (min age/depth) requirements + if (!Params().HasStakeMinAgeOrDepth(nPreviousBlockHeight+1, nTxTime, nBlockFromHeight, nBlockFromTime)) + return error("%s : min age violation - height=%d - nTimeTx=%d, nTimeBlockFrom=%d, nHeightBlockFrom=%d", + __func__, nPreviousBlockHeight, nTxTime, nBlockFromTime, nBlockFromHeight); } + if (!CheckStakeKernelHash(pindexPrev, block.nBits, stake.get(), nTxTime, hashProofOfStake, true)) + return error("%s : INFO: check kernel failed on coinstake %s, hashProof=%s", __func__, + tx.GetHash().GetHex(), hashProofOfStake.GetHex()); + return true; } @@ -471,7 +507,7 @@ unsigned int GetStakeModifierChecksum(const CBlockIndex* pindex) // Check stake modifier hard checkpoints bool CheckStakeModifierCheckpoints(int nHeight, unsigned int nStakeModifierChecksum) { - if (fTestNet) return true; // Testnet has no checkpoints + if (Params().NetworkID() != CBaseChainParams::MAIN) return true; // Testnet has no checkpoints if (mapStakeModifierCheckpoints.count(nHeight)) { return nStakeModifierChecksum == mapStakeModifierCheckpoints[nHeight]; } diff --git a/src/kernel.h b/src/kernel.h index 12ffc115db90..41d450b1c120 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -12,9 +12,6 @@ // MODIFIER_INTERVAL: time to elapse before new modifier is computed static const unsigned int MODIFIER_INTERVAL = 60; -static const unsigned int MODIFIER_INTERVAL_TESTNET = 60; -extern unsigned int nModifierInterval; -extern unsigned int getIntervalVersion(bool fTestNet); // MODIFIER_INTERVAL_RATIO: // ratio of group interval length between the last group and the first group @@ -23,27 +20,24 @@ static const int MODIFIER_INTERVAL_RATIO = 3; // Compute the hash modifier for proof-of-stake bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64_t& nStakeModifier, int& nStakeModifierHeight, int64_t& nStakeModifierTime, bool fPrintProofOfStake); bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeModifier, bool& fGeneratedStakeModifier); +uint256 ComputeStakeModifier(const CBlockIndex* pindexPrev, const uint256& kernel); +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, unsigned int& nTimeTx, uint256& hashProofOfStake); -bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); -bool stakeTargetHit(const uint256& hashProofOfStake, const int64_t& nValueIn, const uint256& bnTargetPerCoinDay); -bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake); +// Initialize the stake input object +bool initStakeInput(const CBlock block, std::unique_ptr& stake, int nPreviousBlockHeight); // Check kernel hash target and coinstake signature // Sets hashProofOfStake on success return bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::unique_ptr& stake, int nPreviousBlockHeight); - -// Check whether the coinstake timestamp meets protocol -bool CheckCoinStakeTimestamp(int64_t nTimeBlock, int64_t nTimeTx); - +bool CheckStakeKernelHash(const CBlockIndex* pindexPrev, const unsigned int nBits, CStakeInput* stake, const unsigned int nTimeTx, uint256& hashProofOfStake, const bool fVerify = false); +// Returns the proof of stake hash +bool GetHashProofOfStake(const CBlockIndex* pindexPrev, CStakeInput* stake, const unsigned int nTimeTx, const bool fVerify, uint256& hashProofOfStakeRet); // Get stake modifier checksum unsigned int GetStakeModifierChecksum(const CBlockIndex* pindex); // Check stake modifier hard checkpoints bool CheckStakeModifierCheckpoints(int nHeight, unsigned int nStakeModifierChecksum); -// Get time weight using supplied timestamps -int64_t GetWeight(int64_t nIntervalBeginning, int64_t nIntervalEnd); - bool ContextualCheckZerocoinStake(int nPreviousBlockHeight, CStakeInput* stake); #endif // BITCOIN_KERNEL_H diff --git a/src/main.cpp b/src/main.cpp index ac2553c83199..90964553e907 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1576,6 +1576,16 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa hash.ToString(), nFees, ::minRelayTxFee.GetFee(nSize) * 10000); + // As zero fee transactions are not going to be accepted in the near future (4.0) and the code will be fully refactored soon. + // This is just a quick inline towards that goal, the mempool by default will not accept them. Blocking + // any subsequent network relay. + if ((Params().NetworkID() != CBaseChainParams::REGTEST) && + nFees == 0 && !tx.HasZerocoinSpendInputs()) { + return error("%s: zero fees not accepted %s, %d > %d", + __func__, + hash.ToString(), + nFees, ::minRelayTxFee.GetFee(nSize) * 10000); + } bool fCLTVHasMajority = CBlockIndex::IsSuperMajority(5, chainActive.Tip(), Params().EnforceBlockUpgradeMajority()); @@ -1956,7 +1966,7 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex) if (!ReadBlockFromDisk(block, pindex->GetBlockPos())) return false; if (block.GetHash() != pindex->GetBlockHash()) { - LogPrintf("%s : block=%s index=%s\n", __func__, block.GetHash().ToString().c_str(), pindex->GetBlockHash().ToString().c_str()); + LogPrintf("%s : block=%s index=%s\n", __func__, block.GetHash().GetHex(), pindex->GetBlockHash().GetHex()); return error("ReadBlockFromDisk(CBlock&, CBlockIndex*) : GetHash() doesn't match index"); } return true; @@ -3154,7 +3164,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin // verify that the view's current state corresponds to the previous block uint256 hashPrevBlock = pindex->pprev == NULL ? uint256(0) : pindex->pprev->GetBlockHash(); if (hashPrevBlock != view.GetBestBlock()) - LogPrintf("%s: hashPrev=%s view=%s\n", __func__, hashPrevBlock.ToString().c_str(), view.GetBestBlock().ToString().c_str()); + LogPrintf("%s: hashPrev=%s view=%s\n", __func__, hashPrevBlock.GetHex(), view.GetBestBlock().GetHex()); assert(hashPrevBlock == view.GetBestBlock()); // Special case for the genesis block, skipping connection of its transactions @@ -4153,15 +4163,19 @@ CBlockIndex* AddToBlockIndex(const CBlock& block) pindexNew->hashProofOfStake = mapProofOfStake[hash]; } - // ppcoin: compute stake modifier - uint64_t nStakeModifier = 0; - bool fGeneratedStakeModifier = false; - if (!ComputeNextStakeModifier(pindexNew->pprev, nStakeModifier, fGeneratedStakeModifier)) - LogPrintf("AddToBlockIndex() : ComputeNextStakeModifier() failed \n"); - pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); - pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew); - if (!CheckStakeModifierCheckpoints(pindexNew->nHeight, pindexNew->nStakeModifierChecksum)) - LogPrintf("AddToBlockIndex() : Rejected by stake modifier checkpoint height=%d, modifier=%s \n", pindexNew->nHeight, std::to_string(nStakeModifier)); + if (!Params().IsStakeModifierV2(pindexNew->nHeight)) { + uint64_t nStakeModifier = 0; + bool fGeneratedStakeModifier = false; + if (!ComputeNextStakeModifier(pindexNew->pprev, nStakeModifier, fGeneratedStakeModifier)) + LogPrintf("AddToBlockIndex() : ComputeNextStakeModifier() failed \n"); + pindexNew->SetStakeModifier(nStakeModifier, fGeneratedStakeModifier); + pindexNew->nStakeModifierChecksum = GetStakeModifierChecksum(pindexNew); + if (!CheckStakeModifierCheckpoints(pindexNew->nHeight, pindexNew->nStakeModifierChecksum)) + LogPrintf("AddToBlockIndex() : Rejected by stake modifier checkpoint height=%d, modifier=%s \n", pindexNew->nHeight, std::to_string(nStakeModifier)); + } else { + // compute v2 stake modifier + pindexNew->nStakeModifierV2 = ComputeStakeModifier(pindexNew->pprev, block.vtx[1].vin[0].prevout.hash); + } } pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew); pindexNew->RaiseValidity(BLOCK_VALID_TREE); @@ -4326,17 +4340,23 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, bool f bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bool fCheckMerkleRoot, bool fCheckSig) { // These are checks that are independent of context. + const bool IsPoS = block.IsProofOfStake(); + LogPrint("debug", "%s: block=%s is proof of stake=%d\n", __func__, block.GetHash().ToString().c_str(), IsPoS); + // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. - if (!CheckBlockHeader(block, state, block.IsProofOfWork())) - return state.DoS(100, error("CheckBlock() : CheckBlockHeader failed"), - REJECT_INVALID, "bad-header", true); + if (!CheckBlockHeader(block, state, !IsPoS)) + return state.DoS(100, error("%s : CheckBlockHeader failed", __func__), REJECT_INVALID, "bad-header", true); + + // All potential-corruption validation must be done before we do any + // transaction validation, as otherwise we may mark the header as invalid + // because we receive the wrong transactions for it. // Check timestamp - LogPrint("debug", "%s: block=%s is proof of stake=%d\n", __func__, block.GetHash().ToString().c_str(), block.IsProofOfStake()); - if (Params().NetworkID() != CBaseChainParams::REGTEST && block.GetBlockTime() > GetAdjustedTime() + (block.IsProofOfStake() ? 180 : 7200)) // 3 minute future drift for PoS - return state.Invalid(error("CheckBlock() : block timestamp too far in the future"), + if (Params().NetworkID() != CBaseChainParams::REGTEST && + block.GetBlockTime() > Params().MaxFutureBlockTime(GetAdjustedTime(), IsPoS)) // 3 minute future drift for PoS + return state.Invalid(error("%s : block timestamp too far in the future", __func__), REJECT_INVALID, "time-too-new"); // Check the merkle root. @@ -4344,47 +4364,43 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo bool mutated; uint256 hashMerkleRoot2 = block.BuildMerkleTree(&mutated); if (block.hashMerkleRoot != hashMerkleRoot2) - return state.DoS(100, error("CheckBlock() : hashMerkleRoot mismatch"), + return state.DoS(100, error("%s : hashMerkleRoot mismatch", __func__), REJECT_INVALID, "bad-txnmrklroot", true); // Check for merkle tree malleability (CVE-2012-2459): repeating sequences // of transactions in a block without affecting the merkle root of a block, // while still invalidating it. if (mutated) - return state.DoS(100, error("CheckBlock() : duplicate transaction"), + return state.DoS(100, error("%s : duplicate transaction", __func__), REJECT_INVALID, "bad-txns-duplicate", true); } - // All potential-corruption validation must be done before we do any - // transaction validation, as otherwise we may mark the header as invalid - // because we receive the wrong transactions for it. - // Size limits unsigned int nMaxBlockSize = MAX_BLOCK_SIZE_CURRENT; if (block.vtx.empty() || block.vtx.size() > nMaxBlockSize || ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION) > nMaxBlockSize) - return state.DoS(100, error("CheckBlock() : size limits failed"), + return state.DoS(100, error("%s : size limits failed", __func__), REJECT_INVALID, "bad-blk-length"); // First transaction must be coinbase, the rest must not be if (block.vtx.empty() || !block.vtx[0].IsCoinBase()) - return state.DoS(100, error("CheckBlock() : first tx is not coinbase"), + return state.DoS(100, error("%s : first tx is not coinbase", __func__), REJECT_INVALID, "bad-cb-missing"); for (unsigned int i = 1; i < block.vtx.size(); i++) if (block.vtx[i].IsCoinBase()) - return state.DoS(100, error("CheckBlock() : more than one coinbase"), + return state.DoS(100, error("%s : more than one coinbase", __func__), REJECT_INVALID, "bad-cb-multiple"); - if (block.IsProofOfStake()) { + if (IsPoS) { // Coinbase output should be empty if proof-of-stake block if (block.vtx[0].vout.size() != 1 || !block.vtx[0].vout[0].IsEmpty()) - return state.DoS(100, error("CheckBlock() : coinbase output not empty for proof-of-stake block")); + return state.DoS(100, error("%s : coinbase output not empty for proof-of-stake block", __func__)); // Second transaction must be coinstake, the rest must not be if (block.vtx.empty() || !block.vtx[1].IsCoinStake()) - return state.DoS(100, error("CheckBlock() : second tx is not coinstake")); + return state.DoS(100, error("%s : second tx is not coinstake", __func__)); for (unsigned int i = 2; i < block.vtx.size(); i++) if (block.vtx[i].IsCoinStake()) - return state.DoS(100, error("CheckBlock() : more than one coinstake")); + return state.DoS(100, error("%s : more than one coinstake", __func__)); } // ----------- swiftTX transaction scanning ----------- @@ -4396,8 +4412,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo if (mapLockedInputs.count(in.prevout)) { if (mapLockedInputs[in.prevout] != tx.GetHash()) { mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); - LogPrintf("CheckBlock() : found conflicting transaction with transaction lock %s %s\n", mapLockedInputs[in.prevout].ToString(), tx.GetHash().ToString()); - return state.DoS(0, error("CheckBlock() : found conflicting transaction with transaction lock"), + LogPrintf("%s : found conflicting transaction with transaction lock %s %s\n", __func__, + mapLockedInputs[in.prevout].ToString(), tx.GetHash().GetHex()); + return state.DoS(0, error("%s : found conflicting transaction with transaction lock", __func__), REJECT_INVALID, "conflicting-tx-ix"); } } @@ -4405,7 +4422,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo } } } else { - LogPrintf("CheckBlock() : skipping transaction locking checks\n"); + LogPrintf("%s : skipping transaction locking checks\n", __func__); } // masternode payments / budgets @@ -4429,12 +4446,12 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo if (nHeight != 0 && !IsInitialBlockDownload()) { if (!IsBlockPayeeValid(block, nHeight)) { mapRejectedBlocks.insert(std::make_pair(block.GetHash(), GetTime())); - return state.DoS(0, error("CheckBlock() : Couldn't find masternode/budget payment"), + return state.DoS(0, error("%s : Couldn't find masternode/budget payment", __func__), REJECT_INVALID, "bad-cb-payee"); } } else { if (fDebug) - LogPrintf("CheckBlock(): Masternode payment check skipped on sync - skipping IsBlockPayeeValid()\n"); + LogPrintf("%s: Masternode payment check skipped on sync - skipping IsBlockPayeeValid()\n", __func__); } } @@ -4451,7 +4468,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo state, isBlockBetweenFakeSerialAttackRange(blockHeight) )) - return error("CheckBlock() : CheckTransaction failed"); + return error("%s : CheckTransaction failed", __func__); // double check that there are no double spent zPIV spends in this block if (tx.HasZerocoinSpendInputs()) { @@ -4485,7 +4502,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo } unsigned int nMaxBlockSigOps = fZerocoinActive ? MAX_BLOCK_SIGOPS_CURRENT : MAX_BLOCK_SIGOPS_LEGACY; if (nSigOps > nMaxBlockSigOps) - return state.DoS(100, error("CheckBlock() : out-of-bounds SigOpCount"), + return state.DoS(100, error("%s : out-of-bounds SigOpCount", __func__), REJECT_INVALID, "bad-blk-sigops", true); return true; @@ -4494,7 +4511,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo bool CheckWork(const CBlock block, CBlockIndex* const pindexPrev) { if (pindexPrev == NULL) - return error("%s : null pindexPrev for block %s", __func__, block.GetHash().ToString().c_str()); + return error("%s : null pindexPrev for block %s", __func__, block.GetHash().GetHex()); unsigned int nBitsRequired = GetNextWorkRequired(pindexPrev, &block); @@ -4510,7 +4527,7 @@ bool CheckWork(const CBlock block, CBlockIndex* const pindexPrev) if (block.nBits != nBitsRequired) { // Pivx Specific reference to the block with the wrong threshold was used. - if ((block.nTime == Params().PivxBadBlockTime()) && (block.nBits == Params().PivxBadBlocknBits())) { + if ((block.nTime == (uint32_t) Params().PivxBadBlockTime()) && (block.nBits == (uint32_t) Params().PivxBadBlocknBits())) { // accept PIVX block minted with incorrect proof of work threshold return true; } @@ -4532,7 +4549,7 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta int nHeight = pindexPrev->nHeight + 1; - if ((Params().NetworkIDString() == "regtest") && block.nBits != GetNextWorkRequired(pindexPrev, &block)) + if ((Params().NetworkID() == CBaseChainParams::REGTEST) && block.nBits != GetNextWorkRequired(pindexPrev, &block)) return state.DoS(100, error("%s : incorrect proof of work", __func__), REJECT_INVALID, "bad-diffbits"); @@ -4542,12 +4559,9 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta if (chainActive.Height() - nHeight >= nMaxReorgDepth) return state.DoS(1, error("%s: forked chain older than max reorganization depth (height %d)", __func__, chainActive.Height() - nHeight)); - // Check timestamp against prev - if (block.GetBlockTime() <= pindexPrev->GetMedianTimePast() && (Params().NetworkIDString() != "regtest")) { - LogPrintf("Block time = %d , GetMedianTimePast = %d \n", block.GetBlockTime(), pindexPrev->GetMedianTimePast()); - return state.Invalid(error("%s : block's timestamp is too early", __func__), - REJECT_INVALID, "time-too-old"); - } + // Check blocktime against prev (WANT: blk_time > MedianTimePast) + if (Params().NetworkID() != CBaseChainParams::REGTEST && block.GetBlockTime() <= pindexPrev->GetMedianTimePast()) + return state.DoS(50, error("%s : block timestamp too old", __func__), REJECT_INVALID, "time-too-old"); // Check that the block chain matches the known block chain up to a checkpoint if (!Checkpoints::CheckBlock(nHeight, hash)) @@ -4559,23 +4573,11 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta if (pcheckpoint && nHeight < pcheckpoint->nHeight) return state.DoS(0, error("%s : forked chain older than last checkpoint (height %d)", __func__, nHeight)); - // Reject block.nVersion=1 blocks when 95% (75% on testnet) of the network has upgraded: - if (block.nVersion < 2 && - CBlockIndex::IsSuperMajority(2, pindexPrev, Params().RejectBlockOutdatedMajority())) { - return state.Invalid(error("%s : rejected nVersion=1 block", __func__), - REJECT_OBSOLETE, "bad-version"); - } - - // Reject block.nVersion=2 blocks when 95% (75% on testnet) of the network has upgraded: - if (block.nVersion < 3 && CBlockIndex::IsSuperMajority(3, pindexPrev, Params().RejectBlockOutdatedMajority())) { - return state.Invalid(error("%s : rejected nVersion=2 block", __func__), - REJECT_OBSOLETE, "bad-version"); - } - - // Reject block.nVersion=4 blocks when 95% (75% on testnet) of the network has upgraded: - if (block.nVersion < 5 && CBlockIndex::IsSuperMajority(5, pindexPrev, Params().RejectBlockOutdatedMajority())) { - return state.Invalid(error("%s : rejected nVersion=4 block", __func__), - REJECT_OBSOLETE, "bad-version"); + // Reject block.nVersion=1, ..., CURRENT_VERSION-1 blocks when 95% (75% on testnet) of the network has upgraded: + for (int version = 2; version <= CBlockHeader::CURRENT_VERSION; version++) { + if (block.nVersion < version && CBlockIndex::IsSuperMajority(version, pindexPrev, Params().RejectBlockOutdatedMajority())) { + return state.Invalid(error("%s : rejected nVersion=%d block", __func__, block.nVersion), REJECT_OBSOLETE, "bad-version"); + } } return true; @@ -4660,7 +4662,7 @@ bool AcceptBlockHeader(const CBlock& block, CValidationState& state, CBlockIndex if (hash != Params().HashGenesisBlock()) { BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi == mapBlockIndex.end()) - return state.DoS(0, error("%s : prev block %s not found", __func__, block.hashPrevBlock.ToString().c_str()), 0, "bad-prevblk"); + return state.DoS(0, error("%s : prev block %s not found", __func__, block.hashPrevBlock.GetHex()), 0, "bad-prevblk"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { //If this "invalid" block is an exact match from the checkpoints, then reconsider it @@ -4703,7 +4705,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CBlockIndex** ppindex, if (block.GetHash() != Params().HashGenesisBlock()) { BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi == mapBlockIndex.end()) - return state.DoS(0, error("%s : prev block %s not found", __func__, block.hashPrevBlock.ToString().c_str()), 0, "bad-prevblk"); + return state.DoS(0, error("%s : prev block %s not found", __func__, block.hashPrevBlock.GetHex()), 0, "bad-prevblk"); pindexPrev = (*mi).second; if (pindexPrev->nStatus & BLOCK_FAILED_MASK) { //If this "invalid" block is an exact match from the checkpoints, then reconsider it @@ -5051,30 +5053,6 @@ void CBlockIndex::BuildSkip() bool ProcessNewBlock(CValidationState& state, CNode* pfrom, CBlock* pblock, CDiskBlockPos* dbp) { - // Preliminary checks - int64_t nStartTime = GetTimeMillis(); - bool checked = CheckBlock(*pblock, state); - - int nMints = 0; - int nSpends = 0; - for (const CTransaction& tx : pblock->vtx) { - if (tx.ContainsZerocoins()) { - for (const CTxIn& in : tx.vin) { - if (in.IsZerocoinSpend()) - nSpends++; - } - for (const CTxOut& out : tx.vout) { - if (out.IsZerocoinMint()) - nMints++; - } - } - } - if (nMints || nSpends) - LogPrintf("%s : block contains %d zPIV mints and %d zPIV spends\n", __func__, nMints, nSpends); - - if (!CheckBlockSignature(*pblock)) - return error("ProcessNewBlock() : bad proof-of-stake block signature"); - if (pblock->GetHash() != Params().HashGenesisBlock() && pfrom != NULL) { //if we get this far, check if the prev block is our prev block, if not then request sync and return false BlockMap::iterator mi = mapBlockIndex.find(pblock->hashPrevBlock); @@ -5084,17 +5062,26 @@ bool ProcessNewBlock(CValidationState& state, CNode* pfrom, CBlock* pblock, CDis } } + // Preliminary checks + int64_t nStartTime = GetTimeMillis(); + + // check block + bool checked = CheckBlock(*pblock, state); + + if (!CheckBlockSignature(*pblock)) + return error("ProcessNewBlock() : bad proof-of-stake block signature"); + { - LOCK(cs_main); // Replaces the former TRY_LOCK loop because busy waiting wastes too much resources + LOCK(cs_main); - MarkBlockAsReceived (pblock->GetHash ()); + MarkBlockAsReceived(pblock->GetHash()); if (!checked) { return error ("%s : CheckBlock FAILED for block %s", __func__, pblock->GetHash().GetHex()); } // Store to disk CBlockIndex* pindex = nullptr; - bool ret = AcceptBlock (*pblock, state, &pindex, dbp, checked); + bool ret = AcceptBlock(*pblock, state, &pindex, dbp, checked); if (pindex && pfrom) { mapBlockSource[pindex->GetBlockHash ()] = pfrom->GetId (); } diff --git a/src/main.h b/src/main.h index c8c6a0ecbc5c..25a63ee80394 100644 --- a/src/main.h +++ b/src/main.h @@ -84,8 +84,6 @@ static const unsigned int MAX_BLOCKFILE_SIZE = 0x8000000; // 128 MiB static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB /** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB -/** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */ -static const int COINBASE_MATURITY = 100; /** Maximum number of script-checking threads allowed */ static const int MAX_SCRIPTCHECK_THREADS = 16; /** -par default (number of script-checking threads, 0 = auto) */ @@ -117,7 +115,7 @@ static const int64_t DEFAULT_MAX_TIP_AGE = 24 * 60 * 60; /** Default for -blockspamfilter, use header spam filter */ static const bool DEFAULT_BLOCK_SPAM_FILTER = true; /** Default for -blockspamfiltermaxsize, maximum size of the list of indexes in the block spam filter */ -static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE = COINBASE_MATURITY; +static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_SIZE = 100; /** Default for -blockspamfiltermaxavg, maximum average size of an index occurrence in the block spam filter */ static const unsigned int DEFAULT_BLOCK_SPAM_FILTER_MAX_AVG = 10; diff --git a/src/miner.cpp b/src/miner.cpp index 89502c2667e3..5b0533a110a2 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -118,7 +118,11 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, // Make sure to create the correct block version after zerocoin is enabled bool fZerocoinActive = nHeight >= Params().Zerocoin_StartHeight(); - pblock->nVersion = 5; // Supports CLTV activation + if(Params().IsStakeModifierV2(nHeight)) { + pblock->nVersion = 6; //!> Supports V2 Stake Modifiers. + } else { + pblock->nVersion = 5; //!> Supports CLTV activation + } // -regtest only: allow overriding block.nVersion with // -blockversion=N to test forking scenarios diff --git a/src/primitives/block.h b/src/primitives/block.h index e9b9ece76a13..a22785c38480 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -27,7 +27,7 @@ class CBlockHeader { public: // header - static const int32_t CURRENT_VERSION=5; // Version 5 supports CLTV activation + static const int32_t CURRENT_VERSION=6; //!> Version 6 supports V2 Stake Modifiers int32_t nVersion; uint256 hashPrevBlock; uint256 hashMerkleRoot; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index daa2bbf0712a..d1651e5118cb 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -133,9 +133,14 @@ SendCoinsDialog::SendCoinsDialog(QWidget* parent) : QDialog(parent, Qt::WindowSy ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt()); ui->customFee->setValue(settings.value("nTransactionFee").toLongLong()); ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool()); - ui->checkBoxFreeTx->setChecked(settings.value("fSendFreeTransactions").toBool()); + //ui->checkBoxFreeTx->setChecked(settings.value("fSendFreeTransactions").toBool()); ui->checkzPIV->hide(); + // Making zero fee txes option not visible (no need to clean this.. wallet 4.0 is right behind the corner) + ui->checkBoxFreeTx->setChecked(false); + ui->checkBoxFreeTx->setVisible(false); + ui->labelFreeTx->setVisible(false); + minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool()); // If SwiftX activated hide button 'Choose'. Show otherwise. ui->buttonChooseFee->setVisible(!useSwiftTX); diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 908c8df9127d..506f63d1a5e8 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -271,8 +271,8 @@ QString TransactionDesc::toHTML(CWallet* wallet, CWalletTx& wtx, TransactionReco } if (wtx.IsCoinBase()) { - quint32 numBlocksToMaturity = Params().COINBASE_MATURITY() + 1; - strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "
"; + //quint32 numBlocksToMaturity = Params().COINBASE_MATURITY() + 1; + //strHTML += "
" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "
"; } // diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 25d29c1c4ea2..9c8f942e4bc4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -8,6 +8,7 @@ #include "base58.h" #include "checkpoints.h" #include "clientversion.h" +#include "kernel.h" #include "main.h" #include "rpc/server.h" #include "sync.h" @@ -135,6 +136,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); result.push_back(Pair("modifier", strprintf("%016x", blockindex->nStakeModifier))); + result.push_back(Pair("modifierV2", blockindex->nStakeModifierV2.GetHex())); result.push_back(Pair("moneysupply",ValueFromAmount(blockindex->nMoneySupply))); @@ -145,6 +147,31 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx zpivObj.push_back(Pair("total", ValueFromAmount(blockindex->GetZerocoinSupply()))); result.push_back(Pair("zPIVsupply", zpivObj)); + ////////// + ////////// Coin stake data //////////////// + ///////// + if (block.IsProofOfStake()) { + // First grab it + uint256 hashProofOfStakeRet; + std::unique_ptr stake; + // Initialize the stake object (we should look for this in some other place and not initialize it every time..) + if (!initStakeInput(block, stake, blockindex->nHeight - 1)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot initialize stake input"); + + unsigned int nTxTime = block.nTime; + // todo: Add the debug as param.. + if (!GetHashProofOfStake(blockindex->pprev, stake.get(), nTxTime, false, hashProofOfStakeRet)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Cannot get proof of stake hash"); + + UniValue stakeData(UniValue::VOBJ); + stakeData.push_back(Pair("BlockFromHash", stake.get()->GetIndexFrom()->GetBlockHash().GetHex())); + stakeData.push_back(Pair("BlockFromHeight", stake.get()->GetIndexFrom()->nHeight)); + stakeData.push_back(Pair("hashProofOfStake", hashProofOfStakeRet.GetHex())); + stakeData.push_back(Pair("stakeModifierHeight", ((stake->IsZPIV()) ? "Not available" : std::to_string( + stake->getStakeModifierHeight())))); + result.push_back(Pair("CoinStake", stakeData)); + } + return result; } @@ -583,6 +610,12 @@ UniValue getblock(const UniValue& params, bool fHelp) " \"1000\" : n, (numeric) supply of 1000 zPIV denomination\n" " \"5000\" : n, (numeric) supply of 5000 zPIV denomination\n" " \"total\" : n, (numeric) The total supply of all zPIV denominations\n" + " },\n" + " \"CoinStake\" :\n" + " \"BlockFromHash\" : \"hash\", (string) Block hash of the coin stake input\n" + " \"BlockFromHeight\" : n, (numeric) Block Height of the coin stake input\n" + " \"hashProofOfStake\" : \"hash\", (string) Proof of Stake hash\n" + " \"stakeModifierHeight\" : \"nnn\" (string) Stake modifier block height\n" " }\n" "}\n" diff --git a/src/stakeinput.cpp b/src/stakeinput.cpp index adf37c636dc6..8ef2f5402326 100644 --- a/src/stakeinput.cpp +++ b/src/stakeinput.cpp @@ -15,7 +15,6 @@ CZPivStake::CZPivStake(const libzerocoin::CoinSpend& spend) this->denom = spend.getDenomination(); uint256 nSerial = spend.getCoinSerialNumber().getuint256(); this->hashSerial = Hash(nSerial.begin(), nSerial.end()); - this->pindexFrom = nullptr; fMint = false; } @@ -121,7 +120,7 @@ bool CZPivStake::CreateTxIn(CWallet* pwallet, CTxIn& txIn, uint256 hashTxOut) CZerocoinSpendReceipt receipt; if (!pwallet->MintToTxIn(mint, hashTxOut, txIn, receipt, libzerocoin::SpendType::STAKE, pindexCheckpoint)) - return error("%s\n", receipt.GetStatusMessage()); + return error("%s", receipt.GetStatusMessage()); return true; } @@ -232,15 +231,16 @@ bool CPivStake::CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmoun bool CPivStake::GetModifier(uint64_t& nStakeModifier) { - int nStakeModifierHeight = 0; - int64_t nStakeModifierTime = 0; - GetIndexFrom(); - if (!pindexFrom) - return error("%s: failed to get index from", __func__); - - if (!GetKernelStakeModifier(pindexFrom->GetBlockHash(), nStakeModifier, nStakeModifierHeight, nStakeModifierTime, false)) - return error("CheckStakeKernelHash(): failed to get kernel stake modifier \n"); - + if (this->nStakeModifier == 0) { + // look for the modifier + GetIndexFrom(); + if (!pindexFrom) + return error("%s: failed to get index from", __func__); + // TODO: This method must be removed from here in the short terms.. it's a call to an static method in kernel.cpp when this class method is only called from kernel.cpp, no comments.. + if (!GetKernelStakeModifier(pindexFrom->GetBlockHash(), this->nStakeModifier, this->nStakeModifierHeight, this->nStakeModifierTime, false)) + return error("CheckStakeKernelHash(): failed to get kernel stake modifier"); + } + nStakeModifier = this->nStakeModifier; return true; } @@ -255,6 +255,8 @@ CDataStream CPivStake::GetUniqueness() //The block that the UTXO was added to the chain CBlockIndex* CPivStake::GetIndexFrom() { + if (pindexFrom) + return pindexFrom; uint256 hashBlock = 0; CTransaction tx; if (GetTransaction(txFrom.GetHash(), tx, hashBlock, true)) { diff --git a/src/stakeinput.h b/src/stakeinput.h index 887b18db3ce2..c63458e96b40 100644 --- a/src/stakeinput.h +++ b/src/stakeinput.h @@ -16,7 +16,7 @@ class CWalletTx; class CStakeInput { protected: - CBlockIndex* pindexFrom; + CBlockIndex* pindexFrom = nullptr; public: virtual ~CStakeInput(){}; @@ -29,6 +29,10 @@ class CStakeInput virtual bool IsZPIV() = 0; virtual CDataStream GetUniqueness() = 0; virtual uint256 GetSerialHash() const = 0; + + virtual uint64_t getStakeModifierHeight() const { + return 0; + } }; @@ -48,7 +52,6 @@ class CZPivStake : public CStakeInput { this->denom = denom; this->hashSerial = hashSerial; - this->pindexFrom = nullptr; fMint = true; } @@ -74,11 +77,13 @@ class CPivStake : public CStakeInput private: CTransaction txFrom; unsigned int nPosition; + + // cached data + uint64_t nStakeModifier = 0; + int nStakeModifierHeight = 0; + int64_t nStakeModifierTime = 0; public: - CPivStake() - { - this->pindexFrom = nullptr; - } + CPivStake(){} bool SetInput(CTransaction txPrev, unsigned int n); @@ -91,6 +96,8 @@ class CPivStake : public CStakeInput bool CreateTxOuts(CWallet* pwallet, std::vector& vout, CAmount nTotal) override; bool IsZPIV() override { return false; } uint256 GetSerialHash() const override { return uint256(0); } + + uint64_t getStakeModifierHeight() const override { return nStakeModifierHeight; } }; diff --git a/src/txdb.cpp b/src/txdb.cpp index 1eb12c739fdf..00d2eb0ad912 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -256,7 +256,11 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nMint = diskindex.nMint; pindexNew->nMoneySupply = diskindex.nMoneySupply; pindexNew->nFlags = diskindex.nFlags; - pindexNew->nStakeModifier = diskindex.nStakeModifier; + if (!Params().IsStakeModifierV2(pindexNew->nHeight)) { + pindexNew->nStakeModifier = diskindex.nStakeModifier; + } else { + pindexNew->nStakeModifierV2 = diskindex.nStakeModifierV2; + } pindexNew->prevoutStake = diskindex.prevoutStake; pindexNew->nStakeTime = diskindex.nStakeTime; pindexNew->hashProofOfStake = diskindex.hashProofOfStake; diff --git a/src/version.h b/src/version.h index 0faf75be6388..38600b8b72bf 100644 --- a/src/version.h +++ b/src/version.h @@ -11,7 +11,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 70916; +static const int PROTOCOL_VERSION = 70917; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index aab74e0c1309..32ed01e5e4dd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1787,7 +1787,8 @@ bool less_then_denom(const COutput& out1, const COutput& out2) return (!found1 && found2); } -bool CWallet::SelectStakeCoins(std::list >& listInputs, CAmount nTargetAmount, bool fPrecompute) +bool CWallet::SelectStakeCoins(std::list >& listInputs, CAmount nTargetAmount, + int blockHeight, bool fPrecompute) { LOCK(cs_main); //Add PIV @@ -1800,20 +1801,12 @@ bool CWallet::SelectStakeCoins(std::list >& listInp if (nAmountSelected + out.tx->vout[out.i].nValue > nTargetAmount) continue; - //if zerocoinspend, then use the block time - int64_t nTxTime = out.tx->GetTxTime(); - if (out.tx->vin[0].IsZerocoinSpend()) { - if (!out.tx->IsInMainChain()) - continue; - nTxTime = mapBlockIndex.at(out.tx->hashBlock)->GetBlockTime(); - } - - //check for min age - if (GetAdjustedTime() - nTxTime < Params().StakeMinAge() && Params().NetworkID() != CBaseChainParams::REGTEST) + if (out.tx->vin[0].IsZerocoinSpend() && !out.tx->IsInMainChain()) continue; - //check that it is matured - if (out.nDepth < (out.tx->IsCoinStake() ? Params().COINBASE_MATURITY() : 10)) + CBlockIndex* utxoBlock = mapBlockIndex.at(out.tx->hashBlock); + //check for maturity (min age/depth) + if (!Params().HasStakeMinAgeOrDepth(blockHeight, GetAdjustedTime(), utxoBlock->nHeight, utxoBlock->GetBlockTime())) continue; //add to our stake set @@ -1865,6 +1858,8 @@ bool CWallet::MintableCoins() CAmount nBalance = GetBalance(); CAmount nZpivBalance = GetZerocoinBalance(false); + int chainHeight = chainActive.Height(); + // Regular PIV if (nBalance > 0) { if (mapArgs.count("-reservebalance") && !ParseMoney(mapArgs["-reservebalance"], nReserveBalance)) @@ -1875,15 +1870,11 @@ bool CWallet::MintableCoins() std::vector vCoins; AvailableCoins(vCoins, true); + int64_t time = GetAdjustedTime(); for (const COutput& out : vCoins) { - int64_t nTxTime = out.tx->GetTxTime(); - if (out.tx->vin[0].IsZerocoinSpend()) { - if (!out.tx->IsInMainChain()) - continue; - nTxTime = mapBlockIndex.at(out.tx->hashBlock)->GetBlockTime(); - } - - if (Params().NetworkID() == CBaseChainParams::REGTEST || GetAdjustedTime() - nTxTime >= Params().StakeMinAge()) + CBlockIndex* utxoBlock = mapBlockIndex.at(out.tx->hashBlock); + //check for maturity (min age/depth) + if (Params().HasStakeMinAgeOrDepth(chainHeight, time, utxoBlock->nHeight, utxoBlock->nTime)) return true; } } @@ -1894,7 +1885,7 @@ bool CWallet::MintableCoins() for (auto mint : setMints) { if (mint.nVersion < CZerocoinMint::STAKABLE_VERSION) continue; - if (mint.nHeight > chainActive.Height() - Params().Zerocoin_RequiredStakeDepth()) + if (mint.nHeight > chainHeight - Params().Zerocoin_RequiredStakeDepth()) continue; return true; } @@ -2339,6 +2330,7 @@ bool CWallet::CreateCoinStake( // The following split & combine thresholds are important to security // Should not be adjusted if you don't understand the consequences //int64_t nCombineThreshold = 0; + const CBlockIndex* pindexPrev = chainActive.Tip(); txNew.vin.clear(); txNew.vout.clear(); @@ -2358,7 +2350,7 @@ bool CWallet::CreateCoinStake( // Get the list of stakable inputs std::list > listInputs; - if (!SelectStakeCoins(listInputs, nBalance - nReserveBalance)) { + if (!SelectStakeCoins(listInputs, nBalance - nReserveBalance, pindexPrev->nHeight + 1)) { LogPrintf("CreateCoinStake(): selectStakeCoins failed\n"); return false; } @@ -2379,31 +2371,24 @@ bool CWallet::CreateCoinStake( CScript scriptPubKeyKernel; bool fKernelFound = false; int nAttempts = 0; + + // Block time. + nTxNewTime = GetAdjustedTime(); + // If the block time is in the future, then starts there. + if (pindexPrev->nTime > nTxNewTime) { + nTxNewTime = pindexPrev->nTime; + } + for (std::unique_ptr& stakeInput : listInputs) { nCredit = 0; // Make sure the wallet is unlocked and shutdown hasn't been requested if (IsLocked() || ShutdownRequested()) return false; - //make sure that enough time has elapsed between - CBlockIndex* pindex = stakeInput->GetIndexFrom(); - if (!pindex || pindex->nHeight < 1) { - LogPrintf("CreateCoinStake(): no pindexfrom\n"); - continue; - } - - // Read block header - CBlockHeader block = pindex->GetBlockHeader(); uint256 hashProofOfStake = 0; - nTxNewTime = GetAdjustedTime(); nAttempts++; //iterates each utxo inside of CheckStakeKernelHash() - if (Stake(stakeInput.get(), nBits, block.GetBlockTime(), nTxNewTime, hashProofOfStake)) { - //Double check that this will pass time requirements - if (nTxNewTime <= chainActive.Tip()->GetMedianTimePast() && Params().NetworkID() != CBaseChainParams::REGTEST) { - LogPrintf("CreateCoinStake() : kernel found, but it is too far in the past \n"); - continue; - } + if (Stake(pindexPrev, stakeInput.get(), nBits, nTxNewTime, hashProofOfStake)) { // Found a kernel LogPrintf("CreateCoinStake : kernel found\n"); @@ -2466,8 +2451,6 @@ bool CWallet::CreateCoinStake( fKernelFound = true; break; } - if (fKernelFound) - break; // if kernel is found stop searching } LogPrint("staking", "%s: attempted staking %d times\n", __func__, nAttempts); @@ -3387,7 +3370,8 @@ void CWallet::AutoZeromint() void CWallet::AutoCombineDust() { LOCK2(cs_main, cs_wallet); - if (chainActive.Tip()->nTime < (GetAdjustedTime() - 300) || IsLocked()) { + const CBlockIndex* tip = chainActive.Tip(); + if (tip->nTime < (GetAdjustedTime() - 300) || IsLocked()) { return; } @@ -3483,11 +3467,13 @@ bool CWallet::MultiSend() { LOCK2(cs_main, cs_wallet); // Stop the old blocks from sending multisends - if (chainActive.Tip()->nTime < (GetAdjustedTime() - 300) || IsLocked()) { + const CBlockIndex* tip = chainActive.Tip(); + int chainTipHeight = tip->nHeight; + if (tip->nTime < (GetAdjustedTime() - 300) || IsLocked()) { return false; } - if (chainActive.Tip()->nHeight <= nLastMultiSendHeight) { + if (chainTipHeight <= nLastMultiSendHeight) { LogPrintf("Multisend: lastmultisendheight is higher than current best height\n"); return false; } @@ -4800,6 +4786,8 @@ void ThreadPrecomputeSpends() void CWallet::PrecomputeSpends() { + // We don't even need to worry about this code.. no zPIV. + /* LogPrintf("Precomputer started\n"); RenameThread("pivx-precomputer"); @@ -5072,6 +5060,7 @@ void CWallet::PrecomputeSpends() LogPrint("precompute", "%s: Finished precompute round...\n\n", __func__); MilliSleep(5000); - } + + }*/ } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index eb48efbb91bf..cb8b941d1a5b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -197,7 +197,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface public: bool MintableCoins(); - bool SelectStakeCoins(std::list >& listInputs, CAmount nTargetAmount, bool fPrecompute = false); + bool SelectStakeCoins(std::list >& listInputs, CAmount nTargetAmount, int blockHeight, bool fPrecompute = false); bool IsCollateralAmount(CAmount nInputAmount) const; // Zerocoin additions diff --git a/test/functional/p2p_pos_fakestake_accepted.py b/test/functional/p2p_pos_fakestake_accepted.py index 6463bd42b12c..1aefd143867b 100755 --- a/test/functional/p2p_pos_fakestake_accepted.py +++ b/test/functional/p2p_pos_fakestake_accepted.py @@ -17,7 +17,7 @@ class PoSFakeStakeAccepted(PIVX_FakeStakeTest): def run_test(self): self.description = "Covers the scenario of a valid PoS block where the coinstake input prevout is spent on main chain, but not on the fork branch. These blocks must be accepted." self.init_test() - INITAL_MINED_BLOCKS = 200 # First mined blocks (rewards collected to spend) + INITAL_MINED_BLOCKS = 189 # First mined blocks (rewards collected to spend) FORK_DEPTH = 50 # number of blocks after INITIAL_MINED_BLOCKS before the coins are spent MORE_MINED_BLOCKS = 10 # number of blocks after spending of the collected coins self.NUM_BLOCKS = 3 # Number of spammed blocks @@ -28,7 +28,7 @@ def run_test(self): # 2) Collect the possible prevouts self.log.info("Collecting all unspent coins which we generated from mining...") - staking_utxo_list = self.node.listunspent() + staking_utxo_list = self.node.listunspent(100 - FORK_DEPTH + 1) sleep(2) # 3) Mine more blocks