Skip to content

Commit

Permalink
[Zerocoin][Validation] Cache checksum heights
Browse files Browse the repository at this point in the history
Validating zerocoin spends requires determining the height of an
accumulator hash, which is done by searching through all the blocks
in the chain starting from genesis, for every txin.

Since the height of a particular hash-denomination pair is not going
to change, we can cache it. This is most useful for in-wallet block
template generation, since each mining thread performs these
validations separately (while holding cs_main).

Adds a SimpleLRUCache template based on 7c9e97a and uses it to
store checksum heights. I chose a particular size for it that should
be large enough for significant spends.

This results in a roughly 30+x speedup of zerocoin spend validation
(26-30ms/txin -> 0.4-0.9ms/txin). Vastly improves #862 alongside #915.
  • Loading branch information
Zannick committed May 2, 2021
1 parent 4fe97db commit 1235e1c
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,6 @@ add_executable(veil
src/versionbits.h
src/walletinitinterface.h
src/warnings.cpp
src/warnings.h src/veil/zerocoin/spendreceipt.h src/veil/ringct/temprecipient.h src/veil/ringct/temprecipient.cpp src/veil/zerocoin/spendreceipt.cpp src/veil/ringct/outputrecord.cpp src/veil/ringct/outputrecord.h src/wallet/walletbalances.h src/veil/ringct/transactionrecord.h src/veil/zerocoin/mintmeta.h src/test/zerocoin_zkp_tests.cpp src/veil/zerocoin/lrucache.h src/veil/zerocoin/lrucache.cpp src/veil/zerocoin/precompute.h src/veil/zerocoin/precompute.cpp src/libzerocoin/PubcoinSignature.h src/libzerocoin/PubcoinSignature.cpp src/test/zerocoin_pubcoinsig_tests.cpp src/veil/invalid_list.h src/veil/invalid_list.cpp)
src/warnings.h src/veil/lru_cache.h src/veil/zerocoin/spendreceipt.h src/veil/ringct/temprecipient.h src/veil/ringct/temprecipient.cpp src/veil/zerocoin/spendreceipt.cpp src/veil/ringct/outputrecord.cpp src/veil/ringct/outputrecord.h src/wallet/walletbalances.h src/veil/ringct/transactionrecord.h src/veil/zerocoin/mintmeta.h src/test/zerocoin_zkp_tests.cpp src/veil/zerocoin/lrucache.h src/veil/zerocoin/lrucache.cpp src/veil/zerocoin/precompute.h src/veil/zerocoin/precompute.cpp src/libzerocoin/PubcoinSignature.h src/libzerocoin/PubcoinSignature.cpp src/test/zerocoin_pubcoinsig_tests.cpp src/veil/invalid_list.h src/veil/invalid_list.cpp)

qt5_use_modules(veil Core Widgets Gui)
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ BITCOIN_CORE_H = \
veil/dandelioninventory.h \
veil/invalid.h \
veil/invalid_list.h \
veil/lru_cache.h \
veil/proofoffullnode/proofoffullnode.h \
veil/proofofstake/kernel.h \
veil/proofofstake/blockvalidation.h \
Expand Down
82 changes: 82 additions & 0 deletions src/veil/lru_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) 2021 Veil developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef VEIL_LRU_CACHE_H
#define VEIL_LRU_CACHE_H

#include "sync.h"

#include <list>
#include <unordered_map>
#include <utility>

