Skip to content

Commit ddae610

Browse files
committed
[Zerocoin][Validation] Cache checksum heights
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.
1 parent 2207ed0 commit ddae610

File tree

4 files changed

+118
-6
lines changed

4 files changed

+118
-6
lines changed

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,6 @@ add_executable(veil
10221022
src/versionbits.h
10231023
src/walletinitinterface.h
10241024
src/warnings.cpp
1025-
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)
1025+
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)
10261026

10271027
qt5_use_modules(veil Core Widgets Gui)

src/Makefile.am

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ BITCOIN_CORE_H = \
192192
veil/dandelioninventory.h \
193193
veil/invalid.h \
194194
veil/invalid_list.h \
195+
veil/lru_cache.h \
195196
veil/proofoffullnode/proofoffullnode.h \
196197
veil/proofofstake/kernel.h \
197198
veil/proofofstake/blockvalidation.h \

src/veil/lru_cache.h

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2021 Veil developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef VEIL_LRU_CACHE_H
6+
#define VEIL_LRU_CACHE_H
7+
8+
#include "sync.h"
9+
10+
#include <list>
11+
#include <unordered_map>
12+
#include <utility>
13+
14+
namespace veil {
15+
16+
/**
17+
* The SimpleLRUCache is a fixed-size key-value map that automatically
18+
* evicts the least-recently-used item when a new item is added while the cache is full.
19+
*
20+
* This is a naive, non-optimized implementation of an LRU cache that uses an
21+
* internal mutex to prevent concurrent access.
22+
*
23+
* K must be a hashable type, but you can define your own Hash for it, or equivalently implement
24+
* std::hash<K> for your type. It must be a default-constructible struct or class
25+
* defining std::size_t operator()(const K&) const, e.g.
26+
*
27+
* namespace std {
28+
* template<> struct hash<MyType>
29+
* {
30+
* std::size_t operator()(const MyType& m) const {
31+
* return std::hash<std::string>()(m.ImportantString()) ^ m.ImportantInteger;
32+
* }
33+
* };
34+
* }
35+
* SimpleLRUCache<MyType, MyValue> cache(100);
36+
*/
37+
template<typename K, typename V = K, class Hash = std::hash<K>>
38+
class SimpleLRUCache
39+
{
40+
41+
private:
42+
std::list<K> items;
43+
std::unordered_map<K, std::pair<V, typename std::list<K>::iterator>, Hash> keyValuesMap;
44+
int csize;
45+
CCriticalSection cs_mycache;
46+
47+
public:
48+
SimpleLRUCache(int s) : csize(s < 1 ? 10 : s), keyValuesMap(csize) {}
49+
50+
void set(const K key, const V value) {
51+
LOCK(cs_mycache);
52+
auto pos = keyValuesMap.find(key);
53+
if (pos == keyValuesMap.end()) {
54+
items.push_front(key);
55+
keyValuesMap[key] = { value, items.begin() };
56+
if (keyValuesMap.size() > csize) {
57+
keyValuesMap.erase(items.back());
58+
items.pop_back();
59+
}
60+
} else {
61+
items.erase(pos->second.second);
62+
items.push_front(key);
63+
keyValuesMap[key] = { value, items.begin() };
64+
}
65+
}
66+
67+
bool get(const K key, V &value) {
68+
LOCK(cs_mycache);
69+
auto pos = keyValuesMap.find(key);
70+
if (pos == keyValuesMap.end())
71+
return false;
72+
items.erase(pos->second.second);
73+
items.push_front(key);
74+
keyValuesMap[key] = { pos->second.first, items.begin() };
75+
value = pos->second.first;
76+
return true;
77+
}
78+
};
79+
80+
} // namespace veil
81+
82+
#endif // VEIL_LRU_CACHE_H

src/veil/zerocoin/accumulators.cpp

+34-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,26 @@
1212
#include "veil/zerocoin/zchain.h"
1313
#include "primitives/zerocoin.h"
1414
#include "shutdown.h"
15+
#include "veil/lru_cache.h"
16+
17+
#include <utility>
1518

1619
using namespace libzerocoin;
1720

21+
// This is only used to store pair<hash, denom> in a map, so the cheap hash is fine.
22+
struct ChecksumHeightHash
23+
{
24+
std::size_t operator()(const std::pair<uint256, CoinDenomination> p) const
25+
{
26+
return p.first.GetCheapHash() ^ std::hash<CoinDenomination>()(p.second);
27+
}
28+
};
29+
1830
std::map<uint256, CBigNum> mapAccumulatorValues;
1931
std::list<uint256> listAccCheckpointsNoDB;
32+
// This needs to be able to contain a reasonable number of distinct (accumulator hash, denom) pairs
33+
// that could occur in a block or it may not be effective.
34+
static veil::SimpleLRUCache<std::pair<uint256, CoinDenomination>, int, ChecksumHeightHash> cacheChecksumHeights(1024);
2035

2136
uint256 GetChecksum(const CBigNum &bnValue)
2237
{
@@ -28,20 +43,34 @@ uint256 GetChecksum(const CBigNum &bnValue)
2843
// Find the first occurrence of a certain accumulator checksum. Return 0 if not found.
2944
int GetChecksumHeight(uint256 hashChecksum, CoinDenomination denomination)
3045
{
46+
int height = 0;
47+
std::pair<uint256, CoinDenomination> p(hashChecksum, denomination);
48+
if (cacheChecksumHeights.get(p, height) && height > 0) {
49+
// Verify that the block in question is in the main chain still
50+
CBlockIndex* pindex = chainActive[height];
51+
if (pindex && pindex->GetAccumulatorHash(denomination) == hashChecksum)
52+
return height;
53+
54+
// fall through to search and re-insert if possible
55+
}
3156
CBlockIndex* pindex = chainActive[0];
3257
if (!pindex)
3358
return 0;
3459

3560
//Search through blocks to find the checksum
3661
while (pindex) {
62+
height = pindex->nHeight;
3763
if (pindex->GetAccumulatorHash(denomination) == hashChecksum)
38-
return pindex->nHeight;
64+
{
65+
cacheChecksumHeights.set(p, height);
66+
return height;
67+
}
3968

4069
//Skip forward in groups of 10 blocks since checkpoints only change every 10 blocks
41-
if (pindex->nHeight % 10 == 0) {
42-
if (pindex->nHeight + 10 > chainActive.Height())
43-
return 0;
44-
pindex = chainActive[pindex->nHeight + 10];
70+
if (height % 10 == 0) {
71+
if (height + 10 > chainActive.Height())
72+
break;
73+
pindex = chainActive[height + 10];
4574
continue;
4675
}
4776

0 commit comments

Comments
 (0)