// Copyright (c) 2017-2019 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "accumulators.h" #include "accumulatormap.h" #include "chainparams.h" #include "main.h" #include "txdb.h" #include "init.h" #include "spork.h" #include "accumulatorcheckpoints.h" #include "zagrchain.h" #include "tinyformat.h" using namespace libzerocoin; std::map mapAccumulatorValues; std::list listAccCheckpointsNoDB; uint32_t ParseChecksum(uint256 nChecksum, CoinDenomination denomination) { //shift to the beginning bit of this denomination and trim any remaining bits by returning 32 bits only int pos = distance(zerocoinDenomList.begin(), find(zerocoinDenomList.begin(), zerocoinDenomList.end(), denomination)); nChecksum = nChecksum >> (32*((zerocoinDenomList.size() - 1) - pos)); return nChecksum.Get32(); } uint32_t GetChecksum(const CBigNum &bnValue) { CDataStream ss(SER_GETHASH, 0); ss << bnValue; uint256 hash = Hash(ss.begin(), ss.end()); return hash.Get32(); } // Find the first occurance of a certain accumulator checksum. Return 0 if not found. int GetChecksumHeight(uint32_t nChecksum, CoinDenomination denomination) { CBlockIndex* pindex = chainActive[Params().Zerocoin_StartHeight()]; if (!pindex) return 0; //Search through blocks to find the checksum while (pindex) { if (ParseChecksum(pindex->nAccumulatorCheckpoint, denomination) == nChecksum) return pindex->nHeight; //Skip forward in groups of 10 blocks since checkpoints only change every 10 blocks if (pindex->nHeight % 10 == 0) { if (pindex->nHeight + 10 > chainActive.Height()) return 0; pindex = chainActive[pindex->nHeight + 10]; continue; } pindex = chainActive.Next(pindex); } return 0; } bool GetAccumulatorValueFromChecksum(uint32_t nChecksum, bool fMemoryOnly, CBigNum& bnAccValue) { if (mapAccumulatorValues.count(nChecksum)) { bnAccValue = mapAccumulatorValues.at(nChecksum); return true; } if (fMemoryOnly) return false; if (!zerocoinDB->ReadAccumulatorValue(nChecksum, bnAccValue)) { bnAccValue = 0; } return true; } bool GetAccumulatorValueFromDB(uint256 nCheckpoint, CoinDenomination denom, CBigNum& bnAccValue) { uint32_t nChecksum = ParseChecksum(nCheckpoint, denom); return GetAccumulatorValueFromChecksum(nChecksum, false, bnAccValue); } void AddAccumulatorChecksum(const uint32_t nChecksum, const CBigNum &bnValue) { //Since accumulators are switching at v2, stop databasing v1 because its useless. Only focus on v2. if (chainActive.Height() >= Params().Zerocoin_Block_V2_Start()) { zerocoinDB->WriteAccumulatorValue(nChecksum, bnValue); mapAccumulatorValues.insert(make_pair(nChecksum, bnValue)); } } void DatabaseChecksums(AccumulatorMap& mapAccumulators) { uint256 nCheckpoint = 0; for (auto& denom : zerocoinDenomList) { CBigNum bnValue = mapAccumulators.GetValue(denom); uint32_t nCheckSum = GetChecksum(bnValue); AddAccumulatorChecksum(nCheckSum, bnValue); nCheckpoint = nCheckpoint << 32 | nCheckSum; } } 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) { uint32_t nChecksumErase = ParseChecksum(nCheckpointErase, denomination); uint32_t nChecksumPrevious = ParseChecksum(nCheckpointPrevious, denomination); //if the previous checksum is the same, then it should remain in the database and map if(nChecksumErase == nChecksumPrevious) continue; if (!EraseChecksum(nChecksumErase)) return false; } return true; } bool LoadAccumulatorValuesFromDB(const uint256 nCheckpoint) { for (auto& denomination : zerocoinDenomList) { uint32_t nChecksum = ParseChecksum(nCheckpoint, denomination); //if read is not successful then we are not in a state to verify zerocoin transactions CBigNum bnValue; if (!zerocoinDB->ReadAccumulatorValue(nChecksum, bnValue)) { if (!count(listAccCheckpointsNoDB.begin(), listAccCheckpointsNoDB.end(), nCheckpoint)) listAccCheckpointsNoDB.push_back(nCheckpoint); LogPrint("zero", "%s : Missing databased value for checksum %d\n", __func__, nChecksum); return false; } mapAccumulatorValues.insert(make_pair(nChecksum, bnValue)); } 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; } bool InitializeAccumulators(const int nHeight, int& nHeightCheckpoint, AccumulatorMap& mapAccumulators) { if (nHeight < Params().Zerocoin_StartHeight()) return error("%s: height is below zerocoin activated", __func__); //On a specific block, a recalculation of the accumulators will be forced if (nHeight == Params().Zerocoin_Block_RecalculateAccumulators() && Params().NetworkID() != CBaseChainParams::REGTEST) { mapAccumulators.Reset(); if (!mapAccumulators.Load(chainActive[Params().Zerocoin_Block_LastGoodCheckpoint()]->nAccumulatorCheckpoint)) return error("%s: failed to reset to previous checkpoint when recalculating accumulators", __func__); // Erase the checkpoints from the period of time that bad mints were being made if (!EraseCheckpoints(Params().Zerocoin_Block_LastGoodCheckpoint() + 1, nHeight)) return error("%s : failed to erase Checkpoints while recalculating checkpoints", __func__); nHeightCheckpoint = Params().Zerocoin_Block_LastGoodCheckpoint(); return true; } if (nHeight >= Params().Zerocoin_Block_V2_Start()) { //after v2_start, accumulators need to use v2 params mapAccumulators.Reset(Params().Zerocoin_Params(false)); // 20 after v2 start is when the new checkpoints will be in the block, so don't need to load hard checkpoints if (nHeight <= Params().Zerocoin_Block_V2_Start() + 20 && Params().NetworkID() != CBaseChainParams::REGTEST) { //Load hard coded checkpointed value AccumulatorCheckpoints::Checkpoint checkpoint = AccumulatorCheckpoints::GetClosestCheckpoint(nHeight, nHeightCheckpoint); if (nHeightCheckpoint < 0) return error("%s: failed to load hard-checkpoint for block %s", __func__, nHeight); mapAccumulators.Load(checkpoint); return true; } } //Use the previous block's checkpoint to initialize the accumulator's state uint256 nCheckpointPrev = chainActive[nHeight - 1]->nAccumulatorCheckpoint; if (nCheckpointPrev == 0) mapAccumulators.Reset(); else if (!mapAccumulators.Load(nCheckpointPrev)) return error("%s: failed to reset to previous checkpoint", __func__); nHeightCheckpoint = nHeight; return true; } //Get checkpoint value for a specific block height bool CalculateAccumulatorCheckpoint(int nHeight, uint256& nCheckpoint, AccumulatorMap& mapAccumulators) { if (nHeight < Params().Zerocoin_Block_V2_Start()) { nCheckpoint = 0; return true; } //the checkpoint is updated every ten blocks, return current active checkpoint if not update block if (nHeight % 10 != 0) { nCheckpoint = chainActive[nHeight - 1]->nAccumulatorCheckpoint; return true; } //set the accumulators to last checkpoint value int nHeightCheckpoint; mapAccumulators.Reset(); if (!InitializeAccumulators(nHeight, nHeightCheckpoint, mapAccumulators)) return error("%s: failed to initialize accumulators", __func__); //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[nHeightCheckpoint - 20]; while (pindex->nHeight < nHeight - 10) { // checking whether we should stop this process due to a shutdown request if (ShutdownRequested()) return false; //make sure this block is eligible for accumulation if (pindex->nHeight < Params().Zerocoin_StartHeight()) { pindex = chainActive[pindex->nHeight + 1]; continue; } //grab mints from this block CBlock block; if(!ReadBlockFromDisk(block, pindex)) return error("%s: failed to read block from disk", __func__); std::list listPubcoins; if (!BlockToPubcoinList(block, listPubcoins, fFilterInvalid)) return error("%s: failed to get zerocoin mintlist from block %d", __func__, pindex->nHeight); nTotalMintsFound += listPubcoins.size(); LogPrint("zero", "%s found %d mints\n", __func__, listPubcoins.size()); //add the pubcoins to accumulator for (const PublicCoin& pubcoin : listPubcoins) { if(!mapAccumulators.Accumulate(pubcoin, true)) return error("%s: failed to add pubcoin to accumulator at height %d", __func__, pindex->nHeight); } pindex = chainActive.Next(pindex); } // if there were no new mints found, the accumulator checkpoint will be the same as the last checkpoint if (nTotalMintsFound == 0) nCheckpoint = chainActive[nHeight - 1]->nAccumulatorCheckpoint; else nCheckpoint = mapAccumulators.GetCheckpoint(); LogPrint("zero", "%s checkpoint=%s\n", __func__, nCheckpoint.GetHex()); return true; } bool InvalidCheckpointRange(int nHeight) { return nHeight > Params().Zerocoin_Block_LastGoodCheckpoint() && nHeight < Params().Zerocoin_Block_RecalculateAccumulators(); } bool ValidateAccumulatorCheckpoint(const CBlock& block, CBlockIndex* pindex, AccumulatorMap& mapAccumulators) { //V1 accumulators are completely phased out by the time this code hits the public and begins generating new checkpoints //It is VERY IMPORTANT that when this is being run and height < v2_start, then zAGR need to be disabled at the same time!! if (pindex->nHeight < Params().Zerocoin_Block_V2_Start() || fVerifyingBlocks) return true; if (pindex->nHeight % 10 == 0) { uint256 nCheckpointCalculated = 0; if (!CalculateAccumulatorCheckpoint(pindex->nHeight, nCheckpointCalculated, mapAccumulators)) return error("%s : failed to calculate accumulator checkpoint", __func__); if (nCheckpointCalculated != block.nAccumulatorCheckpoint) { LogPrintf("%s: block=%d calculated: %s\n block: %s\n", __func__, pindex->nHeight, nCheckpointCalculated.GetHex(), block.nAccumulatorCheckpoint.GetHex()); return error("%s : accumulator does not match calculated value", __func__); } return true; } if (block.nAccumulatorCheckpoint != pindex->pprev->nAccumulatorCheckpoint) return error("%s : new accumulator checkpoint generated on a block that is not multiple of 10", __func__); return true; } //########################## Witness //Compute how many coins were added to an accumulator up to the end height int ComputeAccumulatedCoins(int nHeightEnd, libzerocoin::CoinDenomination denom) { CBlockIndex* pindex = chainActive[GetZerocoinStartHeight()]; int n = 0; while (pindex->nHeight < nHeightEnd) { n += count(pindex->vMintDenominationsInBlock.begin(), pindex->vMintDenominationsInBlock.end(), denom); pindex = chainActive.Next(pindex); } return n; } list GetPubcoinFromBlock(const CBlockIndex* pindex){ //grab mints from this block CBlock block; if(!ReadBlockFromDisk(block, pindex)) throw GetPubcoinException("GetPubcoinFromBlock: failed to read block from disk while adding pubcoins to witness"); list listPubcoins; if(!BlockToPubcoinList(block, listPubcoins, true)) throw GetPubcoinException("GetPubcoinFromBlock: failed to get zerocoin mintlist from block "+std::to_string(pindex->nHeight)+"\n"); return listPubcoins; } int AddBlockMintsToAccumulator(const CoinDenomination den, const CBloomFilter filter, const CBlockIndex* pindex, libzerocoin::Accumulator* accumulator, bool isWitness, list& notAddedCoins) { // if this block contains mints of the denomination that is being spent, then add them to the witness int nMintsAdded = 0; if (pindex->MintedDenomination(den)) { //add the mints to the witness for (const PublicCoin& pubcoin : GetPubcoinFromBlock(pindex)) { if (pubcoin.getDenomination() != den) { continue; } if (isWitness && filter.contains(pubcoin.getValue().getvch())) { notAddedCoins.emplace_back(pubcoin.getValue()); continue; } accumulator->increment(pubcoin.getValue()); ++nMintsAdded; } } return nMintsAdded; } int AddBlockMintsToAccumulator(const libzerocoin::PublicCoin& coin, const int nHeightMintAdded, const CBlockIndex* pindex, libzerocoin::Accumulator* accumulator, bool isWitness) { // if this block contains mints of the denomination that is being spent, then add them to the witness int nMintsAdded = 0; if (pindex->MintedDenomination(coin.getDenomination())) { //add the mints to the witness for (const PublicCoin& pubcoin : GetPubcoinFromBlock(pindex)) { if (pubcoin.getDenomination() != coin.getDenomination()) continue; if (isWitness && pindex->nHeight == nHeightMintAdded && pubcoin.getValue() == coin.getValue()) continue; accumulator->increment(pubcoin.getValue()); ++nMintsAdded; } } return nMintsAdded; } int AddBlockMintsToAccumulator(CoinWitnessData* coinWitness, const CBlockIndex* pindex, bool isWitness) { // TODO: This should be the witness.. return AddBlockMintsToAccumulator( *coinWitness->coin.get(), coinWitness->nHeightMintAdded, pindex, coinWitness->pAccumulator.get(), isWitness ); } bool GetAccumulatorValue(int& nHeight, const libzerocoin::CoinDenomination denom, CBigNum& bnAccValue) { if (nHeight > chainActive.Height()) return error("%s: height %d is more than active chain height", __func__, nHeight); //Every situation except for about 20 blocks should use this method uint256 nCheckpointBeforeMint = chainActive[nHeight]->nAccumulatorCheckpoint; if (nHeight > Params().Zerocoin_Block_V2_Start() + 20) { return GetAccumulatorValueFromDB(nCheckpointBeforeMint, denom, bnAccValue); } int nHeightCheckpoint = 0; AccumulatorCheckpoints::Checkpoint checkpoint = AccumulatorCheckpoints::GetClosestCheckpoint(nHeight, nHeightCheckpoint); if (nHeightCheckpoint < 0) { //Start at the first zerocoin libzerocoin::Accumulator accumulator(Params().Zerocoin_Params(false), denom); bnAccValue = accumulator.getValue(); nHeight = Params().Zerocoin_StartHeight() + 10; return true; } nHeight = nHeightCheckpoint; bnAccValue = checkpoint.at(denom); return true; } //############ Witness Generation /** * TODO: Why we are locking the wallet in this way? * @return */ bool LockMethod(){ int nLockAttempts = 0; while (nLockAttempts < 100) { TRY_LOCK(cs_main, lockMain); if(!lockMain) { MilliSleep(50); nLockAttempts++; continue; } break; } if (nLockAttempts == 100) return error("%s: could not get lock on cs_main", __func__); // locked return true; } int SearchMintHeightOf(CBigNum value){ uint256 txid; if (!zerocoinDB->ReadCoinMint(value, txid)) throw searchMintHeightException("searchForMintHeightOf:: failed to read mint from db"); CTransaction txMinted; uint256 hashBlock; if (!GetTransaction(txid, txMinted, hashBlock)) throw searchMintHeightException("searchForMintHeightOf:: failed to read tx"); int nHeightTest; if (!IsTransactionInChain(txid, nHeightTest)) throw searchMintHeightException("searchForMintHeightOf:: mint tx "+ txid.GetHex() +" is not in chain"); return mapBlockIndex[hashBlock]->nHeight; } void AccumulateRange(CoinWitnessData* coinWitness, int nHeightEnd) { bool fDoubleCounted = false; int64_t nTimeStart = GetTimeMicros(); int nHeightStart = std::max(coinWitness->nHeightAccStart, coinWitness->nHeightAccEnd + 1); CBlockIndex* pindex = chainActive[nHeightStart]; LogPrint("zero", "%s: start=%d end=%d\n", __func__, nHeightStart, nHeightEnd); while (pindex && pindex->nHeight <= nHeightEnd) { coinWitness->nMintsAdded += AddBlockMintsToAccumulator(coinWitness, pindex, true); coinWitness->nHeightAccEnd = pindex->nHeight; // 10 blocks were accumulated twice when zAGR v2 was activated if (pindex->nHeight == Params().Zerocoin_Block_Double_Accumulated() + 10 && !fDoubleCounted) { pindex = chainActive[Params().Zerocoin_Block_Double_Accumulated()]; fDoubleCounted = true; continue; } pindex = chainActive.Next(pindex); } int64_t nTimeEnd = GetTimeMicros(); LogPrint("bench", " - Range accumulation completed in %.2fms\n", 0.001 * (nTimeEnd - nTimeStart)); } bool GenerateAccumulatorWitness(CoinWitnessData* coinWitness, AccumulatorMap& mapAccumulators, CBlockIndex* pindexCheckpoint) { try { // Lock LogPrint("zero", "%s: generating\n", __func__); if (!LockMethod()) return false; LogPrint("zero", "%s: after lock\n", __func__); int64_t nTimeStart = GetTimeMicros(); //If there is a Acc End height filled in, then this has already been partially accumulated. if (!coinWitness->nHeightAccEnd) { LogPrintf("RESET ACC\n"); coinWitness->pAccumulator = std::unique_ptr(new Accumulator(Params().Zerocoin_Params(false), coinWitness->denom)); coinWitness->pWitness = std::unique_ptr(new AccumulatorWitness(Params().Zerocoin_Params(false), *coinWitness->pAccumulator, *coinWitness->coin)); } // Mint added height coinWitness->SetHeightMintAdded(SearchMintHeightOf(coinWitness->coin->getValue())); // Set the initial state of the witness accumulator for this coin. CBigNum bnAccValue = 0; if (!coinWitness->nHeightAccEnd && GetAccumulatorValue(coinWitness->nHeightCheckpoint, coinWitness->coin->getDenomination(), bnAccValue)) { libzerocoin::Accumulator witnessAccumulator(Params().Zerocoin_Params(false), coinWitness->denom, bnAccValue); coinWitness->pAccumulator->setValue(witnessAccumulator.getValue()); } //add the pubcoins from the blockchain up to the next checksum starting from the block int nChainHeight = chainActive.Height(); int nHeightMax = nChainHeight % 10; nHeightMax = nChainHeight - nHeightMax - 20; // at least two checkpoints deep // Determine the height to stop at int nHeightStop; if (pindexCheckpoint) { nHeightStop = pindexCheckpoint->nHeight - 10; nHeightStop -= nHeightStop % 10; LogPrint("zero", "%s: using checkpoint height %d\n", __func__, pindexCheckpoint->nHeight); } else { nHeightStop = nHeightMax; } if (nHeightStop > coinWitness->nHeightAccEnd) AccumulateRange(coinWitness, nHeightStop - 1); mapAccumulators.Load(chainActive[nHeightStop + 10]->nAccumulatorCheckpoint); coinWitness->pWitness->resetValue(*coinWitness->pAccumulator, *coinWitness->coin); if(!coinWitness->pWitness->VerifyWitness(mapAccumulators.GetAccumulator(coinWitness->denom), *coinWitness->coin)) return error("%s: failed to verify witness", __func__); // A certain amount of accumulated coins are required if (coinWitness->nMintsAdded < Params().Zerocoin_RequiredAccumulation()) return error("%s : Less than %d mints added, unable to create spend. %s", __func__, Params().Zerocoin_RequiredAccumulation(), coinWitness->ToString()); // calculate how many mints of this denomination existed in the accumulator we initialized coinWitness->nMintsAdded += ComputeAccumulatedCoins(coinWitness->nHeightAccStart, coinWitness->denom); LogPrint("zero", "%s : %d mints added to witness\n", __func__, coinWitness->nMintsAdded); int64_t nTime1 = GetTimeMicros(); LogPrint("bench", " - Witness generated in %.2fms\n", 0.001 * (nTime1 - nTimeStart)); return true; // TODO: I know that could merge all of this exception but maybe it's not really good.. think if we should have a different treatment for each one } catch (const searchMintHeightException& e) { return error("%s: searchMintHeightException: %s", __func__, e.message); } catch (const ChecksumInDbNotFoundException& e) { return error("%s: ChecksumInDbNotFoundException: %s", __func__, e.message); } catch (const GetPubcoinException& e) { return error("%s: GetPubcoinException: %s", __func__, e.message); } } bool calculateAccumulatedBlocksFor( int startHeight, int nHeightStop, CBlockIndex *pindex, CBigNum &bnAccValue, libzerocoin::Accumulator &accumulator, libzerocoin::CoinDenomination den, CBloomFilter filter, libzerocoin::Accumulator &witnessAccumulator, list& ret, string& strError ){ bool fDoubleCounted = false; int nMintsAdded = 0; while (pindex) { if (pindex->nHeight >= nHeightStop) { //If this height is within the invalid range (when fraudulent coins were being minted), then continue past this range if(InvalidCheckpointRange(pindex->nHeight)) continue; bnAccValue = 0; uint256 nCheckpointSpend = chainActive[pindex->nHeight + 10]->nAccumulatorCheckpoint; if (!GetAccumulatorValueFromDB(nCheckpointSpend, den, bnAccValue) || bnAccValue == 0) { throw new ChecksumInDbNotFoundException( "calculateAccumulatedBlocksFor : failed to find checksum in database for accumulator"); } accumulator.setValue(bnAccValue); break; } nMintsAdded += AddBlockMintsToAccumulator(den, filter, pindex, &witnessAccumulator, true, ret); // 10 blocks were accumulated twice when zAGR v2 was activated if (pindex->nHeight == 1050010 && !fDoubleCounted) { pindex = chainActive[1050000]; fDoubleCounted = true; continue; } pindex = chainActive.Next(pindex); } // A certain amount of accumulated coins are required if (nMintsAdded < Params().Zerocoin_RequiredAccumulation()) { strError = _(strprintf("Less than %d mints added, unable to create spend", Params().Zerocoin_RequiredAccumulation()).c_str()); throw NotEnoughMintsException(strError); } LogPrintf("calculateAccumulatedBlocksFor() : nMintsAdded %d",nMintsAdded); return true; } bool calculateAccumulatedBlocksFor( int startHeight, int nHeightStop, int nHeightMintAdded, CBlockIndex *pindex, int &nCheckpointsAdded, CBigNum &bnAccValue, libzerocoin::Accumulator &accumulator, libzerocoin::Accumulator &witnessAccumulator, libzerocoin::PublicCoin coin, string& strError ){ int amountOfScannedBlocks = 0; bool fDoubleCounted = false; int nMintsAdded = 0; while (pindex) { if (pindex->nHeight >= nHeightStop) { //If this height is within the invalid range (when fraudulent coins were being minted), then continue past this range if(InvalidCheckpointRange(pindex->nHeight)) continue; bnAccValue = 0; uint256 nCheckpointSpend = chainActive[pindex->nHeight + 10]->nAccumulatorCheckpoint; if (!GetAccumulatorValueFromDB(nCheckpointSpend, coin.getDenomination(), bnAccValue) || bnAccValue == 0) { throw new ChecksumInDbNotFoundException( "calculateAccumulatedBlocksFor : failed to find checksum in database for accumulator"); } accumulator.setValue(bnAccValue); break; } // Add it nMintsAdded += AddBlockMintsToAccumulator(coin, nHeightMintAdded, pindex, &witnessAccumulator, true); // 10 blocks were accumulated twice when zAGR v2 was activated if (pindex->nHeight == 1050010 && !fDoubleCounted) { pindex = chainActive[1050000]; fDoubleCounted = true; continue; } amountOfScannedBlocks++; pindex = chainActive.Next(pindex); } // A certain amount of accumulated coins are required if (nMintsAdded < Params().Zerocoin_RequiredAccumulation()) { strError = _(strprintf("Less than %d mints added, unable to create spend", Params().Zerocoin_RequiredAccumulation()).c_str()); throw NotEnoughMintsException(strError); } LogPrintf("calculateAccumulatedBlocksFor() : nMintsAdded %d",nMintsAdded); return true; } bool CalculateAccumulatorWitnessFor( const ZerocoinParams* params, int startHeight, int maxCalulationRange, CoinDenomination den, const CBloomFilter& filter, Accumulator& accumulator, AccumulatorWitness& witness, int& nMintsAdded, string& strError, list& ret, int &heightStop ){ // Lock if (!LockMethod()) return false; try { // Dummy coin init PublicCoin temp(params, 0, den); // Dummy Acc init Accumulator testingAcc(params, den); //get the checkpoint added at the next multiple of 10 int nHeightCheckpoint = startHeight + (10 - (startHeight % 10)); // Get the base accumulator // TODO: This needs to be changed to the partial witness calculation on the next version. CBigNum bnAccValue = 0; if (GetAccumulatorValue(nHeightCheckpoint, den, bnAccValue)) { accumulator.setValue(bnAccValue); witness.resetValue(accumulator, temp); } // Add the pubcoins from the blockchain up to the next checksum starting from the block CBlockIndex *pindex = chainActive[nHeightCheckpoint -10]; int nChainHeight = chainActive.Height(); int nHeightStop = nChainHeight % 10; nHeightStop = nChainHeight - nHeightStop - 20; // at least two checkpoints deep if (nHeightStop - startHeight > maxCalulationRange) { int stop = (startHeight + maxCalulationRange); int nHeightStop = stop % 10; nHeightStop = stop - nHeightStop - 20; } heightStop = nHeightStop; nMintsAdded = 0; // Starts on top of the witness that the node sent libzerocoin::Accumulator witnessAccumulator(params, den, witness.getValue()); if(!calculateAccumulatedBlocksFor( startHeight, nHeightStop, pindex, bnAccValue, accumulator, den, filter, witnessAccumulator, ret, strError )) return error("CalculateAccumulatorWitnessFor(): Calculate accumulated coins failed"); // reset the value witness.resetValue(witnessAccumulator, temp); // calculate how many mints of this denomination existed in the accumulator we initialized nMintsAdded += ComputeAccumulatedCoins(startHeight, den); LogPrint("zero", "%s : %d mints added to witness\n", __func__, nMintsAdded); return true; } catch (const ChecksumInDbNotFoundException& e) { return error("%s: ChecksumInDbNotFoundException: %s", __func__, e.message); } catch (const GetPubcoinException& e) { return error("%s: GetPubcoinException: %s", __func__, e.message); } } bool GenerateAccumulatorWitness( const PublicCoin &coin, Accumulator& accumulator, AccumulatorWitness& witness, int& nMintsAdded, string& strError, CBlockIndex* pindexCheckpoint) { try { // Lock LogPrint("zero", "%s: generating\n", __func__); if (!LockMethod()) return false; LogPrint("zero", "%s: after lock\n", __func__); int nHeightMintAdded = SearchMintHeightOf(coin.getValue()); //get the checkpoint added at the next multiple of 10 int nHeightCheckpoint = nHeightMintAdded + (10 - (nHeightMintAdded % 10)); //the height to start accumulating coins to add to witness int nAccStartHeight = nHeightMintAdded - (nHeightMintAdded % 10); //Get the accumulator that is right before the cluster of blocks containing our mint was added to the accumulator CBigNum bnAccValue = 0; if (GetAccumulatorValue(nHeightCheckpoint, coin.getDenomination(), bnAccValue)) { if(!bnAccValue && Params().NetworkID() == CBaseChainParams::REGTEST){ accumulator.setInitialValue(); witness.resetValue(accumulator, coin); }else { accumulator.setValue(bnAccValue); witness.resetValue(accumulator, coin); } } //add the pubcoins from the blockchain up to the next checksum starting from the block CBlockIndex *pindex = chainActive[nHeightCheckpoint - 10]; int nChainHeight = chainActive.Height(); int nHeightStop = nChainHeight % 10; nHeightStop = nChainHeight - nHeightStop - 20; // at least two checkpoints deep //If looking for a specific checkpoint if (pindexCheckpoint) nHeightStop = pindexCheckpoint->nHeight - 10; //Iterate through the chain and calculate the witness int nCheckpointsAdded = 0; nMintsAdded = 0; libzerocoin::Accumulator witnessAccumulator = accumulator; if(!calculateAccumulatedBlocksFor( nAccStartHeight, nHeightStop, nHeightMintAdded, pindex, nCheckpointsAdded, bnAccValue, accumulator, witnessAccumulator, coin, strError )){ return error("GenerateAccumulatorWitness(): Calculate accumulated coins failed"); } witness.resetValue(witnessAccumulator, coin); if (!witness.VerifyWitness(accumulator, coin)) return error("%s: failed to verify witness", __func__); // A certain amount of accumulated coins are required if (nMintsAdded < Params().Zerocoin_RequiredAccumulation()) { strError = _(strprintf("Less than %d mints added, unable to create spend", Params().Zerocoin_RequiredAccumulation()).c_str()); return error("%s : %s", __func__, strError); } // calculate how many mints of this denomination existed in the accumulator we initialized nMintsAdded += ComputeAccumulatedCoins(nAccStartHeight, coin.getDenomination()); LogPrint("zero", "%s : %d mints added to witness\n", __func__, nMintsAdded); return true; // TODO: I know that could merge all of this exception but maybe it's not really good.. think if we should have a different treatment for each one } catch (const searchMintHeightException& e) { return error("%s: searchMintHeightException: %s", __func__, e.message); } catch (const ChecksumInDbNotFoundException& e) { return error("%s: ChecksumInDbNotFoundException: %s", __func__, e.message); } catch (const GetPubcoinException& e) { return error("%s: GetPubcoinException: %s", __func__, e.message); } } map GetMintMaturityHeight() { map > mapDenomMaturity; for (auto denom : libzerocoin::zerocoinDenomList) mapDenomMaturity.insert(make_pair(denom, make_pair(0, 0))); int nConfirmedHeight = chainActive.Height() - Params().Zerocoin_MintRequiredConfirmations(); // A mint need to get to at least the min maturity height before it will spend. int nMinimumMaturityHeight = nConfirmedHeight - (nConfirmedHeight % 10); CBlockIndex* pindex = chainActive[nConfirmedHeight]; while (pindex && pindex->nHeight > Params().Zerocoin_StartHeight()) { bool isFinished = true; for (auto denom : libzerocoin::zerocoinDenomList) { //If the denom has not already had a mint added to it, then see if it has a mint added on this block if (mapDenomMaturity.at(denom).first < Params().Zerocoin_RequiredAccumulation()) { mapDenomMaturity.at(denom).first += count(pindex->vMintDenominationsInBlock.begin(), pindex->vMintDenominationsInBlock.end(), denom); //if mint was found then record this block as the first block that maturity occurs. if (mapDenomMaturity.at(denom).first >= Params().Zerocoin_RequiredAccumulation()) mapDenomMaturity.at(denom).second = std::min(pindex->nHeight, nMinimumMaturityHeight); //Signal that we are finished isFinished = false; } } if (isFinished) break; pindex = chainActive[pindex->nHeight - 1]; } //Generate final map map mapRet; for (auto denom : libzerocoin::zerocoinDenomList) mapRet.insert(make_pair(denom, mapDenomMaturity.at(denom).second)); return mapRet; }