Auto merge of #3170 - ebfull:sapling-merkle-tree, r=ebfull

Sapling merkle tree implementation

Closes #3056.

Please also review https://github.com/zcash/librustzcash/pull/8

This PR:

1. Introduces ZCSaplingIncrementalMerkleTree using Pedersen hashes.
2. Adds support for Sapling anchors into consensus rules. (Adds commitments, checks anchors are correct, handles block (dis)connects, etc.)
3. Handles mempool eviction for obsolete anchors.
4. Enforces correctness of block's Sapling root field
5. Changes miner to correctly apply the Sapling root to the block header
6. Handles mempool consistency checks for anchors
This commit is contained in:
Homu 2018-05-07 20:37:46 -07:00
commit 333b9a0d0b
37 changed files with 1166 additions and 309 deletions

View File

@ -3,8 +3,8 @@ $(package)_crate_name=sapling-crypto
$(package)_download_path=https://github.com/zcash-hackworks/$($(package)_crate_name)/archive/ $(package)_download_path=https://github.com/zcash-hackworks/$($(package)_crate_name)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz $(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz $(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=5eb4040bc223a689341b3f1a1fc53d6064c4c032b23ae0c2c653b063e1da24db $(package)_sha256_hash=5062b9e752066ad959f14063d496b0a156ce96004a13a6823494249793c01f96
$(package)_git_commit=e554b473dd10885d232f42237c13282f5b6fee43 $(package)_git_commit=7beeb52730e24724ee10ea2458ecf7776cb59c58
$(package)_crate_versioned_name=$($(package)_crate_name) $(package)_crate_versioned_name=$($(package)_crate_name)
define $(package)_preprocess_cmds define $(package)_preprocess_cmds

View File

@ -3,8 +3,8 @@ $(package)_version=0.1
$(package)_download_path=https://github.com/zcash/$(package)/archive/ $(package)_download_path=https://github.com/zcash/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz $(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz $(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=b63ba98d569d332764f27706038c04d03ac7e2c836dc15dc4eaa24b04b8c7f4a $(package)_sha256_hash=c5442a57d8961aab12fd395a5004edbb96b973511fab3949a087faa2a865a002
$(package)_git_commit=6cc1813ae3bb1e42224fd8ca0a8977b95c576738 $(package)_git_commit=ef676eff5084d394e6c6eaf2b9d9817effe662a7
$(package)_dependencies=rust $(rust_crates) $(package)_dependencies=rust $(rust_crates)
$(package)_patches=cargo.config $(package)_patches=cargo.config

View File

@ -8,7 +8,7 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash-hackworks/sapling-crypto"] [source."https://github.com/zcash-hackworks/sapling-crypto"]
git = "https://github.com/zcash-hackworks/sapling-crypto" git = "https://github.com/zcash-hackworks/sapling-crypto"
rev = "e554b473dd10885d232f42237c13282f5b6fee43" rev = "7beeb52730e24724ee10ea2458ecf7776cb59c58"
replace-with = "vendored-sources" replace-with = "vendored-sources"
[source.vendored-sources] [source.vendored-sources]

View File

@ -32,6 +32,12 @@ JSON_TEST_FILES = \
test/data/merkle_witness_serialization.json \ test/data/merkle_witness_serialization.json \
test/data/merkle_path.json \ test/data/merkle_path.json \
test/data/merkle_commitments.json \ test/data/merkle_commitments.json \
test/data/merkle_roots_sapling.json \
test/data/merkle_roots_empty_sapling.json \
test/data/merkle_serialization_sapling.json \
test/data/merkle_witness_serialization_sapling.json \
test/data/merkle_path_sapling.json \
test/data/merkle_commitments_sapling.json \
test/data/g1_compressed.json \ test/data/g1_compressed.json \
test/data/g2_compressed.json test/data/g2_compressed.json

View File

@ -152,10 +152,10 @@ public:
boost::optional<uint32_t> nCachedBranchId; boost::optional<uint32_t> nCachedBranchId;
//! The anchor for the tree state up to the start of this block //! The anchor for the tree state up to the start of this block
uint256 hashAnchor; uint256 hashSproutAnchor;
//! (memory only) The anchor for the tree state up to the end of this block //! (memory only) The anchor for the tree state up to the end of this block
uint256 hashAnchorEnd; uint256 hashFinalSproutRoot;
//! Change in value held by the Sprout circuit over this block. //! Change in value held by the Sprout circuit over this block.
//! Will be boost::none for older blocks on old nodes until a reindex has taken place. //! Will be boost::none for older blocks on old nodes until a reindex has taken place.
@ -169,7 +169,7 @@ public:
//! block header //! block header
int nVersion; int nVersion;
uint256 hashMerkleRoot; uint256 hashMerkleRoot;
uint256 hashReserved; uint256 hashFinalSaplingRoot;
unsigned int nTime; unsigned int nTime;
unsigned int nBits; unsigned int nBits;
uint256 nNonce; uint256 nNonce;
@ -192,15 +192,15 @@ public:
nChainTx = 0; nChainTx = 0;
nStatus = 0; nStatus = 0;
nCachedBranchId = boost::none; nCachedBranchId = boost::none;
hashAnchor = uint256(); hashSproutAnchor = uint256();
hashAnchorEnd = uint256(); hashFinalSproutRoot = uint256();
nSequenceId = 0; nSequenceId = 0;
nSproutValue = boost::none; nSproutValue = boost::none;
nChainSproutValue = boost::none; nChainSproutValue = boost::none;
nVersion = 0; nVersion = 0;
hashMerkleRoot = uint256(); hashMerkleRoot = uint256();
hashReserved = uint256(); hashFinalSaplingRoot = uint256();
nTime = 0; nTime = 0;
nBits = 0; nBits = 0;
nNonce = uint256(); nNonce = uint256();
@ -218,7 +218,7 @@ public:
nVersion = block.nVersion; nVersion = block.nVersion;
hashMerkleRoot = block.hashMerkleRoot; hashMerkleRoot = block.hashMerkleRoot;
hashReserved = block.hashReserved; hashFinalSaplingRoot = block.hashFinalSaplingRoot;
nTime = block.nTime; nTime = block.nTime;
nBits = block.nBits; nBits = block.nBits;
nNonce = block.nNonce; nNonce = block.nNonce;
@ -250,7 +250,7 @@ public:
if (pprev) if (pprev)
block.hashPrevBlock = pprev->GetBlockHash(); block.hashPrevBlock = pprev->GetBlockHash();
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashReserved = hashReserved; block.hashFinalSaplingRoot = hashFinalSaplingRoot;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;
@ -366,13 +366,13 @@ public:
READWRITE(branchId); READWRITE(branchId);
} }
} }
READWRITE(hashAnchor); READWRITE(hashSproutAnchor);
// block header // block header
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrev); READWRITE(hashPrev);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashReserved); READWRITE(hashFinalSaplingRoot);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
READWRITE(nNonce); READWRITE(nNonce);
@ -391,7 +391,7 @@ public:
block.nVersion = nVersion; block.nVersion = nVersion;
block.hashPrevBlock = hashPrev; block.hashPrevBlock = hashPrev;
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashReserved = hashReserved; block.hashFinalSaplingRoot = hashFinalSaplingRoot;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;

View File

