mirror of https://github.com/oxen-io/oxen-core.git
Pulse: DRY quorum signature verification
- By merging the quorum verification with pre-existing checkpointing code, checkpoints votes are currently being sorted by the vote index order. This was also enforced on the pulse signatures.
This commit is contained in:
parent
96bf8f757e
commit
63ba795c7e
|
@ -61,7 +61,7 @@ enum struct lmdb_version
|
|||
{
|
||||
v4 = 4,
|
||||
v5, // alt_block_data_1_t => alt_block_data_t: Alt block data has boolean for if the block was checkpointed
|
||||
v6, // remigrate voter_to_signature struct due to alignment change
|
||||
v6, // remigrate quorum_signature struct due to alignment change
|
||||
v7, // rebuild the checkpoint table because v6 update in-place made MDB_LAST not give us the newest checkpoint
|
||||
_count
|
||||
};
|
||||
|
@ -408,7 +408,7 @@ struct blk_checkpoint_header
|
|||
uint64_t num_signatures;
|
||||
};
|
||||
static_assert(sizeof(blk_checkpoint_header) == 2*sizeof(uint64_t) + sizeof(crypto::hash), "blk_checkpoint_header has unexpected padding");
|
||||
static_assert(sizeof(service_nodes::voter_to_signature) == sizeof(uint16_t) + 6 /*padding*/ + sizeof(crypto::signature), "Unexpected padding/struct size change. DB checkpoint signature entries need to be re-migrated to the new size");
|
||||
static_assert(sizeof(service_nodes::quorum_signature) == sizeof(uint16_t) + 6 /*padding*/ + sizeof(crypto::signature), "Unexpected padding/struct size change. DB checkpoint signature entries need to be re-migrated to the new size");
|
||||
|
||||
typedef struct blk_height {
|
||||
crypto::hash bh_hash;
|
||||
|
@ -3967,7 +3967,7 @@ uint64_t BlockchainLMDB::add_block(const std::pair<block, blobdata>& blk, size_t
|
|||
|
||||
struct checkpoint_mdb_buffer
|
||||
{
|
||||
char data[sizeof(blk_checkpoint_header) + (sizeof(service_nodes::voter_to_signature) * service_nodes::CHECKPOINT_QUORUM_SIZE)];
|
||||
char data[sizeof(blk_checkpoint_header) + (sizeof(service_nodes::quorum_signature) * service_nodes::CHECKPOINT_QUORUM_SIZE)];
|
||||
size_t len;
|
||||
};
|
||||
|
||||
|
@ -4060,7 +4060,7 @@ static checkpoint_t convert_mdb_val_to_checkpoint(MDB_val const value)
|
|||
checkpoint_t result = {};
|
||||
auto const *header = static_cast<blk_checkpoint_header const *>(value.mv_data);
|
||||
auto const *signatures =
|
||||
reinterpret_cast<service_nodes::voter_to_signature *>(static_cast<uint8_t *>(value.mv_data) + sizeof(*header));
|
||||
reinterpret_cast<service_nodes::quorum_signature *>(static_cast<uint8_t *>(value.mv_data) + sizeof(*header));
|
||||
|
||||
auto num_sigs = little_to_native(header->num_signatures);
|
||||
result.height = little_to_native(header->height);
|
||||
|
@ -6023,7 +6023,7 @@ void BlockchainLMDB::migrate_5_6()
|
|||
char r[32];
|
||||
};
|
||||
|
||||
struct unaligned_voter_to_signature
|
||||
struct unaligned_quorum_signature
|
||||
{
|
||||
uint16_t voter_index;
|
||||
unaligned_signature signature;
|
||||
|
@ -6052,7 +6052,7 @@ void BlockchainLMDB::migrate_5_6()
|
|||
|
||||
auto const *header = static_cast<blk_checkpoint_header const *>(val.mv_data);
|
||||
auto num_sigs = little_to_native(header->num_signatures);
|
||||
auto const *aligned_signatures = reinterpret_cast<service_nodes::voter_to_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
|
||||
auto const *aligned_signatures = reinterpret_cast<service_nodes::quorum_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
|
||||
if (num_sigs == 0) continue; // NOTE: Hardcoded checkpoints
|
||||
|
||||
checkpoint_t checkpoint = {};
|
||||
|
@ -6067,7 +6067,7 @@ void BlockchainLMDB::migrate_5_6()
|
|||
{
|
||||
auto const &entry = aligned_signatures[i];
|
||||
size_t const actual_num_bytes_for_signatures = val.mv_size - sizeof(*header);
|
||||
size_t const expected_num_bytes_for_signatures = sizeof(service_nodes::voter_to_signature) * num_sigs;
|
||||
size_t const expected_num_bytes_for_signatures = sizeof(service_nodes::quorum_signature) * num_sigs;
|
||||
if (actual_num_bytes_for_signatures != expected_num_bytes_for_signatures)
|
||||
{
|
||||
unaligned_checkpoint = true;
|
||||
|
@ -6078,12 +6078,12 @@ void BlockchainLMDB::migrate_5_6()
|
|||
|
||||
if (unaligned_checkpoint)
|
||||
{
|
||||
auto const *unaligned_signatures = reinterpret_cast<unaligned_voter_to_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
|
||||
auto const *unaligned_signatures = reinterpret_cast<unaligned_quorum_signature *>(static_cast<uint8_t *>(val.mv_data) + sizeof(*header));
|
||||
for (size_t i = 0; i < num_sigs; i++)
|
||||
{
|
||||
auto const &unaligned = unaligned_signatures[i];
|
||||
service_nodes::voter_to_signature aligned = {};
|
||||
aligned.voter_index = unaligned.voter_index;
|
||||
auto const &unaligned = unaligned_signatures[i];
|
||||
service_nodes::quorum_signature aligned = {};
|
||||
aligned.voter_index = unaligned.voter_index;
|
||||
memcpy(aligned.signature.c.data, unaligned.signature.c, sizeof(aligned.signature.c));
|
||||
memcpy(aligned.signature.r.data, unaligned.signature.r, sizeof(aligned.signature.r));
|
||||
checkpoint.signatures.push_back(aligned);
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace cryptonote
|
|||
checkpoint_type type;
|
||||
uint64_t height;
|
||||
crypto::hash block_hash;
|
||||
std::vector<service_nodes::voter_to_signature> signatures; // Only service node checkpoints use signatures
|
||||
std::vector<service_nodes::quorum_signature> signatures; // Only service node checkpoints use signatures
|
||||
uint64_t prev_height; // TODO(doyle): Unused
|
||||
|
||||
bool check (crypto::hash const &block_hash) const;
|
||||
|
|
|
@ -45,6 +45,29 @@
|
|||
#include "ringct/rctTypes.h"
|
||||
#include "device/device.hpp"
|
||||
|
||||
namespace service_nodes
|
||||
{
|
||||
struct quorum_signature
|
||||
{
|
||||
uint16_t voter_index;
|
||||
char padding[6];
|
||||
crypto::signature signature;
|
||||
|
||||
quorum_signature() = default;
|
||||
quorum_signature(uint16_t voter_index, crypto::signature const &signature)
|
||||
: voter_index(voter_index)
|
||||
, signature(signature)
|
||||
{
|
||||
std::memset(padding, 0, sizeof(padding));
|
||||
}
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(voter_index)
|
||||
FIELD(signature)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
};
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
typedef std::vector<crypto::signature> ring_signature;
|
||||
|
@ -410,24 +433,11 @@ namespace cryptonote
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* */
|
||||
/************************************************************************/
|
||||
struct pulse_random_value { unsigned char data[16]; };
|
||||
|
||||
struct pulse_verification
|
||||
{
|
||||
uint8_t quorum_index;
|
||||
crypto::signature signature;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(quorum_index);
|
||||
FIELD(signature);
|
||||
END_SERIALIZE();
|
||||
};
|
||||
|
||||
struct pulse_header
|
||||
{
|
||||
pulse_random_value random_value;
|
||||
|
@ -470,10 +480,10 @@ namespace cryptonote
|
|||
|
||||
public:
|
||||
block() = default;
|
||||
block(const block &b): block_header(b), miner_tx{b.miner_tx}, tx_hashes{b.tx_hashes}, verification{b.verification} { copy_hash(b); }
|
||||
block &operator=(const block &b) { block_header::operator=(b); miner_tx = b.miner_tx; tx_hashes = b.tx_hashes; verification = b.verification; copy_hash(b); return *this; }
|
||||
block(block &&b) : block_header(std::move(b)), miner_tx{std::move(b.miner_tx)}, tx_hashes{std::move(b.tx_hashes)}, verification{std::move(b.verification)} { copy_hash(b); }
|
||||
block &operator=(block &&b) { block_header::operator=(std::move(b)); miner_tx = std::move(b.miner_tx); tx_hashes = std::move(b.tx_hashes); verification = std::move(b.verification); copy_hash(b); return *this; }
|
||||
block(const block &b): block_header(b), miner_tx{b.miner_tx}, tx_hashes{b.tx_hashes}, signatures{b.signatures} { copy_hash(b); }
|
||||
block &operator=(const block &b) { block_header::operator=(b); miner_tx = b.miner_tx; tx_hashes = b.tx_hashes; signatures = b.signatures; copy_hash(b); return *this; }
|
||||
block(block &&b) : block_header(std::move(b)), miner_tx{std::move(b.miner_tx)}, tx_hashes{std::move(b.tx_hashes)}, signatures{std::move(b.signatures)} { copy_hash(b); }
|
||||
block &operator=(block &&b) { block_header::operator=(std::move(b)); miner_tx = std::move(b.miner_tx); tx_hashes = std::move(b.tx_hashes); signatures = std::move(b.signatures); copy_hash(b); return *this; }
|
||||
void invalidate_hashes() { set_hash_valid(false); }
|
||||
bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); }
|
||||
void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); }
|
||||
|
@ -483,7 +493,7 @@ namespace cryptonote
|
|||
|
||||
// hash cash
|
||||
mutable crypto::hash hash;
|
||||
std::vector<pulse_verification> verification;
|
||||
std::vector<service_nodes::quorum_signature> signatures;
|
||||
|
||||
BEGIN_SERIALIZE_OBJECT()
|
||||
if (Archive::is_deserializer)
|
||||
|
@ -495,7 +505,7 @@ namespace cryptonote
|
|||
if (tx_hashes.size() > CRYPTONOTE_MAX_TX_PER_BLOCK)
|
||||
throw std::invalid_argument{"too many txs in block"};
|
||||
if (major_version >= cryptonote::network_version_16)
|
||||
FIELD(verification)
|
||||
FIELD(signatures)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
|
|
|
@ -4307,7 +4307,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
|
|||
}
|
||||
|
||||
abort_block.cancel();
|
||||
char const *BLOCK_TYPE = bl.verification.size() ? "PULSE" : "MINER";
|
||||
char const *BLOCK_TYPE = bl.signatures.size() ? "PULSE" : "MINER";
|
||||
MINFO("+++++ " << BLOCK_TYPE << " BLOCK SUCCESSFULLY ADDED" << std::endl << "id:\t" << id << std::endl << "PoW:\t" << proof_of_work << std::endl << "HEIGHT " << new_height-1 << ", difficulty:\t" << current_diffic << std::endl << "block reward: " << print_money(fee_summary + base_reward) << "(" << print_money(base_reward) << " + " << print_money(fee_summary) << "), coinbase_weight: " << coinbase_weight << ", cumulative weight: " << cumulative_block_weight << ", " << block_processing_time << "(" << target_calculating_time << "/" << longhash_calculating_time << ")ms");
|
||||
if(m_show_time_stats)
|
||||
{
|
||||
|
|
|
@ -1251,17 +1251,17 @@ namespace service_nodes
|
|||
stream << "Participating Validators: " << participation_bits << "\n";
|
||||
|
||||
stream << "Signatures: ";
|
||||
if (block.verification.empty()) stream << "(none)";
|
||||
if (block.signatures.empty()) stream << "(none)";
|
||||
stream << "\n";
|
||||
|
||||
for (size_t i = 0; i < block.verification.size(); i++)
|
||||
for (size_t i = 0; i < block.signatures.size(); i++)
|
||||
{
|
||||
if (i) stream << "\n";
|
||||
cryptonote::pulse_verification const &entry = block.verification[i];
|
||||
stream << " [" << static_cast<int>(entry.quorum_index) << "] validator: ";
|
||||
service_nodes::quorum_signature const &entry = block.signatures[i];
|
||||
stream << " [" << static_cast<int>(entry.voter_index) << "] validator: ";
|
||||
if (quorum)
|
||||
{
|
||||
stream << ((entry.quorum_index >= quorum->validators.size()) ? "(invalid quorum index)" : epee::string_tools::pod_to_hex(quorum->validators[entry.quorum_index]));
|
||||
stream << ((entry.voter_index >= quorum->validators.size()) ? "(invalid quorum index)" : epee::string_tools::pod_to_hex(quorum->validators[entry.voter_index]));
|
||||
stream << ", signature: " << epee::string_tools::pod_to_hex(entry.signature);
|
||||
}
|
||||
else stream << "(invalid quorum)";
|
||||
|
@ -1284,12 +1284,6 @@ namespace service_nodes
|
|||
std::shared_ptr<const quorum> quorum = get_quorum(quorum_type::pulse, m_state.height);
|
||||
if (quorum)
|
||||
{
|
||||
if (block.verification.size() != PULSE_BLOCK_REQUIRED_SIGNATURES)
|
||||
{
|
||||
LOG_PRINT_L1("Pulse block has " << block.verification.size() << " signatures but requires " << PULSE_BLOCK_REQUIRED_SIGNATURES << "\n" << dump_pulse_block_data(block, quorum.get()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((block.pulse.validator_participation_bits & (~service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK)) > 0)
|
||||
{
|
||||
auto mask = std::bitset<sizeof(service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK) * 8>(service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK);
|
||||
|
@ -1297,31 +1291,20 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
for (cryptonote::pulse_verification const &verification : block.verification)
|
||||
if (!service_nodes::verify_quorum_signatures(*quorum,
|
||||
quorum_type::pulse,
|
||||
block.major_version,
|
||||
cryptonote::get_block_height(block),
|
||||
cryptonote::get_block_hash(block),
|
||||
block.signatures, &block))
|
||||
{
|
||||
if (verification.quorum_index >= quorum->validators.size())
|
||||
{
|
||||
LOG_PRINT_L1("Received pulse signature with quorum index out of array bounds " << static_cast<int>(verification.quorum_index) << "\n" << dump_pulse_block_data(block, quorum.get()));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t bit = 1 << verification.quorum_index;
|
||||
if ((block.pulse.validator_participation_bits & bit) == 0)
|
||||
{
|
||||
LOG_PRINT_L1("Received pulse signature from validator " << static_cast<int>(verification.quorum_index) << " that is not participating in round " << static_cast<int>(block.pulse.round) << "\n" << dump_pulse_block_data(block, quorum.get()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!crypto::check_signature(cryptonote::get_block_hash(block), quorum->validators[verification.quorum_index], verification.signature))
|
||||
{
|
||||
LOG_PRINT_L1("Received pulse signature from validator " << static_cast<int>(verification.quorum_index) << " that is invalid in round " << static_cast<int>(block.pulse.round) << "\n" << dump_pulse_block_data(block, quorum.get()));
|
||||
return false;
|
||||
}
|
||||
LOG_PRINT_L1(dump_pulse_block_data(block, quorum.get()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool expect_pulse_quorum = (block.pulse.validator_participation_bits > 0 || block.verification.size());
|
||||
bool expect_pulse_quorum = (block.pulse.validator_participation_bits > 0 || block.signatures.size());
|
||||
if (expect_pulse_quorum)
|
||||
{
|
||||
LOG_PRINT_L1("Failed to get pulse quorum for block\n" << dump_pulse_block_data(block, quorum.get()));
|
||||
|
|
|
@ -576,7 +576,7 @@ namespace service_nodes
|
|||
checkpoint.signatures.reserve(service_nodes::CHECKPOINT_QUORUM_SIZE);
|
||||
std::sort(checkpoint.signatures.begin(),
|
||||
checkpoint.signatures.end(),
|
||||
[](service_nodes::voter_to_signature const &lhs, service_nodes::voter_to_signature const &rhs) {
|
||||
[](service_nodes::quorum_signature const &lhs, service_nodes::quorum_signature const &rhs) {
|
||||
return lhs.voter_index < rhs.voter_index;
|
||||
});
|
||||
|
||||
|
@ -585,7 +585,7 @@ namespace service_nodes
|
|||
auto it = std::lower_bound(checkpoint.signatures.begin(),
|
||||
checkpoint.signatures.end(),
|
||||
pool_vote,
|
||||
[](voter_to_signature const &lhs, pool_vote_entry const &vote) {
|
||||
[](quorum_signature const &lhs, pool_vote_entry const &vote) {
|
||||
return lhs.voter_index < vote.vote.index_in_group;
|
||||
});
|
||||
|
||||
|
@ -593,7 +593,7 @@ namespace service_nodes
|
|||
pool_vote.vote.index_in_group != it->voter_index)
|
||||
{
|
||||
update_checkpoint = true;
|
||||
checkpoint.signatures.insert(it, voter_to_signature(pool_vote.vote));
|
||||
checkpoint.signatures.insert(it, quorum_signature(pool_vote.vote.index_in_group, pool_vote.vote.signature));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -604,7 +604,7 @@ namespace service_nodes
|
|||
checkpoint = make_empty_service_node_checkpoint(vote.checkpoint.block_hash, vote.block_height);
|
||||
checkpoint.signatures.reserve(votes.size());
|
||||
for (pool_vote_entry const &pool_vote : votes)
|
||||
checkpoint.signatures.push_back(voter_to_signature(pool_vote.vote));
|
||||
checkpoint.signatures.push_back(quorum_signature(pool_vote.vote.index_in_group, pool_vote.vote.signature));
|
||||
}
|
||||
|
||||
if (update_checkpoint)
|
||||
|
|
|
@ -229,6 +229,109 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
bool verify_quorum_signatures(service_nodes::quorum const &quorum, service_nodes::quorum_type type, uint8_t hf_version, uint64_t height, crypto::hash const &hash, std::vector<quorum_signature> const &signatures, void const *context)
|
||||
{
|
||||
bool enforce_vote_ordering = true;
|
||||
constexpr size_t MAX_QUORUM_SIZE = std::max(CHECKPOINT_QUORUM_SIZE, PULSE_QUORUM_NUM_VALIDATORS);
|
||||
std::array<size_t, MAX_QUORUM_SIZE> unique_vote_set = {};
|
||||
|
||||
switch(type)
|
||||
{
|
||||
default:
|
||||
assert(!"Invalid Code Path");
|
||||
break;
|
||||
|
||||
// TODO(loki): DRY quorum verification with state change obligations.
|
||||
|
||||
case quorum_type::checkpointing:
|
||||
{
|
||||
if (signatures.size() < service_nodes::CHECKPOINT_MIN_VOTES)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has insufficient signatures to be considered at height: " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signatures.size() > service_nodes::CHECKPOINT_QUORUM_SIZE)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has too many signatures to be considered at height: " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
enforce_vote_ordering = hf_version >= cryptonote::network_version_13_enforce_checkpoints;
|
||||
}
|
||||
break;
|
||||
|
||||
case quorum_type::pulse:
|
||||
{
|
||||
if (signatures.size() != PULSE_BLOCK_REQUIRED_SIGNATURES)
|
||||
{
|
||||
LOG_PRINT_L1("Pulse block has " << signatures.size() << " signatures but requires " << PULSE_BLOCK_REQUIRED_SIGNATURES);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const *block = reinterpret_cast<cryptonote::block const *>(context);
|
||||
if ((block->pulse.validator_participation_bits & (~service_nodes::PULSE_VALIDATOR_PARTICIPATION_MASK)) > 0)
|
||||
{
|
||||
auto mask = std::bitset<sizeof(PULSE_VALIDATOR_PARTICIPATION_MASK) * 8>(PULSE_VALIDATOR_PARTICIPATION_MASK);
|
||||
LOG_PRINT_L1("Pulse block specifies validator participation bits out of bounds. Expected the bit mask " << mask);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < signatures.size(); i++)
|
||||
{
|
||||
service_nodes::quorum_signature const &quorum_signature = signatures[i];
|
||||
if (enforce_vote_ordering && i < (signatures.size() - 1))
|
||||
{
|
||||
auto curr = signatures[i].voter_index;
|
||||
auto next = signatures[i + 1].voter_index;
|
||||
|
||||
if (curr >= next)
|
||||
{
|
||||
LOG_PRINT_L1("Voters in signatures are not given in ascending order, failed verification at height: " << height);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bounds_check_validator_index(quorum, quorum_signature.voter_index, nullptr))
|
||||
return false;
|
||||
|
||||
if (type == quorum_type::pulse)
|
||||
{
|
||||
auto const *block = reinterpret_cast<cryptonote::block const *>(context);
|
||||
uint16_t bit = 1 << quorum_signature.voter_index;
|
||||
if ((block->pulse.validator_participation_bits & bit) == 0)
|
||||
{
|
||||
LOG_PRINT_L1("Received pulse signature from validator " << static_cast<int>(quorum_signature.voter_index) << " that is not participating in round " << static_cast<int>(block->pulse.round));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
crypto::public_key const &key = quorum.validators[quorum_signature.voter_index];
|
||||
if (quorum_signature.voter_index >= unique_vote_set.size())
|
||||
{
|
||||
MERROR("Internal Error: Voter Index indexes out of bounds of the vote set, index: " << quorum_signature.voter_index << "vote set size: " << unique_vote_set.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (unique_vote_set[quorum_signature.voter_index]++)
|
||||
{
|
||||
LOG_PRINT_L1("Voter: " << epee::string_tools::pod_to_hex(key) << ", quorum index is duplicated: " << quorum_signature.voter_index << ", failed verification at height: " << height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!crypto::check_signature(hash, key, quorum_signature.signature))
|
||||
{
|
||||
LOG_PRINT_L1("Incorrect signature for vote, failed verification at height: " << height << " for voter: " << epee::string_tools::pod_to_hex(key));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify_checkpoint(uint8_t hf_version, cryptonote::checkpoint_t const &checkpoint, service_nodes::quorum const &quorum)
|
||||
{
|
||||
if (checkpoint.type == cryptonote::checkpoint_type::service_node)
|
||||
|
@ -239,48 +342,8 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
if (checkpoint.signatures.size() < service_nodes::CHECKPOINT_MIN_VOTES)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has insufficient signatures to be considered at height: " << checkpoint.height);
|
||||
if (!verify_quorum_signatures(quorum, quorum_type::checkpointing, hf_version, checkpoint.height, checkpoint.block_hash, checkpoint.signatures, &checkpoint))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkpoint.signatures.size() > service_nodes::CHECKPOINT_QUORUM_SIZE)
|
||||
{
|
||||
LOG_PRINT_L1("Checkpoint has too many signatures to be considered at height: " << checkpoint.height);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<size_t, service_nodes::CHECKPOINT_QUORUM_SIZE> unique_vote_set = {};
|
||||
for (size_t i = 0; i < checkpoint.signatures.size(); i++)
|
||||
{
|
||||
service_nodes::voter_to_signature const &voter_to_signature = checkpoint.signatures[i];
|
||||
if (hf_version >= cryptonote::network_version_13_enforce_checkpoints && i < (checkpoint.signatures.size() - 1))
|
||||
{
|
||||
auto curr = checkpoint.signatures[i].voter_index;
|
||||
auto next = checkpoint.signatures[i + 1].voter_index;
|
||||
|
||||
if (curr >= next)
|
||||
{
|
||||
LOG_PRINT_L1("Voters in checkpoints are not given in ascending order, checkpoint failed verification at height: " << checkpoint.height);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bounds_check_validator_index(quorum, voter_to_signature.voter_index, nullptr)) return false;
|
||||
crypto::public_key const &key = quorum.validators[voter_to_signature.voter_index];
|
||||
if (unique_vote_set[voter_to_signature.voter_index]++)
|
||||
{
|
||||
LOG_PRINT_L1("Voter: " << epee::string_tools::pod_to_hex(key) << ", quorum index is duplicated: " << voter_to_signature.voter_index << ", checkpoint failed verification at height: " << checkpoint.height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!crypto::check_signature(checkpoint.block_hash, key, voter_to_signature.signature))
|
||||
{
|
||||
LOG_PRINT_L1("Invalid signatures for votes, checkpoint failed verification at height: " << checkpoint.height << " for voter: " << epee::string_tools::pod_to_hex(key));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -99,20 +99,6 @@ namespace service_nodes
|
|||
void serialize(Archive &ar, const unsigned int /*version*/) { }
|
||||
};
|
||||
|
||||
struct voter_to_signature
|
||||
{
|
||||
voter_to_signature() = default;
|
||||
voter_to_signature(quorum_vote_t const &vote) : voter_index(vote.index_in_group), signature(vote.signature) { }
|
||||
uint16_t voter_index;
|
||||
char padding[6];
|
||||
crypto::signature signature;
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(voter_index)
|
||||
FIELD(signature)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
struct service_node_keys;
|
||||
|
||||
quorum_vote_t make_state_change_vote(uint64_t block_height, uint16_t index_in_group, uint16_t worker_index, new_state state, const service_node_keys &keys);
|
||||
|
@ -123,6 +109,7 @@ namespace service_nodes
|
|||
bool verify_tx_state_change (const cryptonote::tx_extra_service_node_state_change& state_change, uint64_t latest_height, cryptonote::tx_verification_context& vvc, const service_nodes::quorum &quorum, uint8_t hf_version);
|
||||
bool verify_vote_age (const quorum_vote_t& vote, uint64_t latest_height, cryptonote::vote_verification_context &vvc);
|
||||
bool verify_vote_signature (uint8_t hf_version, const quorum_vote_t& vote, cryptonote::vote_verification_context &vvc, const service_nodes::quorum &quorum);
|
||||
bool verify_quorum_signatures (service_nodes::quorum const &quorum, service_nodes::quorum_type type, uint8_t hf_version, uint64_t height, crypto::hash const &hash, std::vector<quorum_signature> const &signatures, void const *context);
|
||||
crypto::signature make_signature_from_vote (quorum_vote_t const &vote, const service_node_keys &keys);
|
||||
crypto::signature make_signature_from_tx_state_change(cryptonote::tx_extra_service_node_state_change const &state_change, const service_node_keys &keys);
|
||||
|
||||
|
|
|
@ -1248,7 +1248,7 @@ KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::request)
|
|||
KV_SERIALIZE_MAP_CODE_END()
|
||||
|
||||
|
||||
KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::voter_to_signature_serialized)
|
||||
KV_SERIALIZE_MAP_CODE_BEGIN(GET_CHECKPOINTS::quorum_signature_serialized)
|
||||
KV_SERIALIZE(voter_index);
|
||||
KV_SERIALIZE(signature);
|
||||
KV_SERIALIZE_MAP_CODE_END()
|
||||
|
|
|
@ -2265,13 +2265,13 @@ namespace rpc {
|
|||
KV_MAP_SERIALIZABLE
|
||||
};
|
||||
|
||||
struct voter_to_signature_serialized
|
||||
struct quorum_signature_serialized
|
||||
{
|
||||
uint16_t voter_index; // Index of the voter in the relevant quorum
|
||||
std::string signature; // The signature generated by the voter in the quorum
|
||||
|
||||
voter_to_signature_serialized() = default;
|
||||
voter_to_signature_serialized(service_nodes::voter_to_signature const &entry)
|
||||
quorum_signature_serialized() = default;
|
||||
quorum_signature_serialized(service_nodes::quorum_signature const &entry)
|
||||
: voter_index(entry.voter_index)
|
||||
, signature(epee::string_tools::pod_to_hex(entry.signature)) { }
|
||||
|
||||
|
@ -2286,11 +2286,11 @@ namespace rpc {
|
|||
struct checkpoint_serialized
|
||||
{
|
||||
uint8_t version;
|
||||
std::string type; // Either "Hardcoded" or "ServiceNode" for checkpoints generated by Service Nodes or declared in the code
|
||||
uint64_t height; // The height the checkpoint is relevant for
|
||||
std::string block_hash; // The block hash the checkpoint is specifying
|
||||
std::vector<voter_to_signature_serialized> signatures; // Signatures from Service Nodes who agree on the block hash
|
||||
uint64_t prev_height; // The previous height the checkpoint is based off
|
||||
std::string type; // Either "Hardcoded" or "ServiceNode" for checkpoints generated by Service Nodes or declared in the code
|
||||
uint64_t height; // The height the checkpoint is relevant for
|
||||
std::string block_hash; // The block hash the checkpoint is specifying
|
||||
std::vector<quorum_signature_serialized> signatures; // Signatures from Service Nodes who agree on the block hash
|
||||
uint64_t prev_height; // The previous height the checkpoint is based off
|
||||
|
||||
checkpoint_serialized() = default;
|
||||
checkpoint_serialized(checkpoint_t const &checkpoint)
|
||||
|
@ -2301,7 +2301,7 @@ namespace rpc {
|
|||
, prev_height(checkpoint.prev_height)
|
||||
{
|
||||
signatures.reserve(checkpoint.signatures.size());
|
||||
for (service_nodes::voter_to_signature const &entry : checkpoint.signatures)
|
||||
for (service_nodes::quorum_signature const &entry : checkpoint.signatures)
|
||||
signatures.push_back(entry);
|
||||
}
|
||||
|
||||
|
|
|
@ -532,7 +532,7 @@ cryptonote::checkpoint_t loki_chain_generator::create_service_node_checkpoint(ui
|
|||
{
|
||||
auto keys = get_cached_keys(quorum.validators[i]);
|
||||
service_nodes::quorum_vote_t vote = service_nodes::make_checkpointing_vote(entry.block.major_version, result.block_hash, block_height, i, keys);
|
||||
result.signatures.push_back(service_nodes::voter_to_signature(vote));
|
||||
result.signatures.push_back(service_nodes::quorum_signature(vote.index_in_group, vote.signature));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -913,18 +913,18 @@ void loki_chain_generator::block_fill_pulse_data(loki_blockchain_entry &entry, l
|
|||
assert(pulse_quorum.workers.size() == 1);
|
||||
|
||||
crypto::hash block_hash = cryptonote::get_block_hash(entry.block);
|
||||
assert(entry.block.verification.empty());
|
||||
assert(entry.block.signatures.empty());
|
||||
|
||||
// NOTE: Fill Pulse Verification Data
|
||||
// NOTE: Fill Pulse Signature Data
|
||||
for (size_t i = 0; i < service_nodes::PULSE_BLOCK_REQUIRED_SIGNATURES; i++)
|
||||
{
|
||||
service_nodes::service_node_keys validator_keys = get_cached_keys(pulse_quorum.validators[i]);
|
||||
assert(validator_keys.pub == pulse_quorum.validators[i]);
|
||||
|
||||
cryptonote::pulse_verification verification = {};
|
||||
verification.quorum_index = i;
|
||||
crypto::generate_signature(block_hash, validator_keys.pub, validator_keys.key, verification.signature);
|
||||
entry.block.verification.push_back(verification);
|
||||
service_nodes::quorum_signature signature = {};
|
||||
signature.voter_index = i;
|
||||
crypto::generate_signature(block_hash, validator_keys.pub, validator_keys.key, signature.signature);
|
||||
entry.block.signatures.push_back(signature);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -150,9 +150,10 @@ int main(int argc, char* argv[])
|
|||
GENERATE_AND_PLAY(loki_service_nodes_test_swarms_basic);
|
||||
GENERATE_AND_PLAY(loki_pulse_invalid_participation_bits);
|
||||
GENERATE_AND_PLAY(loki_pulse_invalid_signature);
|
||||
GENERATE_AND_PLAY(loki_pulse_oob_quorum_index);
|
||||
GENERATE_AND_PLAY(loki_pulse_oob_voter_index);
|
||||
GENERATE_AND_PLAY(loki_pulse_non_participating_validator);
|
||||
GENERATE_AND_PLAY(loki_pulse_generate_all_rounds);
|
||||
GENERATE_AND_PLAY(loki_pulse_out_of_order_voters);
|
||||
|
||||
// NOTE: Monero Tests
|
||||
GENERATE_AND_PLAY(gen_simple_chain_001);
|
||||
|
|
|
@ -3079,14 +3079,14 @@ bool loki_pulse_invalid_signature::generate(std::vector<test_event_entry> &event
|
|||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Overwrite signature
|
||||
entry.block.verification[0].signature = {};
|
||||
entry.block.signatures[0].signature = {};
|
||||
gen.block_end(entry, params);
|
||||
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "Invalid Pulse Block, specifies the wrong participation bits");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loki_pulse_oob_quorum_index::generate(std::vector<test_event_entry> &events)
|
||||
bool loki_pulse_oob_voter_index::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
loki_chain_generator gen = setup_pulse_tests(events);
|
||||
gen.add_event_msg("Invalid Block: Quorum index that indexes out of bounds");
|
||||
|
@ -3095,8 +3095,8 @@ bool loki_pulse_oob_quorum_index::generate(std::vector<test_event_entry> &events
|
|||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Overwrite oob quorum index
|
||||
entry.block.verification[0].quorum_index = service_nodes::PULSE_QUORUM_NUM_VALIDATORS + 1;
|
||||
// NOTE: Overwrite oob voter index
|
||||
entry.block.signatures.back().voter_index = service_nodes::PULSE_QUORUM_NUM_VALIDATORS + 1;
|
||||
gen.block_end(entry, params);
|
||||
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "Invalid Pulse Block, specifies the wrong participation bits");
|
||||
|
||||
|
@ -3134,19 +3134,19 @@ bool loki_pulse_non_participating_validator::generate(std::vector<test_event_ent
|
|||
// first 6 in the quorum, then the 8th validator in the quorum (who is not
|
||||
// meant to be participating).
|
||||
entry.block.pulse.validator_participation_bits = 0b0000'000'0111'1111;
|
||||
size_t const quorum_indexes[] = {0, 1, 2, 3, 4, 5, 7};
|
||||
size_t const voter_indexes[] = {0, 1, 2, 3, 4, 5, 7};
|
||||
|
||||
crypto::hash block_hash = cryptonote::get_block_hash(entry.block);
|
||||
assert(entry.block.verification.empty());
|
||||
for (size_t index : quorum_indexes)
|
||||
assert(entry.block.signatures.empty());
|
||||
for (size_t index : voter_indexes)
|
||||
{
|
||||
service_nodes::service_node_keys validator_keys = gen.get_cached_keys(quorum.validators[index]);
|
||||
assert(validator_keys.pub == quorum.validators[index]);
|
||||
|
||||
cryptonote::pulse_verification verification = {};
|
||||
verification.quorum_index = index;
|
||||
crypto::generate_signature(block_hash, validator_keys.pub, validator_keys.key, verification.signature);
|
||||
entry.block.verification.push_back(verification);
|
||||
service_nodes::quorum_signature signature = {};
|
||||
signature.voter_index = index;
|
||||
crypto::generate_signature(block_hash, validator_keys.pub, validator_keys.key, signature.signature);
|
||||
entry.block.signatures.push_back(signature);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3172,3 +3172,23 @@ bool loki_pulse_generate_all_rounds::generate(std::vector<test_event_entry> &eve
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loki_pulse_out_of_order_voters::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
loki_chain_generator gen = setup_pulse_tests(events);
|
||||
gen.add_event_msg("Invalid Block: Quorum voters are out of order");
|
||||
loki_blockchain_entry entry = {};
|
||||
loki_create_block_params params = gen.next_block_params();
|
||||
gen.block_begin(entry, params, {} /*tx_list*/);
|
||||
gen.block_fill_pulse_data(entry, params, entry.block.pulse.round);
|
||||
|
||||
// NOTE: Swap voters so that the votes are not sorted in order
|
||||
auto tmp = entry.block.signatures.back();
|
||||
entry.block.signatures.back() = entry.block.signatures.front();
|
||||
entry.block.signatures.front() = tmp;
|
||||
gen.block_end(entry, params);
|
||||
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "Invalid Pulse Block, specifies the signatures not in sorted order");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,8 @@ struct loki_service_nodes_test_rollback
|
|||
struct loki_service_nodes_test_swarms_basic : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_invalid_participation_bits : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_invalid_signature : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_oob_quorum_index : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_oob_voter_index : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_non_participating_validator : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_generate_all_rounds : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
struct loki_pulse_out_of_order_voters : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
|
||||
|
||||
|
|
Loading…
Reference in New Issue