mirror of https://github.com/oxen-io/oxen-core.git
Service node checkpointing: rebased on relaxed deregistration (#662)
* core: do not commit half constructed batch db txn * Add defer macro * Revert dumb extra copy/move change * Fix pop_blocks not calling hooks, fix BaseTestDB missing prototypes * Merge ServiceNodeCheckpointing5 branch, syncing and integration fixes * Update tests to compile with relaxed-registration changes * Get back to feature parity pre-relaxed registration changes * Remove debug changes noticed in code review and some small bugs
This commit is contained in:
parent
26080045b6
commit
fdc340b0ee
|
@ -754,6 +754,21 @@ public:
|
|||
*/
|
||||
virtual void batch_stop() = 0;
|
||||
|
||||
/**
|
||||
* @brief aborts a batch transaction
|
||||
*
|
||||
* If the subclass implements batching, this function should abort the
|
||||
* batch it is currently on.
|
||||
*
|
||||
* If no batch is in-progress, this function should throw a DB_ERROR.
|
||||
* This exception may change in the future if it is deemed necessary to
|
||||
* have a more granular exception type for this scenario.
|
||||
*
|
||||
* If any of this cannot be done, the subclass should throw the corresponding
|
||||
* subclass of DB_EXCEPTION
|
||||
*/
|
||||
virtual void batch_abort() = 0;
|
||||
|
||||
/**
|
||||
* @brief sets whether or not to batch transactions
|
||||
*
|
||||
|
|
|
@ -55,6 +55,7 @@ public:
|
|||
virtual void unlock() override { }
|
||||
virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override { return true; }
|
||||
virtual void batch_stop() override {}
|
||||
virtual void batch_abort() override {}
|
||||
virtual void set_batch_transactions(bool) override {}
|
||||
virtual void block_wtxn_start() override {}
|
||||
virtual void block_wtxn_stop() override {}
|
||||
|
@ -160,6 +161,7 @@ public:
|
|||
virtual void update_block_checkpoint(struct checkpoint_t const &checkpoint) override {}
|
||||
virtual bool get_block_checkpoint (uint64_t height, struct checkpoint_t &checkpoint) const override { return false; }
|
||||
virtual bool get_top_checkpoint (struct checkpoint_t &checkpoint) const override { return false; }
|
||||
virtual void remove_block_checkpoint(uint64_t height) override { }
|
||||
virtual std::vector<cryptonote::checkpoint_t> get_checkpoints_range(uint64_t start, uint64_t end, size_t num_desired_checkpoints) const override { return {}; }
|
||||
|
||||
virtual bool get_output_blacklist (std::vector<uint64_t> &blacklist) const override { return false; }
|
||||
|
|
|
@ -136,32 +136,21 @@ int pop_blocks(cryptonote::core& core, int num_blocks)
|
|||
{
|
||||
bool use_batch = opt_batch;
|
||||
|
||||
if (use_batch)
|
||||
core.get_blockchain_storage().get_db().batch_start();
|
||||
if (use_batch) core.get_blockchain_storage().get_db().batch_start();
|
||||
|
||||
int quit = 0;
|
||||
block popped_block;
|
||||
std::vector<transaction> popped_txs;
|
||||
for (int i=0; i < num_blocks; ++i)
|
||||
try
|
||||
{
|
||||
// simple_core.m_storage.pop_block_from_blockchain() is private, so call directly through db
|
||||
core.get_blockchain_storage().get_db().pop_block(popped_block, popped_txs);
|
||||
quit = 1;
|
||||
}
|
||||
|
||||
|
||||
if (use_batch)
|
||||
{
|
||||
if (quit > 1)
|
||||
{
|
||||
// There was an error, so don't commit pending data.
|
||||
// Destructor will abort write txn.
|
||||
}
|
||||
else
|
||||
core.get_blockchain_storage().pop_blocks(num_blocks);
|
||||
if (use_batch)
|
||||
{
|
||||
core.get_blockchain_storage().get_db().batch_stop();
|
||||
core.get_blockchain_storage().get_db().show_stats();
|
||||
}
|
||||
core.get_blockchain_storage().get_db().show_stats();
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
{
|
||||
// There was an error, so don't commit pending data.
|
||||
// Destructor will abort write txn.
|
||||
}
|
||||
|
||||
return num_blocks;
|
||||
|
@ -195,9 +184,8 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block
|
|||
core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes);
|
||||
|
||||
// TODO(doyle): Checkpointing
|
||||
std::vector<checkpoint_t> checkpoints;
|
||||
std::vector<block> pblocks;
|
||||
if (!core.prepare_handle_incoming_blocks(blocks, pblocks, checkpoints))
|
||||
if (!core.prepare_handle_incoming_blocks(blocks, pblocks))
|
||||
{
|
||||
MERROR("Failed to prepare to add blocks");
|
||||
return 1;
|
||||
|
@ -230,7 +218,7 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block
|
|||
|
||||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
|
||||
core.handle_incoming_block(block_entry.block, pblocks.empty() ? NULL : &pblocks[blockidx++], bvc, false); // <--- process block
|
||||
core.handle_incoming_block(block_entry.block, pblocks.empty() ? NULL : &pblocks[blockidx++], bvc, nullptr /*checkpoint*/, false); // <--- process block
|
||||
|
||||
if(bvc.m_verifivation_failed)
|
||||
{
|
||||
|
|
|
@ -139,23 +139,7 @@ namespace cryptonote
|
|||
}
|
||||
bool checkpoints::update_checkpoint(checkpoint_t const &checkpoint)
|
||||
{
|
||||
// TODO(doyle): Verify signatures and hash check out
|
||||
std::array<size_t, service_nodes::CHECKPOINT_QUORUM_SIZE> unique_vote_set = {};
|
||||
if (checkpoint.type == checkpoint_type::service_node)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(checkpoint.signatures.size() >= service_nodes::CHECKPOINT_MIN_VOTES, false, "Checkpoint has insufficient signatures to be considered");
|
||||
for (service_nodes::voter_to_signature const &vote_to_sig : checkpoint.signatures)
|
||||
{
|
||||
++unique_vote_set[vote_to_sig.voter_index];
|
||||
CHECK_AND_ASSERT_MES(vote_to_sig.voter_index < service_nodes::CHECKPOINT_QUORUM_SIZE, false, "Vote is indexing out of bounds");
|
||||
CHECK_AND_ASSERT_MES(unique_vote_set[vote_to_sig.voter_index] == 1, false, "Voter is trying to vote twice");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(checkpoint.signatures.size() == 0, false, "Non service-node checkpoints should have no signatures");
|
||||
}
|
||||
|
||||
// NOTE(loki): Assumes checkpoint is valid
|
||||
bool result = true;
|
||||
bool batch_started = false;
|
||||
try
|
||||
|
@ -218,9 +202,6 @@ namespace cryptonote
|
|||
delete_height > height;
|
||||
delete_height -= service_nodes::CHECKPOINT_INTERVAL)
|
||||
{
|
||||
if (delete_height % service_nodes::CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
m_db->remove_block_checkpoint(delete_height);
|
||||
|
|
|
@ -48,6 +48,7 @@ namespace cryptonote
|
|||
{
|
||||
hardcoded,
|
||||
service_node,
|
||||
count,
|
||||
};
|
||||
|
||||
struct checkpoint_t
|
||||
|
@ -61,13 +62,7 @@ namespace cryptonote
|
|||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(version)
|
||||
// TODO(doyle): Hmm too lazy to change enum decls around the codebase for now
|
||||
{
|
||||
uint8_t serialized_type = 0;
|
||||
if (W) serialized_type = static_cast<uint8_t>(type);
|
||||
FIELD_N("type", serialized_type);
|
||||
if (!W) type = static_cast<checkpoint_type>(serialized_type);
|
||||
}
|
||||
ENUM_FIELD(type, type < checkpoint_type::count);
|
||||
FIELD(height)
|
||||
FIELD(block_hash)
|
||||
FIELD(signatures)
|
||||
|
@ -125,7 +120,7 @@ namespace cryptonote
|
|||
*/
|
||||
bool add_checkpoint(uint64_t height, const std::string& hash_str);
|
||||
|
||||
bool update_checkpoint(checkpoint_t const &checkpoin);
|
||||
bool update_checkpoint(checkpoint_t const &checkpoint);
|
||||
|
||||
/*
|
||||
@brief Remove checkpoints that should not be stored persistently, i.e.
|
||||
|
|
|
@ -38,6 +38,27 @@ double round (double);
|
|||
double exp2 (double);
|
||||
std::string hex64_to_base32z(std::string const& src);
|
||||
|
||||
template <typename lambda_t>
|
||||
struct defer
|
||||
{
|
||||
lambda_t lambda;
|
||||
defer(lambda_t lambda) : lambda(lambda) {}
|
||||
~defer() { lambda(); }
|
||||
};
|
||||
|
||||
struct defer_helper
|
||||
{
|
||||
template <typename lambda_t>
|
||||
defer<lambda_t> operator+(lambda_t lambda)
|
||||
{
|
||||
return defer<lambda_t>(lambda);
|
||||
}
|
||||
};
|
||||
|
||||
#define LOKI_TOKEN_COMBINE2(x, y) x ## y
|
||||
#define LOKI_TOKEN_COMBINE(x, y) LOKI_TOKEN_COMBINE2(x, y)
|
||||
#define LOKI_DEFER auto const LOKI_TOKEN_COMBINE(loki_defer_, __LINE__) = loki::defer_helper() + [&]()
|
||||
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t array_count(T (&)[N]) { return N; }
|
||||
}; // namespace Loki
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace loki
|
|||
{
|
||||
struct fixed_buffer
|
||||
{
|
||||
static const int SIZE = 32768;
|
||||
static const int SIZE = 65536;
|
||||
char data[SIZE];
|
||||
int len;
|
||||
};
|
||||
|
@ -39,7 +39,13 @@ std::vector<std::string> separate_stdin_to_space_delim_args (fixed_buffer cons
|
|||
extern const command_line::arg_descriptor<std::string, false> arg_integration_test_hardforks_override;
|
||||
extern const command_line::arg_descriptor<std::string, false> arg_integration_test_shared_mem_name;
|
||||
extern boost::mutex integration_test_mutex;
|
||||
extern bool core_is_idle;
|
||||
|
||||
extern struct integration_test_t
|
||||
{
|
||||
bool core_is_idle;
|
||||
bool disable_checkpoint_quorum;
|
||||
bool disable_obligation_quorum;
|
||||
} integration_test;
|
||||
|
||||
}; // namespace loki
|
||||
|
||||
|
@ -72,7 +78,9 @@ static sem_t *global_stdin_ready_semaphore;
|
|||
|
||||
namespace loki
|
||||
{
|
||||
bool core_is_idle;
|
||||
|
||||
integration_test_t integration_test;
|
||||
|
||||
const command_line::arg_descriptor<std::string, false> arg_integration_test_hardforks_override = {
|
||||
"integration-test-hardforks-override"
|
||||
, "Specify custom hardfork heights and launch in fakenet mode"
|
||||
|
|
|
@ -179,8 +179,8 @@ namespace cryptonote
|
|||
static char const *version_to_string(txversion v);
|
||||
static char const *type_to_string(txtype type);
|
||||
|
||||
static txversion get_min_version_for_hf(int hf_version, cryptonote::network_type nettype = MAINNET, bool miner_tx = false);
|
||||
static txversion get_max_version_for_hf(int hf_version, cryptonote::network_type nettype = MAINNET);
|
||||
static txversion get_min_version_for_hf(uint8_t hf_version, cryptonote::network_type nettype = MAINNET, bool miner_tx = false);
|
||||
static txversion get_max_version_for_hf(uint8_t hf_version, cryptonote::network_type nettype = MAINNET);
|
||||
|
||||
// tx information
|
||||
txversion version;
|
||||
|
@ -538,7 +538,7 @@ namespace cryptonote
|
|||
return result;
|
||||
}
|
||||
|
||||
inline enum txversion transaction_prefix::get_max_version_for_hf(int hf_version, cryptonote::network_type nettype)
|
||||
inline enum txversion transaction_prefix::get_max_version_for_hf(uint8_t hf_version, cryptonote::network_type nettype)
|
||||
{
|
||||
nettype = validate_nettype(nettype);
|
||||
if (hf_version >= cryptonote::network_version_7 && hf_version <= cryptonote::network_version_8)
|
||||
|
@ -550,7 +550,7 @@ namespace cryptonote
|
|||
return txversion::v4_tx_types;
|
||||
}
|
||||
|
||||
inline enum txversion transaction_prefix::get_min_version_for_hf(int hf_version, cryptonote::network_type nettype, bool miner_tx)
|
||||
inline enum txversion transaction_prefix::get_min_version_for_hf(uint8_t hf_version, cryptonote::network_type nettype, bool miner_tx)
|
||||
{
|
||||
nettype = validate_nettype(nettype);
|
||||
if (nettype == MAINNET) // NOTE(loki): Add an exception for mainnet as there are v2's on mainnet.
|
||||
|
|
|
@ -548,14 +548,14 @@ namespace cryptonote
|
|||
binary_archive<true> nar(oss);
|
||||
|
||||
// sort by:
|
||||
if (!pick<tx_extra_pub_key>(nar, tx_extra_fields, TX_EXTRA_TAG_PUBKEY)) return false;
|
||||
if (!pick<tx_extra_pub_key> (nar, tx_extra_fields, TX_EXTRA_TAG_PUBKEY)) return false;
|
||||
if (!pick<tx_extra_service_node_winner>(nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_WINNER)) return false;
|
||||
if (!pick<tx_extra_additional_pub_keys>(nar, tx_extra_fields, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS)) return false;
|
||||
if (!pick<tx_extra_nonce>(nar, tx_extra_fields, TX_EXTRA_NONCE)) return false;
|
||||
if (!pick<tx_extra_nonce> (nar, tx_extra_fields, TX_EXTRA_NONCE)) return false;
|
||||
|
||||
if (!pick<tx_extra_service_node_register> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_REGISTER)) return false;
|
||||
if (!pick<tx_extra_service_node_deregister_old> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_DEREG_OLD)) return false;
|
||||
if (!pick<tx_extra_service_node_state_change> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_STATE_CHANGE)) return false;
|
||||
if (!pick<tx_extra_service_node_winner> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_WINNER)) return false;
|
||||
if (!pick<tx_extra_service_node_contributor> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_CONTRIBUTOR)) return false;
|
||||
if (!pick<tx_extra_service_node_pubkey> (nar, tx_extra_fields, TX_EXTRA_TAG_SERVICE_NODE_PUBKEY)) return false;
|
||||
if (!pick<tx_extra_tx_secret_key> (nar, tx_extra_fields, TX_EXTRA_TAG_TX_SECRET_KEY)) return false;
|
||||
|
@ -953,7 +953,7 @@ namespace cryptonote
|
|||
//---------------------------------------------------------------
|
||||
uint64_t get_block_height(const block& b)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1");
|
||||
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1 (size is: " << b.miner_tx.vin.size() << ")");
|
||||
CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], const txin_gen, coinbase_in, 0);
|
||||
return coinbase_in.height;
|
||||
}
|
||||
|
@ -1536,7 +1536,7 @@ namespace cryptonote
|
|||
bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height)
|
||||
{
|
||||
const blobdata bd = get_block_hashing_blob(b);
|
||||
const int hf_version = b.major_version;
|
||||
const uint8_t hf_version = b.major_version;
|
||||
crypto::cn_slow_hash_type cn_type = cn_slow_hash_type::heavy_v1;
|
||||
|
||||
if (hf_version >= network_version_11_infinite_staking)
|
||||
|
|
|
@ -111,6 +111,7 @@ static const hard_fork_record testnet_hard_forks[] =
|
|||
{ network_version_9_service_nodes, 3, 0, 1533631123 },
|
||||
{ network_version_10_bulletproofs, 4, 0, 1542681077 },
|
||||
{ network_version_11_infinite_staking, 5, 0, 1551223964 },
|
||||
{ network_version_12_checkpointing, 6, 0, 1551223966 },
|
||||
};
|
||||
|
||||
static const hard_fork_record stagenet_hard_forks[] =
|
||||
|
@ -133,7 +134,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list
|
|||
m_difficulty_for_next_block_top_hash(crypto::null_hash),
|
||||
m_difficulty_for_next_block(1),
|
||||
m_service_node_list(service_node_list),
|
||||
m_btc_valid(false)
|
||||
m_btc_valid(false),
|
||||
m_batch_success(true)
|
||||
{
|
||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||
}
|
||||
|
@ -365,7 +367,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
|
|||
else
|
||||
{
|
||||
hard_fork_record const *hf_record = mainnet_hard_forks;
|
||||
int hf_record_num_entries = loki::array_count(mainnet_hard_forks);
|
||||
uint8_t hf_record_num_entries = loki::array_count(mainnet_hard_forks);
|
||||
|
||||
if (m_nettype == TESTNET)
|
||||
{
|
||||
|
@ -400,7 +402,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
|
|||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
generate_genesis_block(bl, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE);
|
||||
db_wtxn_guard wtxn_guard(m_db);
|
||||
add_new_block(bl, bvc);
|
||||
add_new_block(bl, bvc, nullptr /*checkpoint*/);
|
||||
CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed, false, "Failed to add genesis block to blockchain");
|
||||
}
|
||||
// TODO: if blockchain load successful, verify blockchain against both
|
||||
|
@ -595,14 +597,7 @@ void Blockchain::pop_blocks(uint64_t nblocks)
|
|||
CRITICAL_REGION_LOCAL(m_tx_pool);
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
|
||||
|
||||
while (!m_db->batch_start())
|
||||
{
|
||||
m_blockchain_lock.unlock();
|
||||
m_tx_pool.unlock();
|
||||
epee::misc_utils::sleep_no_w(1000);
|
||||
m_tx_pool.lock();
|
||||
m_blockchain_lock.lock();
|
||||
}
|
||||
bool stop_batch = m_db->batch_start();
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -613,10 +608,18 @@ void Blockchain::pop_blocks(uint64_t nblocks)
|
|||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_ERROR("Error when popping blocks, only " << i << " blocks are popped: " << e.what());
|
||||
LOG_ERROR("Error when popping blocks after processing " << i << " blocks: " << e.what());
|
||||
if (stop_batch)
|
||||
m_db->batch_abort();
|
||||
return;
|
||||
}
|
||||
|
||||
m_db->batch_stop();
|
||||
auto split_height = m_db->height();
|
||||
for (BlockchainDetachedHook* hook : m_blockchain_detached_hooks)
|
||||
hook->blockchain_detached(split_height);
|
||||
|
||||
if (stop_batch)
|
||||
m_db->batch_stop();
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
// This function tells BlockchainDB to remove the top block from the
|
||||
|
@ -716,7 +719,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b)
|
|||
|
||||
db_wtxn_guard wtxn_guard(m_db);
|
||||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
add_new_block(b, bvc);
|
||||
add_new_block(b, bvc, nullptr /*checkpoint*/);
|
||||
if (!update_next_cumulative_weight_limit())
|
||||
return false;
|
||||
return bvc.m_added_to_main_chain && !bvc.m_verifivation_failed;
|
||||
|
@ -1076,7 +1079,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::
|
|||
for (auto& old_ch_ent : disconnected_chain)
|
||||
{
|
||||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc);
|
||||
bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc, false /*has_checkpoint*/);
|
||||
if(!r)
|
||||
{
|
||||
MERROR("Failed to push ex-main chain blocks to alternative chain ");
|
||||
|
@ -1709,7 +1712,7 @@ bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<blocks_e
|
|||
// if that chain is long enough to become the main chain and re-org accordingly
|
||||
// if so. If not, we need to hang on to the block in case it becomes part of
|
||||
// a long forked chain eventually.
|
||||
bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc)
|
||||
bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc, bool has_checkpoint)
|
||||
{
|
||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||
CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||||
|
@ -1721,10 +1724,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
|
|||
bvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
// this basically says if the blockchain is smaller than the first
|
||||
// checkpoint then alternate blocks are allowed. Alternatively, if the
|
||||
// last checkpoint *before* the end of the current chain is also before
|
||||
// the block to be added, then this is fine.
|
||||
if (!m_checkpoints.is_alternative_block_allowed(get_current_blockchain_height(), block_height))
|
||||
{
|
||||
MERROR_VER("Block with id: " << id << std::endl << " can't be accepted for alternative chain, block height: " << block_height << std::endl << " blockchain height: " << get_current_blockchain_height());
|
||||
|
@ -1769,14 +1768,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
|
|||
return false;
|
||||
}
|
||||
|
||||
bool is_a_checkpoint = false;
|
||||
if(!m_checkpoints.check_block(bei.height, id, &is_a_checkpoint))
|
||||
{
|
||||
LOG_ERROR("CHECKPOINT VALIDATION FAILED");
|
||||
bvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the block's hash against the difficulty target for its alt chain
|
||||
difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei);
|
||||
CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!");
|
||||
|
@ -1817,8 +1808,15 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
|
|||
CHECK_AND_ASSERT_MES(i_res.second, false, "insertion of new alternative block returned as it already exist");
|
||||
alt_chain.push_back(i_res.first);
|
||||
|
||||
// FIXME: is it even possible for a checkpoint to show up not on the main chain?
|
||||
if(is_a_checkpoint)
|
||||
bool is_a_checkpoint = false;
|
||||
if(!has_checkpoint && !m_checkpoints.check_block(bei.height, id, &is_a_checkpoint))
|
||||
{
|
||||
LOG_ERROR("CHECKPOINT VALIDATION FAILED");
|
||||
bvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(has_checkpoint || is_a_checkpoint)
|
||||
{
|
||||
//do reorganize!
|
||||
MGINFO_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db->height() - 1 << ", checkpoint is found in alternative chain on height " << bei.height);
|
||||
|
@ -3130,40 +3128,13 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!service_nodes::verify_tx_state_change(state_change, tvc.m_vote_ctx, *quorum, hf_version))
|
||||
if (!service_nodes::verify_tx_state_change(state_change, get_current_blockchain_height(), tvc.m_vote_ctx, *quorum, hf_version))
|
||||
{
|
||||
tvc.m_verifivation_failed = true;
|
||||
MERROR_VER("tx " << get_transaction_hash(tx) << ": state change tx could not be completely verified reason: " << print_vote_verification_context(tvc.m_vote_ctx));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if state change is too old or too new to hold onto
|
||||
{
|
||||
const uint64_t curr_height = get_current_blockchain_height();
|
||||
if (state_change.block_height >= curr_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received state change tx for height: " << state_change.block_height
|
||||
<< " and service node: " << state_change.service_node_index
|
||||
<< ", is newer than current height: " << curr_height
|
||||
<< " blocks and has been rejected.");
|
||||
tvc.m_vote_ctx.m_invalid_block_height = true;
|
||||
tvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t delta_height = curr_height - state_change.block_height;
|
||||
if (delta_height >= service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS)
|
||||
{
|
||||
LOG_PRINT_L1("Received state change tx for height: " << state_change.block_height
|
||||
<< " and service node: " << state_change.service_node_index
|
||||
<< ", is older than: " << service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS
|
||||
<< " blocks and has been rejected. The current height is: " << curr_height);
|
||||
tvc.m_vote_ctx.m_invalid_block_height = true;
|
||||
tvc.m_verifivation_failed = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t height = state_change.block_height;
|
||||
constexpr size_t num_blocks_to_check = service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS;
|
||||
|
||||
|
@ -3913,6 +3884,7 @@ leave:
|
|||
catch (const KEY_IMAGE_EXISTS& e)
|
||||
{
|
||||
LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what());
|
||||
m_batch_success = false;
|
||||
bvc.m_verifivation_failed = true;
|
||||
return_tx_to_pool(txs);
|
||||
return false;
|
||||
|
@ -3921,6 +3893,7 @@ leave:
|
|||
{
|
||||
//TODO: figure out the best way to deal with this failure
|
||||
LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what());
|
||||
m_batch_success = false;
|
||||
bvc.m_verifivation_failed = true;
|
||||
return_tx_to_pool(txs);
|
||||
return false;
|
||||
|
@ -4105,7 +4078,7 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
|
||||
bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc, checkpoint_t const *checkpoint)
|
||||
{
|
||||
|
||||
LOG_PRINT_L3("Blockchain::" << __func__);
|
||||
|
@ -4121,20 +4094,43 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (checkpoint)
|
||||
{
|
||||
checkpoint_t existing_checkpoint;
|
||||
uint64_t block_height = get_block_height(bl);
|
||||
try
|
||||
{
|
||||
if (m_db->get_block_checkpoint(block_height, existing_checkpoint))
|
||||
{
|
||||
if (checkpoint->signatures.size() < existing_checkpoint.signatures.size())
|
||||
checkpoint = nullptr;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
MERROR("Get block checkpoint from DB failed at height: " << block_height << ", what = " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
rtxn_guard.stop();
|
||||
//check that block refers to chain tail
|
||||
if(!(bl.prev_id == get_tail_id()))
|
||||
{
|
||||
//chain switching or wrong block
|
||||
bvc.m_added_to_main_chain = false;
|
||||
rtxn_guard.stop();
|
||||
bool r = handle_alternative_block(bl, id, bvc);
|
||||
result = handle_alternative_block(bl, id, bvc, (checkpoint != nullptr));
|
||||
m_blocks_txs_check.clear();
|
||||
return r;
|
||||
//never relay alternative blocks
|
||||
}
|
||||
else
|
||||
{
|
||||
result = handle_block_to_main_chain(bl, id, bvc);
|
||||
}
|
||||
|
||||
rtxn_guard.stop();
|
||||
return handle_block_to_main_chain(bl, id, bvc);
|
||||
if (result && checkpoint)
|
||||
update_checkpoint(*checkpoint);
|
||||
return result;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
// returns false if any of the checkpoints loading returns false.
|
||||
|
@ -4242,7 +4238,10 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
|
|||
|
||||
try
|
||||
{
|
||||
m_db->batch_stop();
|
||||
if (m_batch_success)
|
||||
m_db->batch_stop();
|
||||
else
|
||||
m_db->batch_abort();
|
||||
success = true;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
|
@ -4466,7 +4465,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar
|
|||
// vs [k_image, output_keys] (m_scan_table). This is faster because it takes advantage of bulk queries
|
||||
// and is threaded if possible. The table (m_scan_table) will be used later when querying output
|
||||
// keys.
|
||||
bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks, std::vector<checkpoint_t> &checkpoints)
|
||||
bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks)
|
||||
{
|
||||
MTRACE("Blockchain::" << __func__);
|
||||
TIME_MEASURE_START(prepare);
|
||||
|
@ -4489,8 +4488,6 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
|
|||
// needs a batch, since a batch could otherwise be active while the
|
||||
// txpool and blockchain locks were not held
|
||||
|
||||
// TODO(doyle): Checkpointing
|
||||
|
||||
m_tx_pool.lock();
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
|
||||
|
||||
|
@ -4515,39 +4512,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
|
|||
m_tx_pool.lock();
|
||||
m_blockchain_lock.lock();
|
||||
}
|
||||
|
||||
// TODO(loki): Always parse checkpoints, but block syncing have a caching
|
||||
// layer that is pretty complicated but would be nice to have an equivalent
|
||||
// for checkpoints.
|
||||
|
||||
// Place parsing before early returns since the caching layer seems to make
|
||||
// this function early return in the common case, so always ensure checkpoints
|
||||
// are parsed out.
|
||||
for (size_t i = 0; i < blocks.size(); i++)
|
||||
{
|
||||
blobdata const &checkpoint_blob = blocks_entry[i].checkpoint;
|
||||
block const &block = blocks[i];
|
||||
uint64_t block_height = get_block_height(block);
|
||||
bool maybe_has_checkpoint = (block_height % service_nodes::CHECKPOINT_INTERVAL == 0);
|
||||
|
||||
if (checkpoint_blob.size() && !maybe_has_checkpoint)
|
||||
{
|
||||
MDEBUG("Checkpoint blob given but not expecting a checkpoint at this height");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkpoint_blob.size())
|
||||
{
|
||||
checkpoint_t checkpoint;
|
||||
if (!t_serializable_object_from_blob(checkpoint, checkpoint_blob))
|
||||
{
|
||||
MDEBUG("Checkpoint blob available but failed to parse");
|
||||
return false;
|
||||
}
|
||||
|
||||
checkpoints.push_back(checkpoint);
|
||||
}
|
||||
}
|
||||
m_batch_success = true;
|
||||
|
||||
const uint64_t height = m_db->height();
|
||||
if ((height + blocks_entry.size()) < m_blocks_hash_check.size())
|
||||
|
|
|
@ -252,7 +252,7 @@ namespace cryptonote
|
|||
*
|
||||
* @return false on erroneous blocks, else true
|
||||
*/
|
||||
bool prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks, std::vector<checkpoint_t> &checkpoints);
|
||||
bool prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks);
|
||||
|
||||
/**
|
||||
* @brief incoming blocks post-processing, cleanup, and disk sync
|
||||
|
@ -333,12 +333,13 @@ namespace cryptonote
|
|||
* chain. If the block does not belong, is already in the blockchain
|
||||
* or an alternate chain, or is invalid, return false.
|
||||
*
|
||||
* @param bl_ the block to be added
|
||||
* @param bl the block to be added
|
||||
* @param bvc metadata about the block addition's success/failure
|
||||
* @param checkpoint optional checkpoint if there is one associated with the block
|
||||
*
|
||||
* @return true on successful addition to the blockchain, else false
|
||||
*/
|
||||
bool add_new_block(const block& bl_, block_verification_context& bvc);
|
||||
bool add_new_block(const block& bl, block_verification_context& bvc, checkpoint_t const *checkpoint);
|
||||
|
||||
/**
|
||||
* @brief clears the blockchain and starts a new one
|
||||
|
@ -1121,6 +1122,9 @@ namespace cryptonote
|
|||
uint64_t m_btc_expected_reward;
|
||||
bool m_btc_valid;
|
||||
|
||||
|
||||
bool m_batch_success;
|
||||
|
||||
std::shared_ptr<tools::Notify> m_block_notify;
|
||||
std::shared_ptr<tools::Notify> m_reorg_notify;
|
||||
|
||||
|
@ -1193,22 +1197,6 @@ namespace cryptonote
|
|||
*/
|
||||
bool check_tx_inputs(transaction& tx, tx_verification_context &tvc, uint64_t* pmax_used_block_height = NULL);
|
||||
|
||||
/**
|
||||
* @brief validates SN metadata transaction properties
|
||||
*
|
||||
* Checks the given service node metatransaction (i.e. deregister, unlock request, etc.) for
|
||||
* validity. Fails if called with a non-metatransaction, or if there is something wrong with
|
||||
* the metatransaction.
|
||||
*
|
||||
* This fails if called on a standard (non-meta) transaction such as a regular transaction or a
|
||||
* SN registration; you generally want to call check_tx() instead, which calls this if
|
||||
* appropriate.
|
||||
*
|
||||
* @param tx the transaction to validate
|
||||
* @param tvc returned information about tx verification
|
||||
*/
|
||||
bool check_service_node_tx(transaction &tx, tx_verification_context &tvc);
|
||||
|
||||
/**
|
||||
* @brief performs a blockchain reorganization according to the longest chain rule
|
||||
*
|
||||
|
@ -1272,7 +1260,7 @@ namespace cryptonote
|
|||
*
|
||||
* @return true if the block was added successfully, otherwise false
|
||||
*/
|
||||
bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc);
|
||||
bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc, bool has_checkpoint);
|
||||
|
||||
/**
|
||||
* @brief builds a list of blocks connecting a block to the main chain
|
||||
|
|
|
@ -1445,7 +1445,7 @@ namespace cryptonote
|
|||
bool core::relay_service_node_votes()
|
||||
{
|
||||
std::vector<service_nodes::quorum_vote_t> relayable_votes = m_quorum_cop.get_relayable_votes();
|
||||
int hf_version = get_blockchain_storage().get_current_hard_fork_version();
|
||||
uint8_t hf_version = get_blockchain_storage().get_current_hard_fork_version();
|
||||
if (hf_version < cryptonote::network_version_11_infinite_staking)
|
||||
{
|
||||
NOTIFY_NEW_DEREGISTER_VOTE::request req = {};
|
||||
|
@ -1566,14 +1566,13 @@ namespace cryptonote
|
|||
return false;
|
||||
}
|
||||
std::vector<block> pblocks;
|
||||
std::vector<checkpoint_t> checkpoints;
|
||||
if (!prepare_handle_incoming_blocks(blocks, pblocks, checkpoints))
|
||||
if (!prepare_handle_incoming_blocks(blocks, pblocks))
|
||||
{
|
||||
MERROR("Block found, but failed to prepare to add");
|
||||
m_miner.resume();
|
||||
return false;
|
||||
}
|
||||
add_new_block(b, bvc);
|
||||
add_new_block(b, bvc, nullptr /*checkpoint*/);
|
||||
cleanup_handle_incoming_blocks(true);
|
||||
//anyway - update miner template
|
||||
update_miner_block_template();
|
||||
|
@ -1614,16 +1613,22 @@ namespace cryptonote
|
|||
m_blockchain_storage.safesyncmode(onoff);
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::add_new_block(const block& b, block_verification_context& bvc)
|
||||
bool core::add_new_block(const block& b, block_verification_context& bvc, checkpoint_t const *checkpoint)
|
||||
{
|
||||
relay_service_node_votes(); // NOTE: nop if synchronising due to not accepting votes whilst syncing
|
||||
return m_blockchain_storage.add_new_block(b, bvc);
|
||||
bool result = m_blockchain_storage.add_new_block(b, bvc, checkpoint);
|
||||
if (result)
|
||||
{
|
||||
// TODO(loki): PERF(loki): This causes perf problems in integration mode, so in real-time operation it may not be
|
||||
// noticeable but could bubble up and cause slowness if the runtime variables align up undesiredly.
|
||||
relay_service_node_votes(); // NOTE: nop if synchronising due to not accepting votes whilst syncing
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks, std::vector<checkpoint_t> &checkpoints)
|
||||
bool core::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks)
|
||||
{
|
||||
m_incoming_tx_lock.lock();
|
||||
if (!m_blockchain_storage.prepare_handle_incoming_blocks(blocks_entry, blocks, checkpoints))
|
||||
if (!m_blockchain_storage.prepare_handle_incoming_blocks(blocks_entry, blocks))
|
||||
{
|
||||
cleanup_handle_incoming_blocks(false);
|
||||
return false;
|
||||
|
@ -1644,7 +1649,7 @@ namespace cryptonote
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
bool core::handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate)
|
||||
bool core::handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, checkpoint_t const *checkpoint, bool update_miner_blocktemplate)
|
||||
{
|
||||
TRY_ENTRY();
|
||||
bvc = boost::value_initialized<block_verification_context>();
|
||||
|
@ -1672,7 +1677,7 @@ namespace cryptonote
|
|||
}
|
||||
b = &lb;
|
||||
}
|
||||
add_new_block(*b, bvc);
|
||||
add_new_block(*b, bvc, checkpoint);
|
||||
if(update_miner_blocktemplate && bvc.m_added_to_main_chain)
|
||||
update_miner_block_template();
|
||||
return true;
|
||||
|
@ -1867,7 +1872,7 @@ namespace cryptonote
|
|||
m_mempool.on_idle();
|
||||
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
loki::core_is_idle = true;
|
||||
loki::integration_test.core_is_idle = true;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
|
|
|
@ -167,14 +167,14 @@ namespace cryptonote
|
|||
* @return false if loading new checkpoints fails, or the block is not
|
||||
* added, otherwise true
|
||||
*/
|
||||
bool handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate = true);
|
||||
bool handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, checkpoint_t const *checkpoint, bool update_miner_blocktemplate = true);
|
||||
|
||||
/**
|
||||
* @copydoc Blockchain::prepare_handle_incoming_blocks
|
||||
*
|
||||
* @note see Blockchain::prepare_handle_incoming_blocks
|
||||
*/
|
||||
bool prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks, std::vector<checkpoint_t> &checkpoints);
|
||||
bool prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks);
|
||||
|
||||
/**
|
||||
* @copydoc Blockchain::cleanup_handle_incoming_blocks
|
||||
|
@ -780,7 +780,6 @@ namespace cryptonote
|
|||
*
|
||||
* @param type The quorum type to retrieve
|
||||
* @param height Block height to deterministically recreate the quorum list from
|
||||
|
||||
* @return Null shared ptr if quorum has not been determined yet for height
|
||||
*/
|
||||
std::shared_ptr<const service_nodes::testing_quorum> get_testing_quorum(service_nodes::quorum_type type, uint64_t height) const;
|
||||
|
@ -948,7 +947,7 @@ namespace cryptonote
|
|||
*
|
||||
* @note see Blockchain::add_new_block
|
||||
*/
|
||||
bool add_new_block(const block& b, block_verification_context& bvc);
|
||||
bool add_new_block(const block& b, block_verification_context& bvc, checkpoint_t const *checkpoint);
|
||||
|
||||
/**
|
||||
* @brief load any core state stored on disk
|
||||
|
|
|
@ -185,7 +185,7 @@ namespace cryptonote
|
|||
bool v2_rct;
|
||||
|
||||
loki_construct_tx_params() = default;
|
||||
loki_construct_tx_params(int hf_version)
|
||||
loki_construct_tx_params(uint8_t hf_version)
|
||||
{
|
||||
*this = {};
|
||||
v4_allow_tx_types = (hf_version >= cryptonote::network_version_11_infinite_staking);
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
namespace service_nodes
|
||||
{
|
||||
static int get_min_service_node_info_version_for_hf(int hf_version)
|
||||
static int get_min_service_node_info_version_for_hf(uint8_t hf_version)
|
||||
{
|
||||
return service_node_info::version_0_checkpointing; // Versioning reset with the full SN rescan in 4.0.0
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ namespace service_nodes
|
|||
if (!state)
|
||||
{
|
||||
// TODO(loki): Not being able to find a quorum is fatal! We want better caching abilities.
|
||||
MERROR("Uptime quorum for height: " << state_change.block_height << ", was not stored by the daemon");
|
||||
MERROR("Obligation quorum for height: " << state_change.block_height << ", was not stored by the daemon");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -560,7 +560,7 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
int hf_version = m_blockchain.get_hard_fork_version(block_height);
|
||||
uint8_t hf_version = m_blockchain.get_hard_fork_version(block_height);
|
||||
|
||||
if (!check_service_node_portions(hf_version, service_node_portions)) return false;
|
||||
|
||||
|
@ -748,7 +748,7 @@ namespace service_nodes
|
|||
return false; // Is not a contribution TX don't need to check it.
|
||||
|
||||
parsed_tx_contribution parsed_contribution = {};
|
||||
const int hf_version = m_blockchain.get_hard_fork_version(block_height);
|
||||
const uint8_t hf_version = m_blockchain.get_hard_fork_version(block_height);
|
||||
if (!get_contribution(m_blockchain.nettype(), hf_version, tx, block_height, parsed_contribution))
|
||||
{
|
||||
LOG_PRINT_L1("Contribution TX: Could not decode contribution for service node: " << pubkey << " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
|
||||
|
@ -1374,35 +1374,29 @@ namespace service_nodes
|
|||
if (hf_version >= cryptonote::network_version_12_checkpointing)
|
||||
decomm_snode_list = m_transient_state.decommissioned_service_nodes_infos();
|
||||
|
||||
quorum_manager &manager = m_transient_state.quorum_states[height];
|
||||
for (int type_int = 0; type_int < static_cast<int>(quorum_type::count); type_int++)
|
||||
quorum_type const max_quorum_type = max_quorum_type_for_hf(hf_version);
|
||||
quorum_manager &manager = m_transient_state.quorum_states[height];
|
||||
for (int type_int = 0; type_int <= (int)max_quorum_type; type_int++)
|
||||
{
|
||||
auto type = static_cast<quorum_type>(type_int);
|
||||
size_t num_validators = 0, num_workers = 0;
|
||||
auto quorum = std::make_shared<testing_quorum>();
|
||||
std::vector<size_t> pub_keys_indexes;
|
||||
|
||||
size_t total_nodes = active_snode_list.size() + decomm_snode_list.size();
|
||||
if (type == quorum_type::obligations)
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_9_service_nodes)
|
||||
{
|
||||
size_t total_nodes = active_snode_list.size() + decomm_snode_list.size();
|
||||
num_validators = std::min(active_snode_list.size(), STATE_CHANGE_QUORUM_SIZE);
|
||||
pub_keys_indexes = generate_shuffled_service_node_index_list(total_nodes, block_hash, type, num_validators, active_snode_list.size());
|
||||
manager.obligations = quorum;
|
||||
size_t num_remaining_nodes = total_nodes - num_validators;
|
||||
num_workers = std::min(num_remaining_nodes, std::max(STATE_CHANGE_MIN_NODES_TO_TEST, num_remaining_nodes/STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST));
|
||||
}
|
||||
num_validators = std::min(active_snode_list.size(), STATE_CHANGE_QUORUM_SIZE);
|
||||
pub_keys_indexes = generate_shuffled_service_node_index_list(total_nodes, block_hash, type, num_validators, active_snode_list.size());
|
||||
manager.obligations = quorum;
|
||||
size_t num_remaining_nodes = total_nodes - num_validators;
|
||||
num_workers = std::min(num_remaining_nodes, std::max(STATE_CHANGE_MIN_NODES_TO_TEST, num_remaining_nodes/STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST));
|
||||
}
|
||||
else if (type == quorum_type::checkpointing)
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_12_checkpointing)
|
||||
{
|
||||
manager.checkpointing = quorum;
|
||||
num_validators = std::min(pub_keys_indexes.size(), CHECKPOINT_QUORUM_SIZE);
|
||||
size_t num_remaining_nodes = pub_keys_indexes.size() - num_validators;
|
||||
num_workers = std::min(num_remaining_nodes, CHECKPOINT_QUORUM_SIZE);
|
||||
}
|
||||
pub_keys_indexes = generate_shuffled_service_node_index_list(total_nodes, block_hash, type);
|
||||
manager.checkpointing = quorum;
|
||||
num_workers = std::min(pub_keys_indexes.size(), CHECKPOINT_QUORUM_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1460,7 +1454,7 @@ namespace service_nodes
|
|||
|
||||
bool service_node_list::store()
|
||||
{
|
||||
int hf_version = m_blockchain.get_current_hard_fork_version();
|
||||
uint8_t hf_version = m_blockchain.get_current_hard_fork_version();
|
||||
if (hf_version < cryptonote::network_version_9_service_nodes)
|
||||
return true;
|
||||
|
||||
|
@ -1678,7 +1672,7 @@ namespace service_nodes
|
|||
converted_registration_args convert_registration_args(cryptonote::network_type nettype,
|
||||
const std::vector<std::string>& args,
|
||||
uint64_t staking_requirement,
|
||||
int hf_version)
|
||||
uint8_t hf_version)
|
||||
{
|
||||
converted_registration_args result = {};
|
||||
if (args.size() % 2 == 0 || args.size() < 3)
|
||||
|
@ -1844,7 +1838,7 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
bool make_registration_cmd(cryptonote::network_type nettype,
|
||||
int hf_version,
|
||||
uint8_t hf_version,
|
||||
uint64_t staking_requirement,
|
||||
const std::vector<std::string>& args,
|
||||
const crypto::public_key& service_node_pubkey,
|
||||
|
|
|
@ -391,10 +391,10 @@ namespace service_nodes
|
|||
converted_registration_args convert_registration_args(cryptonote::network_type nettype,
|
||||
const std::vector<std::string>& args,
|
||||
uint64_t staking_requirement,
|
||||
int hf_version);
|
||||
uint8_t hf_version);
|
||||
|
||||
bool make_registration_cmd(cryptonote::network_type nettype,
|
||||
int hf_version,
|
||||
uint8_t hf_version,
|
||||
uint64_t staking_requirement,
|
||||
const std::vector<std::string>& args,
|
||||
const crypto::public_key& service_node_pubkey,
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
|
||||
namespace service_nodes
|
||||
{
|
||||
static_assert(quorum_cop::REORG_SAFETY_BUFFER_IN_BLOCKS < STATE_CHANGE_VOTE_LIFETIME, "Safety buffer should always be less than the vote lifetime");
|
||||
static_assert(quorum_cop::REORG_SAFETY_BUFFER_IN_BLOCKS < VOTE_LIFETIME, "Safety buffer should always be less than the vote lifetime");
|
||||
|
||||
quorum_cop::quorum_cop(cryptonote::core& core)
|
||||
: m_core(core), m_obligations_height(0), m_last_checkpointed_height(0)
|
||||
|
@ -118,6 +118,10 @@ namespace service_nodes
|
|||
|
||||
void quorum_cop::process_quorums(cryptonote::block const &block)
|
||||
{
|
||||
uint8_t const hf_version = block.major_version;
|
||||
if (hf_version < cryptonote::network_version_9_service_nodes)
|
||||
return;
|
||||
|
||||
crypto::public_key my_pubkey;
|
||||
crypto::secret_key my_seckey;
|
||||
if (!m_core.get_service_node_keys(my_pubkey, my_seckey))
|
||||
|
@ -126,21 +130,25 @@ namespace service_nodes
|
|||
if (!m_core.is_service_node(my_pubkey, /*require_active=*/ true))
|
||||
return;
|
||||
|
||||
uint64_t const height = cryptonote::get_block_height(block);
|
||||
for (int i = 0; i < (int)quorum_type::count; i++)
|
||||
uint64_t const height = cryptonote::get_block_height(block);
|
||||
uint64_t const latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height());
|
||||
if (latest_height < VOTE_LIFETIME)
|
||||
return;
|
||||
|
||||
uint64_t const start_voting_from_height = latest_height - VOTE_LIFETIME;
|
||||
if (height < start_voting_from_height)
|
||||
return;
|
||||
|
||||
service_nodes::quorum_type const max_quorum_type = service_nodes::max_quorum_type_for_hf(hf_version);
|
||||
for (int i = 0; i <= (int)max_quorum_type; i++)
|
||||
{
|
||||
quorum_type const type = static_cast<quorum_type>(i);
|
||||
uint64_t const vote_lifetime = service_nodes::quorum_vote_lifetime(type);
|
||||
quorum_type const type = static_cast<quorum_type>(i);
|
||||
|
||||
uint64_t const latest_height = std::max(m_core.get_current_blockchain_height(), m_core.get_target_blockchain_height());
|
||||
if (latest_height < vote_lifetime)
|
||||
continue;
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
if (loki::integration_test.disable_checkpoint_quorum && type == quorum_type::checkpointing) continue;
|
||||
if (loki::integration_test.disable_obligation_quorum && type == quorum_type::obligations) continue;
|
||||
#endif
|
||||
|
||||
uint64_t const start_voting_from_height = latest_height - vote_lifetime;
|
||||
if (height < start_voting_from_height)
|
||||
continue;
|
||||
|
||||
int const hf_version = block.major_version;
|
||||
switch(type)
|
||||
{
|
||||
default:
|
||||
|
@ -151,164 +159,144 @@ namespace service_nodes
|
|||
|
||||
case quorum_type::obligations:
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_9_service_nodes)
|
||||
// NOTE: Wait atleast 2 hours before we're allowed to vote so that we collect necessary voting information from people on the network
|
||||
time_t const now = time(nullptr);
|
||||
bool alive_for_min_time = (now - m_core.get_start_time()) >= MIN_TIME_IN_S_BEFORE_VOTING;
|
||||
if (!alive_for_min_time)
|
||||
break;
|
||||
|
||||
m_obligations_height = std::max(m_obligations_height, start_voting_from_height);
|
||||
for (; m_obligations_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_obligations_height++)
|
||||
{
|
||||
// NOTE: Wait atleast 2 hours before we're allowed to vote so that we collect uptimes from everyone on the network
|
||||
time_t const now = time(nullptr);
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
constexpr time_t min_lifetime = 0;
|
||||
#else
|
||||
constexpr time_t min_lifetime = UPTIME_PROOF_MAX_TIME_IN_SECONDS;
|
||||
#endif
|
||||
bool alive_for_min_time = (now - m_core.get_start_time()) >= min_lifetime;
|
||||
if (!alive_for_min_time)
|
||||
if (m_core.get_hard_fork_version(m_obligations_height) < cryptonote::network_version_9_service_nodes) continue;
|
||||
|
||||
std::shared_ptr<const testing_quorum> quorum = m_core.get_testing_quorum(quorum_type::obligations, m_obligations_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Obligations quorum for height: " << m_obligations_height << " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_obligations_height < start_voting_from_height)
|
||||
m_obligations_height = start_voting_from_height;
|
||||
if (quorum->workers.empty()) continue;
|
||||
|
||||
for (; m_obligations_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_obligations_height++)
|
||||
int index_in_group = find_index_in_quorum_group(quorum->validators, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum
|
||||
//
|
||||
auto worker_states = m_core.get_service_node_list_state(quorum->workers);
|
||||
auto worker_it = worker_states.begin();
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
int good = 0, total = 0;
|
||||
for (size_t node_index = 0; node_index < quorum->workers.size(); ++worker_it, ++node_index)
|
||||
{
|
||||
if (m_core.get_hard_fork_version(m_obligations_height) < cryptonote::network_version_9_service_nodes) continue;
|
||||
// If the SN no longer exists then it'll be omitted from the worker_states vector,
|
||||
// so if the elements don't line up skip ahead.
|
||||
while (worker_it->pubkey != quorum->workers[node_index] && node_index < quorum->workers.size())
|
||||
node_index++;
|
||||
if (node_index == quorum->workers.size())
|
||||
break;
|
||||
total++;
|
||||
|
||||
const std::shared_ptr<const testing_quorum> quorum =
|
||||
m_core.get_testing_quorum(quorum_type::obligations, m_obligations_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Obligations quorum for height: " << m_obligations_height << " was not cached in daemon!");
|
||||
continue;
|
||||
const auto &node_key = worker_it->pubkey;
|
||||
const auto &info = worker_it->info;
|
||||
|
||||
bool checks_passed = check_service_node(node_key, info);
|
||||
|
||||
new_state vote_for_state;
|
||||
if (checks_passed) {
|
||||
if (!info.is_decommissioned()) {
|
||||
good++;
|
||||
continue;
|
||||
}
|
||||
|
||||
vote_for_state = new_state::recommission;
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " is now passing required checks; voting to recommission");
|
||||
}
|
||||
else {
|
||||
int64_t credit = calculate_decommission_credit(info, latest_height);
|
||||
|
||||
if (quorum->workers.empty()) continue;
|
||||
|
||||
int index_in_group = find_index_in_quorum_group(quorum->validators, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum
|
||||
//
|
||||
auto worker_states = m_core.get_service_node_list_state(quorum->workers);
|
||||
auto worker_it = worker_states.begin();
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
int good = 0, total = 0;
|
||||
for (size_t node_index = 0; node_index < quorum->workers.size(); ++worker_it, ++node_index)
|
||||
{
|
||||
// If the SN no longer exists then it'll be omitted from the worker_states vector,
|
||||
// so if the elements don't line up skip ahead.
|
||||
while (worker_it->pubkey != quorum->workers[node_index] && node_index < quorum->workers.size())
|
||||
node_index++;
|
||||
if (node_index == quorum->workers.size())
|
||||
break;
|
||||
total++;
|
||||
|
||||
const auto &node_key = worker_it->pubkey;
|
||||
const auto &info = worker_it->info;
|
||||
|
||||
bool checks_passed = check_service_node(node_key, info);
|
||||
|
||||
new_state vote_for_state;
|
||||
if (checks_passed) {
|
||||
if (!info.is_decommissioned()) {
|
||||
good++;
|
||||
if (info.is_decommissioned()) {
|
||||
if (credit >= 0) {
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " is still not passing required checks, but has remaining credit (" <<
|
||||
credit << " blocks); abstaining (to leave decommissioned)");
|
||||
continue;
|
||||
}
|
||||
|
||||
vote_for_state = new_state::recommission;
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " is now passing required checks; voting to recommission");
|
||||
}
|
||||
else {
|
||||
int64_t credit = calculate_decommission_credit(info, latest_height);
|
||||
|
||||
if (info.is_decommissioned()) {
|
||||
if (credit >= 0) {
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " is still not passing required checks, but has remaining credit (" <<
|
||||
credit << " blocks); abstaining (to leave decommissioned)");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " has no remaining credit; voting to deregister");
|
||||
vote_for_state = new_state::deregister; // Credit ran out!
|
||||
LOG_PRINT_L2("Decommissioned service node " << quorum->workers[node_index] << " has no remaining credit; voting to deregister");
|
||||
vote_for_state = new_state::deregister; // Credit ran out!
|
||||
} else {
|
||||
if (credit >= DECOMMISSION_MINIMUM) {
|
||||
vote_for_state = new_state::decommission;
|
||||
LOG_PRINT_L2("Service node " << quorum->workers[node_index] << " has stopped passing required checks, but has sufficient earned credit (" <<
|
||||
credit << " blocks) to avoid deregistration; voting to decommission");
|
||||
} else {
|
||||
if (credit >= DECOMMISSION_MINIMUM) {
|
||||
vote_for_state = new_state::decommission;
|
||||
LOG_PRINT_L2("Service node " << quorum->workers[node_index] << " has stopped passing required checks, but has sufficient earned credit (" <<
|
||||
credit << " blocks) to avoid deregistration; voting to decommission");
|
||||
} else {
|
||||
vote_for_state = new_state::deregister;
|
||||
LOG_PRINT_L2("Service node " << quorum->workers[node_index] << " has stopped passing required checks, but does not have sufficient earned credit (" <<
|
||||
credit << " blocks, " << DECOMMISSION_MINIMUM << " required) to decommission; voting to deregister");
|
||||
}
|
||||
vote_for_state = new_state::deregister;
|
||||
LOG_PRINT_L2("Service node " << quorum->workers[node_index] << " has stopped passing required checks, but does not have sufficient earned credit (" <<
|
||||
credit << " blocks, " << DECOMMISSION_MINIMUM << " required) to decommission; voting to deregister");
|
||||
}
|
||||
}
|
||||
|
||||
quorum_vote_t vote = service_nodes::make_state_change_vote(
|
||||
m_obligations_height, static_cast<uint16_t>(index_in_group), node_index, vote_for_state, my_pubkey, my_seckey);
|
||||
cryptonote::vote_verification_context vvc;
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add uptime check_state vote; reason: " << print_vote_verification_context(vvc, nullptr));
|
||||
}
|
||||
if (good > 0)
|
||||
LOG_PRINT_L2(good << " of " << total << " service nodes are active and passing checks; no state change votes required");
|
||||
|
||||
quorum_vote_t vote = service_nodes::make_state_change_vote(
|
||||
m_obligations_height, static_cast<uint16_t>(index_in_group), node_index, vote_for_state, my_pubkey, my_seckey);
|
||||
cryptonote::vote_verification_context vvc;
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add state change vote; reason: " << print_vote_verification_context(vvc, &vote));
|
||||
}
|
||||
if (good > 0)
|
||||
LOG_PRINT_L2(good << " of " << total << " service nodes are active and passing checks; no state change votes required");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
if (hf_version >= cryptonote::network_version_12_checkpointing)
|
||||
m_last_checkpointed_height = std::max(start_voting_from_height, m_last_checkpointed_height);
|
||||
for (m_last_checkpointed_height += (m_last_checkpointed_height % CHECKPOINT_INTERVAL);
|
||||
m_last_checkpointed_height <= height;
|
||||
m_last_checkpointed_height += CHECKPOINT_INTERVAL)
|
||||
{
|
||||
if (m_last_checkpointed_height < start_voting_from_height)
|
||||
m_last_checkpointed_height = start_voting_from_height;
|
||||
if (m_core.get_hard_fork_version(m_last_checkpointed_height) <= cryptonote::network_version_11_infinite_staking)
|
||||
continue;
|
||||
|
||||
for (m_last_checkpointed_height += (m_last_checkpointed_height % CHECKPOINT_INTERVAL);
|
||||
m_last_checkpointed_height <= height;
|
||||
m_last_checkpointed_height += CHECKPOINT_INTERVAL)
|
||||
const std::shared_ptr<const testing_quorum> quorum =
|
||||
m_core.get_testing_quorum(quorum_type::checkpointing, m_last_checkpointed_height);
|
||||
if (!quorum)
|
||||
{
|
||||
if (m_core.get_hard_fork_version(m_last_checkpointed_height) < cryptonote::network_version_12_checkpointing)
|
||||
continue;
|
||||
|
||||
const std::shared_ptr<const testing_quorum> quorum =
|
||||
m_core.get_testing_quorum(quorum_type::checkpointing, m_last_checkpointed_height);
|
||||
if (!quorum)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Checkpoint quorum for height: " << m_last_checkpointed_height
|
||||
<< " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
|
||||
int index_in_group = find_index_in_quorum_group(quorum->workers, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum, handle checkpointing
|
||||
//
|
||||
quorum_vote_t vote = {};
|
||||
vote.type = quorum_type::checkpointing;
|
||||
vote.checkpoint.block_hash = m_core.get_block_id_by_height(m_last_checkpointed_height);
|
||||
|
||||
if (vote.checkpoint.block_hash == crypto::null_hash)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Could not get block hash for block on height: " << m_last_checkpointed_height);
|
||||
continue;
|
||||
}
|
||||
|
||||
vote.block_height = m_last_checkpointed_height;
|
||||
vote.group = quorum_group::worker;
|
||||
vote.index_in_group = static_cast<uint16_t>(index_in_group);
|
||||
vote.signature = make_signature_from_vote(vote, my_pubkey, my_seckey);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add checkpoint vote reason: " << print_vote_verification_context(vvc, nullptr));
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Checkpoint quorum for height: " << m_last_checkpointed_height << " was not cached in daemon!");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
int index_in_group = find_index_in_quorum_group(quorum->workers, my_pubkey);
|
||||
if (index_in_group <= -1) continue;
|
||||
|
||||
//
|
||||
// NOTE: I am in the quorum, handle checkpointing
|
||||
//
|
||||
quorum_vote_t vote = {};
|
||||
vote.type = quorum_type::checkpointing;
|
||||
vote.checkpoint.block_hash = m_core.get_block_id_by_height(m_last_checkpointed_height);
|
||||
|
||||
if (vote.checkpoint.block_hash == crypto::null_hash)
|
||||
{
|
||||
// TODO(loki): Fatal error
|
||||
LOG_ERROR("Could not get block hash for block on height: " << m_last_checkpointed_height);
|
||||
continue;
|
||||
}
|
||||
|
||||
vote.block_height = m_last_checkpointed_height;
|
||||
vote.group = quorum_group::worker;
|
||||
vote.index_in_group = static_cast<uint16_t>(index_in_group);
|
||||
vote.signature = make_signature_from_vote(vote, my_pubkey, my_seckey);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
if (!handle_vote(vote, vvc))
|
||||
LOG_ERROR("Failed to add checkpoint vote; reason: " << print_vote_verification_context(vvc, &vote));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -446,6 +434,7 @@ namespace service_nodes
|
|||
|
||||
m_core.get_blockchain_storage().update_checkpoint(checkpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L2("Don't have enough votes yet to submit a checkpoint: have " << votes.size() << " of " << CHECKPOINT_MIN_VOTES << " required");
|
||||
}
|
||||
|
|
|
@ -63,6 +63,8 @@ namespace service_nodes
|
|||
struct quorum_manager
|
||||
{
|
||||
std::shared_ptr<const testing_quorum> obligations;
|
||||
// TODO(doyle): Validators aren't used, but I kept this as a testing_quorum
|
||||
// to avoid drastic changes for now to a lot of the service node API
|
||||
std::shared_ptr<const testing_quorum> checkpointing;
|
||||
};
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
namespace service_nodes {
|
||||
|
||||
|
||||
uint64_t get_staking_requirement(cryptonote::network_type m_nettype, uint64_t height, int hf_version)
|
||||
uint64_t get_staking_requirement(cryptonote::network_type m_nettype, uint64_t height, uint8_t hf_version)
|
||||
{
|
||||
if (m_nettype == cryptonote::TESTNET || m_nettype == cryptonote::FAKECHAIN)
|
||||
return COIN * 100;
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include "cryptonote_config.h"
|
||||
#include "service_node_voting.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace service_nodes {
|
||||
// State change quorums are in charge of policing the network by changing the state of a service
|
||||
// node on the network: temporary decommissioning, recommissioning, and permanent deregistration.
|
||||
|
@ -13,7 +11,7 @@ namespace service_nodes {
|
|||
constexpr size_t STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE = 7;
|
||||
constexpr size_t STATE_CHANGE_NTH_OF_THE_NETWORK_TO_TEST = 100;
|
||||
constexpr size_t STATE_CHANGE_MIN_NODES_TO_TEST = 50;
|
||||
constexpr uint64_t STATE_CHANGE_VOTE_LIFETIME = BLOCKS_EXPECTED_IN_HOURS(2);
|
||||
constexpr uint64_t VOTE_LIFETIME = BLOCKS_EXPECTED_IN_HOURS(2);
|
||||
|
||||
static_assert(STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE <= STATE_CHANGE_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
|
||||
|
@ -39,15 +37,17 @@ namespace service_nodes {
|
|||
|
||||
static_assert(DECOMMISSION_INITIAL_CREDIT <= DECOMMISSION_MAX_CREDIT, "Initial registration decommission credit cannot be larger than the maximum decommission credit");
|
||||
|
||||
constexpr uint64_t CHECKPOINT_INTERVAL = 4; // Checkpoint every 4 blocks and prune when too old except if (height % CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
|
||||
constexpr uint64_t CHECKPOINT_STORE_PERSISTENTLY_INTERVAL = 60; // Persistently store the checkpoints at these intervals
|
||||
constexpr uint64_t CHECKPOINT_VOTE_LIFETIME = CHECKPOINT_STORE_PERSISTENTLY_INTERVAL; // Keep the last 60 blocks worth of votes
|
||||
constexpr uint64_t CHECKPOINT_INTERVAL = 4; // Checkpoint every 4 blocks and prune when too old except if (height % CHECKPOINT_STORE_PERSISTENTLY_INTERVAL == 0)
|
||||
constexpr uint64_t CHECKPOINT_STORE_PERSISTENTLY_INTERVAL = 60; // Persistently store the checkpoints at these intervals
|
||||
constexpr uint64_t CHECKPOINT_VOTE_LIFETIME = CHECKPOINT_STORE_PERSISTENTLY_INTERVAL; // Keep the last 60 blocks worth of votes
|
||||
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 1;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 1;
|
||||
constexpr ptrdiff_t MIN_TIME_IN_S_BEFORE_VOTING = 0;
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 1;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 1;
|
||||
#else
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 20;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 18;
|
||||
constexpr ptrdiff_t MIN_TIME_IN_S_BEFORE_VOTING = UPTIME_PROOF_MAX_TIME_IN_SECONDS;
|
||||
constexpr size_t CHECKPOINT_QUORUM_SIZE = 20;
|
||||
constexpr size_t CHECKPOINT_MIN_VOTES = 13;
|
||||
#endif
|
||||
|
||||
static_assert(CHECKPOINT_MIN_VOTES <= CHECKPOINT_QUORUM_SIZE, "The number of votes required to kick can't exceed the actual quorum size, otherwise we never kick.");
|
||||
|
@ -72,13 +72,21 @@ namespace service_nodes {
|
|||
constexpr int MAX_KEY_IMAGES_PER_CONTRIBUTOR = 1;
|
||||
constexpr uint64_t KEY_IMAGE_AWAITING_UNLOCK_HEIGHT = 0;
|
||||
|
||||
constexpr uint64_t STATE_CHANGE_TX_LIFETIME_IN_BLOCKS = STATE_CHANGE_VOTE_LIFETIME;
|
||||
constexpr uint64_t STATE_CHANGE_TX_LIFETIME_IN_BLOCKS = VOTE_LIFETIME;
|
||||
constexpr size_t QUORUM_LIFETIME = (6 * STATE_CHANGE_TX_LIFETIME_IN_BLOCKS);
|
||||
|
||||
|
||||
using swarm_id_t = uint64_t;
|
||||
constexpr swarm_id_t UNASSIGNED_SWARM_ID = UINT64_MAX;
|
||||
|
||||
inline quorum_type max_quorum_type_for_hf(uint8_t hf_version)
|
||||
{
|
||||
quorum_type result = (hf_version <= cryptonote::network_version_11_infinite_staking) ? quorum_type::obligations
|
||||
: quorum_type::checkpointing;
|
||||
assert(result != quorum_type::count);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint64_t staking_num_lock_blocks(cryptonote::network_type nettype)
|
||||
{
|
||||
switch (nettype)
|
||||
|
@ -89,27 +97,12 @@ namespace service_nodes {
|
|||
}
|
||||
}
|
||||
|
||||
inline uint64_t quorum_vote_lifetime(quorum_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case quorum_type::obligations: return STATE_CHANGE_VOTE_LIFETIME;
|
||||
case quorum_type::checkpointing: return CHECKPOINT_VOTE_LIFETIME;
|
||||
default:
|
||||
{
|
||||
assert("Unhandled enum type" == 0);
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static_assert(STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
|
||||
// return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in loki atomic units
|
||||
uint64_t get_min_node_contribution (uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions);
|
||||
uint64_t get_min_node_contribution_in_portions(uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions);
|
||||
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height, int hf_version);
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height, uint8_t hf_version);
|
||||
|
||||
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement);
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "cryptonote_basic/verification_context.h"
|
||||
#include "cryptonote_basic/connection_context.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
||||
#include "checkpoints/checkpoints.h"
|
||||
|
||||
#include "misc_log_ex.h"
|
||||
#include "string_tools.h"
|
||||
|
@ -60,6 +61,7 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO(loki): Post HF12 remove legacy votes, no longer should be propagated
|
||||
quorum_vote_t convert_legacy_deregister_vote(legacy_deregister_vote const &vote)
|
||||
{
|
||||
quorum_vote_t result = {};
|
||||
|
@ -133,22 +135,22 @@ namespace service_nodes
|
|||
return result;
|
||||
}
|
||||
|
||||
static bool bounds_check_worker_index(service_nodes::testing_quorum const &quorum, uint32_t worker_index, cryptonote::vote_verification_context &vvc)
|
||||
static bool bounds_check_worker_index(service_nodes::testing_quorum const &quorum, uint32_t worker_index, cryptonote::vote_verification_context *vvc)
|
||||
{
|
||||
if (worker_index >= quorum.workers.size())
|
||||
{
|
||||
vvc.m_worker_index_out_of_bounds = true;
|
||||
if (vvc) vvc->m_worker_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Quorum worker index in was out of bounds: " << worker_index << ", expected to be in range of: [0, " << quorum.workers.size() << ")");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool bounds_check_validator_index(service_nodes::testing_quorum const &quorum, uint32_t validator_index, cryptonote::vote_verification_context &vvc)
|
||||
static bool bounds_check_validator_index(service_nodes::testing_quorum const &quorum, uint32_t validator_index, cryptonote::vote_verification_context *vvc)
|
||||
{
|
||||
if (validator_index >= quorum.validators.size())
|
||||
{
|
||||
vvc.m_validator_index_out_of_bounds = true;
|
||||
if (vvc) vvc->m_validator_index_out_of_bounds = true;
|
||||
LOG_PRINT_L1("Validator's index was out of bounds: " << validator_index << ", expected to be in range of: [0, " << quorum.validators.size() << ")");
|
||||
return false;
|
||||
}
|
||||
|
@ -156,6 +158,7 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
bool verify_tx_state_change(const cryptonote::tx_extra_service_node_state_change &state_change,
|
||||
uint64_t latest_height,
|
||||
cryptonote::vote_verification_context &vvc,
|
||||
const service_nodes::testing_quorum &quorum,
|
||||
const uint8_t hf_version)
|
||||
|
@ -185,14 +188,39 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!bounds_check_worker_index(quorum, state_change.service_node_index, vvc))
|
||||
if (!bounds_check_worker_index(quorum, state_change.service_node_index, &vvc))
|
||||
return false;
|
||||
|
||||
// Check if state_change is too old or too new to hold onto
|
||||
{
|
||||
if (state_change.block_height >= latest_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received state change tx for height: " << state_change.block_height
|
||||
<< " and service node: " << state_change.service_node_index
|
||||
<< ", is newer than current height: " << latest_height
|
||||
<< " blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t delta_height = latest_height - state_change.block_height;
|
||||
if (latest_height >= state_change.block_height && delta_height >= service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS)
|
||||
{
|
||||
LOG_PRINT_L1("Received state change tx for height: "
|
||||
<< state_change.block_height << " and service node: " << state_change.service_node_index
|
||||
<< ", is older than: " << service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS
|
||||
<< " (current height: " << latest_height << ") "
|
||||
<< "blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
crypto::hash const hash = make_state_change_vote_hash(state_change.block_height, state_change.service_node_index, state_change.state);
|
||||
std::array<int, service_nodes::STATE_CHANGE_QUORUM_SIZE> validator_set = {};
|
||||
for (const auto &vote : state_change.votes)
|
||||
{
|
||||
if (!bounds_check_validator_index(quorum, vote.validator_index, vvc))
|
||||
if (!bounds_check_validator_index(quorum, vote.validator_index, &vvc))
|
||||
return false;
|
||||
|
||||
if (++validator_set[vote.validator_index] > 1)
|
||||
|
@ -214,6 +242,53 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
bool verify_checkpoint(cryptonote::checkpoint_t const &checkpoint, service_nodes::testing_quorum const &quorum)
|
||||
{
|
||||
if (checkpoint.type == cryptonote::checkpoint_type::service_node)
|
||||
{
|
||||
if (checkpoint.signatures.size() < service_nodes::CHECKPOINT_MIN_VOTES)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has insufficient signatures to be considered");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkpoint.signatures.size() > service_nodes::CHECKPOINT_QUORUM_SIZE)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has too many signatures to be considered");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<size_t, service_nodes::CHECKPOINT_QUORUM_SIZE> unique_vote_set = {};
|
||||
for (service_nodes::voter_to_signature const &voter_to_signature : checkpoint.signatures)
|
||||
{
|
||||
if (!bounds_check_worker_index(quorum, voter_to_signature.voter_index, nullptr)) return false;
|
||||
|
||||
if (unique_vote_set[voter_to_signature.voter_index]++)
|
||||
{
|
||||
LOG_PRINT_L1("Voter quorum index is duplicated: " << voter_to_signature.voter_index);
|
||||
return false;
|
||||
}
|
||||
|
||||
crypto::public_key const &key = quorum.workers[voter_to_signature.voter_index];
|
||||
if (!crypto::check_signature(checkpoint.block_hash, key, voter_to_signature.signature))
|
||||
{
|
||||
LOG_PRINT_L1("Invalid signatures for votes");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (checkpoint.signatures.size() != 0)
|
||||
{
|
||||
LOG_PRINT_L1("Non service-node checkpoints should have no signatures");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
quorum_vote_t make_state_change_vote(uint64_t block_height, uint16_t validator_index, uint16_t worker_index, new_state state, crypto::public_key const &pub_key, crypto::secret_key const &sec_key)
|
||||
{
|
||||
quorum_vote_t result = {};
|
||||
|
@ -233,14 +308,39 @@ namespace service_nodes
|
|||
if (vote.group == quorum_group::invalid)
|
||||
result = false;
|
||||
else if (vote.group == quorum_group::validator)
|
||||
result = bounds_check_validator_index(quorum, vote.index_in_group, vvc);
|
||||
result = bounds_check_validator_index(quorum, vote.index_in_group, &vvc);
|
||||
else
|
||||
result = bounds_check_worker_index(quorum, vote.index_in_group, vvc);
|
||||
result = bounds_check_worker_index(quorum, vote.index_in_group, &vvc);
|
||||
|
||||
if (!result)
|
||||
return result;
|
||||
|
||||
uint64_t max_vote_age = 0;
|
||||
//
|
||||
// NOTE: Validate vote age
|
||||
//
|
||||
{
|
||||
uint64_t delta_height = latest_height - vote.block_height;
|
||||
if (vote.block_height < latest_height && delta_height >= VOTE_LIFETIME)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is older than: " << VOTE_LIFETIME
|
||||
<< " blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
else if (vote.block_height > latest_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is newer than: " << latest_height
|
||||
<< " (latest block height) and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
|
||||
if (vvc.m_invalid_block_height)
|
||||
{
|
||||
result = false;
|
||||
vvc.m_verification_failed = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
crypto::public_key key = crypto::null_pkey;
|
||||
crypto::hash hash = crypto::null_hash;
|
||||
|
@ -263,11 +363,10 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
key = quorum.validators[vote.index_in_group];
|
||||
max_vote_age = service_nodes::STATE_CHANGE_VOTE_LIFETIME;
|
||||
hash = make_state_change_vote_hash(vote.block_height, vote.state_change.worker_index, vote.state_change.state);
|
||||
key = quorum.validators[vote.index_in_group];
|
||||
hash = make_state_change_vote_hash(vote.block_height, vote.state_change.worker_index, vote.state_change.state);
|
||||
|
||||
bool result = bounds_check_worker_index(quorum, vote.state_change.worker_index, vvc);
|
||||
bool result = bounds_check_worker_index(quorum, vote.state_change.worker_index, &vvc);
|
||||
if (!result)
|
||||
return result;
|
||||
}
|
||||
|
@ -282,9 +381,8 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
key = quorum.workers[vote.index_in_group];
|
||||
max_vote_age = service_nodes::CHECKPOINT_VOTE_LIFETIME;
|
||||
hash = vote.checkpoint.block_hash;
|
||||
key = quorum.workers[vote.index_in_group];
|
||||
hash = vote.checkpoint.block_hash;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -300,32 +398,6 @@ namespace service_nodes
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE: Validate vote age
|
||||
//
|
||||
{
|
||||
uint64_t delta_height = latest_height - vote.block_height;
|
||||
if (vote.block_height < latest_height && delta_height >= max_vote_age)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is older than: " << max_vote_age
|
||||
<< " blocks and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
else if (vote.block_height > latest_height)
|
||||
{
|
||||
LOG_PRINT_L1("Received vote for height: " << vote.block_height << ", is newer than: " << latest_height
|
||||
<< " (latest block height) and has been rejected.");
|
||||
vvc.m_invalid_block_height = true;
|
||||
}
|
||||
|
||||
if (vvc.m_invalid_block_height)
|
||||
{
|
||||
result = false;
|
||||
vvc.m_verification_failed = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = true;
|
||||
return result;
|
||||
}
|
||||
|
@ -486,11 +558,9 @@ namespace service_nodes
|
|||
void voting_pool::remove_expired_votes(uint64_t height)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_lock);
|
||||
uint64_t deregister_min_height = (height < STATE_CHANGE_VOTE_LIFETIME) ? 0 : height - STATE_CHANGE_VOTE_LIFETIME;
|
||||
cull_votes(m_obligations_pool, deregister_min_height, height);
|
||||
|
||||
uint64_t checkpoint_min_height = (height < CHECKPOINT_VOTE_LIFETIME) ? 0 : height - CHECKPOINT_VOTE_LIFETIME;
|
||||
cull_votes(m_checkpoint_pool, checkpoint_min_height, height);
|
||||
uint64_t min_height = (height < VOTE_LIFETIME) ? 0 : height - VOTE_LIFETIME;
|
||||
cull_votes(m_obligations_pool, min_height, height);
|
||||
cull_votes(m_checkpoint_pool, min_height, height);
|
||||
}
|
||||
}; // namespace service_nodes
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
namespace cryptonote
|
||||
{
|
||||
struct vote_verification_context;
|
||||
struct checkpoint_t;
|
||||
};
|
||||
|
||||
namespace service_nodes
|
||||
|
@ -70,6 +71,16 @@ namespace service_nodes
|
|||
count,
|
||||
};
|
||||
|
||||
inline char const *quorum_type_to_string(quorum_type v)
|
||||
{
|
||||
switch(v)
|
||||
{
|
||||
case quorum_type::obligations: return "obligation";
|
||||
case quorum_type::checkpointing: return "checkpointing";
|
||||
default: assert(false); return "xx_unhandled_type";
|
||||
}
|
||||
}
|
||||
|
||||
enum struct quorum_group : uint8_t { invalid, validator, worker };
|
||||
struct quorum_vote_t
|
||||
{
|
||||
|
@ -89,7 +100,8 @@ namespace service_nodes
|
|||
|
||||
quorum_vote_t make_state_change_vote(uint64_t block_height, uint16_t index_in_group, uint16_t worker_index, new_state state, crypto::public_key const &pub_key, crypto::secret_key const &secret_key);
|
||||
|
||||
bool verify_tx_state_change (const cryptonote::tx_extra_service_node_state_change& state_change, cryptonote::vote_verification_context& vvc, const service_nodes::testing_quorum &quorum, uint8_t hf_version);
|
||||
bool verify_checkpoint (cryptonote::checkpoint_t const &checkpoint, service_nodes::testing_quorum const &quorum);
|
||||
bool verify_tx_state_change (const cryptonote::tx_extra_service_node_state_change& state_change, uint64_t latest_height, cryptonote::vote_verification_context& vvc, const service_nodes::testing_quorum &quorum, uint8_t hf_version);
|
||||
bool verify_vote (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc, const service_nodes::testing_quorum &quorum);
|
||||
crypto::signature make_signature_from_vote (quorum_vote_t const &vote, const crypto::public_key& pub, const crypto::secret_key& sec);
|
||||
crypto::signature make_signature_from_tx_state_change(cryptonote::tx_extra_service_node_state_change const &state_change, crypto::public_key const &pub, crypto::secret_key const &sec);
|
||||
|
|
|
@ -99,13 +99,17 @@ namespace cryptonote
|
|||
// the whole prepare/handle/cleanup incoming block sequence.
|
||||
class LockedTXN {
|
||||
public:
|
||||
LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false) {
|
||||
LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false), m_active(false) {
|
||||
m_batch = m_blockchain.get_db().batch_start();
|
||||
m_active = true;
|
||||
}
|
||||
~LockedTXN() { try { if (m_batch) { m_blockchain.get_db().batch_stop(); } } catch (const std::exception &e) { MWARNING("LockedTXN dtor filtering exception: " << e.what()); } }
|
||||
void commit() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_stop(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::commit filtering exception: " << e.what()); } }
|
||||
void abort() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_abort(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::abort filtering exception: " << e.what()); } }
|
||||
~LockedTXN() { this->abort(); }
|
||||
private:
|
||||
Blockchain &m_blockchain;
|
||||
bool m_batch;
|
||||
bool m_active;
|
||||
};
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
|
@ -340,6 +344,7 @@ namespace cryptonote
|
|||
if (!insert_key_images(tx, id, kept_by_block))
|
||||
return false;
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(non_standard_tx, fee / (double)tx_weight, receive_time), id);
|
||||
lock.commit();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
@ -384,6 +389,7 @@ namespace cryptonote
|
|||
if (!insert_key_images(tx, id, kept_by_block))
|
||||
return false;
|
||||
m_txs_by_fee_and_receive_time.emplace(std::tuple<bool, double, std::time_t>(non_standard_tx, fee / (double)tx_weight, receive_time), id);
|
||||
lock.commit();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
@ -535,6 +541,7 @@ namespace cryptonote
|
|||
return;
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
if (changed)
|
||||
++m_cookie;
|
||||
if (m_txpool_weight > bytes)
|
||||
|
@ -631,6 +638,7 @@ namespace cryptonote
|
|||
m_blockchain.remove_txpool_tx(id);
|
||||
m_txpool_weight -= tx_weight;
|
||||
remove_transaction_keyimages(tx, id);
|
||||
lock.commit();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
|
@ -715,6 +723,7 @@ namespace cryptonote
|
|||
// ignore error
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
++m_cookie;
|
||||
}
|
||||
return true;
|
||||
|
@ -800,6 +809,7 @@ namespace cryptonote
|
|||
// continue
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const
|
||||
|
@ -1278,6 +1288,7 @@ namespace cryptonote
|
|||
}
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
if (changed)
|
||||
++m_cookie;
|
||||
}
|
||||
|
@ -1438,6 +1449,7 @@ namespace cryptonote
|
|||
append_key_images(k_images, tx);
|
||||
LOG_PRINT_L2(" added, new block weight " << total_weight << "/" << max_total_weight << ", coinbase " << print_money(best_coinbase));
|
||||
}
|
||||
lock.commit();
|
||||
|
||||
expected_reward = best_coinbase;
|
||||
LOG_PRINT_L2("Block template filled with " << bl.tx_hashes.size() << " txes, weight "
|
||||
|
@ -1503,6 +1515,7 @@ namespace cryptonote
|
|||
// continue
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
}
|
||||
if (n_removed > 0)
|
||||
++m_cookie;
|
||||
|
@ -1564,6 +1577,7 @@ namespace cryptonote
|
|||
// ignore error
|
||||
}
|
||||
}
|
||||
lock.commit();
|
||||
}
|
||||
|
||||
m_cookie = 0;
|
||||
|
|
|
@ -647,18 +647,16 @@ namespace cryptonote
|
|||
{
|
||||
MDEBUG("We have all needed txes for this fluffy block");
|
||||
|
||||
block_complete_entry b;
|
||||
b.block = arg.b.block;
|
||||
b.txs = have_tx;
|
||||
block_complete_entry b = {};
|
||||
b.block = arg.b.block;
|
||||
b.txs = have_tx;
|
||||
|
||||
std::vector<block_complete_entry> blocks;
|
||||
blocks.push_back(b);
|
||||
|
||||
|
||||
// TODO(doyle): Checkpointing
|
||||
std::vector<block> pblocks;
|
||||
std::vector<checkpoint_t> checkpoints;
|
||||
if (!m_core.prepare_handle_incoming_blocks(blocks, pblocks, checkpoints))
|
||||
if (!m_core.prepare_handle_incoming_blocks(blocks, pblocks))
|
||||
{
|
||||
LOG_PRINT_CCONTEXT_L0("Failure in prepare_handle_incoming_blocks");
|
||||
m_core.resume_mine();
|
||||
|
@ -666,7 +664,7 @@ namespace cryptonote
|
|||
}
|
||||
|
||||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
m_core.handle_incoming_block(arg.b.block, pblocks.empty() ? NULL : &pblocks[0], bvc); // got block from handle_notify_new_block
|
||||
m_core.handle_incoming_block(arg.b.block, pblocks.empty() ? NULL : &pblocks[0], bvc, nullptr /*checkpoint*/); // got block from handle_notify_new_block
|
||||
if (!m_core.cleanup_handle_incoming_blocks(true))
|
||||
{
|
||||
LOG_PRINT_CCONTEXT_L0("Failure in cleanup_handle_incoming_blocks");
|
||||
|
@ -752,7 +750,7 @@ namespace cryptonote
|
|||
|
||||
if (vvc.m_verification_failed)
|
||||
{
|
||||
LOG_PRINT_CCONTEXT_L1("Checkpoint vote verification failed, dropping connection");
|
||||
LOG_PRINT_CCONTEXT_L1("Vote type: " << service_nodes::quorum_type_to_string(it->type) << ", verification failed, dropping connection");
|
||||
drop_connection(context, false /*add_fail*/, false /*flush_all_spans i.e. delete cached block data from this peer*/);
|
||||
return 1;
|
||||
}
|
||||
|
@ -1271,9 +1269,8 @@ namespace cryptonote
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<checkpoint_t> checkpoints;
|
||||
std::vector<block> pblocks;
|
||||
if (!m_core.prepare_handle_incoming_blocks(blocks, pblocks, checkpoints))
|
||||
if (!m_core.prepare_handle_incoming_blocks(blocks, pblocks))
|
||||
{
|
||||
LOG_ERROR_CCONTEXT("Failure in prepare_handle_incoming_blocks");
|
||||
return 1;
|
||||
|
@ -1333,12 +1330,63 @@ namespace cryptonote
|
|||
TIME_MEASURE_FINISH(transactions_process_time);
|
||||
transactions_process_time_full += transactions_process_time;
|
||||
|
||||
//
|
||||
// NOTE: Checkpoint parsing
|
||||
//
|
||||
checkpoint_t checkpoint_allocated_on_stack_;
|
||||
checkpoint_t *checkpoint = nullptr;
|
||||
if (block_entry.checkpoint.size())
|
||||
{
|
||||
// TODO(doyle): It's wasteful to have to parse the checkpoint to
|
||||
// figure out the height when at some point during the syncing
|
||||
// step we know exactly what height the block entries are for
|
||||
|
||||
if (!t_serializable_object_from_blob(checkpoint_allocated_on_stack_, block_entry.checkpoint))
|
||||
{
|
||||
MERROR("Checkpoint blob available but failed to parse");
|
||||
return false;
|
||||
}
|
||||
|
||||
checkpoint = &checkpoint_allocated_on_stack_;
|
||||
bool maybe_has_checkpoint = (checkpoint->height % service_nodes::CHECKPOINT_INTERVAL == 0);
|
||||
|
||||
if (!maybe_has_checkpoint)
|
||||
{
|
||||
MERROR("Checkpoint blob given but not expecting a checkpoint at this height");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(doyle): If we are receiving alternative blocks, we won't
|
||||
// have the quorum for the alternative chain meaning we will not
|
||||
// be able to verify the checkpoint. For now always accept
|
||||
// whatever checkpoint we receive
|
||||
#if 0
|
||||
std::shared_ptr<const service_nodes::testing_quorum> quorum =
|
||||
get_testing_quorum(service_nodes::quorum_type::checkpointing, checkpoint.height);
|
||||
if (!quorum)
|
||||
{
|
||||
MERROR(
|
||||
"Failed to get service node quorum for height: "
|
||||
<< checkpoint.height
|
||||
<< ", quorum should be available as we are syncing the chain and deriving the current relevant quorum");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(doyle): add reasoning, important for sync failures
|
||||
if (!service_nodes::verify_checkpoint(checkpoint, *quorum))
|
||||
{
|
||||
MERROR("Failed to verify checkpoint at height: " << checkpoint.height);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// process block
|
||||
|
||||
TIME_MEASURE_START(block_process_time);
|
||||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||||
|
||||
m_core.handle_incoming_block(block_entry.block, pblocks.empty() ? NULL : &pblocks[blockidx], bvc, false); // <--- process block
|
||||
m_core.handle_incoming_block(block_entry.block, pblocks.empty() ? NULL : &pblocks[blockidx], bvc, checkpoint, false); // <--- process block
|
||||
|
||||
if(bvc.m_verifivation_failed)
|
||||
{
|
||||
|
@ -1385,13 +1433,6 @@ namespace cryptonote
|
|||
|
||||
} // each download block
|
||||
|
||||
// TODO(doyle): Horribly incomplete
|
||||
for (checkpoint_t const &checkpoint : checkpoints)
|
||||
{
|
||||
Blockchain &blockchain = m_core.get_blockchain_storage();
|
||||
blockchain.update_checkpoint(checkpoint);
|
||||
}
|
||||
|
||||
MDEBUG(context << "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << block_process_time_full + transactions_process_time_full << " (" << transactions_process_time_full << "/" << block_process_time_full << ") ms");
|
||||
|
||||
if (!m_core.cleanup_handle_incoming_blocks())
|
||||
|
|
|
@ -369,12 +369,38 @@ t_command_server::t_command_server(
|
|||
);
|
||||
|
||||
m_command_lookup.set_handler(
|
||||
"debug_mine_n_blocks", std::bind([rpc_server](std::vector<std::string> const &args) {
|
||||
uint64_t num_blocks = 0;
|
||||
if (args.size() == 2 && epee::string_tools::get_xtype_from_string(num_blocks, args[1]))
|
||||
rpc_server->on_debug_mine_n_blocks(args[0], num_blocks);
|
||||
else
|
||||
std::cout << "Invalid args, expected debug_mine_n_blocks <address> <num_blocks>";
|
||||
"integration_test", std::bind([rpc_server](std::vector<std::string> const &args) {
|
||||
bool valid_cmd = false;
|
||||
if (args.size() == 1)
|
||||
{
|
||||
valid_cmd = true;
|
||||
if (args[0] == "toggle_checkpoint_quorum")
|
||||
{
|
||||
loki::integration_test.disable_checkpoint_quorum = !loki::integration_test.disable_checkpoint_quorum;
|
||||
}
|
||||
else if (args[0] == "toggle_obligation_quorum")
|
||||
{
|
||||
loki::integration_test.disable_obligation_quorum = !loki::integration_test.disable_obligation_quorum;
|
||||
}
|
||||
else
|
||||
{
|
||||
valid_cmd = false;
|
||||
}
|
||||
|
||||
if (valid_cmd) std::cout << args[0] << " toggled";
|
||||
}
|
||||
else if (args.size() == 3)
|
||||
{
|
||||
uint64_t num_blocks = 0;
|
||||
if (args[0] == "debug_mine_n_blocks" && epee::string_tools::get_xtype_from_string(num_blocks, args[2]))
|
||||
{
|
||||
rpc_server->on_debug_mine_n_blocks(args[1], num_blocks);
|
||||
valid_cmd = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid_cmd)
|
||||
std::cout << "integration_test invalid command";
|
||||
|
||||
loki::write_redirected_stdout_to_shared_mem();
|
||||
return true;
|
||||
|
@ -411,7 +437,7 @@ bool t_command_server::start_handling(std::function<void(void)> exit_handler)
|
|||
auto handle_shared_mem_ins_and_outs = [&]()
|
||||
{
|
||||
// TODO(doyle): Hack, don't hook into input until the daemon has completely initialised, i.e. you can print the status
|
||||
while(!loki::core_is_idle) {}
|
||||
while(!loki::integration_test.core_is_idle) {}
|
||||
mlog_set_categories(""); // TODO(doyle): We shouldn't have to do this.
|
||||
|
||||
for (;;)
|
||||
|
|
|
@ -2826,7 +2826,7 @@ bool t_rpc_command_executor::prepare_registration()
|
|||
|
||||
// Query the latest known block height and nettype
|
||||
uint64_t block_height = 0;
|
||||
int hf_version = cryptonote::network_version_9_service_nodes;
|
||||
uint8_t hf_version = cryptonote::network_version_9_service_nodes;
|
||||
cryptonote::network_type nettype = cryptonote::UNDEFINED;
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_INFO::request req;
|
||||
|
|
|
@ -2586,7 +2586,7 @@ namespace cryptonote
|
|||
}
|
||||
|
||||
std::string err_msg;
|
||||
int hf_version = m_core.get_hard_fork_version(m_core.get_current_blockchain_height());
|
||||
uint8_t hf_version = m_core.get_hard_fork_version(m_core.get_current_blockchain_height());
|
||||
if (!service_nodes::make_registration_cmd(m_core.get_nettype(), hf_version, req.staking_requirement, req.args, service_node_pubkey, service_node_key, res.registration_cmd, req.make_friendly, err_msg))
|
||||
{
|
||||
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
|
||||
|
|
|
@ -197,7 +197,7 @@ bool tests::proxy_core::handle_incoming_txs(const std::vector<blobdata>& tx_blob
|
|||
return true;
|
||||
}
|
||||
|
||||
bool tests::proxy_core::handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block_, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate) {
|
||||
bool tests::proxy_core::handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block_, cryptonote::block_verification_context& bvc, cryptonote::checkpoint_t const *checkpoint, bool update_miner_blocktemplate) {
|
||||
block b = AUTO_VAL_INIT(b);
|
||||
|
||||
if(!parse_and_validate_block_from_blob(block_blob, b)) {
|
||||
|
@ -217,7 +217,7 @@ bool tests::proxy_core::handle_incoming_block(const cryptonote::blobdata& block_
|
|||
|
||||
cout << endl << "ENDBLOCK" << endl << endl;
|
||||
|
||||
if (!add_block(h, lh, b, block_blob))
|
||||
if (!add_block(h, lh, b, block_blob, checkpoint))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -242,7 +242,7 @@ void tests::proxy_core::get_blockchain_top(uint64_t& height, crypto::hash& top_i
|
|||
bool tests::proxy_core::init(const boost::program_options::variables_map& /*vm*/) {
|
||||
generate_genesis_block(m_genesis, config::GENESIS_TX, config::GENESIS_NONCE);
|
||||
crypto::hash h = get_block_hash(m_genesis);
|
||||
add_block(h, get_block_longhash(m_genesis, 0), m_genesis, block_to_blob(m_genesis));
|
||||
add_block(h, get_block_longhash(m_genesis, 0), m_genesis, block_to_blob(m_genesis), nullptr /*checkpoint*/);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@ void tests::proxy_core::build_short_history(std::list<crypto::hash> &m_history,
|
|||
} while (m_hash2blkidx.end() != cit && get_block_hash(cit->second.blk) != cit->first);*/
|
||||
}
|
||||
|
||||
bool tests::proxy_core::add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob) {
|
||||
bool tests::proxy_core::add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob, cryptonote::checkpoint_t const *) {
|
||||
size_t height = 0;
|
||||
|
||||
if (crypto::null_hash != _blk.prev_id) {
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace tests
|
|||
crypto::hash m_lastblk;
|
||||
std::list<cryptonote::transaction> txes;
|
||||
|
||||
bool add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob);
|
||||
bool add_block(const crypto::hash &_id, const crypto::hash &_longhash, const cryptonote::block &_blk, const cryptonote::blobdata &_blob, const cryptonote::checkpoint_t *);
|
||||
void build_short_history(std::list<crypto::hash> &m_history, const crypto::hash &m_start);
|
||||
|
||||
|
||||
|
@ -78,7 +78,7 @@ namespace tests
|
|||
void get_blockchain_top(uint64_t& height, crypto::hash& top_id);
|
||||
bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
|
||||
bool handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay);
|
||||
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true);
|
||||
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, cryptonote::checkpoint_t const *checkpoint, bool update_miner_blocktemplate = true);
|
||||
bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof);
|
||||
void pause_mine(){}
|
||||
void resume_mine(){}
|
||||
|
@ -88,7 +88,7 @@ namespace tests
|
|||
cryptonote::Blockchain &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class proxy_core."); }
|
||||
bool get_test_drop_download() {return true;}
|
||||
bool get_test_drop_download_height() {return true;}
|
||||
bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks, std::vector<cryptonote::checkpoint_t> &checkpoints) { return true; }
|
||||
bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; }
|
||||
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
|
||||
uint64_t get_target_blockchain_height() const { return 1; }
|
||||
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
|
||||
|
|
|
@ -398,7 +398,7 @@ bool gen_block_miner_tx_has_no_out::generate(std::vector<test_event_entry>& even
|
|||
|
||||
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
|
||||
miner_tx.vout.clear();
|
||||
miner_tx.version = 1;
|
||||
miner_tx.version = txversion::v1;
|
||||
|
||||
block blk_1;
|
||||
generator.construct_block_manually(blk_1, blk_0, miner_account, test_generator::bf_miner_tx, 0, 0, 0, crypto::hash(), 0, miner_tx);
|
||||
|
@ -441,7 +441,7 @@ static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx,
|
|||
block_reward -= governance_reward;
|
||||
}
|
||||
|
||||
tx.version = 1;
|
||||
tx.version = txversion::v1;
|
||||
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
|
||||
|
||||
/// half of the miner reward goes to the other account
|
||||
|
@ -521,7 +521,7 @@ bool gen_block_is_too_big::generate(std::vector<test_event_entry>& events) const
|
|||
|
||||
// Creating a huge miner_tx, it will have a lot of outs
|
||||
MAKE_MINER_TX_MANUALLY(miner_tx, blk_0);
|
||||
miner_tx.version = 1;
|
||||
miner_tx.version = txversion::v1;
|
||||
static const size_t tx_out_count = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V1 / 2;
|
||||
|
||||
uint64_t amount = get_outs_money_amount(miner_tx);
|
||||
|
|
|
@ -239,7 +239,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
|
|||
bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT(context);
|
||||
CHECK_TEST_CONDITION(tx.version >= 2);
|
||||
CHECK_TEST_CONDITION(tx.version >= txversion::v2_ringct);
|
||||
CHECK_TEST_CONDITION(rct::is_rct_bulletproof(tx.rct_signatures.type));
|
||||
size_t n_sizes = 0, n_amounts = 0;
|
||||
for (size_t n = 0; n < tx_idx; ++n)
|
||||
|
|
|
@ -197,7 +197,7 @@ cryptonote::block linear_chain_generator::create_block_on_fork(const cryptonote:
|
|||
|
||||
QuorumState linear_chain_generator::get_quorum_idxs(const cryptonote::block& block) const
|
||||
{
|
||||
if (sn_list_.size() <= service_nodes::DEREGISTER_QUORUM_SIZE) {
|
||||
if (sn_list_.size() <= service_nodes::STATE_CHANGE_QUORUM_SIZE) {
|
||||
std::cerr << "Not enough service nodes\n";
|
||||
return {};
|
||||
}
|
||||
|
@ -213,16 +213,16 @@ QuorumState linear_chain_generator::get_quorum_idxs(const cryptonote::block& blo
|
|||
pub_keys_indexes[i] = i;
|
||||
}
|
||||
|
||||
service_nodes::loki_shuffle(pub_keys_indexes, seed);
|
||||
service_nodes::loki_shuffle(pub_keys_indexes.begin(), pub_keys_indexes.end(), seed);
|
||||
}
|
||||
|
||||
QuorumState quorum;
|
||||
|
||||
for (auto i = 0u; i < service_nodes::DEREGISTER_QUORUM_SIZE; ++i) {
|
||||
for (auto i = 0u; i < service_nodes::STATE_CHANGE_QUORUM_SIZE; ++i) {
|
||||
quorum.voters.push_back({ sn_list_.at(pub_keys_indexes[i]).keys.pub, i });
|
||||
}
|
||||
|
||||
for (auto i = service_nodes::DEREGISTER_QUORUM_SIZE; i < pub_keys_indexes.size(); ++i) {
|
||||
for (auto i = service_nodes::STATE_CHANGE_QUORUM_SIZE; i < pub_keys_indexes.size(); ++i) {
|
||||
quorum.to_test.push_back({ sn_list_.at(pub_keys_indexes[i]).keys.pub, i });
|
||||
}
|
||||
|
||||
|
@ -279,16 +279,11 @@ cryptonote::transaction linear_chain_generator::create_deregister_tx(const crypt
|
|||
uint64_t fee,
|
||||
bool commit)
|
||||
{
|
||||
|
||||
cryptonote::tx_extra_service_node_deregister deregister;
|
||||
deregister.block_height = height;
|
||||
|
||||
const auto idx = get_idx_in_tested(pk, height);
|
||||
|
||||
if (!idx) { MERROR("service node could not be found in the servcie node list"); throw std::exception(); }
|
||||
|
||||
deregister.service_node_index = *idx; /// idx inside nodes to test
|
||||
|
||||
cryptonote::tx_extra_service_node_state_change deregister(service_nodes::new_state::deregister, height, *idx);
|
||||
/// need to create DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE (7) votes
|
||||
for (const auto voter : voters) {
|
||||
|
||||
|
@ -299,7 +294,7 @@ cryptonote::transaction linear_chain_generator::create_deregister_tx(const crypt
|
|||
const auto pk = reg->keys.pub;
|
||||
const auto sk = reg->keys.sec;
|
||||
|
||||
service_nodes::quorum_vote_t deregister_vote = service_nodes::make_deregister_vote(deregister.block_height, voter.idx_in_quorum, deregister.service_node_index, pk, sk);
|
||||
service_nodes::quorum_vote_t deregister_vote = service_nodes::make_state_change_vote(deregister.block_height, voter.idx_in_quorum, deregister.service_node_index, service_nodes::new_state::deregister, pk, sk);
|
||||
deregister.votes.push_back({ deregister_vote.signature, (uint32_t)voter.idx_in_quorum });
|
||||
}
|
||||
|
||||
|
@ -324,7 +319,7 @@ boost::optional<uint32_t> linear_chain_generator::get_idx_in_tested(const crypto
|
|||
const auto& to_test = get_quorum_idxs(height).to_test;
|
||||
|
||||
for (const auto& sn : to_test) {
|
||||
if (sn.sn_pk == pk) return sn.idx_in_quorum - service_nodes::DEREGISTER_QUORUM_SIZE;
|
||||
if (sn.sn_pk == pk) return sn.idx_in_quorum - service_nodes::STATE_CHANGE_QUORUM_SIZE;
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
|
@ -716,14 +711,14 @@ cryptonote::transaction make_registration_tx(std::vector<test_event_entry>& even
|
|||
cryptonote::transaction make_deregistration_tx(const std::vector<test_event_entry>& events,
|
||||
const cryptonote::account_base& account,
|
||||
const cryptonote::block& head,
|
||||
const cryptonote::tx_extra_service_node_deregister& deregister,
|
||||
const cryptonote::tx_extra_service_node_state_change& deregister_state_change,
|
||||
uint8_t hf_version,
|
||||
uint64_t fee)
|
||||
{
|
||||
cryptonote::transaction tx;
|
||||
|
||||
std::vector<uint8_t> extra;
|
||||
const bool full_tx_deregister_made = cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister);
|
||||
const bool full_tx_deregister_made = cryptonote::add_service_node_state_change_to_tx_extra(tx.extra, deregister_state_change, hf_version);
|
||||
|
||||
if (!full_tx_deregister_made) {
|
||||
MERROR("Could not add deregister to extra");
|
||||
|
@ -735,7 +730,7 @@ cryptonote::transaction make_deregistration_tx(const std::vector<test_event_entr
|
|||
if (fee) TxBuilder(events, tx, head, account, account, amount, hf_version).with_fee(fee).with_extra(extra).with_per_output_unlock(true).build();
|
||||
|
||||
tx.version = cryptonote::transaction::get_max_version_for_hf(hf_version, cryptonote::FAKECHAIN);
|
||||
tx.type = cryptonote::transaction::type_deregister;
|
||||
tx.type = cryptonote::txtype::state_change;
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
@ -838,12 +833,12 @@ bool init_output_indices(std::vector<output_index>& outs, std::vector<size_t>& o
|
|||
const auto height = boost::get<txin_gen>(*blk.miner_tx.vin.begin()).height; /// replace with front?
|
||||
|
||||
output_index oi(out.target, out.amount, height, i, j, &blk, vtx[i]);
|
||||
oi.unlock_time = (tx.version < 3) ? tx.unlock_time : tx.output_unlock_times[j];
|
||||
oi.unlock_time = (tx.version < txversion::v3_per_output_unlock_times) ? tx.unlock_time : tx.output_unlock_times[j];
|
||||
oi.idx = outs.size();
|
||||
oi.mask = rct::zeroCommit(out.amount);
|
||||
oi.is_coin_base = (i == 0);
|
||||
oi.deterministic_key_pair = false;
|
||||
oi.set_rct(tx.version >= 2);
|
||||
oi.set_rct(tx.version >= txversion::v2_ringct);
|
||||
|
||||
const auto gov_key = cryptonote::get_deterministic_keypair_from_height(height);
|
||||
bool account_received_money = is_out_to_acc(from.get_keys(), boost::get<txout_to_key>(out.target), gov_key.pub, {}, j);
|
||||
|
@ -1139,7 +1134,7 @@ void block_tracker::process(const block* blk, const transaction * tx, size_t i)
|
|||
continue;
|
||||
}
|
||||
|
||||
const uint64_t rct_amount = tx->version == 2 ? 0 : out.amount;
|
||||
const uint64_t rct_amount = tx->version == txversion::v2_ringct ? 0 : out.amount;
|
||||
const output_hasher hid = std::make_pair(tx->hash, j);
|
||||
auto it = find_out(hid);
|
||||
if (it != m_map_outs.end()){
|
||||
|
@ -1147,8 +1142,7 @@ void block_tracker::process(const block* blk, const transaction * tx, size_t i)
|
|||
}
|
||||
|
||||
output_index oi(out.target, out.amount, boost::get<txin_gen>(blk->miner_tx.vin.front()).height, i, j, blk, tx);
|
||||
oi.set_rct(tx->version == 2);
|
||||
oi.idx = m_outs[rct_amount].size();
|
||||
oi.set_rct(tx->version == txversion::v2_ringct); oi.idx = m_outs[rct_amount].size();
|
||||
oi.unlock_time = tx->unlock_time;
|
||||
oi.is_coin_base = tx->vin.size() == 1 && tx->vin.back().type() == typeid(cryptonote::txin_gen);
|
||||
|
||||
|
|
|
@ -881,10 +881,9 @@ public:
|
|||
cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc);
|
||||
cryptonote::blobdata bd = t_serializable_object_to_blob(b);
|
||||
std::vector<cryptonote::block> pblocks;
|
||||
std::vector<cryptonote::checkpoint_t> checkpoints;
|
||||
if (m_c.prepare_handle_incoming_blocks(std::vector<cryptonote::block_complete_entry>(1, {bd, {}, {}}), pblocks, checkpoints))
|
||||
if (m_c.prepare_handle_incoming_blocks(std::vector<cryptonote::block_complete_entry>(1, {bd, {}, {}}), pblocks))
|
||||
{
|
||||
m_c.handle_incoming_block(bd, &b, bvc);
|
||||
m_c.handle_incoming_block(bd, &b, bvc, nullptr);
|
||||
m_c.cleanup_handle_incoming_blocks();
|
||||
}
|
||||
else
|
||||
|
@ -912,10 +911,9 @@ public:
|
|||
|
||||
cryptonote::block_verification_context bvc = AUTO_VAL_INIT(bvc);
|
||||
std::vector<cryptonote::block> pblocks;
|
||||
std::vector<cryptonote::checkpoint_t> checkpoints;
|
||||
if (m_c.prepare_handle_incoming_blocks(std::vector<cryptonote::block_complete_entry>(1, {sr_block.data, {}, {}}), pblocks, checkpoints))
|
||||
if (m_c.prepare_handle_incoming_blocks(std::vector<cryptonote::block_complete_entry>(1, {sr_block.data, {}, {}}), pblocks))
|
||||
{
|
||||
m_c.handle_incoming_block(sr_block.data, NULL, bvc);
|
||||
m_c.handle_incoming_block(sr_block.data, NULL, bvc, nullptr);
|
||||
m_c.cleanup_handle_incoming_blocks();
|
||||
}
|
||||
else
|
||||
|
@ -1215,7 +1213,7 @@ cryptonote::transaction make_default_registration_tx(std::vector<test_event_entr
|
|||
cryptonote::transaction make_deregistration_tx(const std::vector<test_event_entry>& events,
|
||||
const cryptonote::account_base& account,
|
||||
const cryptonote::block& head,
|
||||
const cryptonote::tx_extra_service_node_deregister& deregister, uint8_t hf_version, uint64_t fee);
|
||||
const cryptonote::tx_extra_service_node_state_change& deregister_state_change, uint8_t hf_version, uint64_t fee);
|
||||
|
||||
// NOTE(loki): These macros assume hardfork version 7 and are from the old Monero testing code
|
||||
#define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \
|
||||
|
|
|
@ -125,8 +125,7 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY(sn_test_rollback);
|
||||
GENERATE_AND_PLAY(test_swarms_basic);
|
||||
#else
|
||||
GENERATE_AND_PLAY(test_deregisters_on_split);
|
||||
GENERATE_AND_PLAY(sn_test_rollback);
|
||||
GENERATE_AND_PLAY(test_prefer_deregisters);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -287,7 +287,7 @@ bool test_prefer_deregisters::check_prefer_deregisters(cryptonote::core& c, size
|
|||
|
||||
const auto deregister_count =
|
||||
std::count_if(full_blk.tx_hashes.begin(), full_blk.tx_hashes.end(), [&mtx](const crypto::hash& tx_hash) {
|
||||
return mtx[tx_hash]->get_type() == cryptonote::transaction::type_deregister;
|
||||
return mtx[tx_hash]->type == cryptonote::txtype::state_change;
|
||||
});
|
||||
|
||||
/// test that there are more transactions in tx pool
|
||||
|
@ -369,7 +369,7 @@ bool test_deregister_safety_buffer::generate(std::vector<test_event_entry> &even
|
|||
/// register 21 random service nodes
|
||||
std::vector<cryptonote::transaction> reg_txs;
|
||||
|
||||
constexpr auto SERVICE_NODES_NEEDED = service_nodes::DEREGISTER_QUORUM_SIZE * 2 + 1;
|
||||
constexpr auto SERVICE_NODES_NEEDED = service_nodes::STATE_CHANGE_QUORUM_SIZE * 2 + 1;
|
||||
static_assert(SN_KEYS_COUNT >= SERVICE_NODES_NEEDED, "not enough pre-computed service node keys");
|
||||
|
||||
for (auto i = 0u; i < SERVICE_NODES_NEEDED; ++i)
|
||||
|
@ -522,7 +522,7 @@ bool test_deregisters_on_split::test_on_split(cryptonote::core& c, size_t ev_ind
|
|||
/// obtain the expected deregister from events
|
||||
const size_t dereg_idx = 68;
|
||||
auto dereg_tx = boost::get<cryptonote::transaction>(events.at(dereg_idx));
|
||||
CHECK_AND_ASSERT_MES(dereg_tx.get_type() == cryptonote::transaction::type_deregister, false, "event is not a deregister transaction");
|
||||
CHECK_AND_ASSERT_MES(dereg_tx.type == cryptonote::txtype::state_change, false, "event is not a state change transaction");
|
||||
|
||||
const auto expected_tx_hash = get_transaction_hash(dereg_tx);
|
||||
|
||||
|
@ -541,7 +541,7 @@ bool test_deregisters_on_split::test_on_split(cryptonote::core& c, size_t ev_ind
|
|||
|
||||
/// find the deregister tx:
|
||||
const auto found_tx_hash = std::find_if(blk.tx_hashes.begin(), blk.tx_hashes.end(), [&mtx](const crypto::hash& hash) {
|
||||
return mtx.at(hash)->is_deregister;
|
||||
return mtx.at(hash)->type == txtype::state_change;
|
||||
});
|
||||
|
||||
CHECK_TEST_CONDITION(found_tx_hash != blk.tx_hashes.end());
|
||||
|
@ -589,7 +589,7 @@ bool deregister_too_old::generate(std::vector<test_event_entry>& events)
|
|||
const auto dereg_tx = gen.build_deregister(pk, false).build();
|
||||
|
||||
/// create enough blocks to make deregistrations invalid (60 blocks)
|
||||
gen.rewind_blocks_n(service_nodes::DEREGISTER_TX_LIFETIME_IN_BLOCKS);
|
||||
gen.rewind_blocks_n(service_nodes::STATE_CHANGE_TX_LIFETIME_IN_BLOCKS);
|
||||
|
||||
/// In the real world, this transaction should not make it into a block, but in this case we do try to add it (as in
|
||||
/// tests we must add specify transactions manually), which should exercise the same validation code and reject the
|
||||
|
@ -686,10 +686,11 @@ bool sn_test_rollback::test_registrations(cryptonote::core& c, size_t ev_index,
|
|||
|
||||
CHECK_TEST_CONDITION(event_a.type() == typeid(cryptonote::transaction));
|
||||
const auto dereg_tx = boost::get<cryptonote::transaction>(event_a);
|
||||
CHECK_TEST_CONDITION(dereg_tx.get_type() == transaction::type_deregister);
|
||||
CHECK_TEST_CONDITION(dereg_tx.type == cryptonote::txtype::state_change);
|
||||
|
||||
tx_extra_service_node_deregister deregistration;
|
||||
get_service_node_deregister_from_tx_extra(dereg_tx.extra, deregistration);
|
||||
tx_extra_service_node_state_change deregistration;
|
||||
get_service_node_state_change_from_tx_extra(
|
||||
dereg_tx.extra, deregistration, c.get_blockchain_storage().get_current_hard_fork_version());
|
||||
|
||||
const auto uptime_quorum = c.get_testing_quorum(service_nodes::quorum_type::deregister, deregistration.block_height);
|
||||
CHECK_TEST_CONDITION(uptime_quorum);
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace
|
|||
{
|
||||
struct tx_builder
|
||||
{
|
||||
void step1_init(size_t version = 7, uint64_t unlock_time = 0)
|
||||
void step1_init(txversion version = txversion::v2_ringct, uint64_t unlock_time = 0)
|
||||
{
|
||||
m_tx.vin.clear();
|
||||
m_tx.vout.clear();
|
||||
|
@ -144,7 +144,7 @@ namespace
|
|||
fill_tx_sources_and_destinations(events, blk_head, from, get_address(to), amount, TESTS_DEFAULT_FEE, 0, sources, destinations);
|
||||
|
||||
tx_builder builder;
|
||||
builder.step1_init(cryptonote::network_version_7, unlock_time);
|
||||
builder.step1_init(cryptonote::txversion::v2_ringct, unlock_time);
|
||||
builder.step2_fill_inputs(from.get_keys(), sources);
|
||||
builder.step3_fill_outputs(destinations);
|
||||
builder.step4_calc_hash();
|
||||
|
@ -297,7 +297,7 @@ bool gen_tx_no_inputs_no_outputs::generate(std::vector<test_event_entry>& events
|
|||
MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
|
||||
|
||||
transaction tx = {};
|
||||
tx.version = cryptonote::network_version_7;
|
||||
tx.version = cryptonote::txversion::v2_ringct;
|
||||
add_tx_pub_key_to_extra(tx, keypair::generate(hw::get_device("default")).pub);
|
||||
|
||||
DO_CALLBACK(events, "mark_invalid_tx");
|
||||
|
|
|
@ -53,7 +53,7 @@ public:
|
|||
void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;}
|
||||
bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; }
|
||||
bool handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; }
|
||||
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; }
|
||||
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, cryptonote::checkpoint_t const *checkpoint, bool update_miner_blocktemplate = true) { return true; }
|
||||
bool handle_uptime_proof(const cryptonote::NOTIFY_UPTIME_PROOF::request &proof) { return false; }
|
||||
void pause_mine(){}
|
||||
void resume_mine(){}
|
||||
|
@ -63,7 +63,7 @@ public:
|
|||
cryptonote::Blockchain &get_blockchain_storage() { throw std::runtime_error("Called invalid member function: please never call get_blockchain_storage on the TESTING class test_core."); }
|
||||
bool get_test_drop_download() const {return true;}
|
||||
bool get_test_drop_download_height() const {return true;}
|
||||
bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks, std::vector<cryptonote::checkpoint_t> &checkpoints) { return true; }
|
||||
bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; }
|
||||
bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; }
|
||||
uint64_t get_target_blockchain_height() const { return 1; }
|
||||
size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; }
|
||||
|
|
|
@ -256,7 +256,7 @@ TEST(bulletproof, weight_equal)
|
|||
cryptonote::transaction tx;
|
||||
crypto::hash tx_hash, tx_prefix_hash;
|
||||
ASSERT_TRUE(parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash));
|
||||
ASSERT_TRUE(tx.version == 2);
|
||||
ASSERT_TRUE(tx.version == cryptonote::txversion::v2_ringct);
|
||||
ASSERT_TRUE(rct::is_rct_bulletproof(tx.rct_signatures.type));
|
||||
const uint64_t tx_size = bd.size();
|
||||
const uint64_t tx_weight = cryptonote::get_transaction_weight(tx);
|
||||
|
@ -271,7 +271,7 @@ TEST(bulletproof, weight_more)
|
|||
cryptonote::transaction tx;
|
||||
crypto::hash tx_hash, tx_prefix_hash;
|
||||
ASSERT_TRUE(parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash));
|
||||
ASSERT_TRUE(tx.version == 2);
|
||||
ASSERT_TRUE(tx.version == cryptonote::txversion::v2_ringct);
|
||||
ASSERT_TRUE(rct::is_rct_bulletproof(tx.rct_signatures.type));
|
||||
const uint64_t tx_size = bd.size();
|
||||
const uint64_t tx_weight = cryptonote::get_transaction_weight(tx);
|
||||
|
|
|
@ -139,7 +139,7 @@ TEST(service_nodes, vote_validation)
|
|||
|
||||
// Valid vote
|
||||
uint64_t block_height = 70;
|
||||
service_nodes::quorum_vote_t valid_vote = service_nodes::make_deregister_vote(block_height, voter_index, 1 /*worker_index*/, service_node_voter.pub, service_node_voter.sec);
|
||||
service_nodes::quorum_vote_t valid_vote = service_nodes::make_state_change_vote(block_height, voter_index, 1 /*worker_index*/, service_nodes::new_state::decommission, service_node_voter.pub, service_node_voter.sec);
|
||||
{
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_vote(valid_vote, block_height, vvc, state);
|
||||
|
@ -162,9 +162,9 @@ TEST(service_nodes, vote_validation)
|
|||
|
||||
// Voters worker index out of bounds
|
||||
{
|
||||
auto vote = valid_vote;
|
||||
vote.deregister.worker_index = state.workers.size() + 10;
|
||||
vote.signature = service_nodes::make_signature_from_vote(vote, service_node_voter.pub, service_node_voter.sec);
|
||||
auto vote = valid_vote;
|
||||
vote.state_change.worker_index = state.workers.size() + 10;
|
||||
vote.signature = service_nodes::make_signature_from_vote(vote, service_node_voter.pub, service_node_voter.sec);
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_vote(vote, block_height, vvc, state);
|
||||
|
@ -197,7 +197,7 @@ TEST(service_nodes, vote_validation)
|
|||
}
|
||||
}
|
||||
|
||||
TEST(service_nodes, tx_extra_deregister_validation)
|
||||
TEST(service_nodes, tx_extra_state_change_validation)
|
||||
{
|
||||
// Generate a quorum and the voter
|
||||
const size_t num_voters = 10;
|
||||
|
@ -216,76 +216,94 @@ TEST(service_nodes, tx_extra_deregister_validation)
|
|||
}
|
||||
}
|
||||
|
||||
// Valid deregister
|
||||
cryptonote::tx_extra_service_node_deregister valid_deregister = {};
|
||||
// Valid state_change
|
||||
cryptonote::tx_extra_service_node_state_change valid_state_change = {};
|
||||
uint8_t hf_version = cryptonote::network_version_11_infinite_staking;
|
||||
const uint64_t HEIGHT = 100;
|
||||
{
|
||||
valid_deregister.block_height = 10;
|
||||
valid_deregister.service_node_index = 1;
|
||||
valid_deregister.votes.reserve(num_voters);
|
||||
valid_state_change.block_height = HEIGHT - 1;
|
||||
valid_state_change.service_node_index = 1;
|
||||
valid_state_change.votes.reserve(num_voters);
|
||||
for (size_t i = 0; i < num_voters; ++i)
|
||||
{
|
||||
cryptonote::keypair const *voter = voters + i;
|
||||
cryptonote::tx_extra_service_node_deregister::vote vote = {};
|
||||
cryptonote::tx_extra_service_node_state_change::vote vote = {};
|
||||
vote.validator_index = i;
|
||||
vote.signature = service_nodes::make_signature_from_tx_deregister(valid_deregister, voter->pub, voter->sec);
|
||||
valid_deregister.votes.push_back(vote);
|
||||
vote.signature = service_nodes::make_signature_from_tx_state_change(valid_state_change, voter->pub, voter->sec);
|
||||
valid_state_change.votes.push_back(vote);
|
||||
}
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(valid_deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(valid_state_change, HEIGHT, vvc, state, hf_version);
|
||||
if (!result)
|
||||
printf("%s\n", cryptonote::print_vote_verification_context(vvc));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
// Deregister has insufficient votes
|
||||
// State Change has insufficient votes
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
while (deregister.votes.size() >= service_nodes::DEREGISTER_MIN_VOTES_TO_KICK_SERVICE_NODE)
|
||||
deregister.votes.pop_back();
|
||||
auto state_change = valid_state_change;
|
||||
while (state_change.votes.size() >= service_nodes::STATE_CHANGE_MIN_VOTES_TO_CHANGE_STATE)
|
||||
state_change.votes.pop_back();
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister has duplicated voter
|
||||
// State Change has duplicated voter
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.votes[0] = deregister.votes[1];
|
||||
auto state_change = valid_state_change;
|
||||
state_change.votes[0] = state_change.votes[1];
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister has one voter with invalid signature
|
||||
// State Change has one voter with invalid signature
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.votes[0].signature = deregister.votes[1].signature;
|
||||
auto state_change = valid_state_change;
|
||||
state_change.votes[0].signature = state_change.votes[1].signature;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister has one voter with index out of bounds
|
||||
// State Change has one voter with index out of bounds
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.votes[0].validator_index = state.validators.size() + 10;
|
||||
auto state_change = valid_state_change;
|
||||
state_change.votes[0].validator_index = state.validators.size() + 10;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// Deregister service node index is out of bounds
|
||||
// State Change service node index is out of bounds
|
||||
{
|
||||
auto deregister = valid_deregister;
|
||||
deregister.service_node_index = state.workers.size() + 10;
|
||||
auto state_change = valid_state_change;
|
||||
state_change.service_node_index = state.workers.size() + 10;
|
||||
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_deregister(deregister, vvc, state);
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// State Change too old
|
||||
{
|
||||
auto state_change = valid_state_change;
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, 0, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
// State Change too new
|
||||
{
|
||||
auto state_change = valid_state_change;
|
||||
cryptonote::vote_verification_context vvc = {};
|
||||
bool result = service_nodes::verify_tx_state_change(state_change, HEIGHT + 1000, vvc, state, hf_version);
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
virtual void update_block_checkpoint(cryptonote::checkpoint_t const &checkpoint) override {}
|
||||
virtual bool get_block_checkpoint (uint64_t height, cryptonote::checkpoint_t &checkpoint) const override { return false; }
|
||||
virtual bool get_top_checkpoint (cryptonote::checkpoint_t &checkpoint) const override { return false; }
|
||||
virtual void remove_block_checkpoint(uint64_t height) override { }
|
||||
virtual std::vector<cryptonote::checkpoint_t> get_checkpoints_range(uint64_t start, uint64_t end, size_t num_desired_checkpoints) const override { return {};}
|
||||
virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const override { return cryptonote::blobdata(); }
|
||||
virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; }
|
||||
|
|
Loading…
Reference in New Issue