From 0d2b05518d6397353eac598e66025c8c22ece9f8 Mon Sep 17 00:00:00 2001 From: presstab Date: Wed, 8 Nov 2017 13:37:28 -0700 Subject: [PATCH 1/2] Patch libzerocoin. Invalidate spends with bad proofs. --- src/accumulators.cpp | 3 +- src/chainparams.cpp | 12 ++- src/chainparams.h | 8 ++ src/coins.h | 2 +- src/init.cpp | 3 + src/libzerocoin/CoinSpend.cpp | 12 +++ src/libzerocoin/CoinSpend.h | 5 + src/main.cpp | 174 +++++++++++++++++++++++++++++++-- src/main.h | 6 +- src/masternode-payments.cpp | 2 +- src/miner.cpp | 14 ++- src/primitives/transaction.cpp | 20 ++++ src/primitives/transaction.h | 5 + src/qt/privacydialog.cpp | 16 +-- src/rpcmisc.cpp | 3 + src/rpcwallet.cpp | 6 +- src/test/sighash_tests.cpp | 2 +- src/test/transaction_tests.cpp | 8 +- src/txdb.cpp | 4 +- src/walletdb.cpp | 2 +- 20 files changed, 270 insertions(+), 37 deletions(-) diff --git a/src/accumulators.cpp b/src/accumulators.cpp index 7be36d14fbe2..df386dcf291f 100644 --- a/src/accumulators.cpp +++ b/src/accumulators.cpp @@ -126,7 +126,7 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) //set the accumulators to last checkpoint value AccumulatorMap mapAccumulators; - if(!mapAccumulators.Load(chainActive[nHeight - 1]->nAccumulatorCheckpoint)) { + if (!mapAccumulators.Load(chainActive[nHeight - 1]->nAccumulatorCheckpoint)) { if (chainActive[nHeight - 1]->nAccumulatorCheckpoint == 0) { //Before zerocoin is fully activated so set to init state mapAccumulators.Reset(); @@ -157,6 +157,7 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) LogPrint("zero","%s: failed to read block from disk\n", __func__); return false; } + std::list listPubcoins; if(!BlockToPubcoinList(block, listPubcoins)) { LogPrint("zero","%s: failed to get zerocoin mintlist from block %n\n", __func__, pindex->nHeight); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 25ae4abada77..3e65eba52abb 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -63,11 +63,13 @@ static Checkpoints::MapCheckpoints mapCheckpoints = (863795, uint256("2ad866818c4866e0d555181daccc628056216c0db431f88a825e84ed4f469067")) (863805, uint256("a755bd9a22b63c70d3db474f4b2b61a1f86c835b290a081bb3ec1ba2103eb4cb")) (867733, uint256("03b26296bf693de5782c76843d2fb649cb66d4b05550c6a79c047ff7e1c3ae15")) - (879650, uint256("227e1d2b738b6cd83c46d1d64617934ec899d77cee34336a56e61b71acd10bb2")); + (879650, uint256("227e1d2b738b6cd83c46d1d64617934ec899d77cee34336a56e61b71acd10bb2")) + (895400, uint256("7796a0274a608fac12d400198174e50beda992c1d522e52e5b95b884bc1beac6"))//block that serial# range is enforced + (895991, uint256("d53013ed7ea5c325b9696c95e07667d6858f8ff7ee13fecfa90827bf3c9ae316"));//network split here static const Checkpoints::CCheckpointData data = { &mapCheckpoints, - 1509225216, // * UNIX timestamp of last checkpoint block - 1770098, // * total number of transactions between genesis and last checkpoint + 1510187238, // * UNIX timestamp of last checkpoint block + 1816040, // * total number of transactions between genesis and last checkpoint // (the tx=... number in the SetBestChain debug.log lines) 2000 // * estimated number of transactions per day after checkpoint }; @@ -132,6 +134,10 @@ class CMainParams : public CChainParams nLastPOWBlock = 259200; nModifierUpdateBlock = 615800; nZerocoinStartHeight = 863787; + nBlockEnforceSerialRange = 895400; //Enforce serial range starting this block + nBlockRecalculateAccumulators = 9896000; //Trigger a recalculation of accumulators + nBlockFirstFraudulent = 891737; //First block that bad serials emerged + nBlockLastGoodCheckpoint = 891730; //Last valid accumulator checkpoint /** * Build the genesis block. Note that the output of the genesis coinbase cannot diff --git a/src/chainparams.h b/src/chainparams.h index bf91dd9faad0..c440704f6c5a 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -111,6 +111,10 @@ class CChainParams int ModifierUpgradeBlock() const { return nModifierUpdateBlock; } int LAST_POW_BLOCK() const { return nLastPOWBlock; } int Zerocoin_StartHeight() const { return nZerocoinStartHeight; } + int Zerocoin_Block_EnforceSerialRange() const { return nBlockEnforceSerialRange; } + int Zerocoin_Block_RecalculateAccumulators() const { return nBlockRecalculateAccumulators; } + int Zerocoin_Block_FirstFraudulent() const { return nBlockFirstFraudulent; } + int Zerocoin_Block_LastGoodCheckpoint() const { return nBlockLastGoodCheckpoint; } protected: CChainParams() {} @@ -162,6 +166,10 @@ class CChainParams int nZerocoinHeaderVersion; int64_t nBudget_Fee_Confirmations; int nZerocoinStartHeight; + int nBlockEnforceSerialRange; + int nBlockRecalculateAccumulators; + int nBlockFirstFraudulent; + int nBlockLastGoodCheckpoint; }; /** diff --git a/src/coins.h b/src/coins.h index 7f92ea98f51f..b323f83928c7 100644 --- a/src/coins.h +++ b/src/coins.h @@ -271,7 +271,7 @@ class CCoins //! check whether a particular output is still available bool IsAvailable(unsigned int nPos) const { - return (nPos < vout.size() && !vout[nPos].IsNull()); + return (nPos < vout.size() && !vout[nPos].IsNull() && !vout[nPos].scriptPubKey.IsZerocoinMint()); } //! check whether the entire CCoins is spent diff --git a/src/init.cpp b/src/init.cpp index 41e1ab6dafe8..86db92cfd97e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1520,6 +1520,9 @@ bool AppInit2(boost::thread_group& threadGroup) } } + // Populate list of invalid/fraudulent outpoints that are banned from the chain + PopulateInvalidOutPointMap(); + uiInterface.InitMessage(_("Verifying blocks...")); // Flag sent to validation code to let it know it can skip certain checks diff --git a/src/libzerocoin/CoinSpend.cpp b/src/libzerocoin/CoinSpend.cpp index d04e40ef0347..8dcebabcf251 100644 --- a/src/libzerocoin/CoinSpend.cpp +++ b/src/libzerocoin/CoinSpend.cpp @@ -68,4 +68,16 @@ const uint256 CoinSpend::signatureHash() const return h.GetHash(); } +bool CoinSpend::HasValidSerial(ZerocoinParams* params) const +{ + return coinSerialNumber > 0 && coinSerialNumber < params->coinCommitmentGroup.groupOrder; +} + +CBigNum CoinSpend::CalculateValidSerial(ZerocoinParams* params) +{ + CBigNum bnSerial = coinSerialNumber; + bnSerial = bnSerial.mul_mod(CBigNum(1),params->coinCommitmentGroup.groupOrder); + return bnSerial; +} + } /* namespace libzerocoin */ diff --git a/src/libzerocoin/CoinSpend.h b/src/libzerocoin/CoinSpend.h index 0a9aa1a1ab0b..ce3f42dac8fb 100644 --- a/src/libzerocoin/CoinSpend.h +++ b/src/libzerocoin/CoinSpend.h @@ -87,8 +87,13 @@ class CoinSpend * @return the txout hash */ uint256 getTxOutHash() const { return ptxHash; } + CBigNum getAccCommitment() const { return accCommitmentToCoinValue; } + CBigNum getSerialComm() const { return serialCommitmentToCoinValue; } bool Verify(const Accumulator& a) const; + bool HasValidSerial(ZerocoinParams* params) const; + CBigNum CalculateValidSerial(ZerocoinParams* params); + ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) diff --git a/src/main.cpp b/src/main.cpp index 0ba3a8626a12..5a64736e11f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1315,7 +1315,7 @@ bool CheckZerocoinSpend(const CTransaction tx, bool fVerifySignature, CValidatio return state.DoS(100, error("CheckZerocoinSpend(): Zerocoin transactions are not allowed yet")); //max needed non-mint outputs should be 2 - one for redemption address and a possible 2nd for change - if (tx.vout.size() > 2){ + if (tx.vout.size() > 2) { int outs = 0; for (const CTxOut out : tx.vout) { if (out.IsZerocoinMint()) @@ -1402,7 +1402,7 @@ bool CheckZerocoinSpend(const CTransaction tx, bool fVerifySignature, CValidatio return fValidated; } -bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, CValidationState& state) +bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state) { // Basic checks that don't depend on any context if (tx.vin.empty()) @@ -1468,7 +1468,7 @@ bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, CValidationS // Check for duplicate inputs set vInOutPoints; set vZerocoinSpendSerials; - BOOST_FOREACH (const CTxIn& txin, tx.vin) { + for (const CTxIn& txin : tx.vin) { if (vInOutPoints.count(txin.prevout)) return state.DoS(100, error("CheckTransaction() : duplicate inputs"), REJECT_INVALID, "bad-txns-inputs-duplicate"); @@ -1560,7 +1560,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa if (pfMissingInputs) *pfMissingInputs = false; - if (!CheckTransaction(tx, GetAdjustedTime() > GetSporkValue(SPORK_17_ENABLE_ZEROCOIN), state)) + //Temporarily disable zerocoin + if (tx.ContainsZerocoins()) + return state.DoS(10, error("AcceptToMemoryPool : Zerocoin transactions temporarily disabled")); + + if (!CheckTransaction(tx, GetAdjustedTime() > GetSporkValue(SPORK_17_ENABLE_ZEROCOIN), true, state)) return state.DoS(100, error("AcceptToMemoryPool: : CheckTransaction failed"), REJECT_INVALID, "bad-tx"); // Coinbase is only valid in a block, not as a loose transaction @@ -1634,6 +1638,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa if (IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx)) return state.Invalid(error("%s : zPiv spend with serial %s is already in block %d\n", __func__, spend.getCoinSerialNumber().GetHex(), nHeightTx)); + + //Is serial in the acceptable range + if (!spend.HasValidSerial(Params().Zerocoin_Params())) + return state.Invalid(error("%s : zPiv spend with serial %s from tx %s is not in valid range\n", + __func__, spend.getCoinSerialNumber().GetHex(), tx.GetHash().GetHex())); } } else { LOCK(pool.cs); @@ -1647,12 +1656,18 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState& state, const CTransa // do all inputs exist? // Note that this does not check for the presence of actual outputs (see the next check for that), // only helps filling in pfMissingInputs (to determine missing vs spent). - BOOST_FOREACH (const CTxIn txin, tx.vin) { + for (const CTxIn txin : tx.vin) { if (!view.HaveCoins(txin.prevout.hash)) { if (pfMissingInputs) *pfMissingInputs = true; return false; } + + //Check for invalid/fraudulent inputs + if (!ValidOutPoint(txin.prevout, chainActive.Height())) { + return state.Invalid(error("%s : tried to spend invalid input %s in tx %s", __func__, txin.prevout.ToString(), + tx.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-inputs"); + } } // are the actual inputs available? @@ -1780,7 +1795,7 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact if (pfMissingInputs) *pfMissingInputs = false; - if (!CheckTransaction(tx, GetAdjustedTime() > GetSporkValue(SPORK_17_ENABLE_ZEROCOIN), state)) + if (!CheckTransaction(tx, GetAdjustedTime() > GetSporkValue(SPORK_17_ENABLE_ZEROCOIN), true, state)) return error("AcceptableInputs: : CheckTransaction failed"); // Coinbase is only valid in a block, not as a loose transaction @@ -1843,12 +1858,18 @@ bool AcceptableInputs(CTxMemPool& pool, CValidationState& state, const CTransact // do all inputs exist? // Note that this does not check for the presence of actual outputs (see the next check for that), // only helps filling in pfMissingInputs (to determine missing vs spent). - BOOST_FOREACH (const CTxIn txin, tx.vin) { + for (const CTxIn txin : tx.vin) { if (!view.HaveCoins(txin.prevout.hash)) { if (pfMissingInputs) *pfMissingInputs = true; return false; } + + // check for invalid/fraudulent inputs + if (!ValidOutPoint(txin.prevout, chainActive.Height())) { + return state.Invalid(error("%s : tried to spend invalid input %s in tx %s", __func__, txin.prevout.ToString(), + tx.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-inputs"); + } } // are the actual inputs available? @@ -2574,6 +2595,121 @@ bool CScriptCheck::operator()() return true; } +map mapInvalidOutPoints; +list listBadAddresses; +void AddInvalidSpendsToMap(const CBlock& block) +{ + for (const CTransaction tx : block.vtx) { + if (!tx.IsZerocoinSpend()) + continue; + + //Check all zerocoinspends for bad serials + for (const CTxIn in : tx.vin) { + if (in.scriptSig.IsZerocoinSpend()) { + CoinSpend spend = TxInToZerocoinSpend(in); + + //If serial is not valid, mark all outputs as bad + if (!spend.HasValidSerial(Params().Zerocoin_Params())) { + LogPrint("zero", "%s : invalid serial # %s in zerocoinspend tx %s\n", __func__, spend.getCoinSerialNumber().GetHex(), tx.GetHash().GetHex()); + + // Derive the actual valid serial from the invalid serial if possible + CBigNum bnActualSerial = spend.CalculateValidSerial(Params().Zerocoin_Params()); + uint256 txHash; + if (zerocoinDB->ReadCoinSpend(bnActualSerial, txHash)) { + LogPrint("zero", "%s : unmutated serial=%s in tx %s\n", __func__, bnActualSerial.GetHex(), txHash.GetHex()); + + CTransaction txPrev; + uint256 hashBlock; + if (!GetTransaction(txHash, txPrev, hashBlock, true)) + continue; + + //Record all txouts from txPrev as invalid + for (unsigned int i = 0; i < txPrev.vout.size(); i++) { + //map to an empty outpoint to represent that this is the first in the chain of bad outs + mapInvalidOutPoints[COutPoint(txPrev.GetHash(), i)] = COutPoint(); + } + } + + //Record all txouts from this invalid zerocoin spend tx as invalid + for (unsigned int i = 0; i < tx.vout.size(); i++) { + //map to an empty outpoint to represent that this is the first in the chain of bad outs + mapInvalidOutPoints[COutPoint(tx.GetHash(), i)] = COutPoint(); + } + } + } + } + } +} + +// Populate global map (mapInvalidOutPoints) of invalid/fraudulent OutPoints that are banned from being used on the chain. +void PopulateInvalidOutPointMap() +{ + //Calculate over the entire period between the first bad tx and the tip of the chain - or the point at which this becomes enforced + int nHeightLast = min(Params().Zerocoin_Block_RecalculateAccumulators(), chainActive.Height()); + + for (int i = Params().Zerocoin_Block_FirstFraudulent(); i < nHeightLast; i++) { + CBlockIndex* pindex = chainActive[i]; + + CBlock block; + if (!ReadBlockFromDisk(block, pindex)) + continue; + + AddInvalidSpendsToMap(block); + + //Any tx's that use a bad TxOut as an input is marked as bad + for (CTransaction tx : block.vtx) { + for (CTxIn txIn : tx.vin) { + if (mapInvalidOutPoints.count(txIn.prevout)) { + std::list listOutPoints; + + //If this is a stake transaction, masternode payments should not be considered fraudulent + if (tx.IsCoinStake()) { + CScript scriptPubKeyStake = tx.vout[1].scriptPubKey; + for (unsigned int i =0 ; i < tx.vout.size(); i++) { + //If a payment goes to a different address, then count it as a masternode payment + if (tx.vout[i].scriptPubKey == scriptPubKeyStake) { + CTxDestination dest; + if (!ExtractDestination(scriptPubKeyStake, dest)) + continue; + CBitcoinAddress address(dest); + if (!count(listBadAddresses.begin(), listBadAddresses.begin(), address)) + listBadAddresses.emplace_back(address); + listOutPoints.emplace_back(COutPoint(tx.GetHash(), i)); + } + } + } else { + //Consider all outpoints in this transaction as fraudulent + listOutPoints = tx.GetOutPoints(); + + //Generate a list of addresses + for (COutPoint p : listOutPoints) { + CTxDestination dest; + if (!ExtractDestination(tx.vout[p.n].scriptPubKey, dest)) + continue; + CBitcoinAddress address(dest); + if (!count(listBadAddresses.begin(), listBadAddresses.begin(), address)) + listBadAddresses.emplace_back(address); + } + } + + //Record each fraudulent outpoint and its cause. + for (COutPoint o : listOutPoints) + mapInvalidOutPoints[o] = txIn.prevout; + + //The entire tx set of outs are added, break here + break; + } + } + } + } +} + +bool ValidOutPoint(const COutPoint out, int nHeight) +{ + bool isInvalid = nHeight >= Params().Zerocoin_Block_RecalculateAccumulators() && mapInvalidOutPoints.count(out); + return !isInvalid; +} + bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks) { if (!tx.IsCoinBase() && !tx.IsZerocoinSpend()) { @@ -2930,6 +3066,10 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock() : too many sigops"), REJECT_INVALID, "bad-blk-sigops"); + //Temporarily shut off zerocoin transactions + if (pindex->nHeight >= Params().Zerocoin_Block_EnforceSerialRange() && tx.ContainsZerocoins()) + return state.DoS(100, error("ConnectBlock() : zerocoin transactions are disabled")); + if (tx.IsZerocoinSpend()) { int nHeightTx = 0; if (IsTransactionInChain(tx.GetHash(), nHeightTx)) { @@ -2947,6 +3087,16 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin CoinSpend spend = TxInToZerocoinSpend(txIn); int nHeightTx = 0; + // Make sure that the serial number is in valid range + if (!spend.HasValidSerial(Params().Zerocoin_Params())) { + string strError = strprintf("%s : txid=%s in block %d contains invalid serial %s\n", __func__, tx.GetHash().GetHex(), pindex->nHeight, spend.getCoinSerialNumber()); + if (pindex->nHeight >= Params().Zerocoin_Block_EnforceSerialRange()) + return state.DoS(100, error(strError.c_str())); + strError = "NOT ENFORCING : " + strError; + LogPrintf(strError.c_str()); + } + + //Is the serial already in the blockchain? uint256 hashTxFromDB; if (zerocoinDB->ReadCoinSpend(spend.getCoinSerialNumber(), hashTxFromDB)) { if(IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx)) { @@ -2965,6 +3115,14 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock() : inputs missing/spent"), REJECT_INVALID, "bad-txns-inputs-missingorspent"); + // Check that the inputs are not marked as invalid/fraudulent + for (CTxIn in : tx.vin) { + if (!ValidOutPoint(in.prevout, pindex->nHeight)) { + return state.DoS(100, error("%s : tried to spend invalid input %s in tx %s", __func__, in.prevout.ToString(), + tx.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-inputs"); + } + } + if (fStrictPayToScriptHash) { // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating @@ -4057,7 +4215,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, bool fCheckPOW, bo bool fZerocoinActive = block.nTime > GetSporkValue(SPORK_17_ENABLE_ZEROCOIN); vector vBlockSerials; for (const CTransaction& tx : block.vtx) { - if (!CheckTransaction(tx, fZerocoinActive, state)) + if (!CheckTransaction(tx, fZerocoinActive, chainActive.Height() + 1 >= Params().Zerocoin_Block_EnforceSerialRange(), state)) return error("CheckBlock() : CheckTransaction failed"); // double check that there are no double spent zPiv spends in this block diff --git a/src/main.h b/src/main.h index 2fe447baf766..51d9f203bab9 100644 --- a/src/main.h +++ b/src/main.h @@ -156,6 +156,7 @@ extern int64_t nReserveBalance; extern std::map mapRejectedBlocks; extern std::map mapHashedBlocks; +extern std::map mapInvalidOutPoints; extern std::set > setStakeSeen; /** Best header we've seen so far (used for getheaders queries' starting points). */ @@ -350,7 +351,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsVi void UpdateCoins(const CTransaction& tx, CValidationState& state, CCoinsViewCache& inputs, CTxUndo& txundo, int nHeight); /** Context-independent validity checks */ -bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, CValidationState& state); +bool CheckTransaction(const CTransaction& tx, bool fZerocoinActive, bool fRejectBadUTXO, CValidationState& state); bool CheckZerocoinMint(const uint256& txHash, const CTxOut& txout, CValidationState& state, bool fCheckOnly = false); bool CheckZerocoinSpend(const CTransaction tx, bool fVerifySignature, CValidationState& state); libzerocoin::CoinSpend TxInToZerocoinSpend(const CTxIn& txin); @@ -367,6 +368,9 @@ bool RemoveSerialFromDB(const CBigNum& bnSerial); int GetZerocoinStartHeight(); bool IsTransactionInChain(uint256 txId, int& nHeightTx); bool IsBlockHashInChain(const uint256& hashBlock); +void PopulateInvalidOutPointMap(); +bool ValidOutPoint(const COutPoint out, int nHeight); + /** * Check if transaction will be final in the next block to be created. diff --git a/src/masternode-payments.cpp b/src/masternode-payments.cpp index d8b246458a61..050afeb19ecd 100644 --- a/src/masternode-payments.cpp +++ b/src/masternode-payments.cpp @@ -670,7 +670,7 @@ bool CMasternodePaymentWinner::IsValid(CNode* pnode, std::string& strError) if (n > MNPAYMENTS_SIGNATURES_TOTAL * 2) { strError = strprintf("Masternode not in the top %d (%d)", MNPAYMENTS_SIGNATURES_TOTAL * 2, n); LogPrint("masternode","CMasternodePaymentWinner::IsValid - %s\n", strError); - if (masternodeSync.IsSynced()) Misbehaving(pnode->GetId(), 20); + //if (masternodeSync.IsSynced()) Misbehaving(pnode->GetId(), 20); } return false; } diff --git a/src/miner.cpp b/src/miner.cpp index 2e5a673d3ec8..1e7b9165e61f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -188,14 +188,14 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, for (map::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) { const CTransaction& tx = mi->second.GetTx(); - if (tx.IsCoinBase() || tx.IsCoinStake() || !IsFinalTx(tx, nHeight)) + if (tx.IsCoinBase() || tx.IsCoinStake() || !IsFinalTx(tx, nHeight) || tx.ContainsZerocoins()) continue; COrphan* porphan = NULL; double dPriority = 0; CAmount nTotalIn = 0; bool fMissingInputs = false; - BOOST_FOREACH (const CTxIn& txin, tx.vin) { + for (const CTxIn& txin : tx.vin) { //zerocoinspend has special vin if (tx.IsZerocoinSpend()) { nTotalIn = tx.GetZerocoinSpent(); @@ -227,6 +227,14 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, nTotalIn += mempool.mapTx[txin.prevout.hash].GetTx().vout[txin.prevout.n].nValue; continue; } + + //Check for invalid/fraudulent inputs. They shouldn't make it through mempool, but check anyways. + if (mapInvalidOutPoints.count(txin.prevout)) { + LogPrintf("%s : found invalid input %s in tx %s", __func__, txin.prevout.ToString()); + fMissingInputs = true; + break; + } + const CCoins* coins = view.AccessCoins(txin.prevout.hash); assert(coins); @@ -316,6 +324,8 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, CWallet* pwallet, for (const CTxIn txIn : tx.vin) { if (txIn.scriptSig.IsZerocoinSpend()) { libzerocoin::CoinSpend spend = TxInToZerocoinSpend(txIn); + if (!spend.HasValidSerial(Params().Zerocoin_Params())) + fDoubleSerial = true; if (count(vBlockSerials.begin(), vBlockSerials.end(), spend.getCoinSerialNumber())) fDoubleSerial = true; if (count(vTxSerials.begin(), vTxSerials.end(), spend.getCoinSerialNumber())) diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 508f16b745ad..e37b88c6ceab 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -12,6 +12,7 @@ #include "main.h" #include "tinyformat.h" #include "utilstrencodings.h" +#include "transaction.h" #include @@ -161,6 +162,25 @@ CAmount CTransaction::GetZerocoinMinted() const return CAmount(0); } +bool CTransaction::UsesUTXO(const COutPoint out) +{ + for (const CTxIn in : vin) { + if (in.prevout == out) + return true; + } + + return false; +} + +std::list CTransaction::GetOutPoints() const +{ + std::list listOutPoints; + uint256 txHash = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) + listOutPoints.emplace_back(COutPoint(txHash, i)); + return listOutPoints; +} + CAmount CTransaction::GetZerocoinSpent() const { if(!IsZerocoinSpend()) diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 1837c0cd1db7..ea00bfda7730 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -12,6 +12,8 @@ #include "serialize.h" #include "uint256.h" +#include + class CTransaction; /** An outpoint - a combination of a transaction hash and an index n into its vout */ @@ -278,6 +280,9 @@ class CTransaction CAmount GetZerocoinSpent() const; int GetZerocoinMintCount() const; + bool UsesUTXO(const COutPoint out); + std::list GetOutPoints() const; + bool IsCoinBase() const { return (vin.size() == 1 && vin[0].prevout.IsNull() && !ContainsZerocoins()); diff --git a/src/qt/privacydialog.cpp b/src/qt/privacydialog.cpp index 37adb7a723b1..0a816981378c 100644 --- a/src/qt/privacydialog.cpp +++ b/src/qt/privacydialog.cpp @@ -136,10 +136,10 @@ void PrivacyDialog::on_pushButtonMintzPIV_clicked() if (!walletModel || !walletModel->getOptionsModel()) return; - if (GetAdjustedTime() < GetSporkValue(SPORK_17_ENABLE_ZEROCOIN)) { - QMessageBox::information(this, tr("Mint Zerocoin"), tr("Zerocoin functionality is not enabled on the PIVX network yet."), QMessageBox::Ok, QMessageBox::Ok); - return; - } + + QMessageBox::information(this, tr("Mint Zerocoin"), tr("Zerocoin functionality is not enabled on the PIVX network."), QMessageBox::Ok, QMessageBox::Ok); + return; + // Reset message text ui->TEMintStatus->setPlainText(tr("Mint Status: Okay")); @@ -247,10 +247,10 @@ void PrivacyDialog::on_pushButtonSpendzPIV_clicked() if (!walletModel || !walletModel->getOptionsModel() || !pwalletMain) return; - if (GetAdjustedTime() < GetSporkValue(SPORK_17_ENABLE_ZEROCOIN)) { - QMessageBox::information(this, tr("Spend Zerocoin"), tr("Zerocoin functionality is not enabled on the PIVX network yet."), QMessageBox::Ok, QMessageBox::Ok); - return; - } + + QMessageBox::information(this, tr("Spend Zerocoin"), tr("Zerocoin functionality is not enabled on the PIVX network."), QMessageBox::Ok, QMessageBox::Ok); + return; + // Request unlock if wallet was locked or unlocked for mixing: WalletModel::EncryptionStatus encStatus = walletModel->getEncryptionStatus(); diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 3fec05ba15da..b8d333407fd3 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -96,6 +96,9 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("testnet", Params().TestnetToBeDeprecatedFieldRPC())); obj.push_back(Pair("moneysupply",ValueFromAmount(chainActive.Tip()->nMoneySupply))); obj.push_back(Pair("zerocoinsupply",ValueFromAmount(chainActive.Tip()->GetZerocoinSupply()))); + for (auto denom : libzerocoin::zerocoinDenomList) { + obj.push_back(Pair(to_string(denom), ValueFromAmount(chainActive.Tip()->mapZerocoinSupply.at(denom) * (denom*COIN)))); + } #ifdef ENABLE_WALLET if (pwalletMain) { diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 8d582b3da2af..3bf4ba94379c 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -2405,8 +2405,7 @@ Value mintzerocoin(const Array& params, bool fHelp) int64_t nTime = GetTimeMillis(); - if (GetAdjustedTime() < GetSporkValue(SPORK_17_ENABLE_ZEROCOIN)) - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Zerocoin functionality is not enabled on the PIVX network yet."); + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Zerocoin functionality is not enabled on the PIVX network."); if (pwalletMain->IsLocked()) throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); @@ -2451,8 +2450,7 @@ Value spendzerocoin(const Array& params, bool fHelp) "an address is required" + HelpRequiringPassphrase()); - if (GetAdjustedTime() < GetSporkValue(SPORK_17_ENABLE_ZEROCOIN)) - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Zerocoin functionality is not enabled on the PIVX network yet."); + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Zerocoin functionality is not enabled on the PIVX network."); int64_t nTimeStart = GetTimeMillis(); if (pwalletMain->IsLocked()) diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 6e7ccc4ae577..9b61696cd6d5 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -197,7 +197,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data) stream >> tx; CValidationState state; - BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, state), strTest); + BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, false, state), strTest); BOOST_CHECK(state.IsValid()); std::vector raw = ParseHex(raw_script); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index aef4e283dd39..dcc1c543109f 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -132,7 +132,7 @@ BOOST_AUTO_TEST_CASE(tx_valid) stream >> tx; CValidationState state; - BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, state), strTest); + BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, false, state), strTest); BOOST_CHECK(state.IsValid()); for (unsigned int i = 0; i < tx.vin.size(); i++) @@ -208,7 +208,7 @@ BOOST_AUTO_TEST_CASE(tx_invalid) stream >> tx; CValidationState state; - fValid = CheckTransaction(tx, false, state) && state.IsValid(); + fValid = CheckTransaction(tx, false, false, state) && state.IsValid(); for (unsigned int i = 0; i < tx.vin.size() && fValid; i++) { @@ -237,11 +237,11 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests) CMutableTransaction tx; stream >> tx; CValidationState state; - BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, state) && state.IsValid(), "Simple deserialized transaction should be valid."); + BOOST_CHECK_MESSAGE(CheckTransaction(tx, false, false, state) && state.IsValid(), "Simple deserialized transaction should be valid."); // Check that duplicate txins fail tx.vin.push_back(tx.vin[0]); - BOOST_CHECK_MESSAGE(!CheckTransaction(tx, false, state) || !state.IsValid(), "Transaction with duplicate txins should be invalid."); + BOOST_CHECK_MESSAGE(!CheckTransaction(tx, false, false, state) || !state.IsValid(), "Transaction with duplicate txins should be invalid."); } // diff --git a/src/txdb.cpp b/src/txdb.cpp index 61f5be178df1..53193a02c89c 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -348,7 +348,7 @@ bool CZerocoinDB::EraseCoinSpend(const CBigNum& bnSerial) bool CZerocoinDB::WriteAccumulatorValue(const uint32_t& nChecksum, const CBigNum& bnValue) { - LogPrint("zero","*** %s checksum:%d val:%s\n", __func__, nChecksum, bnValue.GetHex()); + LogPrint("zero","%s : checksum:%d val:%s\n", __func__, nChecksum, bnValue.GetHex()); return Write(make_pair('a', nChecksum), bnValue); } @@ -359,6 +359,6 @@ bool CZerocoinDB::ReadAccumulatorValue(const uint32_t& nChecksum, CBigNum& bnVal bool CZerocoinDB::EraseAccumulatorValue(const uint32_t& nChecksum) { - LogPrintf("*** %s checksum:%d\n", __func__, nChecksum); + LogPrint("zero", "%s : checksum:%d\n", __func__, nChecksum); return Erase(make_pair('a', nChecksum)); } diff --git a/src/walletdb.cpp b/src/walletdb.cpp index eaa44fd7b212..e8593bb28dbf 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -438,7 +438,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CW ssValue >> wtx; CValidationState state; // false because there is no reason to go through the zerocoin checks for our own wallet - if (!(CheckTransaction(wtx, false, state) && (wtx.GetHash() == hash) && state.IsValid())) + if (!(CheckTransaction(wtx, false, false, state) && (wtx.GetHash() == hash) && state.IsValid())) return false; // Undo serialize changes in 31600 From 02fe7ccf9a4ecdf0ef80fde80a2d63b73bdfd2ac Mon Sep 17 00:00:00 2001 From: presstab Date: Fri, 10 Nov 2017 08:26:41 -0700 Subject: [PATCH 2/2] Post libzerocoin patch consensus adjustments and supply fix. --- src/accumulators.cpp | 108 ++++++++++++-- src/accumulators.h | 1 + src/chainparams.cpp | 2 +- src/init.cpp | 115 ++------------ src/main.cpp | 338 +++++++++++++++++++++++++++++++++++------- src/main.h | 10 +- src/rpcblockchain.cpp | 150 +++++++++++++++++++ src/rpcmisc.cpp | 19 ++- src/rpcserver.cpp | 1 + src/rpcserver.h | 1 + src/spork.cpp | 3 + src/spork.h | 3 +- src/txdb.cpp | 5 +- src/version.h | 10 +- 14 files changed, 579 insertions(+), 187 deletions(-) diff --git a/src/accumulators.cpp b/src/accumulators.cpp index df386dcf291f..4ad6f659d1c5 100644 --- a/src/accumulators.cpp +++ b/src/accumulators.cpp @@ -73,6 +73,13 @@ void DatabaseChecksums(AccumulatorMap& mapAccumulators) } } +bool EraseChecksum(uint32_t nChecksum) +{ + //erase from both memory and database + mapAccumulatorValues.erase(nChecksum); + return zerocoinDB->EraseAccumulatorValue(nChecksum); +} + bool EraseAccumulatorValues(const uint256& nCheckpointErase, const uint256& nCheckpointPrevious) { for (auto& denomination : zerocoinDenomList) { @@ -83,9 +90,7 @@ bool EraseAccumulatorValues(const uint256& nCheckpointErase, const uint256& nChe if(nChecksumErase == nChecksumPrevious) continue; - //erase from both memory and database - mapAccumulatorValues.erase(nChecksumErase); - if(!zerocoinDB->EraseAccumulatorValue(nChecksumErase)) + if (!EraseChecksum(nChecksumErase)) return false; } @@ -110,6 +115,42 @@ bool LoadAccumulatorValuesFromDB(const uint256 nCheckpoint) return true; } +//Erase accumulator checkpoints for a certain block range +bool EraseCheckpoints(int nStartHeight, int nEndHeight) +{ + if (chainActive.Height() < nStartHeight) + return false; + + nEndHeight = min(chainActive.Height(), nEndHeight); + + CBlockIndex* pindex = chainActive[nStartHeight]; + uint256 nCheckpointPrev = pindex->pprev->nAccumulatorCheckpoint; + + //Keep a list of checkpoints from the previous block so that we don't delete them + list listCheckpointsPrev; + for (auto denom : zerocoinDenomList) + listCheckpointsPrev.emplace_back(ParseChecksum(nCheckpointPrev, denom)); + + while (true) { + uint256 nCheckpointDelete = pindex->nAccumulatorCheckpoint; + + for (auto denom : zerocoinDenomList) { + uint32_t nChecksumDelete = ParseChecksum(nCheckpointDelete, denom); + if (count(listCheckpointsPrev.begin(), listCheckpointsPrev.end(), nCheckpointDelete)) + continue; + EraseChecksum(nChecksumDelete); + } + LogPrintf("%s : erasing checksums for block %d\n", __func__, pindex->nHeight); + + if (pindex->nHeight + 1 <= nEndHeight) + pindex = chainActive.Next(pindex); + else + break; + } + + return true; +} + //Get checkpoint value for a specific block height bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) { @@ -136,9 +177,30 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) } } + //Whether this should filter out invalid/fraudulent outpoints + bool fFilterInvalid = nHeight >= Params().Zerocoin_Block_RecalculateAccumulators(); + //Accumulate all coins over the last ten blocks that havent been accumulated (height - 20 through height - 11) int nTotalMintsFound = 0; CBlockIndex *pindex = chainActive[nHeight - 20]; + + //On a specific block, a recalculation of the accumulators will be forced + if (nHeight == Params().Zerocoin_Block_RecalculateAccumulators()) { + pindex = chainActive[Params().Zerocoin_Block_LastGoodCheckpoint() - 10]; + mapAccumulators.Reset(); + if (!mapAccumulators.Load(chainActive[Params().Zerocoin_Block_LastGoodCheckpoint()]->nAccumulatorCheckpoint)) { + LogPrintf("%s: failed to reset to previous checkpoint when recalculating accumulators\n", __func__); + return false; + } + LogPrintf("*** %s recalculating checkpoint\n", __func__); + + // Erase the checkpoints from the period of time that bad mints were being made + if (!EraseCheckpoints(Params().Zerocoin_Block_LastGoodCheckpoint() + 1, nHeight)) { + LogPrintf("%s : failed to erase Checkpoints while recalculating checkpoints\n", __func__); + return false; + } + } + while (pindex->nHeight < nHeight - 10) { // checking whether we should stop this process due to a shutdown request if (ShutdownRequested()) { @@ -159,7 +221,7 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) } std::list listPubcoins; - if(!BlockToPubcoinList(block, listPubcoins)) { + if (!BlockToPubcoinList(block, listPubcoins, fFilterInvalid)) { LogPrint("zero","%s: failed to get zerocoin mintlist from block %n\n", __func__, pindex->nHeight); return false; } @@ -168,7 +230,7 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) LogPrint("zero", "%s found %d mints\n", __func__, listPubcoins.size()); //add the pubcoins to accumulator - for(const PublicCoin pubcoin : listPubcoins) { + for (const PublicCoin pubcoin : listPubcoins) { if(!mapAccumulators.Accumulate(pubcoin, true)) { LogPrintf("%s: failed to add pubcoin to accumulator at height %n\n", __func__, pindex->nHeight); return false; @@ -191,6 +253,11 @@ bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint) return true; } +bool InvalidCheckpointRange(int nHeight) +{ + return nHeight > Params().Zerocoin_Block_LastGoodCheckpoint() && nHeight < Params().Zerocoin_Block_RecalculateAccumulators(); +} + bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator, AccumulatorWitness& witness, int nSecurityLevel, int& nMintsAdded, string& strError) { uint256 txid; @@ -207,7 +274,7 @@ bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator } int nHeightMintAdded= mapBlockIndex[hashBlock]->nHeight; - uint256 nCheckpointBeforeMint = 0, nCheckpointContainingMint = 0; + uint256 nCheckpointBeforeMint = 0; CBlockIndex* pindex = chainActive[nHeightMintAdded]; int nChanges = 0; @@ -221,20 +288,26 @@ bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator //check if the next checksum was generated if (pindex->nHeight % 10 == 0) { - nCheckpointContainingMint = pindex->nAccumulatorCheckpoint; nChanges++; if (nChanges == 1) nCheckpointBeforeMint = pindex->nAccumulatorCheckpoint; - else if (nChanges == 2) - break; } - pindex = chainActive[pindex->nHeight + 1]; + pindex = chainActive.Next(pindex); } //the height to start accumulating coins to add to witness int nAccStartHeight = nHeightMintAdded - (nHeightMintAdded % 10); + //If the checkpoint is from the recalculated checkpoint period, then adjust it + int nHeight_LastGoodCheckpoint = Params().Zerocoin_Block_LastGoodCheckpoint(); + int nHeight_Recalculate = Params().Zerocoin_Block_RecalculateAccumulators(); + if (pindex->nHeight < nHeight_Recalculate - 10 && pindex->nHeight > nHeight_LastGoodCheckpoint) { + //The checkpoint before the mint will be the last good checkpoint + nCheckpointBeforeMint = chainActive[nHeight_LastGoodCheckpoint]->nAccumulatorCheckpoint; + nAccStartHeight = nHeight_LastGoodCheckpoint - 10; + } + //Get the accumulator that is right before the cluster of blocks containing our mint was added to the accumulator CBigNum bnAccValue = 0; if (GetAccumulatorValueFromDB(nCheckpointBeforeMint, coin.getDenomination(), bnAccValue)) { @@ -269,7 +342,7 @@ bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator //if a new checkpoint was generated on this block, and we have added the specified amount of checkpointed accumulators, //then initialize the accumulator at this point and break - if (pindex->nHeight == nHeightStop || (nSecurityLevel != 100 && nCheckpointsAdded >= nSecurityLevel)) { + if (!InvalidCheckpointRange(pindex->nHeight) && (pindex->nHeight == nHeightStop || (nSecurityLevel != 100 && nCheckpointsAdded >= nSecurityLevel))) { uint32_t nChecksum = ParseChecksum(chainActive[pindex->nHeight + 10]->nAccumulatorCheckpoint, coin.getDenomination()); CBigNum bnAccValue = 0; if (!zerocoinDB->ReadAccumulatorValue(nChecksum, bnAccValue)) { @@ -289,18 +362,21 @@ bool GenerateAccumulatorWitness(const PublicCoin &coin, Accumulator& accumulator return false; } - std::vector vValues; - if(!BlockToMintValueVector(block, coin.getDenomination(), vValues)) { + list listPubcoins; + if(!BlockToPubcoinList(block, listPubcoins, true)) { LogPrintf("%s: failed to get zerocoin mintlist from block %n\n", __func__, pindex->nHeight); return false; } //add the mints to the witness - for (const CBigNum bnValue : vValues) { - if(pindex->nHeight == nHeightMintAdded && bnValue == coin.getValue()) + for (const PublicCoin pubcoin : listPubcoins) { + if (pubcoin.getDenomination() != coin.getDenomination()) + continue; + + if (pindex->nHeight == nHeightMintAdded && pubcoin.getValue() == coin.getValue()) continue; - witness.addRawValue(bnValue); + witness.addRawValue(pubcoin.getValue()); ++nMintsAdded; } } diff --git a/src/accumulators.h b/src/accumulators.h index 3c7c00aa5869..e5778e5a337f 100644 --- a/src/accumulators.h +++ b/src/accumulators.h @@ -20,5 +20,6 @@ bool LoadAccumulatorValuesFromDB(const uint256 nCheckpoint); bool EraseAccumulatorValues(const uint256& nCheckpointErase, const uint256& nCheckpointPrevious); uint32_t ParseChecksum(uint256 nChecksum, libzerocoin::CoinDenomination denomination); uint32_t GetChecksum(const CBigNum &bnValue); +bool InvalidCheckpointRange(int nHeight); #endif //PIVX_ACCUMULATORS_H diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3e65eba52abb..b79c12ac5a72 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -135,7 +135,7 @@ class CMainParams : public CChainParams nModifierUpdateBlock = 615800; nZerocoinStartHeight = 863787; nBlockEnforceSerialRange = 895400; //Enforce serial range starting this block - nBlockRecalculateAccumulators = 9896000; //Trigger a recalculation of accumulators + nBlockRecalculateAccumulators = 908000; //Trigger a recalculation of accumulators nBlockFirstFraudulent = 891737; //First block that bad serials emerged nBlockLastGoodCheckpoint = 891730; //Last valid accumulator checkpoint diff --git a/src/init.cpp b/src/init.cpp index 86db92cfd97e..4e9f671a6383 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -333,6 +333,7 @@ std::string HelpMessage(HelpMessageMode mode) #endif strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files") + " " + _("on startup")); strUsage += HelpMessageOpt("-reindexaccumulators", _("Reindex the accumulator database") + " " + _("on startup")); + strUsage += HelpMessageOpt("-reindexmoneysupply", _("Reindex the PIV and zPIV money supply statistics") + " " + _("on startup")); strUsage += HelpMessageOpt("-resync", _("Delete blockchain folders and resync from scratch") + " " + _("on startup")); #if !defined(WIN32) strUsage += HelpMessageOpt("-sysperms", _("Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)")); @@ -1347,111 +1348,16 @@ bool AppInit2(boost::thread_group& threadGroup) break; } - // Recalculate money supply for blocks that are impacted by accounting issue after zerocoin activation - bool fVecFixed = false; - pblocktree->ReadFlag("msvecfix", fVecFixed); - bool fReindexRange = chainActive.Height() > GetZerocoinStartHeight(); - if (fReindexRange && !fVecFixed) { - uiInterface.InitMessage(_("Recalculating supply statistics may take 30-60 minutes...")); - LogPrintf("%s : Recalculating supply statistics\n", __func__); - - //If recalculation was exited before it was finished, then start where it left off - int nStartHeight = 0; - if (!pblocktree->ReadInt("msvecindex", nStartHeight)) - nStartHeight = GetZerocoinStartHeight(); - CBlockIndex *pindex = chainActive[nStartHeight]; - int nHeightEnd = chainActive.Height(); - while (true) { - if (ShutdownRequested()) - return false; - - double dPercent = 1 - ((nHeightEnd - pindex->nHeight) / (double)(nHeightEnd - nStartHeight)); - string strMessage = strprintf("Recalculating supply statistics may take 30-60 minutes block %d...", pindex->nHeight); - uiInterface.ShowProgress(_(strMessage.c_str()), (int)(dPercent * 100)); - - //overwrite possibly wrong vMintsInBlock data - CBlock block; - if (!ReadBlockFromDisk(block, pindex)) { - return InitError(_("Failed to read block index")); - } - - std::list listMints; - BlockToZerocoinMintList(block, listMints); - - pindex->vMintDenominationsInBlock.clear(); - for (auto mint : listMints) { - pindex->vMintDenominationsInBlock.emplace_back(mint.GetDenomination()); - } - - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { - return InitError(_("Failed to write block index")); - } - - LogPrintf("%s : rewrote vMintDenominationsInBlock for %d\n", __func__, pindex->nHeight); - - //Track progress in case of shutdown - pblocktree->WriteInt("msvecindex", pindex->nHeight); - - if (pindex->nHeight < nHeightEnd) - pindex = chainActive.Next(pindex); - else - break; - } - - //This flag indicates that the fix has already been done and not needed in the future - pblocktree->WriteFlag("msvecfix", true); - } - - bool msIndexFixed = false; - pblocktree->ReadFlag("msindexfix", msIndexFixed); - if (fReindexRange && !msIndexFixed){ - uiInterface.InitMessage(_("Recalculating coin supply may take 30-60 minutes...")); - LogPrintf("%s : Recalculating money supply statistics\n", __func__); - - //If recalculation was exited before it was finished, then start where it left off - int nStartHeight = 0; - if (!pblocktree->ReadInt("msindex", nStartHeight)) - nStartHeight = GetZerocoinStartHeight(); - CBlockIndex *pindex = chainActive[nStartHeight]; - int nHeightEnd = chainActive.Height(); - - // Remove possible zPiv spends from the supply - CAmount nzPivSpent = 0; - while (true) { - // get each blocks amount of zPiv minted and the zpiv money supply change for that block - // figure out amount spent by difference in zpiv supply per denom and the amount minted per denom - CAmount nBlockzPivSpent = 0; - for (auto denom : libzerocoin::zerocoinDenomList) { - int nDenomAdded = count(pindex->vMintDenominationsInBlock.begin(), pindex->vMintDenominationsInBlock.end(), denom); - int64_t nDenomMinted = nDenomAdded * denom; - - //note zPiv supply is quantity of zPiv for the denom, not the amount - int64_t nSupplyZPivLast = pindex->pprev->mapZerocoinSupply.at(denom) * denom; - int64_t nSupplyZPiv = pindex->mapZerocoinSupply.at(denom) * denom; - int64_t nDenomZPivSpent = (nSupplyZPivLast + nDenomMinted) - nSupplyZPiv; - nBlockzPivSpent += nDenomZPivSpent * COIN; - } - - //Total zPiv spent up to and including this block - nzPivSpent += nBlockzPivSpent; - - //Rewrite money supply - pindex->nMoneySupply = pindex->nMoneySupply - nzPivSpent; - if (!pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))) { - return InitError(_("Failed to write block index")); - } - - //Track progress in case of shutdown - pblocktree->WriteInt("msindex", pindex->nHeight); + // Populate list of invalid/fraudulent outpoints that are banned from the chain + PopulateInvalidOutPointMap(); - if (pindex->nHeight < nHeightEnd) - pindex = chainActive.Next(pindex); - else - break; + // Recalculate money supply for blocks that are impacted by accounting issue after zerocoin activation + if (GetBoolArg("-reindexmoneysupply", false)) { + if (chainActive.Height() > Params().Zerocoin_StartHeight()) { + RecalculateZPIVMinted(); + RecalculateZPIVSpent(); } - - //Mark reindexing as done, and not needed again in the futures - pblocktree->WriteFlag("msindexfix", true); + RecalculatePIVSupply(1); } // Force recalculation of accumulators. @@ -1520,9 +1426,6 @@ bool AppInit2(boost::thread_group& threadGroup) } } - // Populate list of invalid/fraudulent outpoints that are banned from the chain - PopulateInvalidOutPointMap(); - uiInterface.InitMessage(_("Verifying blocks...")); // Flag sent to validation code to let it know it can skip certain checks diff --git a/src/main.cpp b/src/main.cpp index 5a64736e11f9..d5c058072b96 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1072,7 +1072,7 @@ void FindMints(vector vMintsToFind, vector& vMints continue; list vMints; - if(!BlockToZerocoinMintList(block, vMints)) + if(!BlockToZerocoinMintList(block, vMints, true)) continue; // search the blocks mints to see if it contains the mint that is requesting meta data updates @@ -1178,13 +1178,32 @@ bool TxOutToPublicCoin(const CTxOut txout, PublicCoin& pubCoin, CValidationState return true; } -bool BlockToPubcoinList(const CBlock& block, list& listPubcoins) +bool BlockToPubcoinList(const CBlock& block, list& listPubcoins, bool fFilterInvalid) { for (const CTransaction tx : block.vtx) { if(!tx.IsZerocoinMint()) continue; - for (const CTxOut txOut : tx.vout) { + // Filter out mints that have used invalid outpoints + if (fFilterInvalid) { + bool fValid = true; + for (const CTxIn in : tx.vin) { + if (!ValidOutPoint(in.prevout, INT_MAX)) { + fValid = false; + break; + } + } + if (!fValid) + continue; + } + + uint256 txHash = tx.GetHash(); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + //Filter out mints that use invalid outpoints - edge case: invalid spend with minted change + if (fFilterInvalid && !ValidOutPoint(COutPoint(txHash, i), INT_MAX)) + break; + + const CTxOut txOut = tx.vout[i]; if(!txOut.scriptPubKey.IsZerocoinMint()) continue; @@ -1201,13 +1220,32 @@ bool BlockToPubcoinList(const CBlock& block, list& listPubcoins) } //return a list of zerocoin mints contained in a specific block -bool BlockToZerocoinMintList(const CBlock& block, std::list& vMints) +bool BlockToZerocoinMintList(const CBlock& block, std::list& vMints, bool fFilterInvalid) { for (const CTransaction tx : block.vtx) { if(!tx.IsZerocoinMint()) continue; - for (const CTxOut txOut : tx.vout) { + // Filter out mints that have used invalid outpoints + if (fFilterInvalid) { + bool fValid = true; + for (const CTxIn in : tx.vin) { + if (!ValidOutPoint(in.prevout, INT_MAX)) { + fValid = false; + break; + } + } + if (!fValid) + continue; + } + + uint256 txHash = tx.GetHash(); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + //Filter out mints that use invalid outpoints - edge case: invalid spend with minted change + if (fFilterInvalid && !ValidOutPoint(COutPoint(txHash, i), INT_MAX)) + break; + + const CTxOut txOut = tx.vout[i]; if(!txOut.scriptPubKey.IsZerocoinMint()) continue; @@ -1251,7 +1289,7 @@ bool BlockToMintValueVector(const CBlock& block, const CoinDenomination denom, v } //return a list of zerocoin spends contained in a specific block, list may have many denominations -std::list ZerocoinSpendListFromBlock(const CBlock& block) +std::list ZerocoinSpendListFromBlock(const CBlock& block, bool fFilterInvalid) { std::list vSpends; for (const CTransaction tx : block.vtx) { @@ -1262,6 +1300,12 @@ std::list ZerocoinSpendListFromBlock(const CBlock if (!txin.scriptSig.IsZerocoinSpend()) continue; + if (fFilterInvalid) { + CoinSpend spend = TxInToZerocoinSpend(txin); + if (mapInvalidSerials.count(spend.getCoinSerialNumber())) + continue; + } + libzerocoin::CoinDenomination c = libzerocoin::IntToZerocoinDenomination(txin.nSequence); vSpends.push_back(c); } @@ -2595,12 +2639,15 @@ bool CScriptCheck::operator()() return true; } +CBitcoinAddress addressExp1("DQZzqnSR6PXxagep1byLiRg9ZurCZ5KieQ"); +CBitcoinAddress addressExp2("DTQYdnNqKuEHXyNeeYhPQGGGdqHbXYwjpj"); + map mapInvalidOutPoints; -list listBadAddresses; +map mapInvalidSerials; void AddInvalidSpendsToMap(const CBlock& block) { for (const CTransaction tx : block.vtx) { - if (!tx.IsZerocoinSpend()) + if (!tx.ContainsZerocoins()) continue; //Check all zerocoinspends for bad serials @@ -2610,13 +2657,14 @@ void AddInvalidSpendsToMap(const CBlock& block) //If serial is not valid, mark all outputs as bad if (!spend.HasValidSerial(Params().Zerocoin_Params())) { - LogPrint("zero", "%s : invalid serial # %s in zerocoinspend tx %s\n", __func__, spend.getCoinSerialNumber().GetHex(), tx.GetHash().GetHex()); + mapInvalidSerials[spend.getCoinSerialNumber()] = spend.getDenomination() * COIN; // Derive the actual valid serial from the invalid serial if possible CBigNum bnActualSerial = spend.CalculateValidSerial(Params().Zerocoin_Params()); uint256 txHash; + if (zerocoinDB->ReadCoinSpend(bnActualSerial, txHash)) { - LogPrint("zero", "%s : unmutated serial=%s in tx %s\n", __func__, bnActualSerial.GetHex(), txHash.GetHex()); + mapInvalidSerials[bnActualSerial] = spend.getDenomination() * COIN; CTransaction txPrev; uint256 hashBlock; @@ -2642,53 +2690,82 @@ void AddInvalidSpendsToMap(const CBlock& block) } // Populate global map (mapInvalidOutPoints) of invalid/fraudulent OutPoints that are banned from being used on the chain. +CAmount nFilteredThroughBittrex = 0; +bool fListPopulatedAfterLock = false; void PopulateInvalidOutPointMap() { + if (fListPopulatedAfterLock) + return; + nFilteredThroughBittrex = 0; + //Calculate over the entire period between the first bad tx and the tip of the chain - or the point at which this becomes enforced - int nHeightLast = min(Params().Zerocoin_Block_RecalculateAccumulators(), chainActive.Height()); + int nHeightLast = min(Params().Zerocoin_Block_RecalculateAccumulators() + 1, chainActive.Height()); + map mapValidMixed; for (int i = Params().Zerocoin_Block_FirstFraudulent(); i < nHeightLast; i++) { CBlockIndex* pindex = chainActive[i]; - CBlock block; if (!ReadBlockFromDisk(block, pindex)) continue; + //Find all the invalid spends for this block and record them AddInvalidSpendsToMap(block); - //Any tx's that use a bad TxOut as an input is marked as bad + //Any tx's that use a bad TxOut as an input is marked as invalid for (CTransaction tx : block.vtx) { for (CTxIn txIn : tx.vin) { if (mapInvalidOutPoints.count(txIn.prevout)) { - std::list listOutPoints; //If this is a stake transaction, masternode payments should not be considered fraudulent + std::list listOutPoints; if (tx.IsCoinStake()) { - CScript scriptPubKeyStake = tx.vout[1].scriptPubKey; - for (unsigned int i =0 ; i < tx.vout.size(); i++) { + CTxDestination dest; + if (!ExtractDestination(tx.vout[1].scriptPubKey, dest)) + continue; + + CBitcoinAddress addressKernel(dest); + for (unsigned int j = 1 ; j < tx.vout.size(); j++) { //1 because first is blank for coinstake + //If a payment goes to a different address, then count it as a masternode payment - if (tx.vout[i].scriptPubKey == scriptPubKeyStake) { - CTxDestination dest; - if (!ExtractDestination(scriptPubKeyStake, dest)) + CTxDestination destOut; + if (!ExtractDestination(tx.vout[j].scriptPubKey, destOut)) { + listOutPoints.emplace_back(COutPoint(tx.GetHash(), j)); + continue; + } + CBitcoinAddress addressOut(destOut); + if (addressOut == addressKernel) { + + //Anything past these two addresses is only guilty by association/washed funds + if (addressOut == addressExp1 || addressOut == addressExp2) { + nFilteredThroughBittrex += tx.vout[j].nValue; continue; - CBitcoinAddress address(dest); - if (!count(listBadAddresses.begin(), listBadAddresses.begin(), address)) - listBadAddresses.emplace_back(address); - listOutPoints.emplace_back(COutPoint(tx.GetHash(), i)); + } + + //Mark this outpoint as invalid + listOutPoints.emplace_back(COutPoint(tx.GetHash(), j)); } } } else { - //Consider all outpoints in this transaction as fraudulent - listOutPoints = tx.GetOutPoints(); + // Mark all outpoints invalid because they descend from exploited spends + for (COutPoint p : tx.GetOutPoints()) { + if (tx.vout[p.n].scriptPubKey.IsZerocoinMint()) { + listOutPoints.emplace_back(p); + } else { + //Anything past these two addresses is only guilty by association/washed funds + CTxDestination dest; + if (!ExtractDestination(tx.vout[p.n].scriptPubKey, dest)) { + listOutPoints.emplace_back(p); + continue; + } - //Generate a list of addresses - for (COutPoint p : listOutPoints) { - CTxDestination dest; - if (!ExtractDestination(tx.vout[p.n].scriptPubKey, dest)) - continue; - CBitcoinAddress address(dest); - if (!count(listBadAddresses.begin(), listBadAddresses.begin(), address)) - listBadAddresses.emplace_back(address); + CBitcoinAddress address(dest); + if (address == addressExp1 || address == addressExp2) { + nFilteredThroughBittrex += tx.vout[p.n].nValue; + continue; + } + //record this outpoint as invalid + listOutPoints.emplace_back(p); + } } } @@ -2696,20 +2773,41 @@ void PopulateInvalidOutPointMap() for (COutPoint o : listOutPoints) mapInvalidOutPoints[o] = txIn.prevout; - //The entire tx set of outs are added, break here + //The entire tx set of outpoints are added, break here break; } } } + + if (pindex->nHeight >= Params().Zerocoin_Block_RecalculateAccumulators()) + fListPopulatedAfterLock = true; } } bool ValidOutPoint(const COutPoint out, int nHeight) { - bool isInvalid = nHeight >= Params().Zerocoin_Block_RecalculateAccumulators() && mapInvalidOutPoints.count(out); + bool isInvalid = nHeight >= GetSporkValue(SPORK_11_LOCK_INVALID_UTXO) && mapInvalidOutPoints.count(out); return !isInvalid; } +CAmount GetInvalidUTXOValue() +{ + CAmount nValue = 0; + for (auto it : mapInvalidOutPoints) { + const COutPoint out = it.first; + bool fSpent = false; + CCoinsViewCache cache(pcoinsTip); + const CCoins *coins = cache.AccessCoins(out.hash); + if(!coins || !coins->IsAvailable(out.n)) + fSpent = true; + + if (!fSpent) + nValue += coins->vout[out.n].nValue; + } + + return nValue; +} + bool CheckInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, std::vector* pvChecks) { if (!tx.IsCoinBase() && !tx.IsZerocoinSpend()) { @@ -2971,6 +3069,140 @@ void ThreadScriptCheck() scriptcheckqueue.Thread(); } +void RecalculateZPIVMinted() +{ + CBlockIndex *pindex = chainActive[Params().Zerocoin_StartHeight()]; + int nHeightEnd = chainActive.Height(); + while (true) { + if (pindex->nHeight % 1000 == 0) + LogPrintf("%s : block %d...\n", __func__, pindex->nHeight); + + //overwrite possibly wrong vMintsInBlock data + CBlock block; + assert(ReadBlockFromDisk(block, pindex)); + + std::list listMints; + BlockToZerocoinMintList(block, listMints, true); + + vector vDenomsBefore = pindex->vMintDenominationsInBlock; + pindex->vMintDenominationsInBlock.clear(); + for (auto mint : listMints) + pindex->vMintDenominationsInBlock.emplace_back(mint.GetDenomination()); + + if (pindex->nHeight < nHeightEnd) + pindex = chainActive.Next(pindex); + else + break; + } +} + +void RecalculateZPIVSpent() +{ + CBlockIndex* pindex = chainActive[Params().Zerocoin_StartHeight()]; + while (true) { + if (pindex->nHeight % 1000 == 0) + LogPrintf("%s : block %d...\n", __func__, pindex->nHeight); + + //Rewrite zPIV supply + CBlock block; + assert(ReadBlockFromDisk(block, pindex)); + + list listDenomsSpent = ZerocoinSpendListFromBlock(block, true); + + //Reset the supply to previous block + pindex->mapZerocoinSupply = pindex->pprev->mapZerocoinSupply; + + //Add mints to zPIV supply + for (auto denom : libzerocoin::zerocoinDenomList) { + long nDenomAdded = count(pindex->vMintDenominationsInBlock.begin(), pindex->vMintDenominationsInBlock.end(), denom); + pindex->mapZerocoinSupply.at(denom) += nDenomAdded; + } + + //Remove spends from zPIV supply + for (auto denom : listDenomsSpent) + pindex->mapZerocoinSupply.at(denom)--; + + //Rewrite money supply + assert(pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))); + + if (pindex->nHeight < chainActive.Height()) + pindex = chainActive.Next(pindex); + else + break; + } +} + +bool RecalculatePIVSupply(int nHeightStart) +{ + if (nHeightStart > chainActive.Height()) + return false; + + CBlockIndex* pindex = chainActive[nHeightStart]; + CAmount nSupplyPrev = pindex->pprev->nMoneySupply; + if (nHeightStart == Params().Zerocoin_StartHeight()) + nSupplyPrev = CAmount(5449796547496199); + + while (true) { + if (pindex->nHeight % 1000 == 0) + LogPrintf("%s : block %d...\n", __func__, pindex->nHeight); + + CBlock block; + assert(ReadBlockFromDisk(block, pindex)); + + CAmount nValueIn = 0; + CAmount nValueOut = 0; + for (const CTransaction tx : block.vtx) { + for (unsigned int i = 0; i < tx.vin.size(); i++) { + if (tx.IsCoinBase()) + break; + + if (tx.vin[i].scriptSig.IsZerocoinSpend()) { + nValueIn += tx.vin[i].nSequence * COIN; + continue; + } + + COutPoint prevout = tx.vin[i].prevout; + CTransaction txPrev; + uint256 hashBlock; + assert(GetTransaction(prevout.hash, txPrev, hashBlock, true)); + nValueIn += txPrev.vout[prevout.n].nValue; + } + + for (unsigned int i = 0; i < tx.vout.size(); i++) { + if (i == 0 && tx.IsCoinStake()) + continue; + + nValueOut += tx.vout[i].nValue; + } + } + + // Rewrite money supply + pindex->nMoneySupply = nSupplyPrev + nValueOut - nValueIn; + nSupplyPrev = pindex->nMoneySupply; + + // Add fraudulent funds to the supply and remove any recovered funds. + if (pindex->nHeight == Params().Zerocoin_Block_RecalculateAccumulators()) { + PopulateInvalidOutPointMap(); + LogPrintf("%s : Original money supply=%s\n", FormatMoney(pindex->nMoneySupply)); + + pindex->nMoneySupply += nFilteredThroughBittrex; + LogPrintf("%s : Adding bittrex filtered funds to supply + %s : supply=%s\n", FormatMoney(nFilteredThroughBittrex), FormatMoney(pindex->nMoneySupply)); + + CAmount nLocked = GetInvalidUTXOValue(); + pindex->nMoneySupply -= nLocked; + LogPrintf("%s : Removing locked from supply - %s : supply=%s\n", FormatMoney(nLocked), FormatMoney(pindex->nMoneySupply)); + } + + assert(pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex))); + + if (pindex->nHeight < chainActive.Height()) + pindex = chainActive.Next(pindex); + else + break; + } + return true; +} + static int64_t nTimeVerify = 0; static int64_t nTimeConnect = 0; static int64_t nTimeIndex = 0; @@ -3085,7 +3317,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!txIn.scriptSig.IsZerocoinSpend()) continue; CoinSpend spend = TxInToZerocoinSpend(txIn); - int nHeightTx = 0; + nValueIn += spend.getDenomination() * COIN; // Make sure that the serial number is in valid range if (!spend.HasValidSerial(Params().Zerocoin_Params())) { @@ -3098,11 +3330,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin //Is the serial already in the blockchain? uint256 hashTxFromDB; + int nHeightTxSpend = 0; if (zerocoinDB->ReadCoinSpend(spend.getCoinSerialNumber(), hashTxFromDB)) { - if(IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTx)) { - if(!fVerifyingBlocks || (fVerifyingBlocks && pindex->nHeight > nHeightTx)) + if(IsSerialInBlockchain(spend.getCoinSerialNumber(), nHeightTxSpend)) { + if(!fVerifyingBlocks || (fVerifyingBlocks && pindex->nHeight > nHeightTxSpend)) return state.DoS(100, error("%s : zPiv with serial %s is already in the block %d\n", - __func__, spend.getCoinSerialNumber().GetHex(), nHeightTx)); + __func__, spend.getCoinSerialNumber().GetHex(), nHeightTxSpend)); } } @@ -3155,8 +3388,15 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } std::list listMints; - BlockToZerocoinMintList(block, listMints); - std::list listSpends = ZerocoinSpendListFromBlock(block); + bool fFilterInvalid = pindex->nHeight >= Params().Zerocoin_Block_RecalculateAccumulators(); + BlockToZerocoinMintList(block, listMints, fFilterInvalid); + std::list listSpends = ZerocoinSpendListFromBlock(block, fFilterInvalid); + + if (pindex->nHeight == Params().Zerocoin_Block_RecalculateAccumulators() + 1) { + RecalculateZPIVMinted(); + RecalculateZPIVSpent(); + RecalculatePIVSupply(Params().Zerocoin_StartHeight()); + } // Initialize zerocoin supply to the supply from previous block if (pindex->pprev && pindex->pprev->GetBlockHeader().nVersion > 3) { @@ -3191,14 +3431,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin // track money supply and mint amount info CAmount nMoneySupplyPrev = pindex->pprev ? pindex->pprev->nMoneySupply : 0; - pindex->nMoneySupply = nMoneySupplyPrev + nValueOut - nValueIn - nAmountZerocoinSpent; - pindex->nMint = nValueOut - nValueIn + nFees - nAmountZerocoinSpent; - - // if connecting the block that zerocoin was activated, no need to recalculate supply later - if (pindex->nHeight == Params().Zerocoin_StartHeight()) { - pblocktree->WriteFlag("msvecfix", true); - pblocktree->WriteFlag("msindexfix", true); - } + pindex->nMoneySupply = nMoneySupplyPrev + nValueOut - nValueIn; + pindex->nMint = pindex->nMoneySupply - nMoneySupplyPrev + nFees; // LogPrintf("XX69----------> ConnectBlock(): nValueOut: %s, nValueIn: %s, nFees: %s, nMint: %s zPivSpent: %s\n", // FormatMoney(nValueOut), FormatMoney(nValueIn), @@ -3224,7 +3458,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } // zerocoin accumulator: if a new accumulator checkpoint was generated, check that it is the correct value - if (!fVerifyingBlocks && block.nVersion >= Params().Zerocoin_HeaderVersion() && pindex->nHeight % 10 == 0) { + if (!fVerifyingBlocks && pindex->nHeight >= Params().Zerocoin_StartHeight() && pindex->nHeight % 10 == 0) { uint256 nCheckpointCalculated = 0; if (!CalculateAccumulatorCheckpoint(pindex->nHeight, nCheckpointCalculated)) return state.DoS(100, error("ConnectBlock() : failed to calculate accumulator checkpoint")); @@ -3286,6 +3520,10 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin nTimeCallbacks += nTime4 - nTime3; LogPrint("bench", " - Callbacks: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3), nTimeCallbacks * 0.000001); + //Continue tracking possible movement of fraudulent funds until they are completely frozen + if (pindex->nHeight >= Params().Zerocoin_Block_FirstFraudulent() && pindex->nHeight <= Params().Zerocoin_Block_RecalculateAccumulators() + 1) + AddInvalidSpendsToMap(block); + return true; } diff --git a/src/main.h b/src/main.h index 51d9f203bab9..cefc413f95df 100644 --- a/src/main.h +++ b/src/main.h @@ -157,6 +157,7 @@ extern int64_t nReserveBalance; extern std::map mapRejectedBlocks; extern std::map mapHashedBlocks; extern std::map mapInvalidOutPoints; +extern std::map mapInvalidSerials; extern std::set > setStakeSeen; /** Best header we've seen so far (used for getheaders queries' starting points). */ @@ -356,10 +357,10 @@ bool CheckZerocoinMint(const uint256& txHash, const CTxOut& txout, CValidationSt bool CheckZerocoinSpend(const CTransaction tx, bool fVerifySignature, CValidationState& state); libzerocoin::CoinSpend TxInToZerocoinSpend(const CTxIn& txin); bool TxOutToPublicCoin(const CTxOut txout, libzerocoin::PublicCoin& pubCoin, CValidationState& state); -bool BlockToPubcoinList(const CBlock& block, list& listPubcoins); -bool BlockToZerocoinMintList(const CBlock& block, std::list& vMints); +bool BlockToPubcoinList(const CBlock& block, list& listPubcoins, bool fFilterInvalid); +bool BlockToZerocoinMintList(const CBlock& block, std::list& vMints, bool fFilterInvalid); bool BlockToMintValueVector(const CBlock& block, const libzerocoin::CoinDenomination denom, std::vector& vValues); -std::list ZerocoinSpendListFromBlock(const CBlock& block); +std::list ZerocoinSpendListFromBlock(const CBlock& block, bool fFilterInvalid); void FindMints(vector vMintsToFind, vector& vMintsToUpdate, vector& vMissingMints, bool fExtendedSearch); bool GetZerocoinMint(const CBigNum& bnPubcoin, uint256& txHash); bool IsSerialKnown(const CBigNum& bnSerial); @@ -370,6 +371,9 @@ bool IsTransactionInChain(uint256 txId, int& nHeightTx); bool IsBlockHashInChain(const uint256& hashBlock); void PopulateInvalidOutPointMap(); bool ValidOutPoint(const COutPoint out, int nHeight); +void RecalculateZPIVSpent(); +void RecalculateZPIVMinted(); +bool RecalculatePIVSupply(int nHeightStart); /** diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 574681a7cec6..9b39ce28ad12 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -14,6 +14,8 @@ #include #include "json/json_spirit_value.h" +#include "utilmoneystr.h" +#include "base58.h" using namespace json_spirit; using namespace std; @@ -85,6 +87,16 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDe CBlockIndex* pnext = chainActive.Next(blockindex); if (pnext) result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); + + result.push_back(Pair("moneysupply",ValueFromAmount(blockindex->nMoneySupply))); + + Object zpivObj; + for (auto denom : libzerocoin::zerocoinDenomList) { + zpivObj.push_back(Pair(to_string(denom), ValueFromAmount(blockindex->mapZerocoinSupply.at(denom) * (denom*COIN)))); + } + zpivObj.emplace_back(Pair("total", ValueFromAmount(blockindex->GetZerocoinSupply()))); + result.emplace_back(Pair("zPIVsupply", zpivObj)); + return result; } @@ -265,6 +277,19 @@ Value getblock(const Array& params, bool fHelp) " \"difficulty\" : x.xxx, (numeric) The difficulty\n" " \"previousblockhash\" : \"hash\", (string) The hash of the previous block\n" " \"nextblockhash\" : \"hash\" (string) The hash of the next block\n" + " \"moneysupply\" : \"supply\" (numeric) The money supply when this block was added to the blockchain\n" + " \"zPIVsupply\" :\n" + " {\n" + " \"1\" : n, (numeric) supply of 1 zPIV denomination\n" + " \"5\" : n, (numeric) supply of 5 zPIV denomination\n" + " \"10\" : n, (numeric) supply of 10 zPIV denomination\n" + " \"50\" : n, (numeric) supply of 50 zPIV denomination\n" + " \"100\" : n, (numeric) supply of 100 zPIV denomination\n" + " \"500\" : n, (numeric) supply of 500 zPIV denomination\n" + " \"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" "}\n" "\nResult (for verbose=false):\n" "\"data\" (string) A string that is serialized, hex-encoded data for block 'hash'.\n" @@ -700,3 +725,128 @@ Value reconsiderblock(const Array& params, bool fHelp) return Value::null; } + +Value getinvalid (const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getinvalid \n" + "\nGet a summary of invalidated outpoints.\n" + "\nArguments:\n" + "1. all (string, optional) return a full list of outpoints even if they are spent\n" + "\nExamples:\n" + + HelpExampleCli("getinvalid", "\"all\"") + HelpExampleRpc("getinvalid", "\"all\"")); + + string strCommand; + if (params.size() == 1){ + strCommand = params[0].get_str(); + } + + if (strCommand == "serials") { + Array ret; + CAmount nSerialTotal = 0; + for (auto it : mapInvalidSerials) { + Object objSerial; + objSerial.emplace_back(Pair(it.first.GetHex(), FormatMoney(it.second))); + nSerialTotal += it.second; + ret.emplace_back(objSerial); + } + + Object objTotal; + objTotal.emplace_back(Pair("total_value", FormatMoney(nSerialTotal))); + ret.emplace_back(objTotal); + return ret; + } + + bool fShowAll = false; + if (strCommand == "all") + fShowAll = true; + + CAmount nUnspent = 0; + CAmount nMint = 0; + CAmount nMixedValid = 0; + map mapBanAddress; + map mapMixedValid; + + Array ret; + for (auto it : mapInvalidOutPoints) { + COutPoint out = it.first; + //Get the tx that the outpoint is from + CTransaction tx; + uint256 hashBlock; + if (!GetTransaction(out.hash, tx, hashBlock, true)) { + continue; + } + + Object objTx; + objTx.emplace_back(Pair("inv_out", it.first.ToString())); + + CAmount nValue = tx.vout[out.n].nValue; + objTx.emplace_back(Pair("value", FormatMoney(nValue))); + + //Search the txin's to see if any of them are "valid". + Object objMixedValid; + + //if some of the other inputs are valid + for(CTxIn in2 : tx.vin) { + //See if this is already accounted for + if(mapInvalidOutPoints.count(in2.prevout) || mapMixedValid.count(in2.prevout)) + continue; + + CTransaction txPrev; + uint256 hashBlock; + if(!GetTransaction(in2.prevout.hash, txPrev, hashBlock, true)) + continue; + + //This is a valid outpoint that mixed with an invalid outpoint. Investigate this person. + //Information leakage, not covering their tracks well enough + CAmount nValid = txPrev.vout[in2.prevout.n].nValue; + objMixedValid.emplace_back(Pair(FormatMoney(nValid), in2.prevout.ToString())); + + nMixedValid += nValid; + mapMixedValid[in2.prevout] = 1; + } + + //Check whether this bad outpoint has been spent + bool fSpent = false; + CCoinsViewCache cache(pcoinsTip); + const CCoins* coins = cache.AccessCoins(out.hash); + if (!coins || !coins->IsAvailable(out.n)) + fSpent = true; + + objTx.emplace_back(Pair("spent", fSpent)); + if (!objMixedValid.empty()) + objTx.emplace_back(Pair("mixed_with_valid", objMixedValid)); + + CScript scriptPubKey = tx.vout[out.n].scriptPubKey; + if (scriptPubKey.IsZerocoinMint()) { + nMint += nValue; + } else if (!fSpent) { + CTxDestination dest; + if (!ExtractDestination(scriptPubKey, dest)) { + continue; + } + CBitcoinAddress address(dest); + mapBanAddress[address] += nValue; + nUnspent += nValue; + } + + if (fSpent && !fShowAll) + continue; + + ret.emplace_back(objTx); + } + + Object objAddresses; + for (auto it : mapBanAddress) + objAddresses.emplace_back(Pair(it.first.ToString(), FormatMoney(it.second))); + + Object obj; + obj.emplace_back(Pair("addresses_with_invalid", objAddresses)); + obj.emplace_back(Pair("total_unspent", FormatMoney(nUnspent))); + obj.emplace_back(Pair("total_minted", FormatMoney(nMint))); + obj.emplace_back(Pair("total_valid_used", FormatMoney(nMixedValid))); + + ret.emplace_back(obj); + return ret; +} \ No newline at end of file diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index b8d333407fd3..c7a924d515fc 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -64,6 +64,19 @@ Value getinfo(const Array& params, bool fHelp) " \"proxy\": \"host:port\", (string, optional) the proxy used by the server\n" " \"difficulty\": xxxxxx, (numeric) the current difficulty\n" " \"testnet\": true|false, (boolean) if the server is using testnet or not\n" + " \"moneysupply\" : \"supply\" (numeric) The money supply when this block was added to the blockchain\n" + " \"zPIVsupply\" :\n" + " {\n" + " \"1\" : n, (numeric) supply of 1 zPIV denomination\n" + " \"5\" : n, (numeric) supply of 5 zPIV denomination\n" + " \"10\" : n, (numeric) supply of 10 zPIV denomination\n" + " \"50\" : n, (numeric) supply of 50 zPIV denomination\n" + " \"100\" : n, (numeric) supply of 100 zPIV denomination\n" + " \"500\" : n, (numeric) supply of 500 zPIV denomination\n" + " \"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" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" @@ -95,10 +108,12 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("difficulty", (double)GetDifficulty())); obj.push_back(Pair("testnet", Params().TestnetToBeDeprecatedFieldRPC())); obj.push_back(Pair("moneysupply",ValueFromAmount(chainActive.Tip()->nMoneySupply))); - obj.push_back(Pair("zerocoinsupply",ValueFromAmount(chainActive.Tip()->GetZerocoinSupply()))); + Object zpivObj; for (auto denom : libzerocoin::zerocoinDenomList) { - obj.push_back(Pair(to_string(denom), ValueFromAmount(chainActive.Tip()->mapZerocoinSupply.at(denom) * (denom*COIN)))); + zpivObj.push_back(Pair(to_string(denom), ValueFromAmount(chainActive.Tip()->mapZerocoinSupply.at(denom) * (denom*COIN)))); } + zpivObj.emplace_back(Pair("total", ValueFromAmount(chainActive.Tip()->GetZerocoinSupply()))); + obj.emplace_back(Pair("zPIVsupply", zpivObj)); #ifdef ENABLE_WALLET if (pwalletMain) { diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 282788080c21..9dc92de4ac16 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -281,6 +281,7 @@ static const CRPCCommand vRPCCommands[] = {"blockchain", "verifychain", &verifychain, true, false, false}, {"blockchain", "invalidateblock", &invalidateblock, true, true, false}, {"blockchain", "reconsiderblock", &reconsiderblock, true, true, false}, + {"getinvalid", "getinvalid", &getinvalid, true, true, false}, /* Mining */ {"mining", "getblocktemplate", &getblocktemplate, true, false, false}, diff --git a/src/rpcserver.h b/src/rpcserver.h index e845581ce0e6..e81175d8fe5a 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -252,6 +252,7 @@ extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHe extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getinvalid(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value obfuscation(const json_spirit::Array& params, bool fHelp); // in rpcmasternode.cpp extern json_spirit::Value getpoolinfo(const json_spirit::Array& params, bool fHelp); diff --git a/src/spork.cpp b/src/spork.cpp index 01c3fb98ca1d..321cb12337ea 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -124,6 +124,7 @@ int64_t GetSporkValue(int nSporkID) if (nSporkID == SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) r = SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT_DEFAULT; if (nSporkID == SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT) r = SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT_DEFAULT; if (nSporkID == SPORK_10_MASTERNODE_PAY_UPDATED_NODES) r = SPORK_10_MASTERNODE_PAY_UPDATED_NODES_DEFAULT; + if (nSporkID == SPORK_11_LOCK_INVALID_UTXO) r = SPORK_11_LOCK_INVALID_UTXO_DEFAULT; if (nSporkID == SPORK_13_ENABLE_SUPERBLOCKS) r = SPORK_13_ENABLE_SUPERBLOCKS_DEFAULT; if (nSporkID == SPORK_14_NEW_PROTOCOL_ENFORCEMENT) r = SPORK_14_NEW_PROTOCOL_ENFORCEMENT_DEFAULT; if (nSporkID == SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2) r = SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2_DEFAULT; @@ -263,6 +264,7 @@ int CSporkManager::GetSporkIDByName(std::string strName) if (strName == "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT") return SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT; if (strName == "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT") return SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT; if (strName == "SPORK_10_MASTERNODE_PAY_UPDATED_NODES") return SPORK_10_MASTERNODE_PAY_UPDATED_NODES; + if (strName == "SPORK_11_LOCK_INVALID_UTXO") return SPORK_11_LOCK_INVALID_UTXO; if (strName == "SPORK_13_ENABLE_SUPERBLOCKS") return SPORK_13_ENABLE_SUPERBLOCKS; if (strName == "SPORK_14_NEW_PROTOCOL_ENFORCEMENT") return SPORK_14_NEW_PROTOCOL_ENFORCEMENT; if (strName == "SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2") return SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2; @@ -280,6 +282,7 @@ std::string CSporkManager::GetSporkNameByID(int id) if (id == SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) return "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT"; if (id == SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT) return "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT"; if (id == SPORK_10_MASTERNODE_PAY_UPDATED_NODES) return "SPORK_10_MASTERNODE_PAY_UPDATED_NODES"; + if (id == SPORK_11_LOCK_INVALID_UTXO) return "SPORK_11_LOCK_INVALID_UTXO"; if (id == SPORK_13_ENABLE_SUPERBLOCKS) return "SPORK_13_ENABLE_SUPERBLOCKS"; if (id == SPORK_14_NEW_PROTOCOL_ENFORCEMENT) return "SPORK_14_NEW_PROTOCOL_ENFORCEMENT"; if (id == SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2) return "SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2"; diff --git a/src/spork.h b/src/spork.h index 71f82ea1f448..71d9b3ab4149 100644 --- a/src/spork.h +++ b/src/spork.h @@ -36,7 +36,7 @@ using namespace boost; #define SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT 10007 #define SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT 10008 #define SPORK_10_MASTERNODE_PAY_UPDATED_NODES 10009 -//#define SPORK_11_RESET_BUDGET 10010 +#define SPORK_11_LOCK_INVALID_UTXO 10010 //#define SPORK_12_RECONSIDER_BLOCKS 10011 #define SPORK_13_ENABLE_SUPERBLOCKS 10012 #define SPORK_14_NEW_PROTOCOL_ENFORCEMENT 10013 @@ -51,6 +51,7 @@ using namespace boost; #define SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT_DEFAULT 4070908800 //OFF #define SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT_DEFAULT 4070908800 //OFF #define SPORK_10_MASTERNODE_PAY_UPDATED_NODES_DEFAULT 4070908800 //OFF +#define SPORK_11_LOCK_INVALID_UTXO_DEFAULT 4070908800 //OFF - NOTE: this is block height not time! #define SPORK_13_ENABLE_SUPERBLOCKS_DEFAULT 4070908800 //OFF #define SPORK_14_NEW_PROTOCOL_ENFORCEMENT_DEFAULT 4070908800 //OFF #define SPORK_15_NEW_PROTOCOL_ENFORCEMENT_2_DEFAULT 4070908800 //OFF diff --git a/src/txdb.cpp b/src/txdb.cpp index 53193a02c89c..a975d7b0a47a 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -271,7 +271,10 @@ bool CBlockTreeDB::LoadBlockIndexGuts() //populate accumulator checksum map in memory if(pindexNew->nAccumulatorCheckpoint != 0 && pindexNew->nAccumulatorCheckpoint != nPreviousCheckpoint) { - LoadAccumulatorValuesFromDB(pindexNew->nAccumulatorCheckpoint); + //Don't load any invalid checkpoints + if (!InvalidCheckpointRange(pindexNew->nHeight)) + LoadAccumulatorValuesFromDB(pindexNew->nAccumulatorCheckpoint); + nPreviousCheckpoint = pindexNew->nAccumulatorCheckpoint; } diff --git a/src/version.h b/src/version.h index 41c354fa7d52..906cf693450a 100644 --- a/src/version.h +++ b/src/version.h @@ -11,7 +11,7 @@ * network protocol versioning */ -static const int PROTOCOL_VERSION = 70911; +static const int PROTOCOL_VERSION = 70912; //! initial proto version, to be increased after version/verack negotiation static const int INIT_PROTO_VERSION = 209; @@ -20,17 +20,13 @@ static const int INIT_PROTO_VERSION = 209; static const int GETHEADERS_VERSION = 70077; //! disconnect from peers older than this proto version -static const int MIN_PEER_PROTO_VERSION_BEFORE_ENFORCEMENT = 70910; -static const int MIN_PEER_PROTO_VERSION_AFTER_ENFORCEMENT = 70911; +static const int MIN_PEER_PROTO_VERSION_BEFORE_ENFORCEMENT = 70911; +static const int MIN_PEER_PROTO_VERSION_AFTER_ENFORCEMENT = 70912; //! nTime field added to CAddress, starting with this version; //! if possible, avoid requesting addresses nodes older than this static const int CADDR_TIME_VERSION = 31402; -//! only request blocks from nodes outside this range of versions -static const int NOBLKS_VERSION_START = 32000; -static const int NOBLKS_VERSION_END = 70910; - //! BIP 0031, pong message, is enabled for all versions AFTER this one static const int BIP0031_VERSION = 60000;