@ -42,16 +42,19 @@ bool CCoins::Spend(uint32_t nPos)
Cleanup(); Cleanup();
return true; return true;
} }
bool CCoinsView::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return false; } bool CCoinsView::GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return false; }
bool CCoinsView::GetNullifier(const uint256 &nullifier, NullifierType type) const { return false; } bool CCoinsView::GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const { return false; }
bool CCoinsView::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return false; }
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; } bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; } bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); } uint256 CCoinsView::GetBestBlock() const { return uint256(); }
uint256 CCoinsView::GetBestAnchor() const { return uint256(); }; uint256 CCoinsView::GetBestAnchor(ShieldedType type) const { return uint256(); };
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, bool CCoinsView::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers) { return false; } CNullifiersMap &mapSaplingNullifiers) { return false; }
bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; } bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
@ -59,19 +62,22 @@ bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return base->GetAnchorAt(rt, tree); } bool CCoinsViewBacked::GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { return base->GetSproutAnchorAt(rt, tree); }
bool CCoinsViewBacked::GetNullifier(const uint256 &nullifier, NullifierType type) const { return base->GetNullifier(nullifier, type); } bool CCoinsViewBacked::GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const { return base->GetSaplingAnchorAt(rt, tree); }
bool CCoinsViewBacked::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return base->GetNullifier(nullifier, type); }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); } bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); } bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
uint256 CCoinsViewBacked::GetBestAnchor() const { return base->GetBestAnchor(); } uint256 CCoinsViewBacked::GetBestAnchor(ShieldedType type) const { return base->GetBestAnchor(type); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; } void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers) { return base->BatchWrite(mapCoins, hashBlock, hashAnchor, mapAnchors, mapSproutNullifiers, mapSaplingNullifiers); } CNullifiersMap &mapSaplingNullifiers) { return base->BatchWrite(mapCoins, hashBlock, hashSproutAnchor, hashSaplingAnchor, mapSproutAnchors, mapSaplingAnchors, mapSproutNullifiers, mapSaplingNullifiers); }
bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); } bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); }
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {} CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
@ -85,7 +91,8 @@ CCoinsViewCache::~CCoinsViewCache()
size_t CCoinsViewCache::DynamicMemoryUsage() const { size_t CCoinsViewCache::DynamicMemoryUsage() const {
return memusage::DynamicUsage(cacheCoins) + return memusage::DynamicUsage(cacheCoins) +
memusage::DynamicUsage(cacheAnchors) + memusage::DynamicUsage(cacheSproutAnchors) +
memusage::DynamicUsage(cacheSaplingAnchors) +
memusage::DynamicUsage(cacheSproutNullifiers) + memusage::DynamicUsage(cacheSproutNullifiers) +
memusage::DynamicUsage(cacheSaplingNullifiers) + memusage::DynamicUsage(cacheSaplingNullifiers) +
cachedCoinsUsage; cachedCoinsUsage;
@ -110,9 +117,9 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
} }
bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { bool CCoinsViewCache::GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const {
CAnchorsMap::const_iterator it = cacheAnchors.find(rt); CAnchorsSproutMap::const_iterator it = cacheSproutAnchors.find(rt);
if (it != cacheAnchors.end()) { if (it != cacheSproutAnchors.end()) {
if (it->second.entered) { if (it->second.entered) {
tree = it->second.tree; tree = it->second.tree;
return true; return true;
@ -121,11 +128,11 @@ bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tr
} }
} }
if (!base->GetAnchorAt(rt, tree)) { if (!base->GetSproutAnchorAt(rt, tree)) {
return false; return false;
} }
CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(rt, CAnchorsCacheEntry())).first; CAnchorsSproutMap::iterator ret = cacheSproutAnchors.insert(std::make_pair(rt, CAnchorsSproutCacheEntry())).first;
ret->second.entered = true; ret->second.entered = true;
ret->second.tree = tree; ret->second.tree = tree;
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage(); cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
@ -133,17 +140,40 @@ bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tr
return true; return true;
} }
bool CCoinsViewCache::GetNullifier(const uint256 &nullifier, NullifierType type) const { bool CCoinsViewCache::GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const {
CAnchorsSaplingMap::const_iterator it = cacheSaplingAnchors.find(rt);
if (it != cacheSaplingAnchors.end()) {
if (it->second.entered) {
tree = it->second.tree;
return true;
} else {
return false;
}
}
if (!base->GetSaplingAnchorAt(rt, tree)) {
return false;
}
CAnchorsSaplingMap::iterator ret = cacheSaplingAnchors.insert(std::make_pair(rt, CAnchorsSaplingCacheEntry())).first;
ret->second.entered = true;
ret->second.tree = tree;
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
return true;
}
bool CCoinsViewCache::GetNullifier(const uint256 &nullifier, ShieldedType type) const {
CNullifiersMap* cacheToUse; CNullifiersMap* cacheToUse;
switch (type) { switch (type) {
case SPROUT_NULLIFIER: case SPROUT:
cacheToUse = &cacheSproutNullifiers; cacheToUse = &cacheSproutNullifiers;
break; break;
case SAPLING_NULLIFIER: case SAPLING:
cacheToUse = &cacheSaplingNullifiers; cacheToUse = &cacheSaplingNullifiers;
break; break;
default: default:
throw std::runtime_error("Unknown nullifier type"); throw std::runtime_error("Unknown shielded type");
} }
CNullifiersMap::iterator it = cacheToUse->find(nullifier); CNullifiersMap::iterator it = cacheToUse->find(nullifier);
if (it != cacheToUse->end()) if (it != cacheToUse->end())
@ -158,10 +188,17 @@ bool CCoinsViewCache::GetNullifier(const uint256 &nullifier, NullifierType type)
return tmp; return tmp;
} }
void CCoinsViewCache::PushAnchor(const ZCIncrementalMerkleTree &tree) { template<typename Tree, typename Cache, typename CacheIterator, typename CacheEntry>
void CCoinsViewCache::AbstractPushAnchor(
const Tree &tree,
ShieldedType type,
Cache &cacheAnchors,
uint256 &hash
)
{
uint256 newrt = tree.root(); uint256 newrt = tree.root();
auto currentRoot = GetBestAnchor(); auto currentRoot = GetBestAnchor(type);
// We don't want to overwrite an anchor we already have. // We don't want to overwrite an anchor we already have.
// This occurs when a block doesn't modify mapAnchors at all, // This occurs when a block doesn't modify mapAnchors at all,
@ -169,24 +206,67 @@ void CCoinsViewCache::PushAnchor(const ZCIncrementalMerkleTree &tree) {
// different way (make all blocks modify mapAnchors somehow) // different way (make all blocks modify mapAnchors somehow)
// but this is simpler to reason about. // but this is simpler to reason about.
if (currentRoot != newrt) { if (currentRoot != newrt) {
auto insertRet = cacheAnchors.insert(std::make_pair(newrt, CAnchorsCacheEntry())); auto insertRet = cacheAnchors.insert(std::make_pair(newrt, CacheEntry()));
CAnchorsMap::iterator ret = insertRet.first; CacheIterator ret = insertRet.first;
ret->second.entered = true; ret->second.entered = true;
ret->second.tree = tree; ret->second.tree = tree;
ret->second.flags = CAnchorsCacheEntry::DIRTY; ret->second.flags = CacheEntry::DIRTY;
if (insertRet.second) { if (insertRet.second) {
// An insert took place // An insert took place
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage(); cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
} }
hashAnchor = newrt; hash = newrt;
} }
} }
void CCoinsViewCache::PopAnchor(const uint256 &newrt) { void CCoinsViewCache::PushSproutAnchor(const ZCIncrementalMerkleTree &tree) {
auto currentRoot = GetBestAnchor(); AbstractPushAnchor<ZCIncrementalMerkleTree, CAnchorsSproutMap, CAnchorsSproutMap::iterator, CAnchorsSproutCacheEntry>(
tree,
SPROUT,
cacheSproutAnchors,
hashSproutAnchor
);
}
void CCoinsViewCache::PushSaplingAnchor(const ZCSaplingIncrementalMerkleTree &tree) {
AbstractPushAnchor<ZCSaplingIncrementalMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingMap::iterator, CAnchorsSaplingCacheEntry>(
tree,
SAPLING,
cacheSaplingAnchors,
hashSaplingAnchor
);
}
template<>
void CCoinsViewCache::BringBestAnchorIntoCache(
const uint256 &currentRoot,
ZCIncrementalMerkleTree &tree
)
{
assert(GetSproutAnchorAt(currentRoot, tree));
}
template<>
void CCoinsViewCache::BringBestAnchorIntoCache(
const uint256 &currentRoot,
ZCSaplingIncrementalMerkleTree &tree
)
{
assert(GetSaplingAnchorAt(currentRoot, tree));
}
template<typename Tree, typename Cache, typename CacheEntry>
void CCoinsViewCache::AbstractPopAnchor(
const uint256 &newrt,
ShieldedType type,
Cache &cacheAnchors,
uint256 &hash
)
{
auto currentRoot = GetBestAnchor(type);
// Blocks might not change the commitment tree, in which // Blocks might not change the commitment tree, in which
// case restoring the "old" anchor during a reorg must // case restoring the "old" anchor during a reorg must
@ -195,18 +275,41 @@ void CCoinsViewCache::PopAnchor(const uint256 &newrt) {
// Bring the current best anchor into our local cache // Bring the current best anchor into our local cache
// so that its tree exists in memory. // so that its tree exists in memory.
{ {
ZCIncrementalMerkleTree tree; Tree tree;
assert(GetAnchorAt(currentRoot, tree)); BringBestAnchorIntoCache(currentRoot, tree);
} }
// Mark the anchor as unentered, removing it from view // Mark the anchor as unentered, removing it from view
cacheAnchors[currentRoot].entered = false; cacheAnchors[currentRoot].entered = false;
// Mark the cache entry as dirty so it's propagated // Mark the cache entry as dirty so it's propagated
cacheAnchors[currentRoot].flags = CAnchorsCacheEntry::DIRTY; cacheAnchors[currentRoot].flags = CacheEntry::DIRTY;
// Mark the new root as the best anchor // Mark the new root as the best anchor
hashAnchor = newrt; hash = newrt;
}
}
void CCoinsViewCache::PopAnchor(const uint256 &newrt, ShieldedType type) {
switch (type) {
case SPROUT:
AbstractPopAnchor<ZCIncrementalMerkleTree, CAnchorsSproutMap, CAnchorsSproutCacheEntry>(
newrt,
SPROUT,
cacheSproutAnchors,
hashSproutAnchor
);
break;
case SAPLING:
AbstractPopAnchor<ZCSaplingIncrementalMerkleTree, CAnchorsSaplingMap, CAnchorsSaplingCacheEntry>(
newrt,
SAPLING,
cacheSaplingAnchors,
hashSaplingAnchor
);
break;
default:
throw std::runtime_error("Unknown shielded type");
} }
} }
@ -280,10 +383,21 @@ uint256 CCoinsViewCache::GetBestBlock() const {
} }
uint256 CCoinsViewCache::GetBestAnchor() const { uint256 CCoinsViewCache::GetBestAnchor(ShieldedType type) const {
if (hashAnchor.IsNull()) switch (type) {
hashAnchor = base->GetBestAnchor(); case SPROUT:
return hashAnchor; if (hashSproutAnchor.IsNull())
hashSproutAnchor = base->GetBestAnchor(type);
return hashSproutAnchor;
break;
case SAPLING:
if (hashSaplingAnchor.IsNull())
hashSaplingAnchor = base->GetBestAnchor(type);
return hashSaplingAnchor;
break;
default:
throw std::runtime_error("Unknown shielded type");
}
} }
void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) { void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
@ -312,10 +426,45 @@ void BatchWriteNullifiers(CNullifiersMap &mapNullifiers, CNullifiersMap &cacheNu
} }
} }
template<typename Map, typename MapIterator, typename MapEntry>
void BatchWriteAnchors(
Map &mapAnchors,
Map &cacheAnchors,
size_t &cachedCoinsUsage
)
{
for (MapIterator child_it = mapAnchors.begin(); child_it != mapAnchors.end();)
{
if (child_it->second.flags & MapEntry::DIRTY) {
MapIterator parent_it = cacheAnchors.find(child_it->first);
if (parent_it == cacheAnchors.end()) {
MapEntry& entry = cacheAnchors[child_it->first];
entry.entered = child_it->second.entered;
entry.tree = child_it->second.tree;
entry.flags = MapEntry::DIRTY;
cachedCoinsUsage += entry.tree.DynamicMemoryUsage();
} else {
if (parent_it->second.entered != child_it->second.entered) {
// The parent may have removed the entry.
parent_it->second.entered = child_it->second.entered;
parent_it->second.flags |= MapEntry::DIRTY;
}
}
}
MapIterator itOld = child_it++;
mapAnchors.erase(itOld);
}
}
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlockIn, const uint256 &hashBlockIn,
const uint256 &hashAnchorIn, const uint256 &hashSproutAnchorIn,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchorIn,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers) { CNullifiersMap &mapSaplingNullifiers) {
assert(!hasModifier); assert(!hasModifier);
@ -354,43 +503,23 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
mapCoins.erase(itOld); mapCoins.erase(itOld);
} }
for (CAnchorsMap::iterator child_it = mapAnchors.begin(); child_it != mapAnchors.end();) ::BatchWriteAnchors<CAnchorsSproutMap, CAnchorsSproutMap::iterator, CAnchorsSproutCacheEntry>(mapSproutAnchors, cacheSproutAnchors, cachedCoinsUsage);
{ ::BatchWriteAnchors<CAnchorsSaplingMap, CAnchorsSaplingMap::iterator, CAnchorsSaplingCacheEntry>(mapSaplingAnchors, cacheSaplingAnchors, cachedCoinsUsage);
if (child_it->second.flags & CAnchorsCacheEntry::DIRTY) {
CAnchorsMap::iterator parent_it = cacheAnchors.find(child_it->first);
if (parent_it == cacheAnchors.end()) {
CAnchorsCacheEntry& entry = cacheAnchors[child_it->first];
entry.entered = child_it->second.entered;
entry.tree = child_it->second.tree;
entry.flags = CAnchorsCacheEntry::DIRTY;
cachedCoinsUsage += entry.tree.DynamicMemoryUsage();
} else {
if (parent_it->second.entered != child_it->second.entered) {
// The parent may have removed the entry.
parent_it->second.entered = child_it->second.entered;
parent_it->second.flags |= CAnchorsCacheEntry::DIRTY;
}
}
}
CAnchorsMap::iterator itOld = child_it++;
mapAnchors.erase(itOld);
}
::BatchWriteNullifiers(mapSproutNullifiers, cacheSproutNullifiers); ::BatchWriteNullifiers(mapSproutNullifiers, cacheSproutNullifiers);
::BatchWriteNullifiers(mapSaplingNullifiers, cacheSaplingNullifiers); ::BatchWriteNullifiers(mapSaplingNullifiers, cacheSaplingNullifiers);
hashAnchor = hashAnchorIn; hashSproutAnchor = hashSproutAnchorIn;
hashSaplingAnchor = hashSaplingAnchorIn;
hashBlock = hashBlockIn; hashBlock = hashBlockIn;
return true; return true;
} }
bool CCoinsViewCache::Flush() { bool CCoinsViewCache::Flush() {
bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashAnchor, cacheAnchors, cacheSproutNullifiers, cacheSaplingNullifiers); bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashSproutAnchor, hashSaplingAnchor, cacheSproutAnchors, cacheSaplingAnchors, cacheSproutNullifiers, cacheSaplingNullifiers);
cacheCoins.clear(); cacheCoins.clear();
cacheAnchors.clear(); cacheSproutAnchors.clear();
cacheSaplingAnchors.clear();
cacheSproutNullifiers.clear(); cacheSproutNullifiers.clear();
cacheSaplingNullifiers.clear(); cacheSaplingNullifiers.clear();
cachedCoinsUsage = 0; cachedCoinsUsage = 0;
@ -430,7 +559,7 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
{ {
BOOST_FOREACH(const uint256& nullifier, joinsplit.nullifiers) BOOST_FOREACH(const uint256& nullifier, joinsplit.nullifiers)
{ {
if (GetNullifier(nullifier, SPROUT_NULLIFIER)) { if (GetNullifier(nullifier, SPROUT)) {
// If the nullifier is set, this transaction // If the nullifier is set, this transaction
// double-spends! // double-spends!
return false; return false;
@ -441,7 +570,7 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
auto it = intermediates.find(joinsplit.anchor); auto it = intermediates.find(joinsplit.anchor);
if (it != intermediates.end()) { if (it != intermediates.end()) {
tree = it->second; tree = it->second;
} else if (!GetAnchorAt(joinsplit.anchor, tree)) { } else if (!GetSproutAnchorAt(joinsplit.anchor, tree)) {
return false; return false;
} }
@ -454,8 +583,13 @@ bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
} }
for (const SpendDescription &spendDescription : tx.vShieldedSpend) { for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
if (GetNullifier(spendDescription.nullifier, SAPLING_NULLIFIER)) // Prevent double spends if (GetNullifier(spendDescription.nullifier, SAPLING)) // Prevent double spends
return false; return false;
ZCSaplingIncrementalMerkleTree tree;
if (!GetSaplingAnchorAt(spendDescription.anchor, tree)) {
return false;
}
} }
return true; return true;

View File

@ -273,7 +273,7 @@ struct CCoinsCacheEntry
CCoinsCacheEntry() : coins(), flags(0) {} CCoinsCacheEntry() : coins(), flags(0) {}
}; };
struct CAnchorsCacheEntry struct CAnchorsSproutCacheEntry
{ {
bool entered; // This will be false if the anchor is removed from the cache bool entered; // This will be false if the anchor is removed from the cache
ZCIncrementalMerkleTree tree; // The tree itself ZCIncrementalMerkleTree tree; // The tree itself
@ -283,7 +283,20 @@ struct CAnchorsCacheEntry
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view. DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
}; };
CAnchorsCacheEntry() : entered(false), flags(0) {} CAnchorsSproutCacheEntry() : entered(false), flags(0) {}
};
struct CAnchorsSaplingCacheEntry
{
bool entered; // This will be false if the anchor is removed from the cache
ZCSaplingIncrementalMerkleTree tree; // The tree itself
unsigned char flags;
enum Flags {
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
};
CAnchorsSaplingCacheEntry() : entered(false), flags(0) {}
}; };
struct CNullifiersCacheEntry struct CNullifiersCacheEntry
@ -298,14 +311,15 @@ struct CNullifiersCacheEntry
CNullifiersCacheEntry() : entered(false), flags(0) {} CNullifiersCacheEntry() : entered(false), flags(0) {}
}; };
enum NullifierType enum ShieldedType
{ {
SPROUT_NULLIFIER, SPROUT,
SAPLING_NULLIFIER, SAPLING,
}; };
typedef boost::unordered_map<uint256, CCoinsCacheEntry, CCoinsKeyHasher> CCoinsMap; typedef boost::unordered_map<uint256, CCoinsCacheEntry, CCoinsKeyHasher> CCoinsMap;
typedef boost::unordered_map<uint256, CAnchorsCacheEntry, CCoinsKeyHasher> CAnchorsMap; typedef boost::unordered_map<uint256, CAnchorsSproutCacheEntry, CCoinsKeyHasher> CAnchorsSproutMap;
typedef boost::unordered_map<uint256, CAnchorsSaplingCacheEntry, CCoinsKeyHasher> CAnchorsSaplingMap;
typedef boost::unordered_map<uint256, CNullifiersCacheEntry, CCoinsKeyHasher> CNullifiersMap; typedef boost::unordered_map<uint256, CNullifiersCacheEntry, CCoinsKeyHasher> CNullifiersMap;
struct CCoinsStats struct CCoinsStats
@ -326,11 +340,14 @@ struct CCoinsStats
class CCoinsView class CCoinsView
{ {
public: public:
//! Retrieve the tree at a particular anchored root in the chain //! Retrieve the tree (Sprout) at a particular anchored root in the chain
virtual bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const; virtual bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const;
//! Retrieve the tree (Sapling) at a particular anchored root in the chain
virtual bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const;
//! Determine whether a nullifier is spent or not //! Determine whether a nullifier is spent or not
virtual bool GetNullifier(const uint256 &nullifier, NullifierType type) const; virtual bool GetNullifier(const uint256 &nullifier, ShieldedType type) const;
//! Retrieve the CCoins (unspent transaction outputs) for a given txid //! Retrieve the CCoins (unspent transaction outputs) for a given txid
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const; virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
@ -343,14 +360,16 @@ public:
virtual uint256 GetBestBlock() const; virtual uint256 GetBestBlock() const;
//! Get the current "tip" or the latest anchored tree root in the chain //! Get the current "tip" or the latest anchored tree root in the chain
virtual uint256 GetBestAnchor() const; virtual uint256 GetBestAnchor(ShieldedType type) const;
//! Do a bulk modification (multiple CCoins changes + BestBlock change). //! Do a bulk modification (multiple CCoins changes + BestBlock change).
//! The passed mapCoins can be modified. //! The passed mapCoins can be modified.
virtual bool BatchWrite(CCoinsMap &mapCoins, virtual bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers); CNullifiersMap &mapSaplingNullifiers);
@ -370,17 +389,20 @@ protected:
public: public:
CCoinsViewBacked(CCoinsView *viewIn); CCoinsViewBacked(CCoinsView *viewIn);
bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const; bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, NullifierType type) const; bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
uint256 GetBestAnchor() const; uint256 GetBestAnchor(ShieldedType type) const;
void SetBackend(CCoinsView &viewIn); void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers); CNullifiersMap &mapSaplingNullifiers);
bool GetStats(CCoinsStats &stats) const; bool GetStats(CCoinsStats &stats) const;
@ -423,8 +445,10 @@ protected:
*/ */
mutable uint256 hashBlock; mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins; mutable CCoinsMap cacheCoins;
mutable uint256 hashAnchor; mutable uint256 hashSproutAnchor;
mutable CAnchorsMap cacheAnchors; mutable uint256 hashSaplingAnchor;
mutable CAnchorsSproutMap cacheSproutAnchors;
mutable CAnchorsSaplingMap cacheSaplingAnchors;
mutable CNullifiersMap cacheSproutNullifiers; mutable CNullifiersMap cacheSproutNullifiers;
mutable CNullifiersMap cacheSaplingNullifiers; mutable CNullifiersMap cacheSaplingNullifiers;
@ -436,28 +460,35 @@ public:
~CCoinsViewCache(); ~CCoinsViewCache();
// Standard CCoinsView methods // Standard CCoinsView methods
bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const; bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, NullifierType type) const; bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
uint256 GetBestAnchor() const; uint256 GetBestAnchor(ShieldedType type) const;
void SetBestBlock(const uint256 &hashBlock); void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers); CNullifiersMap &mapSaplingNullifiers);
// Adds the tree to mapAnchors and sets the current commitment // Adds the tree to mapSproutAnchors and sets the current commitment
// root to this root. // root to this root.
void PushAnchor(const ZCIncrementalMerkleTree &tree); void PushSproutAnchor(const ZCIncrementalMerkleTree &tree);
// Adds the tree to mapSaplingAnchors and sets the current commitment
// root to this root.
void PushSaplingAnchor(const ZCSaplingIncrementalMerkleTree &tree);
// Removes the current commitment root from mapAnchors and sets // Removes the current commitment root from mapAnchors and sets
// the new current root. // the new current root.
void PopAnchor(const uint256 &rt); void PopAnchor(const uint256 &rt, ShieldedType type);
// Marks nullifiers for a given transaction as spent or not. // Marks nullifiers for a given transaction as spent or not.
void SetNullifiers(const CTransaction& tx, bool spent); void SetNullifiers(const CTransaction& tx, bool spent);
@ -520,6 +551,31 @@ private:
* By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache. * By making the copy constructor private, we prevent accidentally using it when one intends to create a cache on top of a base cache.
*/ */
CCoinsViewCache(const CCoinsViewCache &); CCoinsViewCache(const CCoinsViewCache &);
//! Generalized interface for popping anchors
template<typename Tree, typename Cache, typename CacheEntry>
void AbstractPopAnchor(
const uint256 &newrt,
ShieldedType type,
Cache &cacheAnchors,
uint256 &hash
);
//! Generalized interface for pushing anchors
template<typename Tree, typename Cache, typename CacheIterator, typename CacheEntry>
void AbstractPushAnchor(
const Tree &tree,
ShieldedType type,
Cache &cacheAnchors,
uint256 &hash
);
//! Interface for bringing an anchor into the cache.
template<typename Tree>
void BringBestAnchorIntoCache(
const uint256 &currentRoot,
Tree &tree
);
}; };
#endif // BITCOIN_COINS_H #endif // BITCOIN_COINS_H

