mirror of https://github.com/oxen-io/oxen-core.git
384 lines
15 KiB
C++
384 lines
15 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.
|
|
|
|
#pragma once
|
|
|
|
#include "blockchain.h"
|
|
#include <boost/variant.hpp>
|
|
#include "serialization/serialization.h"
|
|
#include "cryptonote_core/service_node_rules.h"
|
|
#include "cryptonote_core/service_node_deregister.h"
|
|
#include "cryptonote_core/service_node_quorum_cop.h"
|
|
|
|
namespace service_nodes
|
|
{
|
|
struct service_node_info // registration information
|
|
{
|
|
struct contribution_t
|
|
{
|
|
uint8_t version = version_2_infinite_staking;
|
|
crypto::public_key key_image_pub_key;
|
|
crypto::key_image key_image;
|
|
uint64_t amount;
|
|
|
|
BEGIN_SERIALIZE_OBJECT()
|
|
VARINT_FIELD(version)
|
|
FIELD(key_image_pub_key)
|
|
FIELD(key_image)
|
|
VARINT_FIELD(amount)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
enum version
|
|
{
|
|
version_0,
|
|
version_1_swarms,
|
|
version_2_infinite_staking,
|
|
version_3_checkpointing,
|
|
};
|
|
|
|
struct contributor_t
|
|
{
|
|
uint8_t version;
|
|
uint64_t amount;
|
|
uint64_t reserved;
|
|
cryptonote::account_public_address address;
|
|
std::vector<contribution_t> locked_contributions;
|
|
|
|
contributor_t() = default;
|
|
contributor_t(uint64_t reserved_, const cryptonote::account_public_address& address_) : reserved(reserved_), address(address_)
|
|
{
|
|
*this = {};
|
|
reserved = reserved_;
|
|
address = address_;
|
|
}
|
|
|
|
BEGIN_SERIALIZE_OBJECT()
|
|
VARINT_FIELD(version)
|
|
VARINT_FIELD(amount)
|
|
VARINT_FIELD(reserved)
|
|
FIELD(address)
|
|
if (version >= version_2_infinite_staking)
|
|
FIELD(locked_contributions)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
uint8_t version;
|
|
uint64_t registration_height;
|
|
uint64_t requested_unlock_height;
|
|
// block_height and transaction_index are to record when the service node last received a reward.
|
|
uint64_t last_reward_block_height;
|
|
uint32_t last_reward_transaction_index;
|
|
std::vector<contributor_t> contributors;
|
|
uint64_t total_contributed;
|
|
uint64_t total_reserved;
|
|
uint64_t staking_requirement;
|
|
uint64_t portions_for_operator;
|
|
swarm_id_t swarm_id;
|
|
cryptonote::account_public_address operator_address;
|
|
|
|
service_node_info() = default;
|
|
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
|
|
size_t total_num_locked_contributions() const;
|
|
|
|
int dummy; // FIXME(doyle)
|
|
BEGIN_SERIALIZE_OBJECT()
|
|
VARINT_FIELD(version)
|
|
VARINT_FIELD(registration_height)
|
|
VARINT_FIELD(requested_unlock_height)
|
|
VARINT_FIELD(last_reward_block_height)
|
|
VARINT_FIELD(last_reward_transaction_index)
|
|
FIELD(contributors)
|
|
VARINT_FIELD(total_contributed)
|
|
VARINT_FIELD(total_reserved)
|
|
VARINT_FIELD(staking_requirement)
|
|
VARINT_FIELD(portions_for_operator)
|
|
FIELD(operator_address)
|
|
|
|
if (version >= service_node_info::version_1_swarms)
|
|
{
|
|
VARINT_FIELD(swarm_id)
|
|
}
|
|
VARINT_FIELD(dummy)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct service_node_pubkey_info
|
|
{
|
|
crypto::public_key pubkey;
|
|
service_node_info info;
|
|
|
|
BEGIN_SERIALIZE_OBJECT()
|
|
FIELD(pubkey)
|
|
FIELD(info)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct key_image_blacklist_entry
|
|
{
|
|
uint8_t version = service_node_info::version_2_infinite_staking;
|
|
crypto::key_image key_image;
|
|
uint64_t unlock_height;
|
|
|
|
BEGIN_SERIALIZE()
|
|
VARINT_FIELD(version)
|
|
FIELD(key_image)
|
|
VARINT_FIELD(unlock_height)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
template<typename T>
|
|
void loki_shuffle(std::vector<T>& a, uint64_t seed)
|
|
{
|
|
if (a.size() <= 1) return;
|
|
std::mt19937_64 mersenne_twister(seed);
|
|
for (size_t i = 1; i < a.size(); i++)
|
|
{
|
|
size_t j = (size_t)uniform_distribution_portable(mersenne_twister, i+1);
|
|
if (i != j)
|
|
std::swap(a[i], a[j]);
|
|
}
|
|
}
|
|
|
|
class service_node_list
|
|
: public cryptonote::Blockchain::BlockAddedHook,
|
|
public cryptonote::Blockchain::BlockchainDetachedHook,
|
|
public cryptonote::Blockchain::InitHook,
|
|
public cryptonote::Blockchain::ValidateMinerTxHook
|
|
{
|
|
public:
|
|
service_node_list(cryptonote::Blockchain& blockchain);
|
|
void block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs) override;
|
|
void blockchain_detached(uint64_t height) override;
|
|
void register_hooks(service_nodes::quorum_cop &quorum_cop);
|
|
void init() override;
|
|
bool validate_miner_tx(const crypto::hash& prev_id, const cryptonote::transaction& miner_tx, uint64_t height, int hard_fork_version, cryptonote::block_reward_parts const &base_reward) const override;
|
|
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> get_winner_addresses_and_portions() const;
|
|
crypto::public_key select_winner() const;
|
|
|
|
bool is_service_node(const crypto::public_key& pubkey) const;
|
|
bool is_key_image_locked(crypto::key_image const &check_image, uint64_t *unlock_height = nullptr, service_node_info::contribution_t *the_locked_contribution = nullptr) const;
|
|
|
|
void update_swarms(uint64_t height);
|
|
|
|
/// Note(maxim): this should not affect thread-safety as the returned object is const
|
|
const std::shared_ptr<const quorum_uptime_proof> get_uptime_quorum (uint64_t height) const;
|
|
const std::shared_ptr<const quorum_checkpointing> get_checkpointing_quorum(uint64_t height) const;
|
|
|
|
std::vector<service_node_pubkey_info> get_service_node_list_state(const std::vector<crypto::public_key> &service_node_pubkeys) const;
|
|
const std::vector<key_image_blacklist_entry> &get_blacklisted_key_images() const { return m_transient_state.key_image_blacklist; }
|
|
|
|
void set_db_pointer(cryptonote::BlockchainDB* db);
|
|
void set_my_service_node_keys(crypto::public_key const *pub_key);
|
|
bool store();
|
|
|
|
void get_all_service_nodes_public_keys(std::vector<crypto::public_key>& keys, bool fully_funded_nodes_only) const;
|
|
|
|
struct rollback_event
|
|
{
|
|
enum rollback_type
|
|
{
|
|
change_type,
|
|
new_type,
|
|
prevent_type,
|
|
key_image_blacklist_type,
|
|
};
|
|
|
|
rollback_event() = default;
|
|
rollback_event(uint64_t block_height, rollback_type type);
|
|
virtual ~rollback_event() { }
|
|
|
|
rollback_type type;
|
|
|
|
uint64_t m_block_height;
|
|
|
|
BEGIN_SERIALIZE()
|
|
VARINT_FIELD(m_block_height)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct rollback_change : public rollback_event
|
|
{
|
|
rollback_change() { type = change_type; }
|
|
rollback_change(uint64_t block_height, const crypto::public_key& key, const service_node_info& info);
|
|
crypto::public_key m_key;
|
|
service_node_info m_info;
|
|
|
|
BEGIN_SERIALIZE()
|
|
FIELDS(*static_cast<rollback_event *>(this))
|
|
FIELD(m_key)
|
|
FIELD(m_info)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct rollback_new : public rollback_event
|
|
{
|
|
rollback_new() { type = new_type; }
|
|
rollback_new(uint64_t block_height, const crypto::public_key& key);
|
|
crypto::public_key m_key;
|
|
|
|
BEGIN_SERIALIZE()
|
|
FIELDS(*static_cast<rollback_event *>(this))
|
|
FIELD(m_key)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct prevent_rollback : public rollback_event
|
|
{
|
|
prevent_rollback() { type = prevent_type; }
|
|
prevent_rollback(uint64_t block_height);
|
|
|
|
BEGIN_SERIALIZE()
|
|
FIELDS(*static_cast<rollback_event *>(this))
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct rollback_key_image_blacklist : public rollback_event
|
|
{
|
|
rollback_key_image_blacklist() { *this = {}; type = key_image_blacklist_type; }
|
|
rollback_key_image_blacklist(uint64_t block_height, key_image_blacklist_entry const &entry, bool is_adding_to_blacklist);
|
|
|
|
key_image_blacklist_entry m_entry;
|
|
bool m_was_adding_to_blacklist;
|
|
|
|
BEGIN_SERIALIZE()
|
|
FIELDS(*static_cast<rollback_event *>(this))
|
|
FIELD(m_entry)
|
|
FIELD(m_was_adding_to_blacklist)
|
|
END_SERIALIZE()
|
|
};
|
|
typedef boost::variant<rollback_change, rollback_new, prevent_rollback, rollback_key_image_blacklist> rollback_event_variant;
|
|
|
|
struct quorum_for_serialization
|
|
{
|
|
uint8_t version;
|
|
uint64_t height;
|
|
quorum_uptime_proof uptime_quorum;
|
|
quorum_checkpointing checkpointing_quorum;
|
|
|
|
BEGIN_SERIALIZE()
|
|
FIELD(version)
|
|
FIELD(height)
|
|
FIELD(uptime_quorum)
|
|
if (version >= service_node_info::version_3_checkpointing)
|
|
FIELD(checkpointing_quorum)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
struct data_members_for_serialization
|
|
{
|
|
uint8_t version;
|
|
uint64_t height;
|
|
std::vector<quorum_for_serialization> quorum_states;
|
|
std::vector<service_node_pubkey_info> infos;
|
|
std::vector<rollback_event_variant> events;
|
|
std::vector<key_image_blacklist_entry> key_image_blacklist;
|
|
|
|
BEGIN_SERIALIZE()
|
|
VARINT_FIELD(version)
|
|
FIELD(quorum_states)
|
|
FIELD(infos)
|
|
FIELD(events)
|
|
FIELD(height)
|
|
if (version >= service_node_info::version_2_infinite_staking)
|
|
FIELD(key_image_blacklist)
|
|
END_SERIALIZE()
|
|
};
|
|
|
|
private:
|
|
|
|
// Note(maxim): private methods don't have to be protected the mutex
|
|
bool process_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index);
|
|
void process_contribution_tx(const cryptonote::transaction& tx, uint64_t block_height, uint32_t index);
|
|
bool process_deregistration_tx(const cryptonote::transaction& tx, uint64_t block_height);
|
|
void process_block(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs);
|
|
|
|
std::vector<crypto::public_key> get_service_nodes_pubkeys() const;
|
|
|
|
bool contribution_tx_output_has_correct_unlock_time(const cryptonote::transaction& tx, size_t i, uint64_t block_height) const;
|
|
void generate_quorums(cryptonote::block const &block);
|
|
|
|
bool is_registration_tx(const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info) const;
|
|
std::vector<crypto::public_key> update_and_get_expired_nodes(const std::vector<cryptonote::transaction> &txs, uint64_t block_height);
|
|
|
|
void clear(bool delete_db_entry = false);
|
|
bool load();
|
|
|
|
mutable boost::recursive_mutex m_sn_mutex;
|
|
cryptonote::Blockchain& m_blockchain;
|
|
bool m_hooks_registered;
|
|
crypto::public_key const *m_service_node_pubkey;
|
|
cryptonote::BlockchainDB *m_db;
|
|
|
|
using block_height = uint64_t;
|
|
struct
|
|
{
|
|
std::unordered_map<crypto::public_key, service_node_info> service_nodes_infos;
|
|
std::vector<key_image_blacklist_entry> key_image_blacklist;
|
|
std::map<block_height, quorum_manager> quorum_states;
|
|
std::list<std::unique_ptr<rollback_event>> rollback_events;
|
|
block_height height;
|
|
} m_transient_state;
|
|
};
|
|
|
|
bool reg_tx_extract_fields(const cryptonote::transaction& tx, std::vector<cryptonote::account_public_address>& addresses, uint64_t& portions_for_operator, std::vector<uint64_t>& portions, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature, crypto::public_key& tx_pub_key);
|
|
|
|
struct converted_registration_args
|
|
{
|
|
bool success;
|
|
std::vector<cryptonote::account_public_address> addresses;
|
|
std::vector<uint64_t> portions;
|
|
uint64_t portions_for_operator;
|
|
std::string err_msg; // if (success == false), this is set to the err msg otherwise empty
|
|
};
|
|
converted_registration_args convert_registration_args(cryptonote::network_type nettype,
|
|
const std::vector<std::string>& args,
|
|
uint64_t staking_requirement,
|
|
int hf_version);
|
|
|
|
bool make_registration_cmd(cryptonote::network_type nettype,
|
|
int hf_version,
|
|
uint64_t staking_requirement,
|
|
const std::vector<std::string>& args,
|
|
const crypto::public_key& service_node_pubkey,
|
|
const crypto::secret_key &service_node_key,
|
|
std::string &cmd,
|
|
bool make_friendly,
|
|
boost::optional<std::string&> err_msg);
|
|
|
|
const static cryptonote::account_public_address null_address{ crypto::null_pkey, crypto::null_pkey };
|
|
const static std::vector<std::pair<cryptonote::account_public_address, uint64_t>> null_winner =
|
|
{std::pair<cryptonote::account_public_address, uint64_t>({null_address, STAKING_PORTIONS})};
|
|
}
|
|
|
|
VARIANT_TAG(binary_archive, service_nodes::service_node_list::data_members_for_serialization, 0xa0);
|
|
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_change, 0xa1);
|
|
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_new, 0xa2);
|
|
VARIANT_TAG(binary_archive, service_nodes::service_node_list::prevent_rollback, 0xa3);
|
|
VARIANT_TAG(binary_archive, service_nodes::service_node_list::rollback_key_image_blacklist, 0xa4);
|