////////////////////////////////////////////////////////////////////////////////
// //
// Copyright (C) 2011-2015, Armory Technologies, Inc. //
// Distributed under the GNU Affero General Public License (AGPL v3) //
// See LICENSE or http://www.gnu.org/licenses/agpl.html //
// //
////////////////////////////////////////////////////////////////////////////////
#include "BlockDataViewer.h"
/////////////////////////////////////////////////////////////////////////////
BlockDataViewer::BlockDataViewer(BlockDataManager_LevelDB* bdm) :
rescanZC_(false), zeroConfCont_(bdm->getIFace())
{
db_ = bdm->getIFace();
bc_ = &bdm->blockchain();
saf_ = bdm->getScrAddrFilter();
bdmPtr_ = bdm;
zcEnabled_ = false;
zcLiteMode_ = false;
groups_.push_back(WalletGroup(this, saf_));
groups_.push_back(WalletGroup(this, saf_));
flagRescanZC(false);
}
/////////////////////////////////////////////////////////////////////////////
BlockDataViewer::~BlockDataViewer()
{
groups_.clear();
}
/////////////////////////////////////////////////////////////////////////////
BtcWallet* BlockDataViewer::registerWallet(
vector const& scrAddrVec, string IDstr, bool wltIsNew)
{
if (IDstr.empty())
return nullptr;
return groups_[group_wallet].registerWallet(scrAddrVec, IDstr, wltIsNew);
}
/////////////////////////////////////////////////////////////////////////////
BtcWallet* BlockDataViewer::registerLockbox(
vector const & scrAddrVec, string IDstr, bool wltIsNew)
{
if (IDstr.empty())
return nullptr;
return groups_[group_lockbox].registerWallet(scrAddrVec, IDstr, wltIsNew);
}
/////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::unregisterWallet(const string& IDstr)
{
groups_[group_wallet].unregisterWallet(IDstr);
}
/////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::unregisterLockbox(const string& IDstr)
{
groups_[group_lockbox].unregisterWallet(IDstr);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::scanWallets(uint32_t startBlock,
uint32_t endBlock, BDV_refresh forceRefresh)
{
if (startBlock == UINT32_MAX)
startBlock = lastScanned_;
if (endBlock == UINT32_MAX)
endBlock = getTopBlockHeight() + 1;
for (auto& group : groups_)
group.merge();
vector startBlocks;
for (auto& group : groups_)
startBlocks.push_back(startBlock);
auto sbIter = startBlocks.begin();
if (!initialized_)
{
//out of date history, page all wallets' history
for (auto& group : groups_)
{
*sbIter = group.pageHistory();
sbIter++;
}
initialized_ = true;
}
map > invalidatedZCKeys;
if (startBlock != endBlock)
{
invalidatedZCKeys = zeroConfCont_.purge(
[this](const BinaryData& sa)->bool
{ return saf_->hasScrAddress(sa); });
}
const bool reorg = (lastScanned_ > startBlock);
sbIter = startBlocks.begin();
for (auto& group : groups_)
{
group.scanWallets(*sbIter, endBlock,
reorg, invalidatedZCKeys);
group.updateGlobalLedgerFirstPage(*sbIter, endBlock,
forceRefresh);
sbIter++;
}
zeroConfCont_.resetNewZC();
lastScanned_ = endBlock;
}
////////////////////////////////////////////////////////////////////////////////
bool BlockDataViewer::hasWallet(const BinaryData& ID) const
{
return groups_[group_wallet].hasID(ID);
}
/////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::pprintRegisteredWallets(void) const
{
groups_[group_wallet].pprintRegisteredWallets();
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::addNewZeroConfTx(BinaryData const & rawTx,
uint32_t txtime,
bool writeToFile)
{
if (!zcEnabled_)
return;
SCOPED_TIMER("addNewZeroConfTx");
if (txtime == 0)
txtime = (uint32_t)time(nullptr);
zeroConfCont_.addRawTx(rawTx, txtime);
flagRescanZC(true);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::enableZeroConf(bool clearMempool)
{
SCOPED_TIMER("enableZeroConf");
LOGINFO << "Enabling zero-conf tracking ";
zcEnabled_ = true;
//zcLiteMode_ = zcLite;
auto zcFilter = [this](const BinaryData& scrAddr)->bool
{ return this->bdmPtr_->getScrAddrFilter()->hasScrAddress(scrAddr); };
zeroConfCont_.loadZeroConfMempool(zcFilter, clearMempool);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::disableZeroConf(void)
{
SCOPED_TIMER("disableZeroConf");
zcEnabled_ = false;
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::purgeZeroConfPool()
{
const map > invalidatedTxIOKeys
= zeroConfCont_.purge(
[this](const BinaryData& sa)->bool { return saf_->hasScrAddress(sa); });
for (auto& group : groups_)
group.purgeZeroConfPool(invalidatedTxIOKeys);
}
////////////////////////////////////////////////////////////////////////////////
bool BlockDataViewer::parseNewZeroConfTx()
{
return zeroConfCont_.parseNewZC(
[this](const BinaryData& sa)->bool { return saf_->hasScrAddress(sa); });
}
////////////////////////////////////////////////////////////////////////////////
bool BlockDataViewer::registerAddresses(const vector& saVec,
BinaryData walletID, bool areNew)
{
if (saVec.empty())
return false;
for (auto& group : groups_)
{
if (group.hasID(walletID))
return group.registerAddresses(saVec, walletID, areNew);
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::registerAddressBatch(
const map < BinaryData, vector>& wltNAddrMap,
bool areNew)
{
//if called from python, feed it a dict such as:
//{wltID1:[addrList1], wltID2:[addrList2]}
map, vector> wlt_addr;
for (auto& batch : wltNAddrMap)
{
for (auto& group : groups_)
{
auto wlt = group.getWalletByID(batch.first);
if (wlt != nullptr)
{
wlt_addr.insert(make_pair(wlt, batch.second));
break;
}
}
}
saf_->registerAddressBatch(wlt_addr, areNew);
}
////////////////////////////////////////////////////////////////////////////////
const LedgerEntry& BlockDataViewer::getTxLedgerByHash_FromWallets(
const BinaryData& txHash) const
{
checkBDMisReady();
return groups_[group_wallet].getTxLedgerByHash(txHash);
}
////////////////////////////////////////////////////////////////////////////////
const LedgerEntry& BlockDataViewer::getTxLedgerByHash_FromLockboxes(
const BinaryData& txHash) const
{
checkBDMisReady();
return groups_[group_lockbox].getTxLedgerByHash(txHash);
}
/////////////////////////////////////////////////////////////////////////////
TX_AVAILABILITY BlockDataViewer::getTxHashAvail(BinaryDataRef txHash) const
{
checkBDMisReady();
if (db_->getTxRef(txHash).isNull())
{
if (!zeroConfCont_.hasTxByHash(txHash))
return TX_DNE; // No tx at all
else
return TX_ZEROCONF; // Zero-conf tx
}
else
return TX_IN_BLOCKCHAIN; // In the blockchain already
}
/////////////////////////////////////////////////////////////////////////////
Tx BlockDataViewer::getTxByHash(HashString const & txhash) const
{
checkBDMisReady();
if (config().armoryDbType == ARMORY_DB_SUPER)
{
LMDBEnv::Transaction tx(db_->dbEnv_[BLKDATA].get(), LMDB::ReadOnly);
TxRef txrefobj = db_->getTxRef(txhash);
if (!txrefobj.isNull())
return txrefobj.attached(db_).getTxCopy();
else
{
// It's not in the blockchain, but maybe in the zero-conf tx list
return zeroConfCont_.getTxByHash(txhash);
}
}
else
{
StoredTx stx;
if (db_->getStoredTx_byHash(txhash, &stx))
return stx.getTxCopy();
else
return zeroConfCont_.getTxByHash(txhash);
}
}
bool BlockDataViewer::isTxMainBranch(const Tx &tx) const
{
checkBDMisReady();
if (!tx.hasTxRef())
return false;
return tx.getTxRef().attached(db_).isMainBranch();
}
////////////////////////////////////////////////////////////////////////////////
TxOut BlockDataViewer::getPrevTxOut(TxIn & txin) const
{
checkBDMisReady();
if (txin.isCoinbase())
return TxOut();
OutPoint op = txin.getOutPoint();
Tx theTx = getTxByHash(op.getTxHash());
if (!theTx.isInitialized())
throw runtime_error("couldn't find prev tx");
uint32_t idx = op.getTxOutIndex();
return theTx.getTxOutCopy(idx);
}
////////////////////////////////////////////////////////////////////////////////
Tx BlockDataViewer::getPrevTx(TxIn & txin) const
{
checkBDMisReady();
if (txin.isCoinbase())
return Tx();
OutPoint op = txin.getOutPoint();
return getTxByHash(op.getTxHash());
}
////////////////////////////////////////////////////////////////////////////////
HashString BlockDataViewer::getSenderScrAddr(TxIn & txin) const
{
checkBDMisReady();
if (txin.isCoinbase())
return HashString(0);
return getPrevTxOut(txin).getScrAddressStr();
}
////////////////////////////////////////////////////////////////////////////////
int64_t BlockDataViewer::getSentValue(TxIn & txin) const
{
checkBDMisReady();
if (txin.isCoinbase())
return -1;
return getPrevTxOut(txin).getValue();
}
////////////////////////////////////////////////////////////////////////////////
LMDBBlockDatabase* BlockDataViewer::getDB(void) const
{
return db_;
}
////////////////////////////////////////////////////////////////////////////////
uint32_t BlockDataViewer::getTopBlockHeight(void) const
{
checkBDMisReady();
return bc_->top().getBlockHeight();
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::reset()
{
for (auto& group : groups_)
group.reset();
rescanZC_ = false;
zcEnabled_ = false;
zcLiteMode_ = false;
zeroConfCont_.clear();
lastScanned_ = 0;
initialized_ = false;
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::scanScrAddrVector(
const map& scrAddrMap,
uint32_t startBlock, uint32_t endBlock) const
{
//create new ScrAddrFilter for the occasion
shared_ptr saf(saf_->copy());
//register scrAddr with it
for (auto& scrAddrPair : scrAddrMap)
saf->regScrAddrForScan(scrAddrPair.first, startBlock);
//scan addresses
saf->applyBlockRangeToDB(startBlock, endBlock, vector());
}
////////////////////////////////////////////////////////////////////////////////
size_t BlockDataViewer::getWalletsPageCount(void) const
{
checkBDMisReady();
return groups_[group_wallet].getPageCount();
}
////////////////////////////////////////////////////////////////////////////////
vector BlockDataViewer::getWalletsHistoryPage(uint32_t pageId,
bool rebuildLedger, bool remapWallets)
{
checkBDMisReady();
return groups_[group_wallet].getHistoryPage(pageId,
rebuildLedger, remapWallets);
}
////////////////////////////////////////////////////////////////////////////////
size_t BlockDataViewer::getLockboxesPageCount(void) const
{
checkBDMisReady();
return groups_[group_lockbox].getPageCount();
}
////////////////////////////////////////////////////////////////////////////////
vector BlockDataViewer::getLockboxesHistoryPage(uint32_t pageId,
bool rebuildLedger, bool remapWallets)
{
checkBDMisReady();
return groups_[group_lockbox].getHistoryPage(pageId,
rebuildLedger, remapWallets);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::updateWalletsLedgerFilter(
const vector& walletsList)
{
groups_[group_wallet].updateLedgerFilter(walletsList);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::updateLockboxesLedgerFilter(
const vector& walletsList)
{
groups_[group_lockbox].updateLedgerFilter(walletsList);
}
////////////////////////////////////////////////////////////////////////////////
void BlockDataViewer::flagRefresh(BDV_refresh refresh, const BinaryData& refreshID)
{
if (saf_->bdmIsRunning() == false)
return;
unique_lock lock(refreshLock_);
if (refresh_ != BDV_refreshAndRescan)
{
if (refresh == BDV_refreshAndRescan)
refresh_ = BDV_refreshAndRescan;
else
refresh_ = BDV_refreshSkipRescan;
}
if (refreshID.getSize())
refreshIDSet_.insert(refreshID);
if (refresh == BDV_filterChanged)
refreshIDSet_.insert(BinaryData("wallet_filter_changed"));
notifyMainThread();
}
////////////////////////////////////////////////////////////////////////////////
StoredHeader BlockDataViewer::getMainBlockFromDB(uint32_t height) const
{
checkBDMisReady();
uint8_t dupID = db_->getValidDupIDForHeight(height);
return getBlockFromDB(height, dupID);
}
////////////////////////////////////////////////////////////////////////////////
StoredHeader BlockDataViewer::getBlockFromDB(uint32_t height, uint8_t dupID) const
{
StoredHeader sbh;
db_->getStoredHeader(sbh, height, dupID, true);
return sbh;
}
////////////////////////////////////////////////////////////////////////////////
bool BlockDataViewer::scrAddressIsRegistered(const BinaryData& scrAddr) const
{
return saf_->hasScrAddress(scrAddr);
}
////////////////////////////////////////////////////////////////////////////////
BlockHeader BlockDataViewer::getHeaderByHash(const BinaryData& blockHash) const
{
checkBDMisReady();
return bc_->getHeaderByHash(blockHash);
}
////////////////////////////////////////////////////////////////////////////////
vector BlockDataViewer::getUnspentTxoutsForAddr160List(
const vector& scrAddrVec, bool ignoreZc) const
{
checkBDMisReady();
ScrAddrFilter* saf = bdmPtr_->getScrAddrFilter();
if (bdmPtr_->config().armoryDbType != ARMORY_DB_SUPER)
{
for (const auto& scrAddr : scrAddrVec)
{
if (!saf->hasScrAddress(scrAddr))
throw std::range_error("Don't have this scrAddr tracked");
}
}
vector UTXOs;
for (const auto& scrAddr : scrAddrVec)
{
const auto& zcTxioMap = zeroConfCont_.getZCforScrAddr(scrAddr);
StoredScriptHistory ssh;
db_->getStoredScriptHistory(ssh, scrAddr);
map scrAddrUtxoMap;
db_->getFullUTXOMapForSSH(ssh, scrAddrUtxoMap);
for (const auto& utxoPair : scrAddrUtxoMap)
{
auto zcIter = zcTxioMap.find(utxoPair.first);
if (zcIter != zcTxioMap.end())
if (zcIter->second.hasTxInZC())
continue;
UTXOs.push_back(utxoPair.second);
}
if (ignoreZc)
continue;
for (const auto& zcTxio : zcTxioMap)
{
if (!zcTxio.second.hasTxOutZC())
continue;
if (zcTxio.second.hasTxInZC())
continue;
TxOut txout = zcTxio.second.getTxOutCopy(db_);
UnspentTxOut UTXO = UnspentTxOut(db_, txout, UINT32_MAX);
UTXOs.push_back(UTXO);
}
}
return UTXOs;
}
////////////////////////////////////////////////////////////////////////////////
WalletGroup BlockDataViewer::getStandAloneWalletGroup(
const vector& wltIDs, HistoryOrdering order)
{
checkBDMisReady();
WalletGroup wg(this, this->saf_);
wg.order_ = order;
auto wallets = groups_[group_wallet].getWalletMap();
auto lockboxes = groups_[group_lockbox].getWalletMap();
for (const auto& wltid : wltIDs)
{
auto wltIter = wallets.find(wltid);
if (wltIter != wallets.end())
{
shared_ptr wltCopy(
new BtcWallet(*(wltIter->second.get())));
wg.wallets_[wltid] = wltCopy;
}
else
{
auto lbIter = lockboxes.find(wltid);
if (lbIter != lockboxes.end())
{
shared_ptr lbCopy(
new BtcWallet(*(lbIter->second.get())));
wg.wallets_[wltid] = lbCopy;
}
}
}
wg.pageHistory(true);
return wg;
}
////////////////////////////////////////////////////////////////////////////////
uint32_t BlockDataViewer::getBlockTimeByHeight(uint32_t height) const
{
auto bh = blockchain().getHeaderByHeight(height);
return bh.getTimestamp();
}
////////////////////////////////////////////////////////////////////////////////
LedgerDelegate BlockDataViewer::getLedgerDelegateForWallets()
{
auto getHist = [this](uint32_t pageID)->vector
{ return this->getWalletsHistoryPage(pageID, false, false); };
auto getBlock = [this](uint32_t block)->uint32_t
{ return this->groups_[group_wallet].getBlockInVicinity(block); };
auto getPageId = [this](uint32_t block)->uint32_t
{ return this->groups_[group_wallet].getPageIdForBlockHeight(block); };
return LedgerDelegate(getHist, getBlock, getPageId);
}
////////////////////////////////////////////////////////////////////////////////
LedgerDelegate BlockDataViewer::getLedgerDelegateForLockboxes()
{
auto getHist = [this](uint32_t pageID)->vector
{ return this->getLockboxesHistoryPage(pageID, false, false); };
auto getBlock = [this](uint32_t block)->uint32_t
{ return this->groups_[group_lockbox].getBlockInVicinity(block); };
auto getPageId = [this](uint32_t block)->uint32_t
{ return this->groups_[group_lockbox].getPageIdForBlockHeight(block); };
return LedgerDelegate(getHist, getBlock, getPageId);
}
////////////////////////////////////////////////////////////////////////////////
LedgerDelegate BlockDataViewer::getLedgerDelegateForScrAddr(
const BinaryData& wltID, const BinaryData& scrAddr)
{
BtcWallet* wlt = nullptr;
for (auto& group : groups_)
{
ReadWriteLock::WriteLock wl(group.lock_);
auto wltIter = group.wallets_.find(wltID);
if (wltIter != group.wallets_.end())
{
wlt = wltIter->second.get();
break;
}
}
if (wlt == nullptr)
throw runtime_error("Unregistered wallet ID");
ScrAddrObj& sca = wlt->getScrAddrObjRef(scrAddr);
auto getHist = [&](uint32_t pageID)->vector
{ return sca.getHistoryPageById(pageID); };
auto getBlock = [&](uint32_t block)->uint32_t
{ return sca.getBlockInVicinity(block); };
auto getPageId = [&](uint32_t block)->uint32_t
{ return sca.getPageIdForBlockHeight(block); };
return LedgerDelegate(getHist, getBlock, getPageId);
}
////////////////////////////////////////////////////////////////////////////////
uint32_t BlockDataViewer::getClosestBlockHeightForTime(uint32_t timestamp)
{
//get timestamp of genesis block
auto& genBlock = blockchain().getGenesisBlock();
//sanity check
if (timestamp < genBlock.getTimestamp())
return 0;
//get time diff and divide by average time per block (600 sec for Bitcoin)
uint32_t diff = timestamp - genBlock.getTimestamp();
int32_t blockHint = diff/600;
//look for a block in the hint vicinity with a timestamp lower than ours
while (blockHint > 0)
{
auto& block = blockchain().getHeaderByHeight(blockHint);
if (block.getTimestamp() < timestamp)
break;
blockHint -= 1000;
}
//another sanity check
if (blockHint < 0)
return 0;
for (uint32_t id = blockHint; id < blockchain().top().getBlockHeight() - 1; id++)
{
//not looking for a really precise block,
//anything within the an hour of the timestamp is enough
auto& block = blockchain().getHeaderByHeight(id);
if (block.getTimestamp() + 3600 > timestamp)
return block.getBlockHeight();
}
return blockchain().top().getBlockHeight() - 1;
}
////////////////////////////////////////////////////////////////////////////////
TxOut BlockDataViewer::getTxOutCopy(
const BinaryData& txHash, uint16_t index) const
{
LMDBEnv::Transaction tx;
db_->beginDBTransaction(&tx, HISTORY, LMDB::ReadOnly);
BinaryData bdkey;
db_->getStoredTx_byHash(txHash, nullptr, &bdkey);
if (bdkey.getSize() == 0)
return TxOut();
return db_->getTxOutCopy(bdkey, index);
}
////////////////////////////////////////////////////////////////////////////////
Tx BlockDataViewer::getSpenderTxForTxOut(uint32_t height, uint32_t txindex,
uint16_t txoutid) const
{
StoredTxOut stxo;
db_->getStoredTxOut(stxo, height, txindex, txoutid);
if (!stxo.isSpent())
return Tx();
TxRef txref(stxo.spentByTxInKey_.getSliceCopy(0, 6));
return txref.attached(db_).getTxCopy();
}
////////////////////////////////////////////////////////////////////////////////
//// WalletGroup
////////////////////////////////////////////////////////////////////////////////
WalletGroup::~WalletGroup()
{
for (auto& wlt : wallets_)
wlt.second->unregister();
}
////////////////////////////////////////////////////////////////////////////////
BtcWallet* WalletGroup::registerWallet(
vector const& scrAddrVec, string IDstr, bool wltIsNew)
{
if (IDstr.empty())
{
return nullptr;
}
// Check if the wallet is already registered
ReadWriteLock::WriteLock wl(lock_);
BinaryData id(IDstr);
{
auto regWlt = wallets_.find(id);
if (regWlt != wallets_.end())
{
bdvPtr_->flagRefresh(BDV_refreshSkipRescan, id);
return regWlt->second.get();
}
}
shared_ptr newWallet;
{
// Main thread isnt running, just register the wallet
// Add it to the list of wallets to watch
// Instantiate the object through insert.
auto insertResult = wallets_.insert(make_pair(
id, shared_ptr(new BtcWallet(bdvPtr_, id))
));
newWallet = insertResult.first->second;
}
newWallet->addAddressBulk(scrAddrVec, wltIsNew);
//register all scrAddr in the wallet with the BDM. It doesn't matter if
//the data is overwritten
vector saVec;
saVec.reserve(newWallet->getScrAddrMap().size());
for (const auto& scrAddrPair : newWallet->getScrAddrMap())
saVec.push_back(scrAddrPair.first);
saf_->registerAddresses(saVec, newWallet, wltIsNew);
//tell the wallet it is registered
newWallet->setRegistered();
return newWallet.get();
}
////////////////////////////////////////////////////////////////////////////////
void WalletGroup::unregisterWallet(const string& IDstr)
{
ReadWriteLock::WriteLock wl(lock_);
BinaryData id(IDstr);
{
auto wltIter = wallets_.find(id);
if (wltIter == wallets_.end())
return;
}
wallets_.erase(id);
bdvPtr_->notifyMainThread();
}
////////////////////////////////////////////////////////////////////////////////
bool WalletGroup::registerAddresses(const vector& saVec,
BinaryData walletID, bool areNew)
{
if (saVec.empty())
return false;
ReadWriteLock::ReadLock rl(lock_);
auto wltIter = wallets_.find(walletID);
if (wltIter == wallets_.end())
return false;
return saf_->registerAddresses(saVec, wltIter->second, areNew);
}
////////////////////////////////////////////////////////////////////////////////
bool WalletGroup::hasID(const BinaryData& ID) const
{
ReadWriteLock::ReadLock rl(lock_);
return wallets_.find(ID) != wallets_.end();
}
/////////////////////////////////////////////////////////////////////////////
void WalletGroup::pprintRegisteredWallets(void) const
{
ReadWriteLock::ReadLock rl(lock_);
for (const auto& wlt : values(wallets_))
{
cout << "Wallet:";
wlt->pprintAlittle(cout);
}
}
/////////////////////////////////////////////////////////////////////////////
void WalletGroup::purgeZeroConfPool(
const map >& invalidatedTxIOKeys)
{
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
wlt->purgeZeroConfTxIO(invalidatedTxIOKeys);
}
/////////////////////////////////////////////////////////////////////////////
const LedgerEntry& WalletGroup::getTxLedgerByHash(
const BinaryData& txHash) const
{
ReadWriteLock::ReadLock rl(lock_);
for (const auto& wlt : values(wallets_))
{
const LedgerEntry& le = wlt->getLedgerEntryForTx(txHash);
if (le.getTxTime() != 0)
return le;
}
return LedgerEntry::EmptyLedger_;
}
/////////////////////////////////////////////////////////////////////////////
void WalletGroup::reset()
{
ReadWriteLock::ReadLock rl(lock_);
for (const auto& wlt : values(wallets_))
wlt->reset();
}
////////////////////////////////////////////////////////////////////////////////
map WalletGroup::computeWalletsSSHSummary(
bool forcePaging)
{
map fullSummary;
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
{
if (forcePaging)
wlt->mapPages();
if (wlt->uiFilter_ == false)
continue;
const auto& wltSummary = wlt->getSSHSummary();
for (auto summary : wltSummary)
fullSummary[summary.first] += summary.second;
}
return fullSummary;
}
////////////////////////////////////////////////////////////////////////////////
uint32_t WalletGroup::pageHistory(bool forcePaging)
{
auto computeSummary = [this](bool force)->map
{ return this->computeWalletsSSHSummary(force); };
hist_.mapHistory(computeSummary, forcePaging);
return hist_.getPageBottom(0);
}
////////////////////////////////////////////////////////////////////////////////
vector WalletGroup::getHistoryPage(uint32_t pageId,
bool rebuildLedger, bool remapWallets)
{
unique_lock mu(globalLedgerLock_);
if (pageId >= hist_.getPageCount())
throw std::range_error("pageId out of range");
if (order_ == order_ascending)
pageId = hist_.getPageCount() - pageId - 1;
//if (pageId == hist_.getCurrentPage() && !rebuildLedger && !remapWallets)
//return globalLedger_;
if (rebuildLedger || remapWallets)
pageHistory(remapWallets);
hist_.setCurrentPage(pageId);
vector vle;
{
//globalLedger_.clear();
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
{
auto getTxio = [&wlt](uint32_t start, uint32_t end,
map& outMap)->void
{ return wlt->getTxioForRange(start, end, outMap); };
auto buildLedgers = [&wlt](map& le,
const map& txioMap,
uint32_t startBlock, uint32_t endBlock)->void
{ wlt->updateWalletLedgersFromTxio(le, txioMap, startBlock, endBlock); };
if (!wlt->uiFilter_)
continue;
map leMap;
hist_.getPageLedgerMap(getTxio, buildLedgers, pageId, leMap);
for (const LedgerEntry& le : values(leMap))
vle.push_back(le);
}
}
if (order_ == order_ascending)
sort(vle.begin(), vle.end());
else
{
LedgerEntry_DescendingOrder desc;
sort(vle.begin(), vle.end(), desc);
}
return vle;
}
////////////////////////////////////////////////////////////////////////////////
void WalletGroup::updateLedgerFilter(const vector& walletsList)
{
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
wlt->uiFilter_ = false;
for (auto walletID : walletsList)
wallets_[walletID]->uiFilter_ = true;
bdvPtr_->flagRefresh(BDV_filterChanged, BinaryData());
}
////////////////////////////////////////////////////////////////////////////////
void WalletGroup::merge()
{
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
wlt->merge();
}
////////////////////////////////////////////////////////////////////////////////
void WalletGroup::scanWallets(uint32_t startBlock, uint32_t endBlock,
bool reorg, map > invalidatedZCKeys)
{
ReadWriteLock::ReadLock rl(lock_);
for (auto& wlt : values(wallets_))
wlt->scanWallet(startBlock, endBlock, reorg, invalidatedZCKeys);
}
////////////////////////////////////////////////////////////////////////////////
void WalletGroup::updateGlobalLedgerFirstPage(uint32_t startBlock,
uint32_t endBlock, BDV_refresh forceRefresh)
{
//There is a fundamental difference between the first history page and all
//the others: the first page maintains ZC, new blocks and can undergo
//reorgs while every other history page is purely static
ReadWriteLock::ReadLock rl(lock_);
if (forceRefresh == BDV_refreshSkipRescan)
getHistoryPage(0, true, false);
else if (forceRefresh == BDV_refreshAndRescan)
getHistoryPage(0, true, true);
else if (hist_.getCurrentPage() == 0)
{
unique_lock mu(globalLedgerLock_);
LedgerEntry::purgeLedgerVectorFromHeight(globalLedger_, startBlock);
for (auto& wlt : values(wallets_))
{
map txioMap;
wlt->getTxioForRange(startBlock, UINT32_MAX, txioMap);
map leMap;
wlt->updateWalletLedgersFromTxio(leMap, txioMap, startBlock, UINT32_MAX);
if (!wlt->uiFilter_)
continue;
for (const auto& lePair : leMap)
globalLedger_.push_back(lePair.second);
}
if (order_ == order_ascending)
sort(globalLedger_.begin(), globalLedger_.end());
else
{
LedgerEntry_DescendingOrder desc;
sort(globalLedger_.begin(), globalLedger_.end(), desc);
}
}
}
////////////////////////////////////////////////////////////////////////////////
map > WalletGroup::getWalletMap(void) const
{
ReadWriteLock::ReadLock rl(lock_);
return wallets_;
}
////////////////////////////////////////////////////////////////////////////////
shared_ptr WalletGroup::getWalletByID(const BinaryData& ID) const
{
auto iter = wallets_.find(ID);
if (iter != wallets_.end())
return iter->second;
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
uint32_t WalletGroup::getBlockInVicinity(uint32_t blk) const
{
//expect history has been computed, it will throw otherwise
return hist_.getBlockInVicinity(blk);
}
////////////////////////////////////////////////////////////////////////////////
uint32_t WalletGroup::getPageIdForBlockHeight(uint32_t blk) const
{
//same as above
return hist_.getPageIdForBlockHeight(blk);
}