View File

@ -19,11 +19,15 @@ class FakeCoinsViewDB : public CCoinsView {
public: public:
FakeCoinsViewDB() {} FakeCoinsViewDB() {}
bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const {
return false; return false;
} }
bool GetNullifier(const uint256 &nf, NullifierType type) const { bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const {
return false;
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false; return false;
} }
@ -47,15 +51,17 @@ public:
return a; return a;
} }
uint256 GetBestAnchor() const { uint256 GetBestAnchor(ShieldedType type) const {
uint256 a; uint256 a;
return a; return a;
} }
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers) { CNullifiersMap &mapSaplingNullifiers) {
return false; return false;

View File

@ -7,6 +7,13 @@
#include "test/data/merkle_path.json.h" #include "test/data/merkle_path.json.h"
#include "test/data/merkle_commitments.json.h" #include "test/data/merkle_commitments.json.h"
#include "test/data/merkle_roots_sapling.json.h"
#include "test/data/merkle_roots_empty_sapling.json.h"
#include "test/data/merkle_serialization_sapling.json.h"
#include "test/data/merkle_witness_serialization_sapling.json.h"
#include "test/data/merkle_path_sapling.json.h"
#include "test/data/merkle_commitments_sapling.json.h"
#include <iostream> #include <iostream>
#include <stdexcept> #include <stdexcept>
@ -51,7 +58,8 @@ void test_tree(
UniValue root_tests, UniValue root_tests,
UniValue ser_tests, UniValue ser_tests,
UniValue witness_ser_tests, UniValue witness_ser_tests,
UniValue path_tests UniValue path_tests,
bool libsnark_test
) )
{ {
size_t witness_ser_i = 0; size_t witness_ser_i = 0;
@ -106,10 +114,9 @@ void test_tree(
ASSERT_THROW(wit.element(), std::runtime_error); ASSERT_THROW(wit.element(), std::runtime_error);
} else { } else {
auto path = wit.path(); auto path = wit.path();
{
expect_test_vector(path_tests[path_i++], path); expect_test_vector(path_tests[path_i++], path);
if (libsnark_test) {
typedef Fr<default_r1cs_ppzksnark_pp> FieldT; typedef Fr<default_r1cs_ppzksnark_pp> FieldT;
protoboard<FieldT> pb; protoboard<FieldT> pb;
@ -188,7 +195,31 @@ TEST(merkletree, vectors) {
UniValue path_tests = read_json(MAKE_STRING(json_tests::merkle_path)); UniValue path_tests = read_json(MAKE_STRING(json_tests::merkle_path));
UniValue commitment_tests = read_json(MAKE_STRING(json_tests::merkle_commitments)); UniValue commitment_tests = read_json(MAKE_STRING(json_tests::merkle_commitments));
test_tree<ZCTestingIncrementalMerkleTree, ZCTestingIncrementalWitness>(commitment_tests, root_tests, ser_tests, witness_ser_tests, path_tests); test_tree<ZCTestingIncrementalMerkleTree, ZCTestingIncrementalWitness>(
commitment_tests,
root_tests,
ser_tests,
witness_ser_tests,
path_tests,
true
);
}
TEST(merkletree, SaplingVectors) {
UniValue root_tests = read_json(MAKE_STRING(json_tests::merkle_roots_sapling));
UniValue ser_tests = read_json(MAKE_STRING(json_tests::merkle_serialization_sapling));
UniValue witness_ser_tests = read_json(MAKE_STRING(json_tests::merkle_witness_serialization_sapling));
UniValue path_tests = read_json(MAKE_STRING(json_tests::merkle_path_sapling));
UniValue commitment_tests = read_json(MAKE_STRING(json_tests::merkle_commitments_sapling));
test_tree<ZCSaplingTestingIncrementalMerkleTree, ZCSaplingTestingIncrementalWitness>(
commitment_tests,
root_tests,
ser_tests,
witness_ser_tests,
path_tests,
false
);
} }
TEST(merkletree, emptyroots) { TEST(merkletree, emptyroots) {
@ -204,6 +235,19 @@ TEST(merkletree, emptyroots) {
ASSERT_TRUE(INCREMENTAL_MERKLE_TREE_DEPTH <= 64); ASSERT_TRUE(INCREMENTAL_MERKLE_TREE_DEPTH <= 64);
} }
TEST(merkletree, EmptyrootsSapling) {
UniValue empty_roots = read_json(MAKE_STRING(json_tests::merkle_roots_empty_sapling));
libzcash::EmptyMerkleRoots<62, libzcash::PedersenHash> emptyroots;
for (size_t depth = 0; depth <= 62; depth++) {
expect_test_vector(empty_roots[depth], emptyroots.empty_root(depth));
}
// Double check that we're testing (at least) all the empty roots we'll use.
ASSERT_TRUE(INCREMENTAL_MERKLE_TREE_DEPTH <= 62);
}
TEST(merkletree, emptyroot) { TEST(merkletree, emptyroot) {
// This literal is the depth-20 empty tree root with the bytes reversed to // This literal is the depth-20 empty tree root with the bytes reversed to
// account for the fact that uint256S() loads a big-endian representation of // account for the fact that uint256S() loads a big-endian representation of
@ -213,6 +257,15 @@ TEST(merkletree, emptyroot) {
ASSERT_TRUE(ZCIncrementalMerkleTree::empty_root() == expected); ASSERT_TRUE(ZCIncrementalMerkleTree::empty_root() == expected);
} }
TEST(merkletree, EmptyrootSapling) {
// This literal is the depth-20 empty tree root with the bytes reversed to
// account for the fact that uint256S() loads a big-endian representation of
// an integer which converted to little-endian internally.
uint256 expected = uint256S("427719cde12e9ef88a2811be36a0ef15018c7674dc8faa76ace727fdbc09af6a");
ASSERT_TRUE(ZCSaplingIncrementalMerkleTree::empty_root() == expected);
}
TEST(merkletree, deserializeInvalid) { TEST(merkletree, deserializeInvalid) {
// attempt to deserialize a small tree from a serialized large tree // attempt to deserialize a small tree from a serialized large tree
// (exceeds depth well-formedness check) // (exceeds depth well-formedness check)

View File

@ -3,13 +3,13 @@
#include "uint256.h" #include "uint256.h"
TEST(PedersenHash, TestAPI) { TEST(PedersenHash, TestAPI) {
const uint256 a = uint256S("0acaa62d40fcdd9192ed35ea9df31660ccf7f6c60566530faaa444fb5d0d410e"); const uint256 a = uint256S("7082b0badf222555f0ca66a0636fef330668cfb957acb74989bb3f02b4655350");
const uint256 b = uint256S("6041357de59ba64959d1b60f93de24dfe5ea1e26ed9e8a73d35b225a1845ba70"); const uint256 b = uint256S("0e5da2185a44dacbce5179b7647857a7fb247bc9f06d8b9a9265d9a7beac8206");
uint256 result; uint256 result;
librustzcash_merkle_hash(25, a.begin(), b.begin(), result.begin()); librustzcash_merkle_hash(25, a.begin(), b.begin(), result.begin());
uint256 expected_result = uint256S("4253b36834b3f64cc6182f1816911e1c9460cb88afeafb155244dd0038ad4717"); uint256 expected_result = uint256S("e8e2b51c00bb224aa8df57f511d306293878896818f41863326fcd2c16cdca42");
ASSERT_TRUE(result == expected_result); ASSERT_TRUE(result == expected_result);
} }

View File

@ -21,11 +21,15 @@ class FakeCoinsViewDB : public CCoinsView {
public: public:
FakeCoinsViewDB() {} FakeCoinsViewDB() {}
bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const {
return false; return false;
} }
bool GetNullifier(const uint256 &nf, NullifierType type) const { bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const {
return false;
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false; return false;
} }
@ -42,15 +46,17 @@ public:
return a; return a;
} }
uint256 GetBestAnchor() const { uint256 GetBestAnchor(ShieldedType type) const {
uint256 a; uint256 a;
return a; return a;
} }
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap saplingNullifiersMap) { CNullifiersMap saplingNullifiersMap) {
return false; return false;

View File

@ -1306,13 +1306,13 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
} }
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) { BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) { BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) {
if (pool.nullifierExists(nf, SPROUT_NULLIFIER)) { if (pool.nullifierExists(nf, SPROUT)) {
return false; return false;
} }
} }
} }
for (const SpendDescription &spendDescription : tx.vShieldedSpend) { for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
if (pool.nullifierExists(spendDescription.nullifier, SAPLING_NULLIFIER)) { if (pool.nullifierExists(spendDescription.nullifier, SAPLING)) {
return false; return false;
} }
} }
@ -2150,8 +2150,19 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
} }
} }
// set the old best anchor back // set the old best Sprout anchor back
view.PopAnchor(blockUndo.old_tree_root); view.PopAnchor(blockUndo.old_sprout_tree_root, SPROUT);
// set the old best Sapling anchor back
// We can get this from the `hashFinalSaplingRoot` of the last block
// However, this is only reliable if the last block was on or after
// the Sapling activation height. Otherwise, the last anchor was the
// empty root.
if (NetworkUpgradeActive(pindex->pprev->nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
view.PopAnchor(pindex->pprev->hashFinalSaplingRoot, SAPLING);
} else {
view.PopAnchor(ZCSaplingIncrementalMerkleTree::empty_root(), SAPLING);
}
// move best block pointer to prevout block // move best block pointer to prevout block
view.SetBestBlock(pindex->pprev->GetBlockHash()); view.SetBestBlock(pindex->pprev->GetBlockHash());
@ -2296,9 +2307,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
view.SetBestBlock(pindex->GetBlockHash()); view.SetBestBlock(pindex->GetBlockHash());
// Before the genesis block, there was an empty tree // Before the genesis block, there was an empty tree
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
pindex->hashAnchor = tree.root(); pindex->hashSproutAnchor = tree.root();
// The genesis block contained no JoinSplits // The genesis block contained no JoinSplits
pindex->hashAnchorEnd = pindex->hashAnchor; pindex->hashFinalSproutRoot = pindex->hashSproutAnchor;
} }
return true; return true;
} }
@ -2331,22 +2342,25 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// Construct the incremental merkle tree at the current // Construct the incremental merkle tree at the current
// block position, // block position,
auto old_tree_root = view.GetBestAnchor(); auto old_sprout_tree_root = view.GetBestAnchor(SPROUT);
// saving the top anchor in the block index as we go. // saving the top anchor in the block index as we go.
if (!fJustCheck) { if (!fJustCheck) {
pindex->hashAnchor = old_tree_root; pindex->hashSproutAnchor = old_sprout_tree_root;
} }
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree sprout_tree;
// This should never fail: we should always be able to get the root // This should never fail: we should always be able to get the root
// that is on the tip of our chain // that is on the tip of our chain
assert(view.GetAnchorAt(old_tree_root, tree)); assert(view.GetSproutAnchorAt(old_sprout_tree_root, sprout_tree));
{ {
// Consistency check: the root of the tree we're given should // Consistency check: the root of the tree we're given should
// match what we asked for. // match what we asked for.
assert(tree.root() == old_tree_root); assert(sprout_tree.root() == old_sprout_tree_root);
} }
ZCSaplingIncrementalMerkleTree sapling_tree;
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
// Grab the consensus branch ID for the block's height // Grab the consensus branch ID for the block's height
auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, Params().GetConsensus()); auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, Params().GetConsensus());
@ -2404,19 +2418,34 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
BOOST_FOREACH(const uint256 &note_commitment, joinsplit.commitments) { BOOST_FOREACH(const uint256 &note_commitment, joinsplit.commitments) {
// Insert the note commitments into our temporary tree. // Insert the note commitments into our temporary tree.
tree.append(note_commitment); sprout_tree.append(note_commitment);
} }
} }
BOOST_FOREACH(const OutputDescription &outputDescription, tx.vShieldedOutput) {
sapling_tree.append(outputDescription.cm);
}
vPos.push_back(std::make_pair(tx.GetHash(), pos)); vPos.push_back(std::make_pair(tx.GetHash(), pos));
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION); pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
} }
view.PushAnchor(tree); view.PushSproutAnchor(sprout_tree);
view.PushSaplingAnchor(sapling_tree);
if (!fJustCheck) { if (!fJustCheck) {
pindex->hashAnchorEnd = tree.root(); pindex->hashFinalSproutRoot = sprout_tree.root();
}
blockundo.old_sprout_tree_root = old_sprout_tree_root;
// If Sapling is active, block.hashFinalSaplingRoot must be the
// same as the root of the Sapling tree
if (NetworkUpgradeActive(pindex->nHeight, chainparams.GetConsensus(), Consensus::UPGRADE_SAPLING)) {
if (block.hashFinalSaplingRoot != sapling_tree.root()) {
return state.DoS(100,
error("ConnectBlock(): block's hashFinalSaplingRoot is incorrect"),
REJECT_INVALID, "bad-sapling-root-in-block");
}
} }
blockundo.old_tree_root = old_tree_root;
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart; int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001); LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
@ -2659,7 +2688,8 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
if (!ReadBlockFromDisk(block, pindexDelete)) if (!ReadBlockFromDisk(block, pindexDelete))
return AbortNode(state, "Failed to read block"); return AbortNode(state, "Failed to read block");
// Apply the block atomically to the chain state. // Apply the block atomically to the chain state.
uint256 anchorBeforeDisconnect = pcoinsTip->GetBestAnchor(); uint256 sproutAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
uint256 saplingAnchorBeforeDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
int64_t nStart = GetTimeMicros(); int64_t nStart = GetTimeMicros();
{ {
CCoinsViewCache view(pcoinsTip); CCoinsViewCache view(pcoinsTip);
@ -2668,7 +2698,8 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
assert(view.Flush()); assert(view.Flush());
} }
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001); LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
uint256 anchorAfterDisconnect = pcoinsTip->GetBestAnchor(); uint256 sproutAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SPROUT);
uint256 saplingAnchorAfterDisconnect = pcoinsTip->GetBestAnchor(SAPLING);
// Write the chain state to disk, if necessary. // Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED)) if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
return false; return false;
@ -2682,10 +2713,15 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL)) if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
mempool.remove(tx, removed, true); mempool.remove(tx, removed, true);
} }
if (anchorBeforeDisconnect != anchorAfterDisconnect) { if (sproutAnchorBeforeDisconnect != sproutAnchorAfterDisconnect) {
// The anchor may not change between block disconnects, // The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet! // in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(anchorBeforeDisconnect); mempool.removeWithAnchor(sproutAnchorBeforeDisconnect, SPROUT);
}
if (saplingAnchorBeforeDisconnect != saplingAnchorAfterDisconnect) {
// The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(saplingAnchorBeforeDisconnect, SAPLING);
} }
} }
@ -2693,7 +2729,7 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) {
UpdateTip(pindexDelete->pprev); UpdateTip(pindexDelete->pprev);
// Get the current commitment tree // Get the current commitment tree
ZCIncrementalMerkleTree newTree; ZCIncrementalMerkleTree newTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), newTree)); assert(pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), newTree));
// Let wallets know transactions went from 1-confirmed to // Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted: // 0-confirmed or conflicted:
BOOST_FOREACH(const CTransaction &tx, block.vtx) { BOOST_FOREACH(const CTransaction &tx, block.vtx) {
@ -2727,7 +2763,7 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
} }
// Get the current commitment tree // Get the current commitment tree
ZCIncrementalMerkleTree oldTree; ZCIncrementalMerkleTree oldTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), oldTree)); assert(pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), oldTree));
// Apply the block atomically to the chain state. // Apply the block atomically to the chain state.
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1; int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
int64_t nTime3; int64_t nTime3;
@ -3928,12 +3964,12 @@ bool static LoadBlockIndexDB()
{ {
CBlockIndex* pindex = item.second; CBlockIndex* pindex = item.second;
// - This relationship will always be true even if pprev has multiple // - This relationship will always be true even if pprev has multiple
// children, because hashAnchor is technically a property of pprev, // children, because hashSproutAnchor is technically a property of pprev,
// not its children. // not its children.
// - This will miss chain tips; we handle the best tip below, and other // - This will miss chain tips; we handle the best tip below, and other
// tips will be handled by ConnectTip during a re-org. // tips will be handled by ConnectTip during a re-org.
if (pindex->pprev) { if (pindex->pprev) {
pindex->pprev->hashAnchorEnd = pindex->hashAnchor; pindex->pprev->hashFinalSproutRoot = pindex->hashSproutAnchor;
} }
} }
@ -3942,8 +3978,8 @@ bool static LoadBlockIndexDB()
if (it == mapBlockIndex.end()) if (it == mapBlockIndex.end())
return true; return true;
chainActive.SetTip(it->second); chainActive.SetTip(it->second);
// Set hashAnchorEnd for the end of best chain // Set hashFinalSproutRoot for the end of best chain
it->second->hashAnchorEnd = pcoinsTip->GetBestAnchor(); it->second->hashFinalSproutRoot = pcoinsTip->GetBestAnchor(SPROUT);
PruneBlockIndexCandidates(); PruneBlockIndexCandidates();

