mirror of
https://github.com/wownero/wownero.git
synced 2026-10-18 08:01:34 +02:00
+3









j-berman
jeffro256
Luke Parker
Lee *!* Clagett
SyntheticBird45
jeetrex17
UkoeHB
MrCyjaneK
tobtoht
selsta
Thomas
navidR
Boog900
a9145f9d38
Co-authored-by: jeffro256 <jeffro256@tutanota.com> Co-authored-by: Luke Parker <lukeparker5132@gmail.com> Co-authored-by: Lee *!* Clagett <code@leeclagett.com> Co-authored-by: SyntheticBird45 <someoneelse.is_on.github.rio7x@simplelogin.com> Co-authored-by: jeetrex17 <jeetrex@tutamail.com> Co-authored-by: UkoeHB <ukoehb@users.noreply.github.com> Co-authored-by: MrCyjaneK Co-authored-by: tobtoht Co-authored-by: selsta Co-authored-by: Thomas <thomas.giudici@proton.me> Co-authored-by: navidR Co-authored-by: Boog900 <boog900@tutanota.com>
755 lines
33 KiB
C++
755 lines
33 KiB
C++
// Copyright (c) 2014, The Monero Project
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "crypto/crypto.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
#include "curve_trees.h"
|
|
#include "fcmp_pp/curve_trees.h"
|
|
#include "fcmp_pp/fcmp_pp_types.h"
|
|
#include "fcmp_pp/tree_cache.h"
|
|
#include "serialization/binary_utils.h"
|
|
#include "string_tools.h"
|
|
|
|
#include <variant>
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
#define INIT_SYNC_TEST(tree_depth) \
|
|
static const std::size_t selene_chunk_width = 2; \
|
|
static const std::size_t helios_chunk_width = 3; \
|
|
\
|
|
uint64_t n_leaves_needed = 0; \
|
|
auto curve_trees = test::init_curve_trees_test(selene_chunk_width, \
|
|
helios_chunk_width, \
|
|
tree_depth, \
|
|
n_leaves_needed); \
|
|
\
|
|
/* Each block, we'll sync a max of just over 2 full chunks, to make sure we're saving all path elems even \
|
|
when the data we need is not in the last chunk */ \
|
|
static const std::size_t max_outputs_per_block = (2*selene_chunk_width)+1; \
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, register_output)
|
|
{
|
|
using namespace fcmp_pp;
|
|
|
|
// 1. Init
|
|
auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1();
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
const std::size_t INIT_LEAVES = 10;
|
|
auto outputs = test::generate_random_outputs(0, INIT_LEAVES);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == INIT_LEAVES, "unexpected size of outputs");
|
|
|
|
// Mock values
|
|
const auto output = outputs.at(0).output_pair;
|
|
|
|
// 2. Register output - valid
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
// 3. Register same output again - already registered
|
|
ASSERT_FALSE(tree_cache->register_output(output));
|
|
|
|
// 3a. Register a copy of the same output again - already registered
|
|
const auto output_copy = output;
|
|
ASSERT_FALSE(tree_cache->register_output(output_copy));
|
|
|
|
// 4. Register another output with the same output pubkey as existing, different commitment - valid
|
|
const OutputPair output_new_commitment = LegacyOutputPair{{
|
|
output_pubkey_cref(output),
|
|
commitment_cref(outputs.at(1).output_pair)
|
|
}};
|
|
|
|
ASSERT_EQ(output_pubkey_cref(output_new_commitment), output_pubkey_cref(output));
|
|
ASSERT_NE(commitment_cref(output_new_commitment), commitment_cref(output));
|
|
|
|
ASSERT_TRUE(tree_cache->register_output(output_new_commitment));
|
|
|
|
// 5. Register another output with same output pubkey and commitment, but different output pair type - valid
|
|
ASSERT_TRUE(std::holds_alternative<LegacyOutputPair>(output));
|
|
const OutputPair output_diff_type = CarrotOutputPairV1{{
|
|
output_pubkey_cref(output),
|
|
commitment_cref(output)
|
|
}};
|
|
|
|
ASSERT_EQ(output_pubkey_cref(output_diff_type), output_pubkey_cref(output));
|
|
ASSERT_EQ(commitment_cref(output_diff_type), commitment_cref(output));
|
|
ASSERT_TRUE(!(output == output_diff_type));
|
|
|
|
ASSERT_TRUE(tree_cache->register_output(output_diff_type));
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, sync_block_simple)
|
|
{
|
|
// 1. Init
|
|
static const std::size_t INIT_LEAVES = 10;
|
|
|
|
auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1();
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
auto outputs = test::generate_random_outputs(0, INIT_LEAVES);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == INIT_LEAVES, "unexpected size of outputs");
|
|
|
|
// Mock values
|
|
const uint64_t last_locked_block_idx = 1;
|
|
const auto output = outputs[0].output_pair;
|
|
|
|
// 2. Register output
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
// 3. Sync the block of outputs
|
|
crypto::hash block_hash{0x01};
|
|
crypto::hash prev_block_hash{};
|
|
tree_cache->sync_block(0, block_hash, prev_block_hash, {{ last_locked_block_idx, outputs }});
|
|
|
|
// 4. Sync 1 more block so the outputs unlock and enter the tree
|
|
prev_block_hash = block_hash;
|
|
block_hash = crypto::hash{0x02};
|
|
tree_cache->sync_block(last_locked_block_idx, block_hash, prev_block_hash, {});
|
|
|
|
// 5. Get the output's path in the tree
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
|
|
// If the c1 layer chunk width < INIT_LEAVES, the test won't use expected values below
|
|
ASSERT_GE(curve_trees->m_c1_width, INIT_LEAVES);
|
|
ASSERT_EQ(output_path.leaves.size(), INIT_LEAVES);
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, output, INIT_LEAVES));
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, sync_n_chunks_of_blocks)
|
|
{
|
|
// 1. Init
|
|
static const std::size_t INIT_LEAVES = 10;
|
|
static const std::size_t N_CHUNKS = 10;
|
|
static const std::size_t N_BLOCKS_PER_CHUNK = 100;
|
|
|
|
const std::size_t rand_chunk = crypto::rand_idx(N_CHUNKS);
|
|
const std::size_t rand_block = crypto::rand_idx(N_BLOCKS_PER_CHUNK - 1);
|
|
const std::size_t rand_leaf = crypto::rand_idx(INIT_LEAVES);
|
|
|
|
auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1();
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
const crypto::hash mock_block_hash{};
|
|
|
|
// 2. Make chunks of blocks
|
|
std::vector<std::vector<crypto::hash>> chunks_of_block_hashes;
|
|
std::vector<std::vector<fcmp_pp::OutsByLastLockedBlock>> chunks_of_outputs;
|
|
std::size_t leaf_count = 0;
|
|
uint64_t last_locked_block_idx = 0;
|
|
fcmp_pp::OutputPair output;
|
|
for (std::size_t i = 0; i < N_CHUNKS; ++i)
|
|
{
|
|
std::vector<crypto::hash> block_hashes;
|
|
std::vector<fcmp_pp::OutsByLastLockedBlock> outs;
|
|
for (std::size_t j = 0; j < N_BLOCKS_PER_CHUNK; ++j)
|
|
{
|
|
auto outputs = test::generate_random_outputs(leaf_count, INIT_LEAVES);
|
|
leaf_count += INIT_LEAVES;
|
|
const auto last_locked_block = 1 + i * N_BLOCKS_PER_CHUNK + j;
|
|
|
|
if (i == rand_chunk && j == rand_block)
|
|
{
|
|
last_locked_block_idx = last_locked_block;
|
|
output = outputs[rand_leaf].output_pair;
|
|
}
|
|
|
|
outs.push_back({{ last_locked_block, std::move(outputs) }});
|
|
block_hashes.push_back(mock_block_hash);
|
|
}
|
|
chunks_of_block_hashes.push_back(std::move(block_hashes));
|
|
chunks_of_outputs.push_back(std::move(outs));
|
|
}
|
|
ASSERT_TRUE(last_locked_block_idx > 0);
|
|
|
|
// 3. Register output
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
// 4. Sync the chunks of blocks
|
|
for (std::size_t i = 0; i < N_CHUNKS; ++i)
|
|
{
|
|
const uint64_t start_block_idx = i * N_BLOCKS_PER_CHUNK;
|
|
|
|
fcmp_pp::curve_trees::TreeCache<Selene, Helios>::CacheStateChange cache_state_change;
|
|
|
|
tree_cache->prepare_to_grow_cache(start_block_idx,
|
|
mock_block_hash,
|
|
chunks_of_block_hashes[i],
|
|
chunks_of_outputs[i],
|
|
cache_state_change);
|
|
|
|
tree_cache->grow_cache(start_block_idx,
|
|
chunks_of_block_hashes[i],
|
|
std::move(cache_state_change));
|
|
|
|
// Audit the output's path in the tree
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
if (!output_path.empty())
|
|
{
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, output, tree_cache->get_n_leaf_tuples()));
|
|
}
|
|
}
|
|
|
|
// 5. Make sure the output was actually added to the tree
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
ASSERT_TRUE(!output_path.empty());
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, sync_n_blocks_register_n_outputs)
|
|
{
|
|
// Init
|
|
static const std::size_t tree_depth = 6;
|
|
INIT_SYNC_TEST(tree_depth);
|
|
|
|
// Sync until we've synced all the leaves needed to get to the desired tree depth
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
uint64_t block_idx = 0;
|
|
uint64_t n_outputs = 0;
|
|
uint64_t n_unlocked_outputs = 0;
|
|
crypto::hash prev_block_hash = crypto::hash{};
|
|
|
|
// Keep track of all registered outputs so that we can make sure ALL output paths update correctly every block
|
|
std::vector<fcmp_pp::OutputPair> registered_outputs;
|
|
registered_outputs.reserve(n_leaves_needed);
|
|
|
|
while (n_unlocked_outputs < n_leaves_needed)
|
|
{
|
|
const auto sync_n_outputs = (block_idx % max_outputs_per_block) + 1;
|
|
LOG_PRINT_L1("Syncing "<< sync_n_outputs << " outputs in block " << (block_idx+1)
|
|
<< " (" << n_unlocked_outputs << " unlocked / " << n_leaves_needed << " outputs)");
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs, sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Pick an output to register
|
|
auto output_to_register = block_idx % sync_n_outputs;
|
|
const auto output = outputs[output_to_register].output_pair;
|
|
MDEBUG("Registering output " << n_outputs + output_to_register);
|
|
|
|
// Register the output
|
|
const uint64_t last_locked_block_idx = block_idx + 1;
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
// Sync the outputs generated above
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(std::size_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {{ last_locked_block_idx, outputs }});
|
|
|
|
n_unlocked_outputs = n_outputs;
|
|
n_outputs += sync_n_outputs;
|
|
|
|
// Audit all registered output paths
|
|
for (const auto &o : registered_outputs)
|
|
{
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(o, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, o, n_unlocked_outputs));
|
|
}
|
|
|
|
// Update for next iteration
|
|
registered_outputs.push_back(output);
|
|
prev_block_hash = block_hash;
|
|
++block_idx;
|
|
}
|
|
|
|
// Sync 1 more empty block so all outputs unlock
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(std::size_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {});
|
|
|
|
// Check all registered output paths
|
|
for (const auto &o : registered_outputs)
|
|
{
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(o, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, o, n_unlocked_outputs));
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, sync_n_blocks_register_one_output)
|
|
{
|
|
// Init
|
|
static const std::size_t tree_depth = 5;
|
|
INIT_SYNC_TEST(tree_depth);
|
|
|
|
// For every output, sync until the tree reaches the expected tree depth, registering 1 unique output each separate
|
|
// sync. We audit the output path every block while syncing
|
|
for (std::size_t i = 0; i < n_leaves_needed; ++i)
|
|
{
|
|
LOG_PRINT_L1("Register output " << (i+1) << " / " << n_leaves_needed);
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
fcmp_pp::OutputPair registered_output;
|
|
bool registered = false;
|
|
|
|
crypto::hash prev_block_hash = crypto::hash{};
|
|
|
|
uint64_t block_idx = 0;
|
|
uint64_t n_outputs = 0;
|
|
uint64_t n_unlocked_outputs = 0;
|
|
while (n_unlocked_outputs < n_leaves_needed)
|
|
{
|
|
const auto sync_n_outputs = (block_idx % max_outputs_per_block) + 1;
|
|
MDEBUG("Syncing "<< sync_n_outputs << " outputs in block " << (block_idx+1)
|
|
<< " (" << n_unlocked_outputs << " unlocked / " << n_leaves_needed << " outputs)");
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs, sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Check if this chunk includes the output we're supposed to register
|
|
const uint64_t last_locked_block_idx = block_idx + 1;
|
|
bool just_registered = n_outputs <= i && i < (n_outputs + sync_n_outputs);
|
|
if (just_registered)
|
|
{
|
|
MDEBUG("Registering output");
|
|
ASSERT_FALSE(registered);
|
|
|
|
auto output_to_register = i - n_outputs;
|
|
const auto output = outputs[output_to_register].output_pair;
|
|
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
registered = true;
|
|
registered_output = output;
|
|
}
|
|
|
|
// Sync the outputs generated above
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(uint64_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {{ last_locked_block_idx, outputs }});
|
|
|
|
n_unlocked_outputs = n_outputs;
|
|
n_outputs += sync_n_outputs;
|
|
|
|
// Audit registered output path
|
|
if (registered && !just_registered)
|
|
{
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_unlocked_outputs));
|
|
}
|
|
|
|
// Update for next iteration
|
|
prev_block_hash = block_hash;
|
|
++block_idx;
|
|
}
|
|
|
|
ASSERT_TRUE(registered);
|
|
|
|
// Sync 1 more empty block so all outputs unlock
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(std::size_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {});
|
|
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_unlocked_outputs));
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, sync_past_max_reorg_depth)
|
|
{
|
|
// This test is useful for making sure TreeSync syncs correctly even after syncing past the reorg depth. Internally
|
|
// the class drops cached values from beyond the reorg depth. This test makes sure registered output paths still
|
|
// update correctly even after cached values from the tree get dropped.
|
|
|
|
// Init
|
|
static const std::size_t max_reorg_depth = 1;
|
|
static const std::size_t tree_depth = 5;
|
|
INIT_SYNC_TEST(tree_depth);
|
|
|
|
// For every output, sync until the tree reaches the expected tree depth AND we sync past the max reorg depth,
|
|
// registering 1 unique output each separate sync. We audit the output path every block while syncing
|
|
for (std::size_t i = 0; i < n_leaves_needed; ++i)
|
|
{
|
|
LOG_PRINT_L1("Register output " << (i+1) << " / " << n_leaves_needed);
|
|
|
|
// Sync until we've synced all the leaves needed to get to the desired tree depth
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees, max_reorg_depth);
|
|
|
|
uint64_t block_idx = 0;
|
|
uint64_t n_outputs = 0;
|
|
uint64_t n_unlocked_outputs = 0;
|
|
crypto::hash prev_block_hash = crypto::hash{};
|
|
|
|
fcmp_pp::OutputPair registered_output;
|
|
bool registered = false;
|
|
|
|
while (n_unlocked_outputs < n_leaves_needed || block_idx <= max_reorg_depth)
|
|
{
|
|
// TODO: de-dup this code with above test
|
|
const auto sync_n_outputs = (block_idx % max_outputs_per_block) + 1;
|
|
MDEBUG("Syncing "<< sync_n_outputs << " outputs in block " << block_idx);
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs, sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Check if this chunk includes the output we're supposed to register
|
|
const uint64_t last_locked_block_idx = block_idx + 1;
|
|
const bool just_registered = n_outputs <= i && i < (n_outputs + sync_n_outputs);
|
|
if (just_registered)
|
|
{
|
|
ASSERT_FALSE(registered);
|
|
|
|
auto output_to_register = i - n_outputs;
|
|
const auto output = outputs[output_to_register].output_pair;
|
|
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
registered = true;
|
|
registered_output = output;
|
|
}
|
|
|
|
// Sync the outputs generated above
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(uint64_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {{ last_locked_block_idx, outputs }});
|
|
|
|
n_unlocked_outputs = n_outputs;
|
|
n_outputs += sync_n_outputs;
|
|
|
|
// Audit registered output path
|
|
if (registered && !just_registered)
|
|
{
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_unlocked_outputs));
|
|
}
|
|
|
|
// Update for next iteration
|
|
prev_block_hash = block_hash;
|
|
++block_idx;
|
|
}
|
|
|
|
ASSERT_TRUE(registered);
|
|
|
|
// Sync 1 more empty block so all outputs unlock
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(std::size_t), block_hash);
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {});
|
|
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_unlocked_outputs));
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, reorg_after_register)
|
|
{
|
|
// Init
|
|
static const std::size_t tree_depth = 5;
|
|
INIT_SYNC_TEST(tree_depth);
|
|
|
|
// For every output, sync until the tree reaches the expected tree depth, registering 1 unique output each separate
|
|
// sync. We audit the output path every block while syncing
|
|
for (std::size_t i = 0; i < n_leaves_needed; ++i)
|
|
{
|
|
LOG_PRINT_L1("Register output " << (i+1) << " / " << n_leaves_needed);
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
fcmp_pp::OutputPair registered_output;
|
|
bool registered = false;
|
|
uint64_t last_locked_block_idx = 0;
|
|
crypto::hash last_locked_block_hash;
|
|
|
|
crypto::hash prev_block_hash = crypto::hash{};
|
|
|
|
uint64_t block_idx = 0;
|
|
uint64_t n_outputs = 0;
|
|
uint64_t n_unlocked_outputs = 0;
|
|
|
|
std::vector<uint64_t> n_outputs_synced_by_block;
|
|
while (n_unlocked_outputs < n_leaves_needed)
|
|
{
|
|
if (registered && block_idx > (last_locked_block_idx + 1))
|
|
{
|
|
uint64_t cur_block_idx = block_idx;
|
|
ASSERT_EQ(n_outputs_synced_by_block.size(), block_idx);
|
|
|
|
LOG_PRINT_L1("Popping blocks back to block " << last_locked_block_idx + 1 << " , then re-syncing");
|
|
|
|
auto n_outputs_unlocked = [&n_outputs_synced_by_block](uint64_t blk_idx) -> uint64_t
|
|
{ return blk_idx < 2 ? 0 : n_outputs_synced_by_block[blk_idx - 2]; };
|
|
|
|
// Pop blocks until the included block is the top block
|
|
while (cur_block_idx > (last_locked_block_idx + 1))
|
|
{
|
|
ASSERT_TRUE(tree_cache->pop_block());
|
|
--cur_block_idx;
|
|
|
|
MDEBUG("cur_block_idx: " << cur_block_idx
|
|
<< " , n_outputs_unlocked(cur_block_idx): " << n_outputs_unlocked(cur_block_idx));
|
|
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_outputs_unlocked(cur_block_idx)));
|
|
}
|
|
|
|
// Sync back up again until cur_block_idx == block_idx
|
|
prev_block_hash = last_locked_block_hash;
|
|
while (cur_block_idx < block_idx)
|
|
{
|
|
const auto sync_n_outputs = (cur_block_idx % max_outputs_per_block) + 1;
|
|
MDEBUG("Re-syncing "<< sync_n_outputs << " outputs in block " << (cur_block_idx+1)
|
|
<< " (" << (n_outputs_unlocked(cur_block_idx)) << " unlocked / " << n_leaves_needed << " outputs)");
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs_unlocked(cur_block_idx+1), sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Sync the outputs generated above
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&cur_block_idx, sizeof(uint64_t), block_hash);
|
|
tree_cache->sync_block(cur_block_idx,
|
|
block_hash,
|
|
prev_block_hash,
|
|
{{ cur_block_idx + 1, outputs }});
|
|
++cur_block_idx;
|
|
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_outputs_unlocked(cur_block_idx)));
|
|
|
|
prev_block_hash = block_hash;
|
|
}
|
|
}
|
|
|
|
const auto sync_n_outputs = (block_idx % max_outputs_per_block) + 1;
|
|
MDEBUG("Syncing "<< sync_n_outputs << " outputs in block " << (block_idx+1)
|
|
<< " (" << n_unlocked_outputs << " unlocked / " << n_leaves_needed << " outputs)");
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs, sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Block metadata
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(uint64_t), block_hash);
|
|
|
|
// Check if this chunk includes the output we're supposed to register
|
|
const bool just_registered = n_outputs <= i && i < (n_outputs + sync_n_outputs);
|
|
if (just_registered)
|
|
{
|
|
ASSERT_FALSE(registered);
|
|
|
|
auto output_to_register = i - n_outputs;
|
|
const auto output = outputs[output_to_register].output_pair;
|
|
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
registered = true;
|
|
registered_output = output;
|
|
last_locked_block_idx = block_idx + 1;
|
|
}
|
|
|
|
if (registered && block_idx == last_locked_block_idx)
|
|
last_locked_block_hash = block_hash;
|
|
|
|
// Sync the outputs generated above
|
|
tree_cache->sync_block(block_idx,
|
|
block_hash,
|
|
prev_block_hash,
|
|
{{ block_idx + 1, outputs }});
|
|
|
|
n_unlocked_outputs = n_outputs;
|
|
n_outputs += sync_n_outputs;
|
|
n_outputs_synced_by_block.push_back(n_outputs);
|
|
|
|
// Audit registered output path
|
|
if (registered && !just_registered)
|
|
{
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(registered_output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, registered_output, n_unlocked_outputs));
|
|
}
|
|
|
|
// Update for next iteration
|
|
prev_block_hash = block_hash;
|
|
++block_idx;
|
|
}
|
|
|
|
ASSERT_TRUE(registered);
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, register_after_reorg)
|
|
{
|
|
// Init
|
|
static const std::size_t tree_depth = 5;
|
|
INIT_SYNC_TEST(tree_depth);
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
|
|
// Sync until we reach expected tree depth
|
|
uint64_t block_idx = 0;
|
|
uint64_t n_outputs = 0;
|
|
uint64_t n_unlocked_outputs = 0;
|
|
std::vector<uint64_t> n_outputs_synced_by_block;
|
|
std::vector<crypto::hash> block_hashes;
|
|
while (n_unlocked_outputs < n_leaves_needed)
|
|
{
|
|
const auto sync_n_outputs = (block_idx % max_outputs_per_block) + 1;
|
|
LOG_PRINT_L1("Syncing "<< sync_n_outputs << " outputs in block " << (block_idx+1)
|
|
<< " (" << n_unlocked_outputs << " unlocked / " << n_leaves_needed << " outputs)");
|
|
|
|
auto outputs = test::generate_random_outputs(n_outputs, sync_n_outputs);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == sync_n_outputs, "unexpected size of outputs");
|
|
|
|
// Block metadata
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(uint64_t), block_hash);
|
|
|
|
// Sync the outputs generated above
|
|
const uint64_t last_locked_block_idx = block_idx + 1;
|
|
tree_cache->sync_block(block_idx,
|
|
block_hash,
|
|
block_hashes.empty() ? crypto::hash{} : block_hashes.back(),
|
|
{{ last_locked_block_idx, outputs }});
|
|
|
|
n_unlocked_outputs = n_outputs;
|
|
n_outputs += sync_n_outputs;
|
|
n_outputs_synced_by_block.push_back(n_outputs);
|
|
block_hashes.push_back(block_hash);
|
|
|
|
// Update for next iteration
|
|
++block_idx;
|
|
}
|
|
|
|
// Reorg 1 block
|
|
// TODO: test reorg 1 block to block_idx blocks
|
|
LOG_PRINT_L1("Popping 1 block");
|
|
ASSERT_TRUE(tree_cache->pop_block());
|
|
--block_idx;
|
|
block_hashes.pop_back();
|
|
n_outputs_synced_by_block.pop_back();
|
|
n_outputs = n_outputs_synced_by_block.back();
|
|
n_unlocked_outputs = n_outputs_synced_by_block[n_outputs_synced_by_block.size() - 2];
|
|
|
|
// Register output and sync it in the next block
|
|
LOG_PRINT_L1("Registering 1 output and syncing in next block");
|
|
auto outputs = test::generate_random_outputs(n_unlocked_outputs, 1);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == 1, "unexpected size of outputs");
|
|
|
|
const auto output = outputs[0].output_pair;
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
// Block metadata
|
|
crypto::hash block_hash;
|
|
crypto::cn_fast_hash(&block_idx, sizeof(uint64_t), block_hash);
|
|
|
|
// Sync the output generated above
|
|
const uint64_t last_locked_block_idx = block_idx + 1;
|
|
tree_cache->sync_block(block_idx,
|
|
block_hash,
|
|
block_hashes.empty() ? crypto::hash{} : block_hashes.back(),
|
|
{{ last_locked_block_idx, outputs }});
|
|
block_hashes.push_back(block_hash);
|
|
n_unlocked_outputs = n_outputs;
|
|
|
|
// Output is not expected to enter the tree until next block
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
ASSERT_TRUE(output_path.leaves.empty() && output_path.c1_layers.empty() && output_path.c2_layers.empty());
|
|
|
|
// Sync 1 more block so the output unlocks and enters the tree
|
|
crypto::cn_fast_hash(&last_locked_block_idx, sizeof(uint64_t), block_hash);
|
|
tree_cache->sync_block(last_locked_block_idx, block_hash, block_hashes.back(), {});
|
|
n_unlocked_outputs += outputs.size();
|
|
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, output, n_unlocked_outputs));
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(tree_cache, serialization)
|
|
{
|
|
// 1. Grow the tree with a registered output
|
|
static const std::size_t INIT_LEAVES = 10;
|
|
auto curve_trees = fcmp_pp::curve_trees::curve_trees_v1();
|
|
auto tree_cache = new fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
auto outputs = test::generate_random_outputs(0, INIT_LEAVES);
|
|
CHECK_AND_ASSERT_THROW_MES(outputs.size() == INIT_LEAVES, "unexpected size of outputs");
|
|
|
|
const uint64_t block_idx = 0;
|
|
const uint64_t last_locked_block_idx = 1;
|
|
const auto output = outputs[0].output_pair;
|
|
ASSERT_TRUE(tree_cache->register_output(output));
|
|
|
|
crypto::hash block_hash{0x01};
|
|
crypto::hash prev_block_hash{};
|
|
tree_cache->sync_block(block_idx, block_hash, prev_block_hash, {{ last_locked_block_idx, outputs }});
|
|
|
|
// 2. Serialize the tree_cache object
|
|
std::string blob;
|
|
ASSERT_TRUE(serialization::dump_binary(*tree_cache, blob));
|
|
|
|
// 3. Make sure the output is present in the serialized string
|
|
const std::string blob_hex = epee::string_tools::buff_to_hex_nodelimer(blob);
|
|
ASSERT_TRUE(blob_hex.find(epee::string_tools::pod_to_hex(fcmp_pp::output_pubkey_cref(output))) != std::string::npos);
|
|
ASSERT_TRUE(blob_hex.find(epee::string_tools::pod_to_hex(fcmp_pp::commitment_cref(output))) != std::string::npos);
|
|
|
|
// 4. De-serialize the string into a new tree_cache2 object
|
|
auto tree_cache2 = fcmp_pp::curve_trees::TreeCache<Selene, Helios>(curve_trees);
|
|
ASSERT_TRUE(serialization::parse_binary(blob, tree_cache2));
|
|
|
|
// 5. Sync 1 more block in tree_cache and tree_cache2 so the init outputs unlock and enter the trees
|
|
prev_block_hash = block_hash;
|
|
block_hash = crypto::hash{0x02};
|
|
tree_cache->sync_block(last_locked_block_idx, block_hash, prev_block_hash, {});
|
|
tree_cache2.sync_block(last_locked_block_idx, block_hash, prev_block_hash, {});
|
|
|
|
// 6. Make sure output's path is the same across both tree_cache and tree_cache2
|
|
CurveTreesV1::Path output_path;
|
|
ASSERT_TRUE(tree_cache->get_output_path(output, output_path));
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path, output, INIT_LEAVES));
|
|
|
|
CurveTreesV1::Path output_path2;
|
|
ASSERT_TRUE(tree_cache2.get_output_path(output, output_path2));
|
|
// Checking the roots match and auditing path suffices for now, TODO: implement constant time eq
|
|
// ASSERT_EQ(output_path, output_path2);
|
|
auto get_root_str = [&curve_trees](const CurveTreesV1::Path &path) -> std::string
|
|
{
|
|
return path.c1_layers.size() > path.c2_layers.size()
|
|
? curve_trees->m_c1->to_string(path.c1_layers.back().front())
|
|
: curve_trees->m_c2->to_string(path.c2_layers.back().front());
|
|
};
|
|
const auto root_str = get_root_str(output_path);
|
|
const auto root_str2 = get_root_str(output_path2);
|
|
ASSERT_EQ(root_str, root_str2);
|
|
ASSERT_TRUE(curve_trees->audit_path(output_path2, output, INIT_LEAVES));
|
|
|
|
// 7. Validate output counts
|
|
ASSERT_EQ(tree_cache->get_output_count(), INIT_LEAVES);
|
|
ASSERT_EQ(tree_cache2.get_output_count(), INIT_LEAVES);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// TODO: test edge cases: duplicate output when syncing, mismatched prev block hash in sync_block
|
|
// TODO: clean up code
|
|
// TODO: test that there are no dangling refs, refs are gc'd as expected
|
|
//----------------------------------------------------------------------------------------------------------------------
|