namespace veil {

/**
* The SimpleLRUCache is a fixed-size key-value map that automatically
* evicts the least-recently-used item when a new item is added while the cache is full.
*
* This is a naive, non-optimized implementation of an LRU cache that uses an
* internal mutex to prevent concurrent access.
*
* K must be a hashable type, but you can define your own Hash for it, or equivalently implement
* std::hash<K> for your type. It must be a default-constructible struct or class
* defining std::size_t operator()(const K&) const, e.g.
*
* namespace std {
* template<> struct hash<MyType>
* {
* std::size_t operator()(const MyType& m) const {
* return std::hash<std::string>()(m.ImportantString()) ^ m.ImportantInteger;
* }
* };
* }
* SimpleLRUCache<MyType, MyValue> cache(100);
*/
template<typename K, typename V = K, class Hash = std::hash<K>>
class SimpleLRUCache
{

private:
std::list<K> items;
std::unordered_map<K, std::pair<V, typename std::list<K>::iterator>, Hash> keyValuesMap;
int csize;
CCriticalSection cs_mycache;

public:
SimpleLRUCache(int s) : csize(s < 1 ? 10 : s), keyValuesMap(csize) {}

void set(const K key, const V value) {
LOCK(cs_mycache);
auto pos = keyValuesMap.find(key);
if (pos == keyValuesMap.end()) {
items.push_front(key);
keyValuesMap[key] = { value, items.begin() };
if (keyValuesMap.size() > csize) {
keyValuesMap.erase(items.back());
items.pop_back();
}
} else {
items.erase(pos->second.second);
items.push_front(key);
keyValuesMap[key] = { value, items.begin() };
}
}

bool get(const K key, V &value) {
LOCK(cs_mycache);
auto pos = keyValuesMap.find(key);
if (pos == keyValuesMap.end())
return false;
items.erase(pos->second.second);
items.push_front(key);
keyValuesMap[key] = { pos->second.first, items.begin() };
value = pos->second.first;
return true;
}
};

} // namespace veil

#endif // VEIL_LRU_CACHE_H
40 changes: 35 additions & 5 deletions src/veil/zerocoin/accumulators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,27 @@
#include "veil/zerocoin/zchain.h"
#include "primitives/zerocoin.h"
#include "shutdown.h"
#include "veil/lru_cache.h"

#include <utility>

using namespace libzerocoin;

// This is only used to store pair<hash, denom> in a map, so the cheap hash is fine.
struct ChecksumHeightHash
{
std::size_t operator()(const std::pair<uint256, CoinDenomination> p) const
{
// convert CoinDenomination to int to use std::hash<int> (for mac compatibility)
return p.first.GetCheapHash() ^ std::hash<int>()(p.second);
}
};

std::map<uint256, CBigNum> mapAccumulatorValues;
std::list<uint256> listAccCheckpointsNoDB;
// This needs to be able to contain a reasonable number of distinct (accumulator hash, denom) pairs
// that could occur in a block or it may not be effective.
static veil::SimpleLRUCache<std::pair<uint256, CoinDenomination>, int, ChecksumHeightHash> cacheChecksumHeights(1024);

uint256 GetChecksum(const CBigNum &bnValue)
{
Expand All @@ -28,20 +44,34 @@ uint256 GetChecksum(const CBigNum &bnValue)
// Find the first occurrence of a certain accumulator checksum. Return 0 if not found.
int GetChecksumHeight(uint256 hashChecksum, CoinDenomination denomination)
{
int height = 0;
std::pair<uint256, CoinDenomination> p(hashChecksum, denomination);
if (cacheChecksumHeights.get(p, height) && height > 0) {
// Verify that the block in question is in the main chain still
CBlockIndex* pindex = chainActive[height];
if (pindex && pindex->GetAccumulatorHash(denomination) == hashChecksum)
return height;

// fall through to search and re-insert if possible
}
CBlockIndex* pindex = chainActive[0];
if (!pindex)
return 0;

//Search through blocks to find the checksum
while (pindex) {
height = pindex->nHeight;
if (pindex->GetAccumulatorHash(denomination) == hashChecksum)
return pindex->nHeight;
{
cacheChecksumHeights.set(p, height);
return height;
}

//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];
if (height % 10 == 0) {
if (height + 10 > chainActive.Height())
break;
pindex = chainActive[height + 10];
continue;
}

Expand Down

0 comments on commit 1235e1c

Please sign in to comment.