View File

@ -149,6 +149,9 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast();
CCoinsViewCache view(pcoinsTip); CCoinsViewCache view(pcoinsTip);
ZCSaplingIncrementalMerkleTree sapling_tree;
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
// Priority order to process transactions // Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move list<COrphan> vOrphan; // list memory doesn't move
map<uint256, vector<COrphan*> > mapDependers; map<uint256, vector<COrphan*> > mapDependers;
@ -301,6 +304,10 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
UpdateCoins(tx, view, nHeight); UpdateCoins(tx, view, nHeight);
BOOST_FOREACH(const OutputDescription &outDescription, tx.vShieldedOutput) {
sapling_tree.append(outDescription.cm);
}
// Added // Added
pblock->vtx.push_back(tx); pblock->vtx.push_back(tx);
pblocktemplate->vTxFees.push_back(nTxFees); pblocktemplate->vTxFees.push_back(nTxFees);
@ -374,7 +381,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
// Fill in header // Fill in header
pblock->hashPrevBlock = pindexPrev->GetBlockHash(); pblock->hashPrevBlock = pindexPrev->GetBlockHash();
pblock->hashReserved = uint256(); pblock->hashFinalSaplingRoot = sapling_tree.root();
UpdateTime(pblock, Params().GetConsensus(), pindexPrev); UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus()); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
pblock->nSolution.clear(); pblock->nSolution.clear();

View File

@ -112,12 +112,12 @@ uint256 CBlock::CheckMerkleBranch(uint256 hash, const std::vector<uint256>& vMer
std::string CBlock::ToString() const std::string CBlock::ToString() const
{ {
std::stringstream s; std::stringstream s;
s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, hashReserved=%s, nTime=%u, nBits=%08x, nNonce=%s, vtx=%u)\n", s << strprintf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, hashFinalSaplingRoot=%s, nTime=%u, nBits=%08x, nNonce=%s, vtx=%u)\n",
GetHash().ToString(), GetHash().ToString(),
nVersion, nVersion,
hashPrevBlock.ToString(), hashPrevBlock.ToString(),
hashMerkleRoot.ToString(), hashMerkleRoot.ToString(),
hashReserved.ToString(), hashFinalSaplingRoot.ToString(),
nTime, nBits, nNonce.ToString(), nTime, nBits, nNonce.ToString(),
vtx.size()); vtx.size());
for (unsigned int i = 0; i < vtx.size(); i++) for (unsigned int i = 0; i < vtx.size(); i++)

View File

@ -26,7 +26,7 @@ public:
int32_t nVersion; int32_t nVersion;
uint256 hashPrevBlock; uint256 hashPrevBlock;
uint256 hashMerkleRoot; uint256 hashMerkleRoot;
uint256 hashReserved; uint256 hashFinalSaplingRoot;
uint32_t nTime; uint32_t nTime;
uint32_t nBits; uint32_t nBits;
uint256 nNonce; uint256 nNonce;
@ -44,7 +44,7 @@ public:
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrevBlock); READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashReserved); READWRITE(hashFinalSaplingRoot);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
READWRITE(nNonce); READWRITE(nNonce);
@ -56,7 +56,7 @@ public:
nVersion = CBlockHeader::CURRENT_VERSION; nVersion = CBlockHeader::CURRENT_VERSION;
hashPrevBlock.SetNull(); hashPrevBlock.SetNull();
hashMerkleRoot.SetNull(); hashMerkleRoot.SetNull();
hashReserved.SetNull(); hashFinalSaplingRoot.SetNull();
nTime = 0; nTime = 0;
nBits = 0; nBits = 0;
nNonce = uint256(); nNonce = uint256();
@ -118,7 +118,7 @@ public:
block.nVersion = nVersion; block.nVersion = nVersion;
block.hashPrevBlock = hashPrevBlock; block.hashPrevBlock = hashPrevBlock;
block.hashMerkleRoot = hashMerkleRoot; block.hashMerkleRoot = hashMerkleRoot;
block.hashReserved = hashReserved; block.hashFinalSaplingRoot = hashFinalSaplingRoot;
block.nTime = nTime; block.nTime = nTime;
block.nBits = nBits; block.nBits = nBits;
block.nNonce = nNonce; block.nNonce = nNonce;
@ -158,7 +158,7 @@ public:
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(hashPrevBlock); READWRITE(hashPrevBlock);
READWRITE(hashMerkleRoot); READWRITE(hashMerkleRoot);
READWRITE(hashReserved); READWRITE(hashFinalSaplingRoot);
READWRITE(nTime); READWRITE(nTime);
READWRITE(nBits); READWRITE(nBits);
} }

View File

@ -155,7 +155,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx
result.push_back(Pair("bits", strprintf("%08x", block.nBits))); result.push_back(Pair("bits", strprintf("%08x", block.nBits)));
result.push_back(Pair("difficulty", GetDifficulty(blockindex))); result.push_back(Pair("difficulty", GetDifficulty(blockindex)));
result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex())); result.push_back(Pair("chainwork", blockindex->nChainWork.GetHex()));
result.push_back(Pair("anchor", blockindex->hashAnchorEnd.GetHex())); result.push_back(Pair("anchor", blockindex->hashFinalSproutRoot.GetHex()));
UniValue valuePools(UniValue::VARR); UniValue valuePools(UniValue::VARR);
valuePools.push_back(ValuePoolDesc("sprout", blockindex->nChainSproutValue, blockindex->nSproutValue)); valuePools.push_back(ValuePoolDesc("sprout", blockindex->nChainSproutValue, blockindex->nSproutValue));
@ -770,7 +770,7 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp)
obj.push_back(Pair("pruned", fPruneMode)); obj.push_back(Pair("pruned", fPruneMode));
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), tree); pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), tree);
obj.push_back(Pair("commitments", static_cast<uint64_t>(tree.size()))); obj.push_back(Pair("commitments", static_cast<uint64_t>(tree.size())));
CBlockIndex* tip = chainActive.Tip(); CBlockIndex* tip = chainActive.Tip();

View File

