537 lines
16 KiB
C++
537 lines
16 KiB
C++
// Copyright (c) 2018-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 <zagr/deterministicmint.h>
|
|
#include "zagrtracker.h"
|
|
#include "util.h"
|
|
#include "sync.h"
|
|
#include "main.h"
|
|
#include "txdb.h"
|
|
#include "wallet/walletdb.h"
|
|
#include "zagr/accumulators.h"
|
|
#include "zagr/zagrwallet.h"
|
|
#include "witness.h"
|
|
|
|
using namespace std;
|
|
|
|
CzAGRTracker::CzAGRTracker(std::string strWalletFile)
|
|
{
|
|
this->strWalletFile = strWalletFile;
|
|
mapSerialHashes.clear();
|
|
mapPendingSpends.clear();
|
|
fInitialized = false;
|
|
}
|
|
|
|
CzAGRTracker::~CzAGRTracker()
|
|
{
|
|
mapSerialHashes.clear();
|
|
mapPendingSpends.clear();
|
|
}
|
|
|
|
void CzAGRTracker::Init()
|
|
{
|
|
//Load all CZerocoinMints and CDeterministicMints from the database
|
|
if (!fInitialized) {
|
|
ListMints(false, false, true);
|
|
fInitialized = true;
|
|
}
|
|
}
|
|
|
|
bool CzAGRTracker::Archive(CMintMeta& meta)
|
|
{
|
|
if (mapSerialHashes.count(meta.hashSerial))
|
|
mapSerialHashes.at(meta.hashSerial).isArchived = true;
|
|
|
|
CWalletDB walletdb(strWalletFile);
|
|
CZerocoinMint mint;
|
|
if (walletdb.ReadZerocoinMint(meta.hashPubcoin, mint)) {
|
|
if (!CWalletDB(strWalletFile).ArchiveMintOrphan(mint))
|
|
return error("%s: failed to archive zerocoinmint", __func__);
|
|
} else {
|
|
//failed to read mint from DB, try reading deterministic
|
|
CDeterministicMint dMint;
|
|
if (!walletdb.ReadDeterministicMint(meta.hashPubcoin, dMint))
|
|
return error("%s: could not find pubcoinhash %s in db", __func__, meta.hashPubcoin.GetHex());
|
|
if (!walletdb.ArchiveDeterministicOrphan(dMint))
|
|
return error("%s: failed to archive deterministic ophaned mint", __func__);
|
|
}
|
|
|
|
LogPrintf("%s: archived pubcoinhash %s\n", __func__, meta.hashPubcoin.GetHex());
|
|
return true;
|
|
}
|
|
|
|
bool CzAGRTracker::UnArchive(const uint256& hashPubcoin, bool isDeterministic)
|
|
{
|
|
CWalletDB walletdb(strWalletFile);
|
|
if (isDeterministic) {
|
|
CDeterministicMint dMint;
|
|
if (!walletdb.UnarchiveDeterministicMint(hashPubcoin, dMint))
|
|
return error("%s: failed to unarchive deterministic mint", __func__);
|
|
Add(dMint, false);
|
|
} else {
|
|
CZerocoinMint mint;
|
|
if (!walletdb.UnarchiveZerocoinMint(hashPubcoin, mint))
|
|
return error("%s: failed to unarchivezerocoin mint", __func__);
|
|
Add(mint, false);
|
|
}
|
|
|
|
LogPrintf("%s: unarchived %s\n", __func__, hashPubcoin.GetHex());
|
|
return true;
|
|
}
|
|
|
|
CMintMeta CzAGRTracker::Get(const uint256 &hashSerial)
|
|
{
|
|
if (!mapSerialHashes.count(hashSerial))
|
|
return CMintMeta();
|
|
|
|
return mapSerialHashes.at(hashSerial);
|
|
}
|
|
|
|
CMintMeta CzAGRTracker::GetMetaFromPubcoin(const uint256& hashPubcoin)
|
|
{
|
|
for (auto it : mapSerialHashes) {
|
|
CMintMeta meta = it.second;
|
|
if (meta.hashPubcoin == hashPubcoin)
|
|
return meta;
|
|
}
|
|
|
|
return CMintMeta();
|
|
}
|
|
|
|
bool CzAGRTracker::GetMetaFromStakeHash(const uint256& hashStake, CMintMeta& meta) const
|
|
{
|
|
for (auto& it : mapSerialHashes) {
|
|
if (it.second.hashStake == hashStake) {
|
|
meta = it.second;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CoinWitnessData* CzAGRTracker::GetSpendCache(const uint256& hashStake)
|
|
{
|
|
AssertLockHeld(cs_spendcache);
|
|
if (!mapStakeCache.count(hashStake)) {
|
|
std::unique_ptr<CoinWitnessData> uptr(new CoinWitnessData());
|
|
mapStakeCache.insert(std::make_pair(hashStake, std::move(uptr)));
|
|
return mapStakeCache.at(hashStake).get();
|
|
}
|
|
|
|
return mapStakeCache.at(hashStake).get();
|
|
}
|
|
|
|
bool CzAGRTracker::ClearSpendCache()
|
|
{
|
|
AssertLockHeld(cs_spendcache);
|
|
if (!mapStakeCache.empty()) {
|
|
mapStakeCache.clear();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint256> CzAGRTracker::GetSerialHashes()
|
|
{
|
|
vector<uint256> vHashes;
|
|
for (auto it : mapSerialHashes) {
|
|
if (it.second.isArchived)
|
|
continue;
|
|
|
|
vHashes.emplace_back(it.first);
|
|
}
|
|
|
|
|
|
return vHashes;
|
|
}
|
|
|
|
CAmount CzAGRTracker::GetBalance(bool fConfirmedOnly, bool fUnconfirmedOnly) const
|
|
{
|
|
CAmount nTotal = 0;
|
|
//! zerocoin specific fields
|
|
std::map<libzerocoin::CoinDenomination, unsigned int> myZerocoinSupply;
|
|
for (auto& denom : libzerocoin::zerocoinDenomList) {
|
|
myZerocoinSupply.insert(make_pair(denom, 0));
|
|
}
|
|
|
|
{
|
|
//LOCK(cs_agrtracker);
|
|
// Get Unused coins
|
|
for (auto& it : mapSerialHashes) {
|
|
CMintMeta meta = it.second;
|
|
if (meta.isUsed || meta.isArchived)
|
|
continue;
|
|
bool fConfirmed = ((meta.nHeight < chainActive.Height() - Params().Zerocoin_MintRequiredConfirmations()) && !(meta.nHeight == 0));
|
|
if (fConfirmedOnly && !fConfirmed)
|
|
continue;
|
|
if (fUnconfirmedOnly && fConfirmed)
|
|
continue;
|
|
|
|
nTotal += libzerocoin::ZerocoinDenominationToAmount(meta.denom);
|
|
myZerocoinSupply.at(meta.denom)++;
|
|
}
|
|
}
|
|
|
|
if (nTotal < 0 ) nTotal = 0; // Sanity never hurts
|
|
|
|
return nTotal;
|
|
}
|
|
|
|
CAmount CzAGRTracker::GetUnconfirmedBalance() const
|
|
{
|
|
return GetBalance(false, true);
|
|
}
|
|
|
|
std::vector<CMintMeta> CzAGRTracker::GetMints(bool fConfirmedOnly) const
|
|
{
|
|
vector<CMintMeta> vMints;
|
|
for (auto& it : mapSerialHashes) {
|
|
CMintMeta mint = it.second;
|
|
if (mint.isArchived || mint.isUsed)
|
|
continue;
|
|
bool fConfirmed = (mint.nHeight < chainActive.Height() - Params().Zerocoin_MintRequiredConfirmations());
|
|
if (fConfirmedOnly && !fConfirmed)
|
|
continue;
|
|
vMints.emplace_back(mint);
|
|
}
|
|
return vMints;
|
|
}
|
|
|
|
//Does a mint in the tracker have this txid
|
|
bool CzAGRTracker::HasMintTx(const uint256& txid)
|
|
{
|
|
for (auto it : mapSerialHashes) {
|
|
if (it.second.txid == txid)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CzAGRTracker::HasPubcoin(const CBigNum &bnValue) const
|
|
{
|
|
// Check if this mint's pubcoin value belongs to our mapSerialHashes (which includes hashpubcoin values)
|
|
uint256 hash = GetPubCoinHash(bnValue);
|
|
return HasPubcoinHash(hash);
|
|
}
|
|
|
|
bool CzAGRTracker::HasPubcoinHash(const uint256& hashPubcoin) const
|
|
{
|
|
for (auto it : mapSerialHashes) {
|
|
CMintMeta meta = it.second;
|
|
if (meta.hashPubcoin == hashPubcoin)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CzAGRTracker::HasSerial(const CBigNum& bnSerial) const
|
|
{
|
|
uint256 hash = GetSerialHash(bnSerial);
|
|
return HasSerialHash(hash);
|
|
}
|
|
|
|
bool CzAGRTracker::HasSerialHash(const uint256& hashSerial) const
|
|
{
|
|
auto it = mapSerialHashes.find(hashSerial);
|
|
return it != mapSerialHashes.end();
|
|
}
|
|
|
|
bool CzAGRTracker::UpdateZerocoinMint(const CZerocoinMint& mint)
|
|
{
|
|
if (!HasSerial(mint.GetSerialNumber()))
|
|
return error("%s: mint %s is not known", __func__, mint.GetValue().GetHex());
|
|
|
|
uint256 hashSerial = GetSerialHash(mint.GetSerialNumber());
|
|
|
|
//Update the meta object
|
|
CMintMeta meta = Get(hashSerial);
|
|
meta.isUsed = mint.IsUsed();
|
|
meta.denom = mint.GetDenomination();
|
|
meta.nHeight = mint.GetHeight();
|
|
mapSerialHashes.at(hashSerial) = meta;
|
|
|
|
//Write to db
|
|
return CWalletDB(strWalletFile).WriteZerocoinMint(mint);
|
|
}
|
|
|
|
bool CzAGRTracker::UpdateState(const CMintMeta& meta)
|
|
{
|
|
CWalletDB walletdb(strWalletFile);
|
|
|
|
if (meta.isDeterministic) {
|
|
CDeterministicMint dMint;
|
|
if (!walletdb.ReadDeterministicMint(meta.hashPubcoin, dMint)) {
|
|
// Check archive just in case
|
|
if (!meta.isArchived)
|
|
return error("%s: failed to read deterministic mint from database", __func__);
|
|
|
|
// Unarchive this mint since it is being requested and updated
|
|
if (!walletdb.UnarchiveDeterministicMint(meta.hashPubcoin, dMint))
|
|
return error("%s: failed to unarchive deterministic mint from database", __func__);
|
|
}
|
|
|
|
dMint.SetTxHash(meta.txid);
|
|
dMint.SetHeight(meta.nHeight);
|
|
dMint.SetUsed(meta.isUsed);
|
|
dMint.SetDenomination(meta.denom);
|
|
dMint.SetStakeHash(meta.hashStake);
|
|
|
|
if (!walletdb.WriteDeterministicMint(dMint))
|
|
return error("%s: failed to update deterministic mint when writing to db", __func__);
|
|
} else {
|
|
CZerocoinMint mint;
|
|
if (!walletdb.ReadZerocoinMint(meta.hashPubcoin, mint))
|
|
return error("%s: failed to read mint from database", __func__);
|
|
|
|
mint.SetTxHash(meta.txid);
|
|
mint.SetHeight(meta.nHeight);
|
|
mint.SetUsed(meta.isUsed);
|
|
mint.SetDenomination(meta.denom);
|
|
|
|
if (!walletdb.WriteZerocoinMint(mint))
|
|
return error("%s: failed to write mint to database", __func__);
|
|
}
|
|
|
|
mapSerialHashes[meta.hashSerial] = meta;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CzAGRTracker::Add(const CDeterministicMint& dMint, bool isNew, bool isArchived, CzAGRWallet* zAGRWallet)
|
|
{
|
|
bool iszAGRWalletInitialized = (NULL != zAGRWallet);
|
|
CMintMeta meta;
|
|
meta.hashPubcoin = dMint.GetPubcoinHash();
|
|
meta.nHeight = dMint.GetHeight();
|
|
meta.nVersion = dMint.GetVersion();
|
|
meta.txid = dMint.GetTxHash();
|
|
meta.isUsed = dMint.IsUsed();
|
|
meta.hashSerial = dMint.GetSerialHash();
|
|
meta.hashStake = dMint.GetStakeHash();
|
|
meta.denom = dMint.GetDenomination();
|
|
meta.isArchived = isArchived;
|
|
meta.isDeterministic = true;
|
|
if (! iszAGRWalletInitialized)
|
|
zAGRWallet = new CzAGRWallet(strWalletFile);
|
|
meta.isSeedCorrect = zAGRWallet->CheckSeed(dMint);
|
|
if (! iszAGRWalletInitialized)
|
|
delete zAGRWallet;
|
|
mapSerialHashes[meta.hashSerial] = meta;
|
|
|
|
if (isNew)
|
|
CWalletDB(strWalletFile).WriteDeterministicMint(dMint);
|
|
}
|
|
|
|
void CzAGRTracker::Add(const CZerocoinMint& mint, bool isNew, bool isArchived)
|
|
{
|
|
CMintMeta meta;
|
|
meta.hashPubcoin = GetPubCoinHash(mint.GetValue());
|
|
meta.nHeight = mint.GetHeight();
|
|
meta.nVersion = libzerocoin::ExtractVersionFromSerial(mint.GetSerialNumber());
|
|
meta.txid = mint.GetTxHash();
|
|
meta.isUsed = mint.IsUsed();
|
|
meta.hashSerial = GetSerialHash(mint.GetSerialNumber());
|
|
uint256 nSerial = mint.GetSerialNumber().getuint256();
|
|
meta.hashStake = Hash(nSerial.begin(), nSerial.end());
|
|
meta.denom = mint.GetDenomination();
|
|
meta.isArchived = isArchived;
|
|
meta.isDeterministic = false;
|
|
meta.isSeedCorrect = true;
|
|
mapSerialHashes[meta.hashSerial] = meta;
|
|
|
|
if (isNew)
|
|
CWalletDB(strWalletFile).WriteZerocoinMint(mint);
|
|
}
|
|
|
|
void CzAGRTracker::SetPubcoinUsed(const uint256& hashPubcoin, const uint256& txid)
|
|
{
|
|
if (!HasPubcoinHash(hashPubcoin))
|
|
return;
|
|
CMintMeta meta = GetMetaFromPubcoin(hashPubcoin);
|
|
meta.isUsed = true;
|
|
mapPendingSpends.insert(make_pair(meta.hashSerial, txid));
|
|
UpdateState(meta);
|
|
}
|
|
|
|
void CzAGRTracker::SetPubcoinNotUsed(const uint256& hashPubcoin)
|
|
{
|
|
if (!HasPubcoinHash(hashPubcoin))
|
|
return;
|
|
CMintMeta meta = GetMetaFromPubcoin(hashPubcoin);
|
|
meta.isUsed = false;
|
|
|
|
if (mapPendingSpends.count(meta.hashSerial))
|
|
mapPendingSpends.erase(meta.hashSerial);
|
|
|
|
UpdateState(meta);
|
|
}
|
|
|
|
void CzAGRTracker::RemovePending(const uint256& txid)
|
|
{
|
|
uint256 hashSerial;
|
|
for (auto it : mapPendingSpends) {
|
|
if (it.second == txid) {
|
|
hashSerial = it.first;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hashSerial > 0)
|
|
mapPendingSpends.erase(hashSerial);
|
|
}
|
|
|
|
bool CzAGRTracker::UpdateStatusInternal(const std::set<uint256>& setMempool, CMintMeta& mint)
|
|
{
|
|
//! Check whether this mint has been spent and is considered 'pending' or 'confirmed'
|
|
// If there is not a record of the block height, then look it up and assign it
|
|
uint256 txidMint;
|
|
bool isMintInChain = zerocoinDB->ReadCoinMint(mint.hashPubcoin, txidMint);
|
|
|
|
//See if there is internal record of spending this mint (note this is memory only, would reset on restart)
|
|
bool isPendingSpend = static_cast<bool>(mapPendingSpends.count(mint.hashSerial));
|
|
|
|
// See if there is a blockchain record of spending this mint
|
|
uint256 txidSpend;
|
|
bool isConfirmedSpend = zerocoinDB->ReadCoinSpend(mint.hashSerial, txidSpend);
|
|
|
|
// Double check the mempool for pending spend
|
|
if (isPendingSpend) {
|
|
uint256 txidPendingSpend = mapPendingSpends.at(mint.hashSerial);
|
|
if (!setMempool.count(txidPendingSpend) || isConfirmedSpend) {
|
|
RemovePending(txidPendingSpend);
|
|
isPendingSpend = false;
|
|
LogPrintf("%s : Pending txid %s removed because not in mempool\n", __func__, txidPendingSpend.GetHex());
|
|
}
|
|
}
|
|
|
|
bool isUsed = isPendingSpend || isConfirmedSpend;
|
|
|
|
if (!mint.nHeight || !isMintInChain || isUsed != mint.isUsed) {
|
|
CTransaction tx;
|
|
uint256 hashBlock;
|
|
|
|
// Txid will be marked 0 if there is no knowledge of the final tx hash yet
|
|
if (mint.txid == 0) {
|
|
if (!isMintInChain) {
|
|
LogPrintf("%s : Failed to find mint in zerocoinDB %s\n", __func__, mint.hashPubcoin.GetHex().substr(0, 6));
|
|
mint.isArchived = true;
|
|
Archive(mint);
|
|
return true;
|
|
}
|
|
mint.txid = txidMint;
|
|
}
|
|
|
|
if (setMempool.count(mint.txid))
|
|
return true;
|
|
|
|
// Check the transaction associated with this mint
|
|
if (!IsInitialBlockDownload() && !GetTransaction(mint.txid, tx, hashBlock, true)) {
|
|
LogPrintf("%s : Failed to find tx for mint txid=%s\n", __func__, mint.txid.GetHex());
|
|
mint.isArchived = true;
|
|
Archive(mint);
|
|
return true;
|
|
}
|
|
|
|
// An orphan tx if hashblock is in mapBlockIndex but not in chain active
|
|
if (mapBlockIndex.count(hashBlock) && !chainActive.Contains(mapBlockIndex.at(hashBlock))) {
|
|
LogPrintf("%s : Found orphaned mint txid=%s\n", __func__, mint.txid.GetHex());
|
|
mint.isUsed = false;
|
|
mint.nHeight = 0;
|
|
if (tx.IsCoinStake()) {
|
|
mint.isArchived = true;
|
|
Archive(mint);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check that the mint has correct used status
|
|
if (mint.isUsed != isUsed) {
|
|
LogPrintf("%s : Set mint %s isUsed to %d\n", __func__, mint.hashPubcoin.GetHex(), isUsed);
|
|
mint.isUsed = isUsed;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::set<CMintMeta> CzAGRTracker::ListMints(bool fUnusedOnly, bool fMatureOnly, bool fUpdateStatus, bool fWrongSeed, bool fExcludeV1)
|
|
{
|
|
CWalletDB walletdb(strWalletFile);
|
|
if (fUpdateStatus) {
|
|
std::list<CZerocoinMint> listMintsDB = walletdb.ListMintedCoins();
|
|
for (auto& mint : listMintsDB)
|
|
Add(mint);
|
|
LogPrint("zero", "%s: added %d zerocoinmints from DB\n", __func__, listMintsDB.size());
|
|
|
|
std::list<CDeterministicMint> listDeterministicDB = walletdb.ListDeterministicMints();
|
|
|
|
CzAGRWallet* zAGRWallet = new CzAGRWallet(strWalletFile);
|
|
for (auto& dMint : listDeterministicDB) {
|
|
if (fExcludeV1 && dMint.GetVersion() < 2)
|
|
continue;
|
|
Add(dMint, false, false, zAGRWallet);
|
|
}
|
|
delete zAGRWallet;
|
|
LogPrint("zero", "%s: added %d dzagr from DB\n", __func__, listDeterministicDB.size());
|
|
}
|
|
|
|
std::vector<CMintMeta> vOverWrite;
|
|
std::set<CMintMeta> setMints;
|
|
std::set<uint256> setMempool;
|
|
{
|
|
LOCK(mempool.cs);
|
|
mempool.getTransactions(setMempool);
|
|
}
|
|
|
|
std::map<libzerocoin::CoinDenomination, int> mapMaturity = GetMintMaturityHeight();
|
|
for (auto& it : mapSerialHashes) {
|
|
CMintMeta mint = it.second;
|
|
|
|
//This is only intended for unarchived coins
|
|
if (mint.isArchived)
|
|
continue;
|
|
|
|
// Update the metadata of the mints if requested
|
|
if (fUpdateStatus && UpdateStatusInternal(setMempool, mint)) {
|
|
if (mint.isArchived)
|
|
continue;
|
|
|
|
// Mint was updated, queue for overwrite
|
|
vOverWrite.emplace_back(mint);
|
|
}
|
|
|
|
if (fUnusedOnly && mint.isUsed)
|
|
continue;
|
|
|
|
if (fMatureOnly) {
|
|
// Not confirmed
|
|
if (!mint.nHeight || mint.nHeight > chainActive.Height() - Params().Zerocoin_MintRequiredConfirmations())
|
|
continue;
|
|
if (mint.nHeight >= mapMaturity.at(mint.denom))
|
|
continue;
|
|
}
|
|
|
|
if (!fWrongSeed && !mint.isSeedCorrect)
|
|
continue;
|
|
|
|
setMints.insert(mint);
|
|
}
|
|
|
|
//overwrite any updates
|
|
for (CMintMeta& meta : vOverWrite)
|
|
UpdateState(meta);
|
|
|
|
return setMints;
|
|
}
|
|
|
|
void CzAGRTracker::Clear()
|
|
{
|
|
mapSerialHashes.clear();
|
|
}
|