mirror of
https://github.com/oxen-io/oxen-core.git
synced 2023-12-14 02:22:56 +01:00
216 lines
8 KiB
C++
216 lines
8 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_config.h"
|
|
#include "cryptonote_core.h"
|
|
#include "quorum_cop.h"
|
|
|
|
#undef LOKI_DEFAULT_LOG_CATEGORY
|
|
#define LOKI_DEFAULT_LOG_CATEGORY "quorum_cop"
|
|
|
|
namespace service_nodes
|
|
{
|
|
quorum_cop::quorum_cop(cryptonote::core& core, service_nodes::service_node_list& service_node_list)
|
|
: m_core(core), m_service_node_list(service_node_list), m_last_height(0)
|
|
{
|
|
init();
|
|
}
|
|
|
|
void quorum_cop::init()
|
|
{
|
|
m_last_height = 0;
|
|
m_uptime_proof_seen.clear();
|
|
}
|
|
|
|
void quorum_cop::blockchain_detached(uint64_t height)
|
|
{
|
|
if (m_last_height >= height)
|
|
{
|
|
LOG_ERROR("The blockchain was detached to height: " << height << ", but quorum cop has already processed votes up to " << m_last_height);
|
|
LOG_ERROR("This implies a reorg occured that was over " << REORG_SAFETY_BUFFER_IN_BLOCKS << ". This should never happen! Please report this to the devs.");
|
|
m_last_height = height;
|
|
}
|
|
}
|
|
|
|
void quorum_cop::block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs)
|
|
{
|
|
crypto::public_key my_pubkey;
|
|
crypto::secret_key my_seckey;
|
|
if (!m_core.get_service_node_keys(my_pubkey, my_seckey))
|
|
return;
|
|
|
|
time_t const now = time(nullptr);
|
|
time_t const min_lifetime = 60 * 60 * 2;
|
|
bool alive_for_min_time = (now - m_core.get_start_time()) >= min_lifetime;
|
|
if (!alive_for_min_time)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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 < loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT)
|
|
return;
|
|
|
|
uint64_t const execute_justice_from_height = latest_height - loki::service_node_deregister::VOTE_LIFETIME_BY_HEIGHT;
|
|
if (height < execute_justice_from_height)
|
|
return;
|
|
|
|
if (m_last_height < execute_justice_from_height)
|
|
m_last_height = execute_justice_from_height;
|
|
|
|
|
|
for (;m_last_height < (height - REORG_SAFETY_BUFFER_IN_BLOCKS); m_last_height++)
|
|
{
|
|
const std::shared_ptr<quorum_state> state = m_core.get_quorum_state(m_last_height);
|
|
if (!state)
|
|
{
|
|
// TODO(loki): Fatal error
|
|
LOG_ERROR("Quorum state for height: " << m_last_height << "was not cached in daemon!");
|
|
continue;
|
|
}
|
|
|
|
auto it = std::find(state->quorum_nodes.begin(), state->quorum_nodes.end(), my_pubkey);
|
|
if (it == state->quorum_nodes.end())
|
|
continue;
|
|
|
|
size_t my_index_in_quorum = it - state->quorum_nodes.begin();
|
|
for (size_t node_index = 0; node_index < state->nodes_to_test.size(); ++node_index)
|
|
{
|
|
const crypto::public_key &node_key = state->nodes_to_test[node_index];
|
|
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
bool vote_off_node = (m_uptime_proof_seen.find(node_key) == m_uptime_proof_seen.end());
|
|
|
|
if (!vote_off_node)
|
|
continue;
|
|
|
|
loki::service_node_deregister::vote vote = {};
|
|
vote.block_height = m_last_height;
|
|
vote.service_node_index = node_index;
|
|
vote.voters_quorum_index = my_index_in_quorum;
|
|
vote.signature = loki::service_node_deregister::sign_vote(vote.block_height, vote.service_node_index, my_pubkey, my_seckey);
|
|
|
|
cryptonote::vote_verification_context vvc = {};
|
|
if (!m_core.add_deregister_vote(vote, vvc))
|
|
{
|
|
if (vvc.m_invalid_block_height)
|
|
LOG_ERROR("block height was invalid: " << vote.block_height);
|
|
|
|
if (vvc.m_voters_quorum_index_out_of_bounds)
|
|
LOG_ERROR("voters quorum index specified was out of bounds: " << vote.voters_quorum_index);
|
|
|
|
if (vvc.m_duplicate_voters)
|
|
LOG_ERROR("voters index was duplicated: " << vote.voters_quorum_index);
|
|
|
|
if (vvc.m_service_node_index_out_of_bounds)
|
|
LOG_ERROR("service node index specified out of bounds: " << vote.service_node_index);
|
|
|
|
if (vvc.m_signature_not_valid)
|
|
LOG_ERROR("signature was not valid, was the signature signed properly?");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static crypto::hash make_hash(crypto::public_key const &pubkey, uint64_t timestamp)
|
|
{
|
|
char buf[44] = "SUP"; // Meaningless magic bytes
|
|
crypto::hash result;
|
|
memcpy(buf + 4, reinterpret_cast<const void *>(&pubkey), sizeof(pubkey));
|
|
memcpy(buf + 4 + sizeof(pubkey), reinterpret_cast<const void *>(×tamp), sizeof(timestamp));
|
|
crypto::cn_fast_hash(buf, sizeof(buf), result);
|
|
|
|
return result;
|
|
}
|
|
|
|
bool quorum_cop::handle_uptime_proof(uint64_t timestamp, const crypto::public_key& pubkey, const crypto::signature& sig)
|
|
{
|
|
uint64_t now = time(nullptr);
|
|
|
|
if ((timestamp < now - UPTIME_PROOF_BUFFER_IN_SECONDS) || (timestamp > now + UPTIME_PROOF_BUFFER_IN_SECONDS))
|
|
return false;
|
|
|
|
// TODO(doyle): the only dependency on m_service_node_lists which could be
|
|
// replaced by the lists stored in db when that is implemented - 2018-07-24
|
|
if (!m_service_node_list.is_service_node(pubkey))
|
|
return false;
|
|
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
if (m_uptime_proof_seen[pubkey] >= now - (UPTIME_PROOF_FREQUENCY_IN_SECONDS / 2))
|
|
return false; // already received one uptime proof for this node recently.
|
|
|
|
crypto::hash hash = make_hash(pubkey, timestamp);
|
|
if (!crypto::check_signature(hash, pubkey, sig))
|
|
return false;
|
|
|
|
m_uptime_proof_seen[pubkey] = timestamp;
|
|
return true;
|
|
}
|
|
|
|
void quorum_cop::generate_uptime_proof_request(const crypto::public_key& pubkey, const crypto::secret_key& seckey, cryptonote::NOTIFY_UPTIME_PROOF::request& req) const
|
|
{
|
|
req.timestamp = time(nullptr);
|
|
req.pubkey = pubkey;
|
|
|
|
crypto::hash hash = make_hash(req.pubkey, req.timestamp);
|
|
crypto::generate_signature(hash, pubkey, seckey, req.sig);
|
|
}
|
|
|
|
bool quorum_cop::prune_uptime_proof()
|
|
{
|
|
uint64_t now = time(nullptr);
|
|
const uint64_t prune_from_timestamp = now - UPTIME_PROOF_MAX_TIME_IN_SECONDS;
|
|
CRITICAL_REGION_LOCAL(m_lock);
|
|
|
|
for (auto it = m_uptime_proof_seen.begin(); it != m_uptime_proof_seen.end();)
|
|
{
|
|
if (it->second < prune_from_timestamp)
|
|
it = m_uptime_proof_seen.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint64_t quorum_cop::get_uptime_proof(const crypto::public_key &pubkey) const
|
|
{
|
|
const auto& it = m_uptime_proof_seen.find(pubkey);
|
|
if (it == m_uptime_proof_seen.end())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return (*it).second;
|
|
}
|
|
}
|