Skip to content

Commit 7b8c950

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 7b8c950

File tree

4 files changed

+119
-6
lines changed

4 files changed

+119
-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

+35-5
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,27 @@
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+
// convert CoinDenomination to int to use std::hash<int> (for mac compatibility)
27+
return p.first.GetCheapHash() ^ std::hash<int>()(p.second);
28+
}
29+
};
30+
1831
std::map<uint256, CBigNum> mapAccumulatorValues;
1932
std::list<uint256> listAccCheckpointsNoDB;
33+
// This needs to be able to contain a reasonable number of distinct (accumulator hash, denom) pairs
34+
// that could occur in a block or it may not be effective.
35+
static veil::SimpleLRUCache<std::pair<uint256, CoinDenomination>, int, ChecksumHeightHash> cacheChecksumHeights(1024);
2036

2137
uint256 GetChecksum(const CBigNum &bnValue)
2238
{
@@ -28,20 +44,34 @@ uint256 GetChecksum(const CBigNum &bnValue)
2844
// Find the first occurrence of a certain accumulator checksum. Return 0 if not found.
2945
int GetChecksumHeight(uint256 hashChecksum, CoinDenomination denomination)
3046
{
47+
int height = 0;
48+
std::pair<uint256, CoinDenomination> p(hashChecksum, denomination);
49+
if (cacheChecksumHeights.get(p, height) && height > 0) {
50+
// Verify that the block in question is in the main chain still
51+
CBlockIndex* pindex = chainActive[height];
52+
if (pindex && pindex->GetAccumulatorHash(denomination) == hashChecksum)
53+
return height;
54+
55+
// fall through to search and re-insert if possible
56+
}
3157
CBlockIndex* pindex = chainActive[0];
3258
if (!pindex)
3359
return 0;
3460

3561
//Search through blocks to find the checksum
3662
while (pindex) {
63+
height = pindex->nHeight;
3764
if (pindex->GetAccumulatorHash(denomination) == hashChecksum)
38-
return pindex->nHeight;
65+
{
66+
cacheChecksumHeights.set(p, height);
67+
return height;
68+
}
3969

4070
//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];
71+
if (height % 10 == 0) {
72+
if (height + 10 > chainActive.Height())
73+
break;
74+
pindex = chainActive[height + 10];
4575
continue;
4676
}
4777

0 commit comments

Comments
 (0)