@ -25,26 +25,29 @@ namespace
class CCoinsViewTest : public CCoinsView class CCoinsViewTest : public CCoinsView
{ {
uint256 hashBestBlock_; uint256 hashBestBlock_;
uint256 hashBestAnchor_; uint256 hashBestSproutAnchor_;
uint256 hashBestSaplingAnchor_;
std::map<uint256, CCoins> map_; std::map<uint256, CCoins> map_;
std::map<uint256, ZCIncrementalMerkleTree> mapAnchors_; std::map<uint256, ZCIncrementalMerkleTree> mapSproutAnchors_;
std::map<uint256, ZCSaplingIncrementalMerkleTree> mapSaplingAnchors_;
std::map<uint256, bool> mapSproutNullifiers_; std::map<uint256, bool> mapSproutNullifiers_;
std::map<uint256, bool> mapSaplingNullifiers_; std::map<uint256, bool> mapSaplingNullifiers_;
public: public:
CCoinsViewTest() { CCoinsViewTest() {
hashBestAnchor_ = ZCIncrementalMerkleTree::empty_root(); hashBestSproutAnchor_ = ZCIncrementalMerkleTree::empty_root();
hashBestSaplingAnchor_ = ZCSaplingIncrementalMerkleTree::empty_root();
} }
bool GetAnchorAt(const uint256& rt, ZCIncrementalMerkleTree &tree) const { bool GetSproutAnchorAt(const uint256& rt, ZCIncrementalMerkleTree &tree) const {
if (rt == ZCIncrementalMerkleTree::empty_root()) { if (rt == ZCIncrementalMerkleTree::empty_root()) {
ZCIncrementalMerkleTree new_tree; ZCIncrementalMerkleTree new_tree;
tree = new_tree; tree = new_tree;
return true; return true;
} }
std::map<uint256, ZCIncrementalMerkleTree>::const_iterator it = mapAnchors_.find(rt); std::map<uint256, ZCIncrementalMerkleTree>::const_iterator it = mapSproutAnchors_.find(rt);
if (it == mapAnchors_.end()) { if (it == mapSproutAnchors_.end()) {
return false; return false;
} else { } else {
tree = it->second; tree = it->second;
@ -52,18 +55,34 @@ public:
} }
} }
bool GetNullifier(const uint256 &nf, NullifierType type) const bool GetSaplingAnchorAt(const uint256& rt, ZCSaplingIncrementalMerkleTree &tree) const {
if (rt == ZCSaplingIncrementalMerkleTree::empty_root()) {
ZCSaplingIncrementalMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, ZCSaplingIncrementalMerkleTree>::const_iterator it = mapSaplingAnchors_.find(rt);
if (it == mapSaplingAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const
{ {
const std::map<uint256, bool>* mapToUse; const std::map<uint256, bool>* mapToUse;
switch (type) { switch (type) {
case SPROUT_NULLIFIER: case SPROUT:
mapToUse = &mapSproutNullifiers_; mapToUse = &mapSproutNullifiers_;
break; break;
case SAPLING_NULLIFIER: case SAPLING:
mapToUse = &mapSaplingNullifiers_; mapToUse = &mapSaplingNullifiers_;
break; break;
default: default:
throw std::runtime_error("Unknown nullifier type"); throw std::runtime_error("Unknown shielded type");
} }
std::map<uint256, bool>::const_iterator it = mapToUse->find(nf); std::map<uint256, bool>::const_iterator it = mapToUse->find(nf);
if (it == mapToUse->end()) { if (it == mapToUse->end()) {
@ -75,7 +94,18 @@ public:
} }
} }
uint256 GetBestAnchor() const { return hashBestAnchor_; } uint256 GetBestAnchor(ShieldedType type) const {
switch (type) {
case SPROUT:
return hashBestSproutAnchor_;
break;
case SAPLING:
return hashBestSaplingAnchor_;
break;
default:
throw std::runtime_error("Unknown shielded type");
}
}
bool GetCoins(const uint256& txid, CCoins& coins) const bool GetCoins(const uint256& txid, CCoins& coins) const
{ {
@ -114,8 +144,10 @@ public:
bool BatchWrite(CCoinsMap& mapCoins, bool BatchWrite(CCoinsMap& mapCoins,
const uint256& hashBlock, const uint256& hashBlock,
const uint256& hashAnchor, const uint256& hashSproutAnchor,
CAnchorsMap& mapAnchors, const uint256& hashSaplingAnchor,
CAnchorsSproutMap& mapSproutAnchors,
CAnchorsSaplingMap& mapSaplingAnchors,
CNullifiersMap& mapSproutNullifiers, CNullifiersMap& mapSproutNullifiers,
CNullifiersMap& mapSaplingNullifiers) CNullifiersMap& mapSaplingNullifiers)
{ {
@ -127,25 +159,38 @@ public:
} }
mapCoins.erase(it++); mapCoins.erase(it++);
} }
for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end(); ) { for (CAnchorsSproutMap::iterator it = mapSproutAnchors.begin(); it != mapSproutAnchors.end(); ) {
if (it->second.entered) { if (it->second.entered) {
std::map<uint256, ZCIncrementalMerkleTree>::iterator ret = std::map<uint256, ZCIncrementalMerkleTree>::iterator ret =
mapAnchors_.insert(std::make_pair(it->first, ZCIncrementalMerkleTree())).first; mapSproutAnchors_.insert(std::make_pair(it->first, ZCIncrementalMerkleTree())).first;
ret->second = it->second.tree; ret->second = it->second.tree;
} else { } else {
mapAnchors_.erase(it->first); mapSproutAnchors_.erase(it->first);
} }
mapAnchors.erase(it++); mapSproutAnchors.erase(it++);
}
for (CAnchorsSaplingMap::iterator it = mapSaplingAnchors.begin(); it != mapSaplingAnchors.end(); ) {
if (it->second.entered) {
std::map<uint256, ZCSaplingIncrementalMerkleTree>::iterator ret =
mapSaplingAnchors_.insert(std::make_pair(it->first, ZCSaplingIncrementalMerkleTree())).first;
ret->second = it->second.tree;
} else {
mapSaplingAnchors_.erase(it->first);
}
mapSaplingAnchors.erase(it++);
} }
BatchWriteNullifiers(mapSproutNullifiers, mapSproutNullifiers_); BatchWriteNullifiers(mapSproutNullifiers, mapSproutNullifiers_);
BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_); BatchWriteNullifiers(mapSaplingNullifiers, mapSaplingNullifiers_);
mapCoins.clear(); mapCoins.clear();
mapAnchors.clear(); mapSproutAnchors.clear();
mapSaplingAnchors.clear();
hashBestBlock_ = hashBlock; hashBestBlock_ = hashBlock;
hashBestAnchor_ = hashAnchor; hashBestSproutAnchor_ = hashSproutAnchor;
hashBestSaplingAnchor_ = hashSaplingAnchor;
return true; return true;
} }
@ -161,7 +206,8 @@ public:
{ {
// Manually recompute the dynamic usage of the whole data, and compare it. // Manually recompute the dynamic usage of the whole data, and compare it.
size_t ret = memusage::DynamicUsage(cacheCoins) + size_t ret = memusage::DynamicUsage(cacheCoins) +
memusage::DynamicUsage(cacheAnchors) + memusage::DynamicUsage(cacheSproutAnchors) +
memusage::DynamicUsage(cacheSaplingAnchors) +
memusage::DynamicUsage(cacheSproutNullifiers) + memusage::DynamicUsage(cacheSproutNullifiers) +
memusage::DynamicUsage(cacheSaplingNullifiers); memusage::DynamicUsage(cacheSaplingNullifiers);
for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) { for (CCoinsMap::iterator it = cacheCoins.begin(); it != cacheCoins.end(); it++) {
@ -215,11 +261,11 @@ BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
void checkNullifierCache(const CCoinsViewCacheTest &cache, const TxWithNullifiers &txWithNullifiers, bool shouldBeInCache) { void checkNullifierCache(const CCoinsViewCacheTest &cache, const TxWithNullifiers &txWithNullifiers, bool shouldBeInCache) {
// Make sure the nullifiers have not gotten mixed up // Make sure the nullifiers have not gotten mixed up
BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.sproutNullifier, SAPLING_NULLIFIER)); BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.sproutNullifier, SAPLING));
BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.saplingNullifier, SPROUT_NULLIFIER)); BOOST_CHECK(!cache.GetNullifier(txWithNullifiers.saplingNullifier, SPROUT));
// Check if the nullifiers either are or are not in the cache // Check if the nullifiers either are or are not in the cache
bool containsSproutNullifier = cache.GetNullifier(txWithNullifiers.sproutNullifier, SPROUT_NULLIFIER); bool containsSproutNullifier = cache.GetNullifier(txWithNullifiers.sproutNullifier, SPROUT);
bool containsSaplingNullifier = cache.GetNullifier(txWithNullifiers.saplingNullifier, SAPLING_NULLIFIER); bool containsSaplingNullifier = cache.GetNullifier(txWithNullifiers.saplingNullifier, SAPLING);
BOOST_CHECK(containsSproutNullifier == shouldBeInCache); BOOST_CHECK(containsSproutNullifier == shouldBeInCache);
BOOST_CHECK(containsSaplingNullifier == shouldBeInCache); BOOST_CHECK(containsSaplingNullifier == shouldBeInCache);
} }
@ -325,21 +371,21 @@ BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
tree.append(cm); tree.append(cm);
// Add the anchor // Add the anchor
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
// Remove the anchor // Remove the anchor
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache1.Flush(); cache1.Flush();
// Add the anchor back // Add the anchor back
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
// The base contains the anchor, of course! // The base contains the anchor, of course!
{ {
ZCIncrementalMerkleTree checktree; ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree)); BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); BOOST_CHECK(checktree.root() == tree.root());
} }
} }
@ -355,15 +401,15 @@ BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
tree.append(cm); tree.append(cm);
// Add the anchor and flush to disk // Add the anchor and flush to disk
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
// Remove the anchor, but don't flush yet! // Remove the anchor, but don't flush yet!
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
{ {
CCoinsViewCacheTest cache2(&cache1); // Build cache on top CCoinsViewCacheTest cache2(&cache1); // Build cache on top
cache2.PushAnchor(tree); // Put the same anchor back! cache2.PushSproutAnchor(tree); // Put the same anchor back!
cache2.Flush(); // Flush to cache1 cache2.Flush(); // Flush to cache1
} }
@ -372,7 +418,7 @@ BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
// treestate... // treestate...
{ {
ZCIncrementalMerkleTree checktree; ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree)); BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks. BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
} }
@ -381,7 +427,7 @@ BOOST_AUTO_TEST_CASE(anchor_pop_regression_test)
cache1.Flush(); cache1.Flush();
{ {
ZCIncrementalMerkleTree checktree; ZCIncrementalMerkleTree checktree;
BOOST_CHECK(cache1.GetAnchorAt(tree.root(), checktree)); BOOST_CHECK(cache1.GetSproutAnchorAt(tree.root(), checktree));
BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks. BOOST_CHECK(checktree.root() == tree.root()); // Oh, shucks.
} }
} }
@ -398,12 +444,12 @@ BOOST_AUTO_TEST_CASE(anchor_regression_test)
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash(); uint256 cm = GetRandHash();
tree.append(cm); tree.append(cm);
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
BOOST_CHECK(cache1.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetAnchorAt(tree.root(), tree)); BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
} }
// Also correct behavior: // Also correct behavior:
@ -415,13 +461,13 @@ BOOST_AUTO_TEST_CASE(anchor_regression_test)
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash(); uint256 cm = GetRandHash();
tree.append(cm); tree.append(cm);
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache1.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache1.Flush(); cache1.Flush();
BOOST_CHECK(cache1.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetAnchorAt(tree.root(), tree)); BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
} }
// Works because we bring the anchor in from parent cache. // Works because we bring the anchor in from parent cache.
@ -433,19 +479,19 @@ BOOST_AUTO_TEST_CASE(anchor_regression_test)
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash(); uint256 cm = GetRandHash();
tree.append(cm); tree.append(cm);
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
{ {
// Pop anchor. // Pop anchor.
CCoinsViewCacheTest cache2(&cache1); CCoinsViewCacheTest cache2(&cache1);
BOOST_CHECK(cache2.GetAnchorAt(tree.root(), tree)); BOOST_CHECK(cache2.GetSproutAnchorAt(tree.root(), tree));
cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache2.Flush(); cache2.Flush();
} }
BOOST_CHECK(cache1.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetAnchorAt(tree.root(), tree)); BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
} }
// Was broken: // Was broken:
@ -457,18 +503,18 @@ BOOST_AUTO_TEST_CASE(anchor_regression_test)
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
uint256 cm = GetRandHash(); uint256 cm = GetRandHash();
tree.append(cm); tree.append(cm);
cache1.PushAnchor(tree); cache1.PushSproutAnchor(tree);
cache1.Flush(); cache1.Flush();
{ {
// Pop anchor. // Pop anchor.
CCoinsViewCacheTest cache2(&cache1); CCoinsViewCacheTest cache2(&cache1);
cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root()); cache2.PopAnchor(ZCIncrementalMerkleTree::empty_root(), SPROUT);
cache2.Flush(); cache2.Flush();
} }
BOOST_CHECK(cache1.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); BOOST_CHECK(cache1.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
BOOST_CHECK(!cache1.GetAnchorAt(tree.root(), tree)); BOOST_CHECK(!cache1.GetSproutAnchorAt(tree.root(), tree));
} }
} }
@ -502,22 +548,22 @@ BOOST_AUTO_TEST_CASE(anchors_flush_test)
{ {
CCoinsViewCacheTest cache(&base); CCoinsViewCacheTest cache(&base);
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
appendRandomCommitment(tree); appendRandomCommitment(tree);
newrt = tree.root(); newrt = tree.root();
cache.PushAnchor(tree); cache.PushSproutAnchor(tree);
cache.Flush(); cache.Flush();
} }
{ {
CCoinsViewCacheTest cache(&base); CCoinsViewCacheTest cache(&base);
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
// Get the cached entry. // Get the cached entry.
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
uint256 check_rt = tree.root(); uint256 check_rt = tree.root();
@ -610,13 +656,13 @@ BOOST_AUTO_TEST_CASE(anchors_test)
CCoinsViewTest base; CCoinsViewTest base;
CCoinsViewCacheTest cache(&base); CCoinsViewCacheTest cache(&base);
BOOST_CHECK(cache.GetBestAnchor() == ZCIncrementalMerkleTree::empty_root()); BOOST_CHECK(cache.GetBestAnchor(SPROUT) == ZCIncrementalMerkleTree::empty_root());
{ {
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), tree));
BOOST_CHECK(cache.GetBestAnchor() == tree.root()); BOOST_CHECK(cache.GetBestAnchor(SPROUT) == tree.root());
appendRandomCommitment(tree); appendRandomCommitment(tree);
appendRandomCommitment(tree); appendRandomCommitment(tree);
appendRandomCommitment(tree); appendRandomCommitment(tree);
@ -631,12 +677,12 @@ BOOST_AUTO_TEST_CASE(anchors_test)
uint256 newrt = tree.root(); uint256 newrt = tree.root();
uint256 newrt2; uint256 newrt2;
cache.PushAnchor(tree); cache.PushSproutAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor() == newrt); BOOST_CHECK(cache.GetBestAnchor(SPROUT) == newrt);
{ {
ZCIncrementalMerkleTree confirm_same; ZCIncrementalMerkleTree confirm_same;
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), confirm_same)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), confirm_same));
BOOST_CHECK(confirm_same.root() == newrt); BOOST_CHECK(confirm_same.root() == newrt);
} }
@ -646,26 +692,26 @@ BOOST_AUTO_TEST_CASE(anchors_test)
newrt2 = tree.root(); newrt2 = tree.root();
cache.PushAnchor(tree); cache.PushSproutAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor() == newrt2); BOOST_CHECK(cache.GetBestAnchor(SPROUT) == newrt2);
ZCIncrementalMerkleTree test_tree; ZCIncrementalMerkleTree test_tree;
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), test_tree)); BOOST_CHECK(cache.GetSproutAnchorAt(cache.GetBestAnchor(SPROUT), test_tree));
BOOST_CHECK(tree.root() == test_tree.root()); BOOST_CHECK(tree.root() == test_tree.root());
{ {
ZCIncrementalMerkleTree test_tree2; ZCIncrementalMerkleTree test_tree2;
cache.GetAnchorAt(newrt, test_tree2); cache.GetSproutAnchorAt(newrt, test_tree2);
BOOST_CHECK(test_tree2.root() == newrt); BOOST_CHECK(test_tree2.root() == newrt);
} }
{ {
cache.PopAnchor(newrt); cache.PopAnchor(newrt, SPROUT);
ZCIncrementalMerkleTree obtain_tree; ZCIncrementalMerkleTree obtain_tree;
assert(!cache.GetAnchorAt(newrt2, obtain_tree)); // should have been popped off assert(!cache.GetSproutAnchorAt(newrt2, obtain_tree)); // should have been popped off
assert(cache.GetAnchorAt(newrt, obtain_tree)); assert(cache.GetSproutAnchorAt(newrt, obtain_tree));
assert(obtain_tree.root() == newrt); assert(obtain_tree.root() == newrt);
} }

View File

@ -0,0 +1,18 @@
[
"556f3af94225d46b1ef652abc9005dee873b2e245eef07fd5be587e0f21023b0",
"d814b127a6c6b8f07ed03f0f6e2843ff04c9851ff824a4e5b4dad5b5f3475722",
"ec030e6d7460f91668cc842ceb78cdb54470469e78cd59cf903d3a6e1aa03e7c",
"b0a0d08406b9e3693ee4c062bd1e6816f95bf14f5a13aafa1d57942c6c1d4250",
"92fc3e7298eb327a88abcc406fbe595e45dddd9b4209803b2e0baa3a8663ecaa",
"f607dd230ada93d14f4de1d9008a5e64a59af87c2e4f64a5f9e55e0cd44867f8",
"ae0bfc1e123edcb6252251611650f3667371f781b60302385c414716c75e8abc",
"91a5e54bf9a9b57e1c163904999ad1527f1e126c685111e18193decca2dd1ada",
"c674f7836089063143fc18b673b2d92f888c63380e3680385d47bcdbd5fe273a",
"7c1dbdb260441b89a08ba411d5f8406e81abd9dc85382f307999fdf77d8fcac8",
"02372c746664e0898576972ca6d0500c7c8ec42f144622349d133b06e837faf0",
"08c6d7dd3d2e387f7b84d6769f2b6cbe308918ab81e0f7321bd0945868d7d4e6",
"a6e8c4061f2ad984d19f2c0a4436b9800e529069c0b0d3186d4683e83bb7eb8c",
"837cc2391338956026521beca5c81b541b7f2d1ead7758bf4d1588dbbcb8fa22",
"1cc467cfd2b504e156c9a38bc5c0e4f5ea6cc208054d2d0653a7e561ac3a3ef4",
"15ac4057a9a94536eca9802de65e985319e89627c9c64bc94626b712bc61363a"
]

View File

