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:
Doyle 2020-06-25 13:55:24 +10:00
parent 96bf8f757e
commit 63ba795c7e
14 changed files with 217 additions and 152 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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