Files
agrarian/src/zagr/accumulators.cpp
T
2026-04-28 16:50:31 +00:00

932 lines
35 KiB
C++

// 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<uint32_t, CBigNum> mapAccumulatorValues;
std::list<uint256> 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<uint32_t> 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<PublicCoin> 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<PublicCoin> 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<libzerocoin::PublicCoin> 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<CBigNum>& 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<Accumulator>(new Accumulator(Params().Zerocoin_Params(false), coinWitness->denom));
coinWitness->pWitness = std::unique_ptr<AccumulatorWitness>(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<CBigNum>& 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<CBigNum>& 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<CoinDenomination, int> GetMintMaturityHeight()
{
map<CoinDenomination, pair<int, int > > 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<CoinDenomination, int> mapRet;
for (auto denom : libzerocoin::zerocoinDenomList)
mapRet.insert(make_pair(denom, mapDenomMaturity.at(denom).second));
return mapRet;
}