oxen-core/src/cryptonote_core/service_node_deregister.cpp

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