@ -0,0 +1,122 @@
[
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620fe60df1ce575aaab663d158a20f9e560f7bdc5179f1527b3a33e4f587b44ffbc20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620c054f0548d44fb121f3e22fe87517c806628adfd0f3cddc67c982f4adc26b62a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620c054f0548d44fb121f3e22fe87517c806628adfd0f3cddc67c982f4adc26b62a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c216206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096200504f659ffb9358e5fba924944396b345006a0abc2cbfd207c97d6b2323bdc2020cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096200504f659ffb9358e5fba924944396b345006a0abc2cbfd207c97d6b2323bdc2020cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096200504f659ffb9358e5fba924944396b345006a0abc2cbfd207c97d6b2323bdc20206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096200504f659ffb9358e5fba924944396b345006a0abc2cbfd207c97d6b2323bdc20206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096207315163946951eb4895ff12f6b0724c6062f021e669b99fed24dfacd28c9c04420cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096207315163946951eb4895ff12f6b0724c6062f021e669b99fed24dfacd28c9c04420cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096207315163946951eb4895ff12f6b0724c6062f021e669b99fed24dfacd28c9c044206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096207315163946951eb4895ff12f6b0724c6062f021e669b99fed24dfacd28c9c044206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620fe60df1ce575aaab663d158a20f9e560f7bdc5179f1527b3a33e4f587b44ffbc20f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620c4952dd79bbb887a721fde83eeb847a32117311e7dd0d107625ab11063b7b7aa20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620c4952dd79bbb887a721fde83eeb847a32117311e7dd0d107625ab11063b7b7aa20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620c4952dd79bbb887a721fde83eeb847a32117311e7dd0d107625ab11063b7b7aa206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620c4952dd79bbb887a721fde83eeb847a32117311e7dd0d107625ab11063b7b7aa206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620c4a10647b2ed9dfd93323f5b2044550c5763cf198da5297d1778c894337253c620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620c4a10647b2ed9dfd93323f5b2044550c5763cf198da5297d1778c894337253c620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"0420c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff09620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"04203f117e549d8140c4bd97b3d8d73dfeaaa458491730e870e7fd4a3d472493c31c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"04206eb318a627c0ff6ffcef86f2353dcec1c7a51aaea37aa43182020fc06c1df9da20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620fe60df1ce575aaab663d158a20f9e560f7bdc5179f1527b3a33e4f587b44ffbc20c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb648201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb648201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb648201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb648201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb64820197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb64820197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb64820197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"042078bbb59b43ce3baff1db1248b4493bc9bd4dd6c1a3b62b1d0d0a5433b0abb64820197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620c91d1d80ecb6cd0be622f9c277c61d19530c86abcc673b93c279abd8384d787620c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620c91d1d80ecb6cd0be622f9c277c61d19530c86abcc673b93c279abd8384d7876203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d04260201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d04260201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d04260201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d04260201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d0426020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d0426020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d0426020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"04208c026cdb0a983dec0d6a34f397abc8f0839514454f87632ce6d8102c25d0426020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381020c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c21620e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa3810203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c216201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080a00000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"0420677c207a1612b09f2e5d38d372d6e6358bc7223a6c7da695d602501e7a01033c20197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4207417b3f92f30771c261160269f694ec7c14b21cf14e01735460e9e9b47f6dffc20e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381020c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4207417b3f92f30771c261160269f694ec7c14b21cf14e01735460e9e9b47f6dffc20e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa3810203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4207417b3f92f30771c261160269f694ec7c14b21cf14e01735460e9e9b47f6dffc201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080a00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4207417b3f92f30771c261160269f694ec7c14b21cf14e01735460e9e9b47f6dffc201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020b00000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c4201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c4201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c4201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c4201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c420197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c420197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c420197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"042020307a3cff449ca02018e525dbd37e2180b023dbe68ffbff1218f0f1149f12c420197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201ef29413a2eb9b0aea89d61def9e74f61453bc5b8a2799e86020ec1468c71c7820e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381020c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201ef29413a2eb9b0aea89d61def9e74f61453bc5b8a2799e86020ec1468c71c7820e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa3810203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201ef29413a2eb9b0aea89d61def9e74f61453bc5b8a2799e86020ec1468c71c78201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080a00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4201ef29413a2eb9b0aea89d61def9e74f61453bc5b8a2799e86020ec1468c71c78201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020b00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a20fe60df1ce575aaab663d158a20f9e560f7bdc5179f1527b3a33e4f587b44ffbc2022fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c830c00000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e37694636201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e37694636201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e37694636201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e37694636201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e3769463620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e3769463620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e3769463620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"04207493b4b9a35a32120f630e420efc048a71c0d73d6910561e8ba6076e3769463620197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4209c308bfafdf79fafdad22d765cdc484c4c3360878d2de6d3cd95069a9db58c6a20e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381020c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4209c308bfafdf79fafdad22d765cdc484c4c3360878d2de6d3cd95069a9db58c6a20e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa3810203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4209c308bfafdf79fafdad22d765cdc484c4c3360878d2de6d3cd95069a9db58c6a201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080a00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4209c308bfafdf79fafdad22d765cdc484c4c3360878d2de6d3cd95069a9db58c6a201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020b00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a2024714e547866d846699654cf8ffa6245589f518fc575a833966236cd38ce2a0a2022fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c830c00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a2024714e547866d846699654cf8ffa6245589f518fc575a833966236cd38ce2a0a208cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60d00000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d40800201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80000000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d40800201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e20cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a20b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550100000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d40800201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca2050421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00200000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d40800201dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e206a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca207c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0300000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60400000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e620022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142620aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920500000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910600000000000000",
"0420920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080020197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e62065d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3420bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0700000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4208850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0220e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381020c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0800000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4208850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0220e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa3810203a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60900000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4208850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae02201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080a00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4208850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae02201be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda20f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020b00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a20bbb52416fefd25274a8a4e9513319cd7b85fa96244796ff402755618d717a1b02022fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c830c00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a20bbb52416fefd25274a8a4e9513319cd7b85fa96244796ff402755618d717a1b0208cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60d00000000000000",
"04206de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac420d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a206ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e203a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac150e00000000000000"
]

View File

@ -0,0 +1,65 @@
[
"8000000000000000000000000000000000000000000000000000000000000000",
"fe60df1ce575aaab663d158a20f9e560f7bdc5179f1527b3a33e4f587b44ffbc",
"1f2610f0650d97f4ecde80d8dab101794631236623c51df290320b2a4c18c216",
"c22d0dcb70b0a84c3440f839a64c9e5259066fff49f6c96a2068bb7fa70ff096",
"52e5469fe719b6afe793c04001a3d92b5af5fcfdf8ba48be2b525ba2db9bf512",
"30560b31aabdc42d32187b57a8c5ed1a0705f0c4079c04fe0c2b70c86612664a",
"056aa19504cf135de6edce4385c447a52888e39dc3816dd29ce85e5cc47d1fd4",
"ff80c661d1fc13591c8d94bd5e5b479efda5ad4e4dc2c232e2c0861903abe716",
"daf8231d59386bd3e9251f010bbe38f44c52275364582f0a1e37200e743e3d56",
"bf14721cc3074ed98066b6eb5f4d83d4a19194915d82ea17e84769d42a62d432",
"7e032b38db8a2e7f9656907f6f74fb7dda94f1d9738c602fbf088d96ac531fc4",
"125abf9164a1f289b4c500c1d871fc88be31c05ad18fd2d96aec2666ed4b70e8",
"69fdd736cce22c44a1050f58fbf4d97db711161302615f46a3d2376a7fdcf556",
"f183596d48ace7d5fe015614c0e466853552cd0badfe4a7e06e387768810b734",
"cd2470c445ae60707986368f7d9db4b34215fb07f6f589677e620c2f42e5840a",
"dcd9f71637d61be27d7b0d0dccd89f9d83de33125b398a686fd05b7eed566026",
"b0152c9b87ce45a02011ed82bbbfce63fb25ae48dbd8cda2fb8cb88d5c2e45c2",
"171bdcbad563410aa9b04ba1fc735151417d450413ae6beef96132bf7310804c",
"a543382d65c96824096b2e625070c7304e2a178a2652a3f0b802bcd9b6ba9f34",
"7f1b88575e956c7332291accb93c9ec53807ca3f511c7699b0bed7b220625112",
"aab00f50fa8f16823bcb954c390ea36950d1e155d19b3cd654781892ac9f1b18",
"73896fbbeea33cf50e1983c24b9395637bcbd1aae6a80675118e1fcc34c4999c",
"e198373975b7953b97d3a105146035492b85dc846f23fe027badd917707b5750",
"e8cede5b9bd42b85d0b188c252bcd3e51c0347b9da6d467d46c0d084db29049c",
"a16cfc7bd8c30be41d110dfc3dd924b05c6b8760e9383a032ef171b2b48ee376",
"448eafd2ff159deae09074cac05dad691b583720c32cf832a16d3d476727bab0",
"5b695b68bd8aa44169d65cdba5ba214cbfb7acf3be934cc147884416ee976ea6",
"41443e9afbf3289a9249f8234ce1f5d2f39ad80e6bf2251d5f4f41bdaf74fe78",
"97c3ab5715d6abd6bdca6854005b6937445b2e13b9adc9dcb6088e9e09681e54",
"9a892c8bfb1327c1f0080b9df0a00ccc5d80e9097c552ef877dec16d68b2a2fc",
"116b047f5a11972b57b36f67e06b3afc010f50e97e6621d744a82dabc7da770a",
"885d2752a3eea80578daff0d588b06077ad1301de5e87209b0714d36d68ab22a",
"6aaf09bcfd27e7ac76aa8fdc74768c0115efa036be11288af89e2ee1cd197742",
"97594310912cc3bcb754e3ba8a60ed8d0f50652475c5bd654191c8faee138ab8",
"a7738d727058a69732e6b75036b321395a4c03b5fa8c482434e03642d6eda2a6",
"c3844d5cd1311e13cb78bf621c93aca5981d03caa7c156310e8c3297878eeb8a",
"798c68f9549fadaffa21b6e557c1c5ead3d16d2db3ac3de7f29cfe2f351e2436",
"87b16cdd406cde2693803894071336fb9efbff101fec12c3bec481190ad22b3a",
"6055d5f60d1b202ab6d7a8e2f63fe434e1c2e389262a82e27b31ed07565f60f4",
"6bfbf247dc13e36239738575bafe5cc13fe5b2f505f870f65262101ede3233e8",
"57c269b80810b91891cf716e1857bf53826545e00f79ba4f8b915a2fd6267c38",
"896bc8135fe0783b54e28817e3c7bbc8bfccd1e022a41465f9c201dab1e42ff2",
"40d35aff33fb5e8c384d4c8252b41f71e9d423a548967a09c4f91f3eef2c38c8",
"2803400ed4bad786302917a17420f69e1fbcd23ec296eb6c199911b3c3dcd8fc",
"5bbef9832cec827a42a152cfd890822866063a96b59507573b1a8c2beb3d0bcc",
"71ad975ec2e034795969eeb387c342140c036b34c972011d34fa040a229df3f8",
"53de2c90267917af80d36fd051cdc89e00182a625c466571028aafdec64d79e0",
"9f713993500d3fa1cd16358624a6aa45ad11e479b5c340d09ff5f40e5a35127c",
"054ef1a5769d76af58eb30f0f1c73cd7bac9638fd92cb677ea1e78fba89e1396",
"45d831a19c20a00dface52b15acea0ea2d5cde27e165939d0fe4b9ff4544f618",
"ab33f64bb9c0cd54682f6c4fd6bf4a5e702f3f2f8484e4ebb8f2417ff2ba9260",
"e4cce49ee7b2685d29e29b079f2eafb0bc4ad8c135bb6b37563bc8e92a5dc2bc",
"75629b841de3fabb0798f802797d5384c0b828485d8056c4b1c58d39fefe881c",
"38ea6af92304ca8ed7c57f50a1578722d096df4cba1bd225c79377a2a1fbab3c",
"f300ab0bf7fe78c2f73da8d557a75a89d9c0b0176375d94b69be97af52bb74f2",
"23854c956b5f6ae2ca6b4b64311b62887913b911e65affe4383ff8c8e6387258",
"390b494667d4ada2da0d41d3ff9980cb20326d2c2c348ad6c4a4fbe1ecbad82c",
"b35578a91591060f7b8869f4cfbeb4e7e7b7b2cb62994e26fa7815aaa4811278",
"af7b7d4bf7952008f95387b59366d8730a35b33103d1e59ae3bf37dae1fe3e3a",
"24ba940af944e3d1d0e43a72d7c15694e52f6b0c084f0ed677f4836346d7285a",
"406a0b8e00cb09cef5159d952c01a531d03fdc3026f75ca21429ddc0c924642a",
"87999570ec0ae957e5052c3e3d37a7146ffc8a5cdbd57ebc9862a9e239943222",
"aff7851ce8e5ba4047cc539bcc1b2df062b8b571efeeb49ea023d3433bae8246"
]

View File

@ -0,0 +1,18 @@
[
"d550175b5487e5ee74fc74703306b372ca50d6a0adc18e3ce5f7aed5951a641c",
"cfa1dadc77cf65b09e19100c26570c80520844167e926bc0314e9d6730c93626",
"7a1afdd852818faae15aed1d36970fadc57ef44c32d8052194549471e2dfd0d8",
"4a649f3597a687b4e254d064c18cd08419424d208abb8e8b7abf46abcdc8492a",
"bd8b624aea2a2883762ff07e0944943542a7de14d6fe9da7c0e2afe55c0da306",
"0daf0cd2fc50eee569b90d94421e78b27f0a2f598c1a833a350c6c075cc4bba2",
"c6f383f0a8fa5127262e2b7602c87a65e4ed9a80aab2f6ee5df6a33fa78a3298",
"d1ae9b7370c5892bfa669915aecade849b01e36689b36bbe1fa46846edacbff0",
"0823f19e48c98093c43b455723b04eb047d2bdebcfbdd22ece8f68cc26ee0b70",
"541d51b310300abd11032f0fbc9dfa389f653e884a81016a3ab482ff30d64420",
"82a9f0ffcf811b98c5ffc4e29acda3d7e5b7834faecc7624bbb2985b949a506a",
"32dbec464312bbf6b5cfaabe5fd913168e27f08f1bae5bd9ec67436c0376b34c",
"12402e3a518b911712e5bcc16ff90ab22bcb6aa6d2aa7100e359b2b6e20bf3aa",
"03ae56fee9cfe40e369d9204402b0b0c7b64c18fbd08abbef8d245cbeaafadb6",
"c99c2d22235998b9f1b34f7b35657294da004c860cea4946f9e143843f837b9c",
"d59c6ef4a7083993049e034fee53bf770c5c7cfd0b9491b1aca2c6471a316dca"
]

View File

@ -0,0 +1,18 @@
[
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f550000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d800",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60803011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"01f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0003016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4",
"01f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c013a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac1503016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4"
]

View File

