// Copyright (c) 2012-2013 The PPCoin developers // Copyright (c) 2015-2019 The PIVX developers // Copyright (c) 2022-2036 Agrarian Developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include "db.h" #include "kernel.h" #include "script/interpreter.h" #include "timedata.h" #include "util.h" #include "stakeinput.h" #include "utilmoneystr.h" #include "zagrchain.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) { return fTestNet ? MODIFIER_INTERVAL_TESTNET : MODIFIER_INTERVAL; } // 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 - nStakeMinAge; } // 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) { if (!pindex) return error("GetLastStakeModifier: null pindex"); while (pindex && pindex->pprev && !pindex->GeneratedStakeModifier()) pindex = pindex->pprev; if (!pindex->GeneratedStakeModifier()) return error("GetLastStakeModifier: no generation at genesis block"); nStakeModifier = pindex->nStakeModifier; nModifierTime = pindex->GetBlockTime(); return true; } // Get selection interval section (in seconds) static int64_t GetStakeModifierSelectionIntervalSection(int nSection) { assert(nSection >= 0 && nSection < 64); int64_t a = getIntervalVersion(fTestNet) * 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. static bool SelectBlockFromCandidates( vector >& vSortedByTimestamp, map& mapSelectedBlocks, int64_t nSelectionIntervalStop, uint64_t nStakeModifierPrev, const CBlockIndex** pindexSelected) { bool fModifierV2 = false; bool fFirstRun = true; bool fSelected = false; uint256 hashBest = 0; *pindexSelected = (const CBlockIndex*)0; for (const PAIRTYPE(int64_t, uint256) & item : vSortedByTimestamp) { if (!mapBlockIndex.count(item.second)) return error("SelectBlockFromCandidates: failed to find block index for candidate block %s", item.second.ToString().c_str()); const CBlockIndex* pindex = mapBlockIndex[item.second]; if (fSelected && pindex->GetBlockTime() > nSelectionIntervalStop) break; // If the lowest block height (vSortedByTimestamp[0]) is >= switch height, use new modifier calc if (fFirstRun) { fModifierV2 = pindex->nHeight >= Params().ModifierUpgradeBlock(); fFirstRun = false; } if (mapSelectedBlocks.count(pindex->GetBlockHash()) > 0) continue; // Compute the selection hash by hashing an input that is unique to that block uint256 hashProof; if (fModifierV2) hashProof = pindex->GetBlockHash(); else hashProof = pindex->IsProofOfStake() ? 0 : pindex->GetBlockHash(); CDataStream ss(SER_GETHASH, 0); ss << hashProof << nStakeModifierPrev; uint256 hashSelection = Hash(ss.begin(), ss.end()); // Selection favors PoS by dividing the hashSelection value if (pindex->IsProofOfStake()) hashSelection >>= 32; if (fSelected && hashSelection < hashBest) { hashBest = hashSelection; *pindexSelected = (const CBlockIndex*)pindex; } else if (!fSelected) { fSelected = true; hashBest = hashSelection; *pindexSelected = (const CBlockIndex*)pindex; } } if (GetBoolArg("-printstakemodifier", false)) LogPrintf("SelectBlockFromCandidates: selection hash=%s\n", hashBest.ToString().c_str()); return fSelected; } // 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. // Stake modifier consists of bits each of which is contributed from a // selected block of a given block group in the past. // The selection of a block is based on a hash of the block's proof-hash and // the previous stake modifier. // Stake modifier is recomputed at a fixed time interval instead of every // block. This is to make it difficult for an attacker to gain control of // additional bits in the stake modifier, even after generating a chain of // blocks. bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeModifier, bool& fGeneratedStakeModifier) { nStakeModifier = 0; fGeneratedStakeModifier = false; if (!pindexPrev) { fGeneratedStakeModifier = true; return true; // genesis block's modifier is 0 } if (pindexPrev->nHeight == 2) { //Give a stake modifier to the first block fGeneratedStakeModifier = true; nStakeModifier = uint64_t("stakemodifier"); return true; } // First find current stake modifier and its generation block time // if it's not old enough, return the same stake modifier int64_t nModifierTime = 0; if (!GetLastStakeModifier(pindexPrev, nStakeModifier, nModifierTime)) return error("ComputeNextStakeModifier: unable to get last modifier"); if (GetBoolArg("-printstakemodifier", false)) LogPrintf("ComputeNextStakeModifier: prev modifier= %s time=%s\n", std::to_string(nStakeModifier).c_str(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nModifierTime).c_str()); if (nModifierTime / getIntervalVersion(fTestNet) >= pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet)) return true; // Sort candidate blocks by timestamp vector > vSortedByTimestamp; vSortedByTimestamp.reserve(64 * getIntervalVersion(fTestNet) / nStakeTargetSpacing); int64_t nSelectionInterval = GetStakeModifierSelectionInterval(); int64_t nSelectionIntervalStart = (pindexPrev->GetBlockTime() / getIntervalVersion(fTestNet)) * getIntervalVersion(fTestNet) - nSelectionInterval; const CBlockIndex* pindex = pindexPrev; while (pindex && pindex->GetBlockTime() >= nSelectionIntervalStart) { vSortedByTimestamp.push_back(make_pair(pindex->GetBlockTime(), pindex->GetBlockHash())); pindex = pindex->pprev; } int nHeightFirstCandidate = pindex ? (pindex->nHeight + 1) : 0; reverse(vSortedByTimestamp.begin(), vSortedByTimestamp.end()); sort(vSortedByTimestamp.begin(), vSortedByTimestamp.end()); // Select 64 blocks from candidate blocks to generate stake modifier uint64_t nStakeModifierNew = 0; int64_t nSelectionIntervalStop = nSelectionIntervalStart; map mapSelectedBlocks; for (int nRound = 0; nRound < min(64, (int)vSortedByTimestamp.size()); nRound++) { // add an interval section to the current selection round nSelectionIntervalStop += GetStakeModifierSelectionIntervalSection(nRound); // select a block from the candidates of current round if (!SelectBlockFromCandidates(vSortedByTimestamp, mapSelectedBlocks, nSelectionIntervalStop, nStakeModifier, &pindex)) return error("ComputeNextStakeModifier: unable to select block at round %d", nRound); // write the entropy bit of the selected block nStakeModifierNew |= (((uint64_t)pindex->GetStakeEntropyBit()) << nRound); // add the selected block from candidates to selected list mapSelectedBlocks.insert(make_pair(pindex->GetBlockHash(), pindex)); if (GetBoolArg("-printstakemodifier", false)) LogPrintf("ComputeNextStakeModifier: selected round %d stop=%s height=%d bit=%d\n", nRound, DateTimeStrFormat("%Y-%m-%d %H:%M:%S", nSelectionIntervalStop).c_str(), pindex->nHeight, pindex->GetStakeEntropyBit()); } // Print selection map for visualization of the selected blocks if (GetBoolArg("-printstakemodifier", false)) { string strSelectionMap = ""; // '-' indicates proof-of-work blocks not selected strSelectionMap.insert(0, pindexPrev->nHeight - nHeightFirstCandidate + 1, '-'); pindex = pindexPrev; while (pindex && pindex->nHeight >= nHeightFirstCandidate) { // '=' indicates proof-of-stake blocks not selected if (pindex->IsProofOfStake()) strSelectionMap.replace(pindex->nHeight - nHeightFirstCandidate, 1, "="); pindex = pindex->pprev; } for (const std::pair &item : mapSelectedBlocks) { // 'S' indicates selected proof-of-stake blocks // 'W' indicates selected proof-of-work blocks strSelectionMap.replace(item.second->nHeight - nHeightFirstCandidate, 1, item.second->IsProofOfStake() ? "S" : "W"); } LogPrintf("ComputeNextStakeModifier: selection height [%d, %d] map %s\n", nHeightFirstCandidate, pindexPrev->nHeight, strSelectionMap.c_str()); } if (GetBoolArg("-printstakemodifier", false)) { LogPrintf("ComputeNextStakeModifier: new modifier=%s time=%s\n", std::to_string(nStakeModifierNew).c_str(), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexPrev->GetBlockTime()).c_str()); } nStakeModifier = nStakeModifierNew; fGeneratedStakeModifier = true; return true; } // 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 bool GetKernelStakeModifier(uint256 hashBlockFrom, uint64_t& nStakeModifier, int& nStakeModifierHeight, int64_t& nStakeModifierTime, bool fPrintProofOfStake) { nStakeModifier = 0; // Ensure the block exists in the block index if (!mapBlockIndex.count(hashBlockFrom)) return error("GetKernelStakeModifier() : block not indexed"); const CBlockIndex* pindexFrom = mapBlockIndex[hashBlockFrom]; nStakeModifierHeight = pindexFrom->nHeight; nStakeModifierTime = pindexFrom->GetBlockTime(); // Enforce PoS start height if (pindexFrom->nHeight < Params().FIRST_POS_BLOCK()) { return error("GetKernelStakeModifier(): PoS not active at block height %d", pindexFrom->nHeight); } // Fixed stake modifier only for regtest if (Params().NetworkID() == CBaseChainParams::REGTEST) { nStakeModifier = pindexFrom->nStakeModifier; return true; } int64_t nStakeModifierSelectionInterval = GetStakeModifierSelectionInterval(); const CBlockIndex* pindex = pindexFrom; CBlockIndex* pindexNext = chainActive[pindexFrom->nHeight + 1]; // Loop to find the stake modifier later by a selection interval while (nStakeModifierTime < pindexFrom->GetBlockTime() + nStakeModifierSelectionInterval) { if (!pindexNext) { return error("GetKernelStakeModifier() : null pindexNext"); } pindex = pindexNext; pindexNext = chainActive[pindexNext->nHeight + 1]; if (pindex->GeneratedStakeModifier()) { nStakeModifierHeight = pindex->nHeight; nStakeModifierTime = pindex->GetBlockTime(); } } nStakeModifier = pindex->nStakeModifier; return true; } // Test hash vs target bool stakeTargetHit(const uint256& hashProofOfStake, const int64_t& nValueIn, const uint256& bnTargetPerCoinDay) { // Get the stake weight - weight is equal to coin amount uint256 bnCoinDayWeight = uint256(nValueIn) / 100; // Check if proof-of-stake hash meets the target protocol return hashProofOfStake < (bnCoinDayWeight* bnTargetPerCoinDay); } 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()); return stakeTargetHit(hashProofOfStake, nValueIn, bnTarget); } bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake) { int nHeightStart = chainActive.Height(); // Ensure staking begins at the correct height if (nHeightStart < Params().FIRST_POS_BLOCK()) { return error("Stake(): PoS staking attempted before allowed start height %d", Params().FIRST_POS_BLOCK()); } if (Params().NetworkID() != CBaseChainParams::REGTEST) { if (nTimeTx < nTimeBlockFrom) return error("CheckStakeKernelHash() : nTime violation"); if ((nTimeBlockFrom + nStakeMinAge > nTimeTx)) // Min age requirement return error("CheckStakeKernelHash() : min age violation - nTimeBlockFrom=%d nStakeMinAge=%d nTimeTx=%d", nTimeBlockFrom, nStakeMinAge, nTimeTx); } // Grab difficulty uint256 bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(nBits); // Grab stake modifier uint64_t nStakeModifier = 0; if (!stakeInput->GetModifier(nStakeModifier)) return error("Stake() : failed to get kernel stake modifier"); bool fSuccess = false; unsigned int nTryTime = 0; int nHashDrift = 60; CDataStream ssUniqueID = stakeInput->GetUniqueness(); CAmount nValueIn = stakeInput->GetValue(); for (int i = 0; i < nHashDrift; i++) // Iterate the hashing { // If a new block comes in, move on if (chainActive.Height() != nHeightStart) break; // Hash this iteration nTryTime = nTimeTx + nHashDrift - i; // If stake hash does not meet the target, continue to next iteration if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, bnTargetPerCoinDay, nTimeBlockFrom, nTryTime, hashProofOfStake)) continue; fSuccess = true; // Successfully created a stake hash nTimeTx = nTryTime; break; } mapHashedBlocks.clear(); mapHashedBlocks[chainActive.Tip()->nHeight] = GetTime(); // Store timestamp of the last hash attempt return fSuccess; } bool ContextualCheckZerocoinStake(int nPreviousBlockHeight, CStakeInput* stake) { // Enforce PoS start height for Zerocoin stakes if (nPreviousBlockHeight < Params().Zerocoin_Block_V2_Start()) { return error("%s: zAGR stake block is less than allowed start height", __func__); } if (CZPivStake* zAGR = dynamic_cast(stake)) { CBlockIndex* pindexFrom = zAGR->GetIndexFrom(); if (!pindexFrom) { return error("%s: failed to get index associated with zAGR stake checksum", __func__); } if (chainActive.Height() - pindexFrom->nHeight < Params().Zerocoin_RequiredStakeDepth()) { return error("%s: zAGR stake does not have required confirmation depth. Current height %d, stakeInput height %d.", __func__, chainActive.Height(), pindexFrom->nHeight); } // Validate the checksum with the block 200 blocks ago uint256 nCheckpoint200 = chainActive[nPreviousBlockHeight - Params().Zerocoin_RequiredStakeDepth()]->nAccumulatorCheckpoint; uint32_t nChecksum200 = ParseChecksum(nCheckpoint200, libzerocoin::AmountToZerocoinDenomination(zAGR->GetValue())); if (nChecksum200 != zAGR->GetChecksum()) { return error("%s: accumulator checksum mismatch: stake=%d, block200=%d", __func__, zAGR->GetChecksum(), nChecksum200); } } else { return error("%s: dynamic_cast of stake pointer failed", __func__); } return true; } // Check kernel hash target and coinstake signature bool CheckProofOfStake(const CBlock block, uint256& hashProofOfStake, std::unique_ptr& stake, int nPreviousBlockHeight) { const CTransaction tx = block.vtx[1]; if (!tx.IsCoinStake()) { return error("CheckProofOfStake(): called on non-coinstake %s", tx.GetHash().ToString().c_str()); } // Kernel (input 0) must match the stake hash target per coin age (nBits) const CTxIn& txin = tx.vin[0]; // Construct the stake input object if (txin.IsZerocoinSpend()) { libzerocoin::CoinSpend spend = TxInToZerocoinSpend(txin); if (spend.getSpendType() != libzerocoin::SpendType::STAKE) { return error("%s: spend uses incorrect SpendType (%d)", __func__, (int)spend.getSpendType()); } stake = std::unique_ptr(new CZPivStake(spend)); if (!ContextualCheckZerocoinStake(nPreviousBlockHeight, stake.get())) { return error("%s: staked zAGR fails context checks", __func__); } } else { uint256 hashBlock; CTransaction txPrev; if (!GetTransaction(txin.prevout.hash, txPrev, hashBlock, true)) { return error("CheckProofOfStake(): failed to read txPrev, tx id prev: %s, block id %s", txin.prevout.hash.GetHex(), block.GetHash().GetHex()); } if (!VerifyScript(txin.scriptSig, txPrev.vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx, 0))) { return error("CheckProofOfStake(): VerifySignature failed on coinstake %s", tx.GetHash().ToString().c_str()); } CPivStake* agrInput = new CPivStake(); agrInput->SetInput(txPrev, txin.prevout.n); stake = std::unique_ptr(agrInput); } CBlockIndex* pindexFrom = stake->GetIndexFrom(); if (!pindexFrom) { return error("CheckProofOfStake(): failed to find block index for stake origin"); } CBlock blockFrom; if (!ReadBlockFromDisk(blockFrom, pindexFrom->GetBlockPos())) { return error("CheckProofOfStake(): failed to read block"); } uint256 bnTargetPerCoinDay; bnTargetPerCoinDay.SetCompact(block.nBits); uint64_t nStakeModifier = 0; if (!stake->GetModifier(nStakeModifier)) { return error("CheckProofOfStake(): failed to get modifier for stake input"); } unsigned int nBlockFromTime = blockFrom.nTime; unsigned int nTxTime = block.nTime; if (!txin.IsZerocoinSpend() && nPreviousBlockHeight >= Params().Zerocoin_Block_Public_Spend_Enabled() - 1) { if (nTxTime < nBlockFromTime) { return error("CheckStakeKernelHash(): nTime violation - nBlockFromTime=%d nTimeTx=%d", nBlockFromTime, nTxTime); } if (nBlockFromTime + nStakeMinAge > nTxTime) { return error("CheckStakeKernelHash(): min age violation - nBlockFromTime=%d nStakeMinAge=%d nTimeTx=%d", nBlockFromTime, nStakeMinAge, nTxTime); } } if (!CheckStake(stake->GetUniqueness(), stake->GetValue(), nStakeModifier, bnTargetPerCoinDay, nBlockFromTime, nTxTime, hashProofOfStake)) { return error("CheckProofOfStake(): kernel check failed on coinstake %s, hashProof=%s", tx.GetHash().GetHex(), hashProofOfStake.GetHex()); } return true; } // Check whether the coinstake timestamp meets protocol bool CheckCoinStakeTimestamp(int64_t nTimeBlock, int64_t nTimeTx) { return (nTimeBlock == nTimeTx); } // Get stake modifier checksum unsigned int GetStakeModifierChecksum(const CBlockIndex* pindex) { assert(pindex->pprev || pindex->GetBlockHash() == Params().HashGenesisBlock()); CDataStream ss(SER_GETHASH, 0); if (pindex->pprev) { ss << pindex->pprev->nStakeModifierChecksum; } ss << pindex->nFlags << pindex->hashProofOfStake << pindex->nStakeModifier; uint256 hashChecksum = Hash(ss.begin(), ss.end()); hashChecksum >>= (256 - 32); return hashChecksum.Get64(); } // Check stake modifier hard checkpoints bool CheckStakeModifierCheckpoints(int nHeight, unsigned int nStakeModifierChecksum) { if (fTestNet) { return true; // Testnet has no checkpoints } if (mapStakeModifierCheckpoints.count(nHeight)) { return nStakeModifierChecksum == mapStakeModifierCheckpoints[nHeight]; } return true; }