mirror of https://github.com/oxen-io/oxen-core.git
324 lines
12 KiB
C++
324 lines
12 KiB
C++
// Copyright (c) 2014-2019, The Monero Project
|
|
// Copyright (c) 2018, The Loki 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.
|
|
//
|
|
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
|
|
|
#include "checkpoints.h"
|
|
|
|
#include "epee/string_tools.h"
|
|
#include "epee/storages/portable_storage_template_helper.h" // epee json include
|
|
#include "epee/serialization/keyvalue_serialization.h"
|
|
#include "cryptonote_core/service_node_rules.h"
|
|
#include <vector>
|
|
#include "blockchain_db/blockchain_db.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
|
|
#include "common/oxen.h"
|
|
#include "common/file.h"
|
|
#include "common/hex.h"
|
|
|
|
#undef OXEN_DEFAULT_LOG_CATEGORY
|
|
#define OXEN_DEFAULT_LOG_CATEGORY "checkpoints"
|
|
|
|
namespace cryptonote
|
|
{
|
|
bool checkpoint_t::check(crypto::hash const &hash) const
|
|
{
|
|
bool result = block_hash == hash;
|
|
if (result) MINFO ("CHECKPOINT PASSED FOR HEIGHT " << height << " " << block_hash);
|
|
else MWARNING("CHECKPOINT FAILED FOR HEIGHT " << height << ". EXPECTED HASH " << block_hash << "GIVEN HASH: " << hash);
|
|
return result;
|
|
}
|
|
|
|
height_to_hash const HARDCODED_MAINNET_CHECKPOINTS[] =
|
|
{
|
|
{0, "08ff156d993012b0bdf2816c4bee47c9bbc7930593b70ee02574edddf15ee933"},
|
|
{1, "647997953a5ea9b5ab329c2291d4cbb08eed587c287e451eeeb2c79bab9b940f"},
|
|
{10, "4a7cd8b9bff380d48d6f3533a5e0509f8589cc77d18218b3f7218846e77738fc"},
|
|
{100, "01b8d33a50713ff837f8ad7146021b8e3060e0316b5e4afc407e46cdb50b6760"},
|
|
{1000, "5e3b0a1f931885bc0ab1d6ecdc625816576feae29e2f9ac94c5ccdbedb1465ac"},
|
|
{86535, "52b7c5a60b97bf1efbf0d63a0aa1a313e8f0abe4627eb354b0c5a73cb1f4391e"},
|
|
{97407, "504af73abbaba85a14ddc16634658bf4dcc241dc288b1eaad09e216836b71023"},
|
|
{98552, "2058d5c675bd91284f4996435593499c9ab84a5a0f569f57a86cde2e815e57da"},
|
|
{144650, "a1ab207afc790675070ecd7aac874eb0691eb6349ea37c44f8f58697a5d6cbc4"},
|
|
{266284, "c42801a37a41e3e9f934a266063483646072a94bfc7269ace178e93c91414b1f"},
|
|
{301187, "e23e4cf3a2fe3e9f0ffced5cc76426e5bdffd3aad822268f4ad63d82cb958559"},
|
|
};
|
|
|
|
crypto::hash get_newest_hardcoded_checkpoint(cryptonote::network_type nettype, uint64_t *height)
|
|
{
|
|
crypto::hash result = crypto::null_hash;
|
|
*height = 0;
|
|
if (nettype != network_type::MAINNET && nettype != network_type::TESTNET)
|
|
return result;
|
|
|
|
if (nettype == network_type::MAINNET)
|
|
{
|
|
uint64_t last_index = oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS) - 1;
|
|
height_to_hash const &entry = HARDCODED_MAINNET_CHECKPOINTS[last_index];
|
|
|
|
if (tools::hex_to_type(entry.hash, result))
|
|
*height = entry.height;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool load_checkpoints_from_json(const fs::path& json_hashfile_fullpath, std::vector<height_to_hash>& checkpoint_hashes)
|
|
{
|
|
if (std::error_code ec; !fs::exists(json_hashfile_fullpath, ec))
|
|
{
|
|
LOG_PRINT_L1("Blockchain checkpoints file not found");
|
|
return true;
|
|
}
|
|
|
|
height_to_hash_json hashes;
|
|
if (std::string contents;
|
|
!tools::slurp_file(json_hashfile_fullpath, contents) ||
|
|
!epee::serialization::load_t_from_json(hashes, contents))
|
|
{
|
|
MERROR("Error loading checkpoints from " << json_hashfile_fullpath);
|
|
return false;
|
|
}
|
|
|
|
checkpoint_hashes = std::move(hashes.hashlines);
|
|
return true;
|
|
}
|
|
|
|
bool checkpoints::get_checkpoint(uint64_t height, checkpoint_t &checkpoint) const
|
|
{
|
|
try
|
|
{
|
|
auto guard = db_rtxn_guard(m_db);
|
|
return m_db->get_block_checkpoint(height, checkpoint);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
MERROR("Get block checkpoint from DB failed at height: " << height << ", what = " << e.what());
|
|
return false;
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool checkpoints::add_checkpoint(uint64_t height, const std::string& hash_str)
|
|
{
|
|
crypto::hash h = crypto::null_hash;
|
|
bool r = tools::hex_to_type(hash_str, h);
|
|
CHECK_AND_ASSERT_MES(r, false, "Failed to parse checkpoint hash string into binary representation!");
|
|
|
|
checkpoint_t checkpoint = {};
|
|
if (get_checkpoint(height, checkpoint))
|
|
{
|
|
crypto::hash const &curr_hash = checkpoint.block_hash;
|
|
CHECK_AND_ASSERT_MES(h == curr_hash, false, "Checkpoint at given height already exists, and hash for new checkpoint was different!");
|
|
}
|
|
else
|
|
{
|
|
checkpoint.type = checkpoint_type::hardcoded;
|
|
checkpoint.height = height;
|
|
checkpoint.block_hash = h;
|
|
r = update_checkpoint(checkpoint);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
bool checkpoints::update_checkpoint(checkpoint_t const &checkpoint)
|
|
{
|
|
// NOTE(oxen): Assumes checkpoint is valid
|
|
bool result = true;
|
|
bool batch_started = false;
|
|
try
|
|
{
|
|
batch_started = m_db->batch_start();
|
|
m_db->update_block_checkpoint(checkpoint);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
MERROR("Failed to add checkpoint with hash: " << checkpoint.block_hash << " at height: " << checkpoint.height << ", what = " << e.what());
|
|
result = false;
|
|
}
|
|
|
|
if (batch_started)
|
|
m_db->batch_stop();
|
|
return result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void checkpoints::block_add(const block_add_info& info)
|
|
{
|
|
uint64_t const height = get_block_height(info.block);
|
|
if (height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL || info.block.major_version < hf::hf12_checkpointing)
|
|
return;
|
|
|
|
uint64_t end_cull_height = 0;
|
|
{
|
|
checkpoint_t immutable_checkpoint;
|
|
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, height + 1))
|
|
end_cull_height = immutable_checkpoint.height;
|
|
}
|
|
uint64_t start_cull_height = (end_cull_height < service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL)
|
|
? 0
|
|
: end_cull_height - service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL;
|
|
|
|
if ((start_cull_height % service_nodes::CHECKPOINT_INTERVAL) > 0)
|
|
start_cull_height += (service_nodes::CHECKPOINT_INTERVAL - (start_cull_height % service_nodes::CHECKPOINT_INTERVAL));
|
|
|
|
m_last_cull_height = std::max(m_last_cull_height, start_cull_height);
|
|
auto guard = db_wtxn_guard(m_db);
|
|
for (; m_last_cull_height < end_cull_height; m_last_cull_height += service_nodes::CHECKPOINT_INTERVAL)
|
|
{
|
|
if (m_last_cull_height % service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
m_db->remove_block_checkpoint(m_last_cull_height);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
MERROR("Pruning block checkpoint on block added failed non-trivially at height: " << m_last_cull_height << ", what = " << e.what());
|
|
}
|
|
}
|
|
|
|
if (info.checkpoint)
|
|
update_checkpoint(*info.checkpoint);
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
void checkpoints::blockchain_detached(uint64_t height)
|
|
{
|
|
m_last_cull_height = std::min(m_last_cull_height, height);
|
|
|
|
checkpoint_t top_checkpoint;
|
|
auto guard = db_wtxn_guard(m_db);
|
|
if (m_db->get_top_checkpoint(top_checkpoint))
|
|
{
|
|
uint64_t start_height = top_checkpoint.height;
|
|
for (size_t delete_height = start_height;
|
|
delete_height >= height && delete_height >= service_nodes::CHECKPOINT_INTERVAL;
|
|
delete_height -= service_nodes::CHECKPOINT_INTERVAL)
|
|
{
|
|
try
|
|
{
|
|
m_db->remove_block_checkpoint(delete_height);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
MERROR("Remove block checkpoint on detach failed non-trivially at height: " << delete_height << ", what = " << e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool checkpoints::is_in_checkpoint_zone(uint64_t height) const
|
|
{
|
|
uint64_t top_checkpoint_height = 0;
|
|
checkpoint_t top_checkpoint;
|
|
if (m_db->get_top_checkpoint(top_checkpoint))
|
|
top_checkpoint_height = top_checkpoint.height;
|
|
|
|
return height <= top_checkpoint_height;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool checkpoints::check_block(uint64_t height, const crypto::hash& h, bool* is_a_checkpoint, bool *service_node_checkpoint) const
|
|
{
|
|
checkpoint_t checkpoint;
|
|
bool found = get_checkpoint(height, checkpoint);
|
|
if (is_a_checkpoint) *is_a_checkpoint = found;
|
|
if (service_node_checkpoint) *service_node_checkpoint = false;
|
|
|
|
if(!found)
|
|
return true;
|
|
|
|
bool result = checkpoint.check(h);
|
|
if (service_node_checkpoint)
|
|
*service_node_checkpoint = (checkpoint.type == checkpoint_type::service_node);
|
|
|
|
return result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool checkpoints::is_alternative_block_allowed(uint64_t blockchain_height, uint64_t block_height, bool *service_node_checkpoint)
|
|
{
|
|
if (service_node_checkpoint)
|
|
*service_node_checkpoint = false;
|
|
|
|
if (0 == block_height)
|
|
return false;
|
|
|
|
{
|
|
std::vector<checkpoint_t> const first_checkpoint = m_db->get_checkpoints_range(0, blockchain_height, 1);
|
|
if (first_checkpoint.empty() || blockchain_height < first_checkpoint[0].height)
|
|
return true;
|
|
}
|
|
|
|
checkpoint_t immutable_checkpoint;
|
|
uint64_t immutable_height = 0;
|
|
if (m_db->get_immutable_checkpoint(&immutable_checkpoint, blockchain_height))
|
|
{
|
|
immutable_height = immutable_checkpoint.height;
|
|
if (service_node_checkpoint)
|
|
*service_node_checkpoint = (immutable_checkpoint.type == checkpoint_type::service_node);
|
|
}
|
|
|
|
m_immutable_height = std::max(immutable_height, m_immutable_height);
|
|
bool result = block_height > m_immutable_height;
|
|
return result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
uint64_t checkpoints::get_max_height() const
|
|
{
|
|
uint64_t result = 0;
|
|
checkpoint_t top_checkpoint;
|
|
if (m_db->get_top_checkpoint(top_checkpoint))
|
|
result = top_checkpoint.height;
|
|
|
|
return result;
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
bool checkpoints::init(network_type nettype, BlockchainDB *db)
|
|
{
|
|
*this = {};
|
|
m_db = db;
|
|
m_nettype = nettype;
|
|
|
|
if (db->is_read_only())
|
|
return true;
|
|
|
|
if (nettype == network_type::MAINNET)
|
|
{
|
|
for (size_t i = 0; i < oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i)
|
|
{
|
|
height_to_hash const &checkpoint = HARDCODED_MAINNET_CHECKPOINTS[i];
|
|
bool added = add_checkpoint(checkpoint.height, checkpoint.hash);
|
|
CHECK_AND_ASSERT(added, false);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|