@ -0,0 +1,138 @@
[
"00000001b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5500",
"00000002b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d800",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000001225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d800",
"00000002b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d801017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000001225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d801017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8000001017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0000",
"00000003b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000002225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80001cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a00",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b000",
"00000003b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000002225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80001cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca000101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc920000",
"00000003b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000002225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80001cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca000101aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f600",
"00000003b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00010165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e34",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000002225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00010165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e34",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80001cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a0101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00010165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e34",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b00101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00010165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e34",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca000101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00010165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e34",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f60101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0000",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6000101bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae0000",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e00",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e00",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e00",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142600",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142600",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a59100",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a59101013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e60001013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c60000",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a59101013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e60001013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c00",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6000101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020001011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020000",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4000101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020000",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6000101f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7ce184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381000",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381000",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60800",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a59101018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e60001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7ce184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60000",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60000",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60000",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60803011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac40001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60000",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e01018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd56142601018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a59101018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e60001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83020001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7ce184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c8300",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa381001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c8300",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60801018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c8300",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60803011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac40001018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c8300",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac40122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c8300",
"00000004b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000003225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80002cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0250421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca011dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e0101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd5614260101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a5910101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e6000101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0002016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7ce184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa38100101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0001016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa38100101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0001016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6080101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0001016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60803011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4000101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0001016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac40122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c830101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0000",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4000101f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0000",
"00000005b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f55000004225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d8cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01b02310f2e087e55bfd07ef5e242e3b87ee5d00c9ab52f61e6bd42542f93a6f5501225747f3b5d5dab4e5a424f81f85c904ff43286e0f3fd07ef0b8c6a627b114d80003cb37f4591361cd846141e7852c3726b37dd9b011cc55eef2ac931dfe769ff55a1dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca0350421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b01dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"017c3ea01a6e3a3d90cf59cd789e467044b5cd78eb2c84cc6816f960746d0e03ec0150421d6c2c94571dfaaa135a4ff15bf916681ebd62c0e43e69e3b90684d0a0b001016a0fa2d0c5c7b8236092de86d99068ce04511d5adbdd848ccf7c969dbf7338ca021dab0949ba4bae1d065b07b0b308e6a197e118f7614caab5f137ad66dfe6ab8e920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9200020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e603f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd561426920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01aaec63863aaa0b2e3b8009429bdddd455e59be6f40ccab887a32eb98723efc9201f86748d40c5ee5f9a5644f2e7cf89aa5645e8a00d9e14d4fd193da0a23dd07f6020001197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602022293d64836b72bc9d1ae36d6e115c8f7cf0a3f5e75d74059f69f81fd561426920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae00020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e602da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"01bc8a5ec71647415c380203b681f7717366f3501661512225b6dc3e121efc0bae01da1adda2ccde9381e11151686c121e7f52d19a990439161c7eb5a9f94be5a591020165d0fa354e4efa95de2eee9329f18bc7db58d03501471348bfdeca4554eb2e3401197fda6b43eb91b445cf8a19541a6b064e9221cf6d43d67987d4cd0c646121e601920d71bf7104c10b4d780495bb772e8d44a507dbf616963a1b3d2410c9d4080000",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c600030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac403c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7ce184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa38108850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0200",
"013a27fed5dbbc475d3880360e38638c882fd9b273b618fc433106896083f774c601c8ca8f7df7fd9979302f3885dcd9ab816e40f8d511a48ba0891b4460b2bd1d7c030000016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402e184db737209fe4b1277cca58b2e6b52c35d3bd71e990ffe0115c4f6bbfa38108850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0200",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c37020003011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac402e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c6088850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0200",
"01f0fa37e8063b139d342246142fc48e7c0c50d0a62c97768589e06466742c370201e6d4d7685894d01b32f7e081ab188930be6c2b9f76d6847b7f382e3dddd7c60803011be5b76ecb233cd650efcff47cd0be43056324a78a48a97df7faec851bc7dcda00016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4018850ac1e804fc8f724dc410aded78f99e07c18000e4bc97458d1c8b6357eae0200",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a600030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac40222fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83bbb52416fefd25274a8a4e9513319cd7b85fa96244796ff402755618d717a1b000",
"018cebb73be883466d18d3b0c06990520e80b936440a2c9fd184d92a1f06c4e8a60122fab8bcdb88154dbf5877ad1e2d7f1b541bc8a5ec1b52266095381339c27c83030001d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac401bbb52416fefd25274a8a4e9513319cd7b85fa96244796ff402755618d717a1b000",
"01f43e3aac61e5a753062d4d0508c26ceaf5e4c0c58ba3c956e104b5d2cf67c41c0003016ceafae082a15d1b4eb70e9eb7a880e5ee2e3d1d04bfdcc03dd73ed2571c6b8e01d04e93b1b3d6620676e56be7175514ebf0bc03a7f5571403d4547e691102b78a016de08a5115fbf297232c7c9aa0f8cb78ee9a7fc467d0dfc1fe889f2f1e340ac4013a3661bc12b72646c94bc6c92796e81953985ee62d80a9ec3645a9a95740ac1500"
]

View File

@ -260,6 +260,9 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
} }
*/ */
// These tests assume null hashFinalSaplingRoot (before Sapling)
pblock->hashFinalSaplingRoot = uint256();
CValidationState state; CValidationState state;
BOOST_CHECK(ProcessNewBlock(state, NULL, pblock, true, NULL)); BOOST_CHECK(ProcessNewBlock(state, NULL, pblock, true, NULL));
BOOST_CHECK_MESSAGE(state.IsValid(), state.GetRejectReason()); BOOST_CHECK_MESSAGE(state.IsValid(), state.GetRejectReason());

View File

@ -17,7 +17,8 @@
using namespace std; using namespace std;
static const char DB_ANCHOR = 'A'; static const char DB_SPROUT_ANCHOR = 'A';
static const char DB_SAPLING_ANCHOR = 'X';
static const char DB_NULLIFIER = 's'; static const char DB_NULLIFIER = 's';
static const char DB_SAPLING_NULLIFIER = 'S'; static const char DB_SAPLING_NULLIFIER = 'S';
static const char DB_COINS = 'c'; static const char DB_COINS = 'c';
@ -26,7 +27,8 @@ static const char DB_TXINDEX = 't';
static const char DB_BLOCK_INDEX = 'b'; static const char DB_BLOCK_INDEX = 'b';
static const char DB_BEST_BLOCK = 'B'; static const char DB_BEST_BLOCK = 'B';
static const char DB_BEST_ANCHOR = 'a'; static const char DB_BEST_SPROUT_ANCHOR = 'a';
static const char DB_BEST_SAPLING_ANCHOR = 'x';
static const char DB_FLAG = 'F'; static const char DB_FLAG = 'F';
static const char DB_REINDEX_FLAG = 'R'; static const char DB_REINDEX_FLAG = 'R';
static const char DB_LAST_BLOCK = 'l'; static const char DB_LAST_BLOCK = 'l';
@ -40,30 +42,42 @@ CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(Get
} }
bool CCoinsViewDB::GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const { bool CCoinsViewDB::GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const {
if (rt == ZCIncrementalMerkleTree::empty_root()) { if (rt == ZCIncrementalMerkleTree::empty_root()) {
ZCIncrementalMerkleTree new_tree; ZCIncrementalMerkleTree new_tree;
tree = new_tree; tree = new_tree;
return true; return true;
} }
bool read = db.Read(make_pair(DB_ANCHOR, rt), tree); bool read = db.Read(make_pair(DB_SPROUT_ANCHOR, rt), tree);
return read; return read;
} }
bool CCoinsViewDB::GetNullifier(const uint256 &nf, NullifierType type) const { bool CCoinsViewDB::GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const {
if (rt == ZCSaplingIncrementalMerkleTree::empty_root()) {
ZCSaplingIncrementalMerkleTree new_tree;
tree = new_tree;
return true;
}
bool read = db.Read(make_pair(DB_SAPLING_ANCHOR, rt), tree);
return read;
}
bool CCoinsViewDB::GetNullifier(const uint256 &nf, ShieldedType type) const {
bool spent = false; bool spent = false;
char dbChar; char dbChar;
switch (type) { switch (type) {
case SPROUT_NULLIFIER: case SPROUT:
dbChar = DB_NULLIFIER; dbChar = DB_NULLIFIER;
break; break;
case SAPLING_NULLIFIER: case SAPLING:
dbChar = DB_SAPLING_NULLIFIER; dbChar = DB_SAPLING_NULLIFIER;
break; break;
default: default:
throw runtime_error("Unknown nullifier type"); throw runtime_error("Unknown shielded type");
} }
return db.Read(make_pair(dbChar, nf), spent); return db.Read(make_pair(dbChar, nf), spent);
} }
@ -83,10 +97,22 @@ uint256 CCoinsViewDB::GetBestBlock() const {
return hashBestChain; return hashBestChain;
} }
uint256 CCoinsViewDB::GetBestAnchor() const { uint256 CCoinsViewDB::GetBestAnchor(ShieldedType type) const {
uint256 hashBestAnchor; uint256 hashBestAnchor;
if (!db.Read(DB_BEST_ANCHOR, hashBestAnchor))
switch (type) {
case SPROUT:
if (!db.Read(DB_BEST_SPROUT_ANCHOR, hashBestAnchor))
return ZCIncrementalMerkleTree::empty_root(); return ZCIncrementalMerkleTree::empty_root();
break;
case SAPLING:
if (!db.Read(DB_BEST_SAPLING_ANCHOR, hashBestAnchor))
return ZCSaplingIncrementalMerkleTree::empty_root();
break;
default:
throw runtime_error("Unknown shielded type");
}
return hashBestAnchor; return hashBestAnchor;
} }
@ -105,10 +131,29 @@ void BatchWriteNullifiers(CDBBatch& batch, CNullifiersMap& mapToUse, const char&
} }
} }
template<typename Map, typename MapIterator, typename MapEntry>
void BatchWriteAnchors(CDBBatch& batch, Map& mapToUse, const char& dbChar)
{
for (MapIterator it = mapToUse.begin(); it != mapToUse.end();) {
if (it->second.flags & MapEntry::DIRTY) {
if (!it->second.entered)
batch.Erase(make_pair(dbChar, it->first));
else {
batch.Write(make_pair(dbChar, it->first), it->second.tree);
}
// TODO: changed++?
}
MapIterator itOld = it++;
mapToUse.erase(itOld);
}
}
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers) { CNullifiersMap &mapSaplingNullifiers) {
CDBBatch batch(db); CDBBatch batch(db);
@ -127,26 +172,18 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins,
mapCoins.erase(itOld); mapCoins.erase(itOld);
} }
for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end();) { ::BatchWriteAnchors<CAnchorsSproutMap, CAnchorsSproutMap::iterator, CAnchorsSproutCacheEntry>(batch, mapSproutAnchors, DB_SPROUT_ANCHOR);
if (it->second.flags & CAnchorsCacheEntry::DIRTY) { ::BatchWriteAnchors<CAnchorsSaplingMap, CAnchorsSaplingMap::iterator, CAnchorsSaplingCacheEntry>(batch, mapSaplingAnchors, DB_SAPLING_ANCHOR);
if (!it->second.entered)
batch.Erase(make_pair(DB_ANCHOR, it->first));
else {
batch.Write(make_pair(DB_ANCHOR, it->first), it->second.tree);
}
// TODO: changed++?
}
CAnchorsMap::iterator itOld = it++;
mapAnchors.erase(itOld);
}
::BatchWriteNullifiers(batch, mapSproutNullifiers, DB_NULLIFIER); ::BatchWriteNullifiers(batch, mapSproutNullifiers, DB_NULLIFIER);
::BatchWriteNullifiers(batch, mapSaplingNullifiers, DB_SAPLING_NULLIFIER); ::BatchWriteNullifiers(batch, mapSaplingNullifiers, DB_SAPLING_NULLIFIER);
if (!hashBlock.IsNull()) if (!hashBlock.IsNull())
batch.Write(DB_BEST_BLOCK, hashBlock); batch.Write(DB_BEST_BLOCK, hashBlock);
if (!hashAnchor.IsNull()) if (!hashSproutAnchor.IsNull())
batch.Write(DB_BEST_ANCHOR, hashAnchor); batch.Write(DB_BEST_SPROUT_ANCHOR, hashSproutAnchor);
if (!hashSaplingAnchor.IsNull())
batch.Write(DB_BEST_SAPLING_ANCHOR, hashSaplingAnchor);
LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
return db.WriteBatch(batch); return db.WriteBatch(batch);
@ -284,10 +321,10 @@ bool CBlockTreeDB::LoadBlockIndexGuts()
pindexNew->nFile = diskindex.nFile; pindexNew->nFile = diskindex.nFile;
pindexNew->nDataPos = diskindex.nDataPos; pindexNew->nDataPos = diskindex.nDataPos;
pindexNew->nUndoPos = diskindex.nUndoPos; pindexNew->nUndoPos = diskindex.nUndoPos;
pindexNew->hashAnchor = diskindex.hashAnchor; pindexNew->hashSproutAnchor = diskindex.hashSproutAnchor;
pindexNew->nVersion = diskindex.nVersion; pindexNew->nVersion = diskindex.nVersion;
pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot; pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
pindexNew->hashReserved = diskindex.hashReserved; pindexNew->hashFinalSaplingRoot = diskindex.hashFinalSaplingRoot;
pindexNew->nTime = diskindex.nTime; pindexNew->nTime = diskindex.nTime;
pindexNew->nBits = diskindex.nBits; pindexNew->nBits = diskindex.nBits;
pindexNew->nNonce = diskindex.nNonce; pindexNew->nNonce = diskindex.nNonce;

View File

@ -35,16 +35,19 @@ protected:
public: public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false); CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const; bool GetSproutAnchorAt(const uint256 &rt, ZCIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nf, NullifierType type) const; bool GetSaplingAnchorAt(const uint256 &rt, ZCSaplingIncrementalMerkleTree &tree) const;
bool GetNullifier(const uint256 &nf, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const; uint256 GetBestBlock() const;
uint256 GetBestAnchor() const; uint256 GetBestAnchor(ShieldedType type) const;
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashSproutAnchor,
CAnchorsMap &mapAnchors, const uint256 &hashSaplingAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CAnchorsSaplingMap &mapSaplingAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap &mapSaplingNullifiers); CNullifiersMap &mapSaplingNullifiers);
bool GetStats(CCoinsStats &stats) const; bool GetStats(CCoinsStats &stats) const;

View File

