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:
Doyle 2019-06-26 14:00:05 +10:00 committed by GitHub
parent 26080045b6
commit fdc340b0ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 685 additions and 566 deletions

View File

@ -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
*

View File

@ -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; }

View File

@ -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)
{

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -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.

View File

@ -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)

View File

@ -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())

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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,

View File

@ -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");
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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())

View File

@ -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 (;;)

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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; }

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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) \

View File

@ -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
}

View File

@ -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);

View File

@ -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");

View File

@ -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; }

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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; }