mirror of https://github.com/oxen-io/oxen-core.git
331 lines
12 KiB
C++
331 lines
12 KiB
C++
// Copyright (c) 2018, The Loki Project
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without modification, are
|
|
// permitted provided that the following conditions are met:
|
|
//
|
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
|
// conditions and the following disclaimer.
|
|
//
|
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
|
// of conditions and the following disclaimer in the documentation and/or other
|
|
// materials provided with the distribution.
|
|
//
|
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without specific
|
|
// prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#include "service_node_deregister.h"
|
|
#include "service_node_list.h"
|
|
#include "cryptonote_basic/tx_extra.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
#include "cryptonote_basic/verification_context.h"
|
|
#include "cryptonote_basic/connection_context.h"
|
|
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
|
|
#include "cryptonote_core/blockchain.h"
|
|
|
|
#include "misc_log_ex.h"
|
|
#include "string_tools.h"
|
|
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#undef LOKI_DEFAULT_LOG_CATEGORY
|
|
#define LOKI_DEFAULT_LOG_CATEGORY "service_nodes"
|
|
|
|
namespace service_nodes
|
|
{
|
|
static crypto::hash make_hash_from(uint64_t block_height, uint32_t service_node_index)
|
|
{
|
|
const int buf_size = sizeof(block_height) + sizeof(service_node_index);
|
|
char buf[buf_size];
|
|
|
|
memcpy(buf, reinterpret_cast<void *>(&block_height), sizeof(block_height));
|
|
memcpy(buf + sizeof(block_height), reinterpret_cast<void *>(&service_node_index), sizeof(service_node_index));
|
|
|
|
crypto::hash result;
|
|
crypto::cn_fast_hash(buf, buf_size, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
crypto::signature deregister_vote::sign_vote(uint64_t block_height, uint32_t service_node_index, const crypto::public_key& pub, const crypto::secret_key& sec)
|
|
{
|
|
crypto::signature result;
|
|
crypto::generate_signature(make_hash_from(block_height, service_node_index), pub, sec, result);
|
|
return result;
|
|
}
|
|
|
|
bool deregister_vote::verify_vote_signature(uint64_t block_height, uint32_t service_node_index, crypto::public_key const &p, crypto::signature const &s)
|
|
{
|
|
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs{ std::make_pair(p, s) };
|
|
return verify_votes_signature(block_height, service_node_index, keys_and_sigs);
|
|
}
|
|
|
|
bool deregister_vote::verify_votes_signature(uint64_t block_height, uint32_t service_node_index, const std::vector<std::pair<crypto::public_key, crypto::signature>>& keys_and_sigs)
|
|
{
|
|
crypto::hash hash = make_hash_from(block_height, service_node_index);
|
|
for (auto& key_and_sig : keys_and_sigs)
|
|
{
|
|
if (!crypto::check_signature(hash, key_and_sig.first, key_and_sig.second))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool verify_votes_helper(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
|
|
cryptonote::vote_verification_context &vvc,
|
|
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
|
{
|
|
if (deregister.service_node_index >= uptime_quorum.nodes_to_test.size())
|
|
{
|
|
vvc.m_service_node_index_out_of_bounds = true;
|
|
LOG_PRINT_L1("Service node index in deregister vote was out of bounds: " << deregister.service_node_index << ", expected to be in range of: [0, " << uptime_quorum.nodes_to_test.size() << ")");
|
|
return false;
|
|
}
|
|
|
|
const std::vector<crypto::public_key>& quorum = uptime_quorum.quorum_nodes;
|
|
std::vector<int8_t> quorum_set;
|
|
|
|
std::vector<std::pair<crypto::public_key, crypto::signature>> keys_and_sigs;
|
|
for (const cryptonote::tx_extra_service_node_deregister::vote& vote : deregister.votes)
|
|
{
|
|
if (vote.voters_quorum_index >= quorum.size())
|
|
{
|
|
vvc.m_voters_quorum_index_out_of_bounds = true;
|
|
LOG_PRINT_L1("Voter's index in deregister vote was out of bounds: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
|
|
return false;
|
|
}
|
|
|
|
quorum_set.resize(quorum.size());
|
|
if (++quorum_set[vote.voters_quorum_index] > 1)
|
|
{
|
|
vvc.m_duplicate_voters = true;
|
|
LOG_PRINT_L1("Voter quorum index is duplicated: " << vote.voters_quorum_index << ", expected to be in range of: [0, " << quorum.size() << ")");
|
|
return false;
|
|
}
|
|
|
|
keys_and_sigs.push_back(std::make_pair(quorum[vote.voters_quorum_index], vote.signature));
|
|
}
|
|
|
|
bool r = deregister_vote::verify_votes_signature(deregister.block_height, deregister.service_node_index, keys_and_sigs);
|
|
if (!r)
|
|
{
|
|
LOG_PRINT_L1("Invalid signatures for votes");
|
|
vvc.m_verification_failed = true;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
bool deregister_vote::verify_deregister(cryptonote::network_type nettype, const cryptonote::tx_extra_service_node_deregister& deregister,
|
|
cryptonote::vote_verification_context &vvc,
|
|
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
|
{
|
|
if (deregister.votes.size() < service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
|
|
{
|
|
LOG_PRINT_L1("Not enough votes");
|
|
vvc.m_not_enough_votes = true;
|
|
return false;
|
|
}
|
|
|
|
bool result = verify_votes_helper(nettype, deregister, vvc, uptime_quorum);
|
|
return result;
|
|
}
|
|
|
|
bool deregister_vote::verify_vote(cryptonote::network_type nettype, const deregister_vote& v, cryptonote::vote_verification_context &vvc,
|
|
const service_nodes::quorum_uptime_proof &uptime_quorum)
|
|
{
|
|
cryptonote::tx_extra_service_node_deregister deregister;
|
|
deregister.block_height = v.block_height;
|
|
deregister.service_node_index = v.service_node_index;
|
|
deregister.votes.push_back(cryptonote::tx_extra_service_node_deregister::vote{ v.signature, v.voters_quorum_index });
|
|
return verify_votes_helper(nettype, deregister, vvc, uptime_quorum);
|
|
}
|
|
|
|
void deregister_vote_pool::set_relayed(const std::vector<deregister_vote>& votes)
|
|
{
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
const time_t now = time(NULL);
|
|
|
|
for (const deregister_vote &find_vote : votes)
|
|
{
|
|
deregister_group desired_group = {};
|
|
desired_group.block_height = find_vote.block_height;
|
|
desired_group.service_node_index = find_vote.service_node_index;
|
|
|
|
auto deregister_entry = m_deregisters.find(desired_group);
|
|
if (deregister_entry != m_deregisters.end())
|
|
{
|
|
std::vector<deregister_pool_entry> &deregister_vector = deregister_entry->second;
|
|
for (auto &deregister : deregister_vector)
|
|
{
|
|
if (deregister.m_vote.voters_quorum_index == find_vote.voters_quorum_index)
|
|
{
|
|
deregister.m_time_last_sent_p2p = now;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<deregister_vote> deregister_vote_pool::get_relayable_votes() const
|
|
{
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
const cryptonote::cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context);
|
|
|
|
// TODO(doyle): Rate-limiting: A better threshold value that follows suite with transaction relay time back-off
|
|
const time_t now = time(NULL);
|
|
#if defined(LOKI_ENABLE_INTEGRATION_TEST_HOOKS)
|
|
const time_t THRESHOLD = 60 * 2;
|
|
#else
|
|
const time_t THRESHOLD = 0;
|
|
#endif
|
|
|
|
std::vector<deregister_vote> result;
|
|
for (const auto &deregister_entry : m_deregisters)
|
|
{
|
|
const std::vector<deregister_pool_entry>& deregister_vector = deregister_entry.second;
|
|
for (const deregister_pool_entry &entry : deregister_vector)
|
|
{
|
|
const time_t last_sent = now - entry.m_time_last_sent_p2p;
|
|
if (last_sent > THRESHOLD)
|
|
{
|
|
result.push_back(entry.m_vote);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool deregister_vote_pool::add_vote(const int hf_version,
|
|
const deregister_vote& new_vote,
|
|
cryptonote::vote_verification_context& vvc,
|
|
const service_nodes::quorum_uptime_proof &uptime_quorum,
|
|
cryptonote::transaction &tx)
|
|
{
|
|
if (!deregister_vote::verify_vote(m_nettype, new_vote, vvc, uptime_quorum))
|
|
{
|
|
LOG_PRINT_L1("Signature verification failed for deregister vote");
|
|
return false;
|
|
}
|
|
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
time_t const now = time(NULL);
|
|
std::vector<deregister_pool_entry> *deregister_votes;
|
|
{
|
|
deregister_group desired_group = {};
|
|
desired_group.block_height = new_vote.block_height;
|
|
desired_group.service_node_index = new_vote.service_node_index;
|
|
deregister_votes = &m_deregisters[desired_group];
|
|
}
|
|
|
|
bool new_deregister_is_unique = true;
|
|
for (const auto &entry : *deregister_votes)
|
|
{
|
|
if (entry.m_vote.voters_quorum_index == new_vote.voters_quorum_index)
|
|
{
|
|
new_deregister_is_unique = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_deregister_is_unique)
|
|
{
|
|
vvc.m_added_to_pool = true;
|
|
deregister_votes->emplace_back(deregister_pool_entry(0 /*time_last_sent_p2p*/, new_vote));
|
|
|
|
if (deregister_votes->size() >= service_nodes::MIN_VOTES_TO_KICK_SERVICE_NODE)
|
|
{
|
|
cryptonote::tx_extra_service_node_deregister deregister;
|
|
deregister.block_height = new_vote.block_height;
|
|
deregister.service_node_index = new_vote.service_node_index;
|
|
deregister.votes.reserve(deregister_votes->size());
|
|
|
|
for (const auto& entry : *deregister_votes)
|
|
{
|
|
cryptonote::tx_extra_service_node_deregister::vote tx_vote = {};
|
|
tx_vote.signature = entry.m_vote.signature;
|
|
tx_vote.voters_quorum_index = entry.m_vote.voters_quorum_index;
|
|
deregister.votes.push_back(tx_vote);
|
|
}
|
|
|
|
vvc.m_full_tx_deregister_made = cryptonote::add_service_node_deregister_to_tx_extra(tx.extra, deregister);
|
|
if (vvc.m_full_tx_deregister_made)
|
|
{
|
|
tx.version = cryptonote::transaction::get_max_version_for_hf(hf_version, m_nettype);
|
|
tx.type = cryptonote::transaction::type_deregister;
|
|
}
|
|
else
|
|
{
|
|
LOG_PRINT_L1("Could not create deregistration transaction from votes");
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void deregister_vote_pool::remove_used_votes(std::vector<cryptonote::transaction> const &txs)
|
|
{
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
for (const auto &tx : txs)
|
|
{
|
|
if (tx.get_type() != cryptonote::transaction::type_deregister)
|
|
continue;
|
|
|
|
cryptonote::tx_extra_service_node_deregister deregister;
|
|
if (!get_service_node_deregister_from_tx_extra(tx.extra, deregister))
|
|
{
|
|
LOG_ERROR("Could not get deregister from tx, possibly corrupt tx");
|
|
continue;
|
|
}
|
|
|
|
deregister_group desired_group = {};
|
|
desired_group.block_height = deregister.block_height;
|
|
desired_group.service_node_index = deregister.service_node_index;
|
|
m_deregisters.erase(desired_group);
|
|
}
|
|
}
|
|
|
|
void deregister_vote_pool::remove_expired_votes(uint64_t height)
|
|
{
|
|
if (height < deregister_vote::VOTE_LIFETIME_BY_HEIGHT)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
uint64_t minimum_height = height - deregister_vote::VOTE_LIFETIME_BY_HEIGHT;
|
|
for (auto it = m_deregisters.begin(); it != m_deregisters.end();)
|
|
{
|
|
const deregister_group &deregister_for = it->first;
|
|
if (deregister_for.block_height < minimum_height)
|
|
{
|
|
it = m_deregisters.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
}; // namespace service_nodes
|
|
|