@ -206,7 +206,7 @@ void CTxMemPool::removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMem
} }
void CTxMemPool::removeWithAnchor(const uint256 &invalidRoot) void CTxMemPool::removeWithAnchor(const uint256 &invalidRoot, ShieldedType type)
{ {
// If a block is disconnected from the tip, and the root changed, // If a block is disconnected from the tip, and the root changed,
// we must invalidate transactions from the mempool which spend // we must invalidate transactions from the mempool which spend
@ -217,12 +217,27 @@ void CTxMemPool::removeWithAnchor(const uint256 &invalidRoot)
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) { for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->GetTx(); const CTransaction& tx = it->GetTx();
switch (type) {
case SPROUT:
BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit) { BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit) {
if (joinsplit.anchor == invalidRoot) { if (joinsplit.anchor == invalidRoot) {
transactionsToRemove.push_back(tx); transactionsToRemove.push_back(tx);
break; break;
} }
} }
break;
case SAPLING:
BOOST_FOREACH(const SpendDescription& spendDescription, tx.vShieldedSpend) {
if (spendDescription.anchor == invalidRoot) {
transactionsToRemove.push_back(tx);
break;
}
}
break;
default:
throw runtime_error("Unknown shielded type");
break;
}
} }
BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) { BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) {
@ -394,7 +409,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) { BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) { BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) {
assert(!pcoins->GetNullifier(nf, SPROUT_NULLIFIER)); assert(!pcoins->GetNullifier(nf, SPROUT));
} }
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
@ -402,7 +417,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
if (it != intermediates.end()) { if (it != intermediates.end()) {
tree = it->second; tree = it->second;
} else { } else {
assert(pcoins->GetAnchorAt(joinsplit.anchor, tree)); assert(pcoins->GetSproutAnchorAt(joinsplit.anchor, tree));
} }
BOOST_FOREACH(const uint256& commitment, joinsplit.commitments) BOOST_FOREACH(const uint256& commitment, joinsplit.commitments)
@ -413,7 +428,10 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
intermediates.insert(std::make_pair(tree.root(), tree)); intermediates.insert(std::make_pair(tree.root(), tree));
} }
for (const SpendDescription &spendDescription : tx.vShieldedSpend) { for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
assert(!pcoins->GetNullifier(spendDescription.nullifier, SAPLING_NULLIFIER)); ZCSaplingIncrementalMerkleTree tree;
assert(pcoins->GetSaplingAnchorAt(spendDescription.anchor, tree));
assert(!pcoins->GetNullifier(spendDescription.nullifier, SAPLING));
} }
if (fDependsWait) if (fDependsWait)
waitingOnDependants.push_back(&(*it)); waitingOnDependants.push_back(&(*it));
@ -452,21 +470,21 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
assert(it->first == it->second.ptx->vin[it->second.n].prevout); assert(it->first == it->second.ptx->vin[it->second.n].prevout);
} }
checkNullifiers(SPROUT_NULLIFIER); checkNullifiers(SPROUT);
checkNullifiers(SAPLING_NULLIFIER); checkNullifiers(SAPLING);
assert(totalTxSize == checkTotal); assert(totalTxSize == checkTotal);
assert(innerUsage == cachedInnerUsage); assert(innerUsage == cachedInnerUsage);
} }
void CTxMemPool::checkNullifiers(NullifierType type) const void CTxMemPool::checkNullifiers(ShieldedType type) const
{ {
const std::map<uint256, const CTransaction*>* mapToUse; const std::map<uint256, const CTransaction*>* mapToUse;
switch (type) { switch (type) {
case SPROUT_NULLIFIER: case SPROUT:
mapToUse = &mapSproutNullifiers; mapToUse = &mapSproutNullifiers;
break; break;
case SAPLING_NULLIFIER: case SAPLING:
mapToUse = &mapSaplingNullifiers; mapToUse = &mapSaplingNullifiers;
break; break;
default: default:
@ -582,12 +600,12 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
return true; return true;
} }
bool CTxMemPool::nullifierExists(const uint256& nullifier, NullifierType type) const bool CTxMemPool::nullifierExists(const uint256& nullifier, ShieldedType type) const
{ {
switch (type) { switch (type) {
case SPROUT_NULLIFIER: case SPROUT:
return mapSproutNullifiers.count(nullifier); return mapSproutNullifiers.count(nullifier);
case SAPLING_NULLIFIER: case SAPLING:
return mapSaplingNullifiers.count(nullifier); return mapSaplingNullifiers.count(nullifier);
default: default:
throw runtime_error("Unknown nullifier type"); throw runtime_error("Unknown nullifier type");
@ -596,7 +614,7 @@ bool CTxMemPool::nullifierExists(const uint256& nullifier, NullifierType type) c
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { } CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetNullifier(const uint256 &nf, NullifierType type) const bool CCoinsViewMemPool::GetNullifier(const uint256 &nf, ShieldedType type) const
{ {
return mempool.nullifierExists(nf, type) || base->GetNullifier(nf, type); return mempool.nullifierExists(nf, type) || base->GetNullifier(nf, type);
} }

View File

@ -134,7 +134,7 @@ private:
std::map<uint256, const CTransaction*> mapSproutNullifiers; std::map<uint256, const CTransaction*> mapSproutNullifiers;
std::map<uint256, const CTransaction*> mapSaplingNullifiers; std::map<uint256, const CTransaction*> mapSaplingNullifiers;
void checkNullifiers(NullifierType type) const; void checkNullifiers(ShieldedType type) const;
public: public:
typedef boost::multi_index_container< typedef boost::multi_index_container<
@ -169,7 +169,7 @@ public:
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true); bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false); void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false);
void removeWithAnchor(const uint256 &invalidRoot); void removeWithAnchor(const uint256 &invalidRoot, ShieldedType type);
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags); void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed); void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
void removeExpired(unsigned int nBlockHeight); void removeExpired(unsigned int nBlockHeight);
@ -192,7 +192,7 @@ public:
void ApplyDeltas(const uint256 hash, double &dPriorityDelta, CAmount &nFeeDelta); void ApplyDeltas(const uint256 hash, double &dPriorityDelta, CAmount &nFeeDelta);
void ClearPrioritisation(const uint256 hash); void ClearPrioritisation(const uint256 hash);
bool nullifierExists(const uint256& nullifier, NullifierType type) const; bool nullifierExists(const uint256& nullifier, ShieldedType type) const;
unsigned long size() unsigned long size()
{ {
@ -243,7 +243,7 @@ protected:
public: public:
CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn); CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn);
bool GetNullifier(const uint256 &txid, NullifierType type) const; bool GetNullifier(const uint256 &txid, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const; bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const; bool HaveCoins(const uint256 &txid) const;
}; };

View File

@ -67,14 +67,14 @@ class CBlockUndo
{ {
public: public:
std::vector<CTxUndo> vtxundo; // for all but the coinbase std::vector<CTxUndo> vtxundo; // for all but the coinbase
uint256 old_tree_root; uint256 old_sprout_tree_root;
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) { inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(vtxundo); READWRITE(vtxundo);
READWRITE(old_tree_root); READWRITE(old_sprout_tree_root);
} }
}; };

View File

@ -429,7 +429,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
auto it = intermediates.find(prevJoinSplit.anchor); auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) { if (it != intermediates.end()) {
tree = it->second; tree = it->second;
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) { } else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor"); throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
} }
@ -693,7 +693,7 @@ UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInf
uint256 anchor; uint256 anchor;
{ {
LOCK(cs_main); LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(); // As there are no inputs, ask the wallet for the best anchor anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
} }
return perform_joinsplit(info, witnesses, anchor); return perform_joinsplit(info, witnesses, anchor);
} }

View File

@ -545,7 +545,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
auto it = intermediates.find(prevJoinSplit.anchor); auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) { if (it != intermediates.end()) {
tree = it->second; tree = it->second;
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) { } else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor"); throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
} }
@ -914,7 +914,7 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info
uint256 anchor; uint256 anchor;
{ {
LOCK(cs_main); LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(); // As there are no inputs, ask the wallet for the best anchor anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
} }
return perform_joinsplit(info, witnesses, anchor); return perform_joinsplit(info, witnesses, anchor);
} }

View File

@ -314,7 +314,7 @@ UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInf
{ {
LOCK(cs_main); LOCK(cs_main);
consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
anchor = pcoinsTip->GetBestAnchor(); anchor = pcoinsTip->GetBestAnchor(SPROUT);
} }

View File

@ -1765,7 +1765,7 @@ void CWallet::WitnessNoteCommitment(std::vector<uint256> commitments,
// Consistency check: we should be able to find the current tree // Consistency check: we should be able to find the current tree
// in our CCoins view. // in our CCoins view.
ZCIncrementalMerkleTree dummy_tree; ZCIncrementalMerkleTree dummy_tree;
assert(pcoinsTip->GetAnchorAt(current_anchor, dummy_tree)); assert(pcoinsTip->GetSproutAnchorAt(current_anchor, dummy_tree));
pindex = chainActive.Next(pindex); pindex = chainActive.Next(pindex);
} }
@ -1819,7 +1819,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
ZCIncrementalMerkleTree tree; ZCIncrementalMerkleTree tree;
// This should never fail: we should always be able to get the tree // This should never fail: we should always be able to get the tree
// state on the path to the tip of our chain // state on the path to the tip of our chain
assert(pcoinsTip->GetAnchorAt(pindex->hashAnchor, tree)); assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, tree));
// Increment note witness caches // Increment note witness caches
IncrementNoteWitnesses(pindex, &block, tree); IncrementNoteWitnesses(pindex, &block, tree);

View File

@ -5,10 +5,41 @@
#include "zcash/IncrementalMerkleTree.hpp" #include "zcash/IncrementalMerkleTree.hpp"
#include "crypto/sha256.h" #include "crypto/sha256.h"
#include "zcash/util.h" #include "zcash/util.h"
#include "librustzcash.h"
namespace libzcash { namespace libzcash {
SHA256Compress SHA256Compress::combine(const SHA256Compress& a, const SHA256Compress& b) PedersenHash PedersenHash::combine(
const PedersenHash& a,
const PedersenHash& b,
size_t depth
)
{
PedersenHash res = PedersenHash();
librustzcash_merkle_hash(
depth,
a.begin(),
b.begin(),
res.begin()
);
return res;
}
PedersenHash PedersenHash::uncommitted() {
PedersenHash res = PedersenHash();
librustzcash_tree_uncommitted(res.begin());
return res;
}
SHA256Compress SHA256Compress::combine(
const SHA256Compress& a,
const SHA256Compress& b,
size_t depth
)
{ {
SHA256Compress res = SHA256Compress(); SHA256Compress res = SHA256Compress();
@ -111,7 +142,7 @@ void IncrementalMerkleTree<Depth, Hash>::append(Hash obj) {
right = obj; right = obj;
} else { } else {
// Combine the leaves and propagate it up the tree // Combine the leaves and propagate it up the tree
boost::optional<Hash> combined = Hash::combine(*left, *right); boost::optional<Hash> combined = Hash::combine(*left, *right, 0);
// Set the "left" leaf to the object and make the "right" leaf none // Set the "left" leaf to the object and make the "right" leaf none
left = obj; left = obj;
@ -120,7 +151,7 @@ void IncrementalMerkleTree<Depth, Hash>::append(Hash obj) {
for (size_t i = 0; i < Depth; i++) { for (size_t i = 0; i < Depth; i++) {
if (i < parents.size()) { if (i < parents.size()) {
if (parents[i]) { if (parents[i]) {
combined = Hash::combine(*parents[i], *combined); combined = Hash::combine(*parents[i], *combined, i+1);
parents[i] = boost::none; parents[i] = boost::none;
} else { } else {
parents[i] = *combined; parents[i] = *combined;
@ -202,15 +233,15 @@ Hash IncrementalMerkleTree<Depth, Hash>::root(size_t depth,
Hash combine_left = left ? *left : filler.next(0); Hash combine_left = left ? *left : filler.next(0);
Hash combine_right = right ? *right : filler.next(0); Hash combine_right = right ? *right : filler.next(0);
Hash root = Hash::combine(combine_left, combine_right); Hash root = Hash::combine(combine_left, combine_right, 0);
size_t d = 1; size_t d = 1;
BOOST_FOREACH(const boost::optional<Hash>& parent, parents) { BOOST_FOREACH(const boost::optional<Hash>& parent, parents) {
if (parent) { if (parent) {
root = Hash::combine(*parent, root); root = Hash::combine(*parent, root, d);
} else { } else {
root = Hash::combine(root, filler.next(d)); root = Hash::combine(root, filler.next(d), d);
} }
d++; d++;
@ -219,7 +250,7 @@ Hash IncrementalMerkleTree<Depth, Hash>::root(size_t depth,
// We may not have parents for ancestor trees, so we fill // We may not have parents for ancestor trees, so we fill
// the rest in here. // the rest in here.
while (d < depth) { while (d < depth) {
root = Hash::combine(root, filler.next(d)); root = Hash::combine(root, filler.next(d), d);
d++; d++;
} }
@ -323,4 +354,10 @@ template class IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, SHA2
template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH, SHA256Compress>; template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH, SHA256Compress>;
template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, SHA256Compress>; template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, SHA256Compress>;
template class IncrementalMerkleTree<SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH, PedersenHash>;
template class IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, PedersenHash>;
template class IncrementalWitness<SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH, PedersenHash>;
template class IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, PedersenHash>;
} // end namespace `libzcash` } // end namespace `libzcash`

View File

@ -57,9 +57,9 @@ template<size_t Depth, typename Hash>
class EmptyMerkleRoots { class EmptyMerkleRoots {
public: public:
EmptyMerkleRoots() { EmptyMerkleRoots() {
empty_roots.at(0) = Hash(); empty_roots.at(0) = Hash::uncommitted();
for (size_t d = 1; d <= Depth; d++) { for (size_t d = 1; d <= Depth; d++) {
empty_roots.at(d) = Hash::combine(empty_roots.at(d-1), empty_roots.at(d-1)); empty_roots.at(d) = Hash::combine(empty_roots.at(d-1), empty_roots.at(d-1), d-1);
} }
} }
Hash empty_root(size_t depth) { Hash empty_root(size_t depth) {
@ -213,7 +213,29 @@ public:
SHA256Compress() : uint256() {} SHA256Compress() : uint256() {}
SHA256Compress(uint256 contents) : uint256(contents) { } SHA256Compress(uint256 contents) : uint256(contents) { }
static SHA256Compress combine(const SHA256Compress& a, const SHA256Compress& b); static SHA256Compress combine(
const SHA256Compress& a,
const SHA256Compress& b,
size_t depth
);
static SHA256Compress uncommitted() {
return SHA256Compress();
}
};
class PedersenHash : public uint256 {
public:
PedersenHash() : uint256() {}
PedersenHash(uint256 contents) : uint256(contents) { }
static PedersenHash combine(
const PedersenHash& a,
const PedersenHash& b,
size_t depth
);
static PedersenHash uncommitted();
}; };
template<size_t Depth, typename Hash> template<size_t Depth, typename Hash>
@ -227,4 +249,10 @@ typedef libzcash::IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, l
typedef libzcash::IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH, libzcash::SHA256Compress> ZCIncrementalWitness; typedef libzcash::IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH, libzcash::SHA256Compress> ZCIncrementalWitness;
typedef libzcash::IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, libzcash::SHA256Compress> ZCTestingIncrementalWitness; typedef libzcash::IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, libzcash::SHA256Compress> ZCTestingIncrementalWitness;
typedef libzcash::IncrementalMerkleTree<SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH, libzcash::PedersenHash> ZCSaplingIncrementalMerkleTree;
typedef libzcash::IncrementalMerkleTree<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, libzcash::PedersenHash> ZCSaplingTestingIncrementalMerkleTree;
typedef libzcash::IncrementalWitness<SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH, libzcash::PedersenHash> ZCSaplingIncrementalWitness;
typedef libzcash::IncrementalWitness<INCREMENTAL_MERKLE_TREE_DEPTH_TESTING, libzcash::PedersenHash> ZCSaplingTestingIncrementalWitness;
#endif /* ZC_INCREMENTALMERKLETREE_H_ */ #endif /* ZC_INCREMENTALMERKLETREE_H_ */

View File

@ -6,6 +6,8 @@
#define INCREMENTAL_MERKLE_TREE_DEPTH 29 #define INCREMENTAL_MERKLE_TREE_DEPTH 29
#define INCREMENTAL_MERKLE_TREE_DEPTH_TESTING 4 #define INCREMENTAL_MERKLE_TREE_DEPTH_TESTING 4
#define SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH 32
#define ZC_NOTEPLAINTEXT_LEADING 1 #define ZC_NOTEPLAINTEXT_LEADING 1
#define ZC_V_SIZE 8 #define ZC_V_SIZE 8
#define ZC_RHO_SIZE 32 #define ZC_RHO_SIZE 32

View File

@ -366,7 +366,7 @@ public:
return false; return false;
} }
bool GetNullifier(const uint256 &nf, NullifierType type) const { bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false; return false;
} }
@ -381,7 +381,7 @@ public:
bool BatchWrite(CCoinsMap &mapCoins, bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock, const uint256 &hashBlock,
const uint256 &hashAnchor, const uint256 &hashAnchor,
CAnchorsMap &mapAnchors, CAnchorsSproutMap &mapSproutAnchors,
CNullifiersMap &mapSproutNullifiers, CNullifiersMap &mapSproutNullifiers,
CNullifiersMap& mapSaplingNullifiers) { CNullifiersMap& mapSaplingNullifiers) {
return false; return false;