// Copyright (c) 2018-2023, 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 "misc_log_ex.h"
#include "cryptonote_config.h"
#include "common/pruning.h"
#define ASSERT_EX(x) do { bool ex = false; try { x; } catch(...) { ex = true; } ASSERT_TRUE(ex); } while(0)
TEST(pruning, parts)
{
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(3, 2)), 3);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(1, 2)), 1);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(7, 7)), 7);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 2)), 2);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 0)), 0);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(1, 7)), 7);
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(7, 7)), 7);
for (uint32_t log_stripes = 1; log_stripes <= tools::PRUNING_SEED_LOG_STRIPES_MASK; ++log_stripes)
{
for (uint32_t stripe = 1; stripe <= (1 << log_stripes); ++stripe)
{
ASSERT_EQ(tools::get_pruning_log_stripes(tools::make_pruning_seed(stripe, log_stripes)), log_stripes);
ASSERT_EQ(tools::get_pruning_stripe(tools::make_pruning_seed(stripe, log_stripes)), stripe);
}
}
}
TEST(pruning, out_of_range)
{
ASSERT_EX(tools::make_pruning_seed(5, 2));
ASSERT_EX(tools::make_pruning_seed(0, 2));
}
TEST(pruning, fits)
{
const uint32_t log_stripes = 3;
const uint32_t num_stripes = 1 << log_stripes;
for (uint32_t stripe = 1; stripe <= num_stripes; ++stripe)
{
const uint32_t seed = tools::make_pruning_seed(stripe, log_stripes);
ASSERT_NE(seed, 0);
ASSERT_EQ(tools::get_pruning_log_stripes(seed), log_stripes);
ASSERT_EQ(tools::get_pruning_stripe(seed), stripe);
}
}
TEST(pruning, tip)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS");
for (uint64_t h = H - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < H; ++h)
{
uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_EQ(pruning_seed, 0);
for (pruning_seed = 0; pruning_seed <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_seed)
ASSERT_TRUE(tools::has_unpruned_block(h, H, pruning_seed));
}
}
TEST(pruning, seed)
{
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
for (uint64_t cycle = 0; cycle < 10; ++cycle)
{
const uint64_t O = TB * cycle;
ASSERT_EQ(tools::get_pruning_stripe(O + 0, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + 1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + SS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
ASSERT_EQ(tools::get_pruning_stripe(O + SS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*2-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 2);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*2, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 3);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS-1, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), NS);
ASSERT_EQ(tools::get_pruning_stripe(O + SS*NS, 10000000, CRYPTONOTE_PRUNING_LOG_STRIPES), 1);
}
}
TEST(pruning, match)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS, "H must be >= CRYPTONOTE_PRUNING_TIP_BLOCKS");
for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h)
{
uint32_t pruning_seed = tools::get_pruning_seed(h, H, CRYPTONOTE_PRUNING_LOG_STRIPES);
uint32_t pruning_stripe = tools::get_pruning_stripe(pruning_seed);
ASSERT_TRUE(pruning_stripe > 0 && pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES));
for (uint32_t other_pruning_stripe = 1; other_pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++other_pruning_stripe)
{
uint32_t other_pruning_seed = tools::make_pruning_seed(other_pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_TRUE(tools::has_unpruned_block(h, H, other_pruning_seed) == (other_pruning_seed == pruning_seed));
}
}
}
TEST(pruning, stripe_size)
{
static constexpr uint64_t H = CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES) + 1000;
static_assert(H >= CRYPTONOTE_PRUNING_TIP_BLOCKS + CRYPTONOTE_PRUNING_STRIPE_SIZE * (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), "H must be >= that stuff in front");
for (uint32_t pruning_stripe = 1; pruning_stripe <= (1 << CRYPTONOTE_PRUNING_LOG_STRIPES); ++pruning_stripe)
{
uint32_t pruning_seed = tools::make_pruning_seed(pruning_stripe, CRYPTONOTE_PRUNING_LOG_STRIPES);
unsigned int current_run = 0, best_run = 0;
for (uint64_t h = 0; h < H - CRYPTONOTE_PRUNING_TIP_BLOCKS; ++h)
{
if (tools::has_unpruned_block(h, H, pruning_seed))
{
++current_run;
}
else if (current_run)
{
ASSERT_EQ(current_run, CRYPTONOTE_PRUNING_STRIPE_SIZE);
best_run = std::max(best_run, current_run);
current_run = 0;
}
}
ASSERT_EQ(best_run, CRYPTONOTE_PRUNING_STRIPE_SIZE);
}
}
TEST(pruning, next_unpruned)
{
static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low");
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
for (uint64_t h = 0; h < 100; ++h)
ASSERT_EQ(tools::get_next_unpruned_block_height(h, 10000000, 0), h);
const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed3 = tools::make_pruning_seed(3, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed4 = tools::make_pruning_seed(4, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed1), 0);
ASSERT_EQ(tools::get_next_unpruned_block_height(1, 10000000, seed1), 1);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS-1, 10000000, seed1), SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB-1, 10000000, seed1), TB);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(1, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS-1, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed2), SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS-1, 10000000, seed2), 2*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS, 10000000, seed2), TB+SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+2*SS,10000000, seed2), TB*2+SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(0, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(2*SS, 10000000, seed3), SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(3*SS-1, 10000000, seed3), SS*3-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(3*SS, 10000000, seed3), TB+SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+3*SS,10000000, seed3), TB*2+SS*2);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seed4), 3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(4*SS-1, 10000000, seed4), 4*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(4*SS, 10000000, seed4), TB+3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+4*SS,10000000, seed4), TB*2+3*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(SS, 10000000, seedNS), (NS-1)*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(NS*SS-1,10000000, seedNS), NS*SS-1);
ASSERT_EQ(tools::get_next_unpruned_block_height(NS*SS, 10000000, seedNS), TB+(NS-1)*SS);
ASSERT_EQ(tools::get_next_unpruned_block_height(TB+NS*SS, 10000000, seedNS), TB*2+(NS-1)*SS);
}
TEST(pruning, next_pruned)
{
static_assert((1 << CRYPTONOTE_PRUNING_LOG_STRIPES) >= 4, "CRYPTONOTE_PRUNING_LOG_STRIPES too low");
const uint64_t SS = CRYPTONOTE_PRUNING_STRIPE_SIZE;
const uint64_t NS = 1 << CRYPTONOTE_PRUNING_LOG_STRIPES;
const uint64_t TB = NS * SS;
const uint32_t seed1 = tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seed2 = tools::make_pruning_seed(2, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS_1 = tools::make_pruning_seed(NS-1, CRYPTONOTE_PRUNING_LOG_STRIPES);
const uint32_t seedNS = tools::make_pruning_seed(NS, CRYPTONOTE_PRUNING_LOG_STRIPES);
for (uint64_t h = 0; h < 100; ++h)
ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000);
for (uint64_t h = 10000000 - 1 - CRYPTONOTE_PRUNING_TIP_BLOCKS; h < 10000000; ++h)
ASSERT_EQ(tools::get_next_pruned_block_height(h, 10000000, 0), 10000000);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seed1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seed1), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(TB, 10000000, seed1), TB+SS);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seed2), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seed2), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seed2), 2*SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seed2), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seedNS_1), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seedNS_1), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seedNS_1), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seedNS_1), TB-1);
ASSERT_EQ(tools::get_next_pruned_block_height(1, 10000000, seedNS), 1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS-1, 10000000, seedNS), SS-1);
ASSERT_EQ(tools::get_next_pruned_block_height(SS, 10000000, seedNS), SS);
ASSERT_EQ(tools::get_next_pruned_block_height(TB-1, 10000000, seedNS), TB);
}