mirror of https://github.com/oxen-io/oxen-core.git
commit
e9a69b6a78
|
@ -51,7 +51,7 @@ message(STATUS "CMake version ${CMAKE_VERSION}")
|
|||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.12 CACHE STRING "macOS deployment target (Apple clang only)")
|
||||
|
||||
project(oxen
|
||||
VERSION 9.2.0
|
||||
VERSION 10.0.0
|
||||
LANGUAGES CXX C)
|
||||
set(OXEN_RELEASE_CODENAME "Audacious Aurochs")
|
||||
|
||||
|
|
|
@ -155,6 +155,18 @@ static inline uint64_t div128_64(uint64_t dividend_hi, uint64_t dividend_lo, uin
|
|||
return remainder;
|
||||
}
|
||||
|
||||
// Calculates a*b/c, using 128-bit precision to avoid overflow. This assumes that the result is
|
||||
// 64-bits, but only checks it (via assertion) in debug builds. As such you should only call this
|
||||
// when this is true: for instance, when c is known to be greater than either a or b.
|
||||
static inline uint64_t mul128_div64(uint64_t a, uint64_t b, uint64_t c) {
|
||||
uint64_t hi;
|
||||
uint64_t lo = mul128(a, b, &hi);
|
||||
uint64_t resulthi, resultlo;
|
||||
div128_64(hi, lo, c, &resulthi, &resultlo);
|
||||
assert(resulthi == 0);
|
||||
return resultlo;
|
||||
}
|
||||
|
||||
#define IDENT16(x) ((uint16_t) (x))
|
||||
#define IDENT32(x) ((uint32_t) (x))
|
||||
#define IDENT64(x) ((uint64_t) (x))
|
||||
|
|
|
@ -29,7 +29,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "portable_storage_base.h"
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <oxenc/variant.h>
|
||||
|
||||
namespace epee
|
||||
{
|
||||
|
@ -110,7 +111,7 @@ namespace epee
|
|||
static_assert(std::is_integral_v<T>);
|
||||
read(&v, sizeof(T));
|
||||
if constexpr (sizeof(T) > 1)
|
||||
boost::endian::little_to_native(v);
|
||||
oxenc::little_to_host(v);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#include "../pragma_comp_defs.h"
|
||||
#include "portable_storage_base.h"
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <oxenmq/variant.h>
|
||||
|
||||
namespace epee
|
||||
|
@ -71,16 +71,15 @@ namespace epee
|
|||
void pack_entry_to_buff(std::ostream& strm, T v)
|
||||
{
|
||||
if constexpr (sizeof(T) > 1)
|
||||
boost::endian::native_to_little_inplace(v);
|
||||
oxenc::host_to_little_inplace(v);
|
||||
strm.write(reinterpret_cast<const char*>(&v), sizeof(v));
|
||||
}
|
||||
|
||||
inline void pack_entry_to_buff(std::ostream& strm, double v)
|
||||
{
|
||||
static_assert(std::numeric_limits<double>::is_iec559 && sizeof(double) == 8 &&
|
||||
(boost::endian::order::native == boost::endian::order::big || boost::endian::order::native == boost::endian::order::little));
|
||||
static_assert(std::numeric_limits<double>::is_iec559 && sizeof(double) == 8 && (oxenc::little_endian || oxenc::big_endian));
|
||||
char* buff = reinterpret_cast<char*>(&v);
|
||||
if constexpr (boost::endian::order::native == boost::endian::order::big) {
|
||||
if constexpr (oxenc::big_endian) {
|
||||
size_t i = 8;
|
||||
while (i) strm.put(buff[--i]);
|
||||
} else {
|
||||
|
|
|
@ -65,6 +65,7 @@ target_link_libraries(epee
|
|||
PUBLIC
|
||||
easylogging
|
||||
oxenmq::oxenmq
|
||||
oxenc::oxenc
|
||||
PRIVATE
|
||||
filesystem
|
||||
Boost::thread
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
if(NOT STATIC AND NOT BUILD_STATIC_DEPS)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(OXENC liboxenc>=1.0.1 IMPORTED_TARGET)
|
||||
pkg_check_modules(OXENC liboxenc>=1.0.3 IMPORTED_TARGET)
|
||||
pkg_check_modules(OXENMQ liboxenmq>=1.2.3 IMPORTED_TARGET)
|
||||
endif()
|
||||
|
||||
|
@ -43,8 +43,12 @@ endif()
|
|||
if(NOT OXENC_FOUND)
|
||||
message(STATUS "Using in-tree oxen-encoding")
|
||||
add_subdirectory(oxen-encoding)
|
||||
add_library(oxenc::oxenc ALIAS oxenc)
|
||||
elseif(NOT TARGET PkgConfig::OXENC AND CMAKE_VERSION VERSION_LESS "3.21")
|
||||
# Work around cmake bug 22180 (PkgConfig::OXENC not set if no flags needed):
|
||||
add_library(_empty_oxenc INTERFACE)
|
||||
add_library(oxenc::oxenc ALIAS _empty_oxenc)
|
||||
else()
|
||||
add_library(oxenc INTERFACE)
|
||||
target_link_libraries(oxenc INTERFACE PkgConfig::OXENC)
|
||||
add_library(oxenc::oxenc ALIAS oxenc)
|
||||
message(STATUS "Found liboxenc ${OXENC_VERSION}")
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit a0912ab4bf3b5e83b42715eff6f632c8912b21e4
|
||||
Subproject commit 79193e58fb26624d40cd2e95156f78160f2b9b3e
|
|
@ -70,6 +70,7 @@ add_subdirectory(p2p)
|
|||
add_subdirectory(daemonizer)
|
||||
add_subdirectory(daemon)
|
||||
add_subdirectory(simplewallet)
|
||||
add_subdirectory(sqlitedb)
|
||||
|
||||
add_subdirectory(gen_multisig)
|
||||
add_subdirectory(blockchain_utilities)
|
||||
|
|
|
@ -35,7 +35,7 @@ add_library(blockchain_db
|
|||
|
||||
target_link_libraries(blockchain_db
|
||||
PUBLIC
|
||||
SQLiteCpp
|
||||
sqlitedb
|
||||
PRIVATE
|
||||
common
|
||||
ringct
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "db_lmdb.h"
|
||||
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
@ -56,7 +56,6 @@
|
|||
|
||||
|
||||
using namespace crypto;
|
||||
using namespace boost::endian;
|
||||
|
||||
enum struct lmdb_version
|
||||
{
|
||||
|
@ -2410,14 +2409,14 @@ static_assert(sizeof(blob_header) == 8, "blob_header layout is unexpected, possi
|
|||
static blob_header write_little_endian_blob_header(blob_type type, uint32_t size)
|
||||
{
|
||||
blob_header result = {type, size};
|
||||
native_to_little_inplace(result.size);
|
||||
oxenc::host_to_little_inplace(result.size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static blob_header native_endian_blob_header(const blob_header *header)
|
||||
static blob_header host_endian_blob_header(const blob_header *header)
|
||||
{
|
||||
blob_header result = {header->type, header->size};
|
||||
little_to_native_inplace(result.size);
|
||||
oxenc::little_to_host_inplace(result.size);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -2436,7 +2435,7 @@ static bool read_alt_block_data_from_mdb_val(MDB_val const v, alt_block_data_t *
|
|||
src = reinterpret_cast<const char *>(alt_data + 1);
|
||||
while (src < end)
|
||||
{
|
||||
blob_header header = native_endian_blob_header(reinterpret_cast<const blob_header *>(src));
|
||||
blob_header header = host_endian_blob_header(reinterpret_cast<const blob_header *>(src));
|
||||
src += sizeof(header);
|
||||
if (header.type == blob_type::block)
|
||||
{
|
||||
|
@ -4029,8 +4028,8 @@ static bool convert_checkpoint_into_buffer(checkpoint_t const &checkpoint, check
|
|||
header.block_hash = checkpoint.block_hash;
|
||||
header.num_signatures = checkpoint.signatures.size();
|
||||
|
||||
native_to_little_inplace(header.height);
|
||||
native_to_little_inplace(header.num_signatures);
|
||||
oxenc::host_to_little_inplace(header.height);
|
||||
oxenc::host_to_little_inplace(header.num_signatures);
|
||||
|
||||
size_t const bytes_for_signatures = sizeof(*checkpoint.signatures.data()) * checkpoint.signatures.size();
|
||||
result.len = sizeof(header) + bytes_for_signatures;
|
||||
|
@ -4113,8 +4112,8 @@ static checkpoint_t convert_mdb_val_to_checkpoint(MDB_val const value)
|
|||
auto const *signatures =
|
||||
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);
|
||||
auto num_sigs = oxenc::little_to_host(header->num_signatures);
|
||||
result.height = oxenc::little_to_host(header->height);
|
||||
result.type = (num_sigs > 0) ? checkpoint_type::service_node : checkpoint_type::hardcoded;
|
||||
result.block_hash = header->block_hash;
|
||||
result.signatures.insert(result.signatures.end(), signatures, signatures + num_sigs);
|
||||
|
@ -5989,12 +5988,12 @@ void BlockchainLMDB::migrate_5_6()
|
|||
// unexpected padding
|
||||
|
||||
auto const *header = static_cast<blk_checkpoint_header const *>(val.mv_data);
|
||||
auto num_sigs = little_to_native(header->num_signatures);
|
||||
auto num_sigs = oxenc::little_to_host(header->num_signatures);
|
||||
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 = {};
|
||||
checkpoint.height = little_to_native(header->height);
|
||||
checkpoint.height = oxenc::little_to_host(header->height);
|
||||
checkpoint.type = (num_sigs > 0) ? checkpoint_type::service_node : checkpoint_type::hardcoded;
|
||||
checkpoint.block_hash = header->block_hash;
|
||||
|
||||
|
@ -6210,15 +6209,15 @@ void BlockchainLMDB::clear_service_node_data()
|
|||
}
|
||||
|
||||
template <typename C>
|
||||
C native_to_little_container(const C& c) {
|
||||
C host_to_little_container(const C& c) {
|
||||
C result{c};
|
||||
for (auto& x : result) native_to_little_inplace(x);
|
||||
for (auto& x : result) oxenc::host_to_little_inplace(x);
|
||||
return result;
|
||||
}
|
||||
template <typename C>
|
||||
C little_to_native_container(const C& c) {
|
||||
C little_to_host_container(const C& c) {
|
||||
C result{c};
|
||||
for (auto& x : result) little_to_native_inplace(x);
|
||||
for (auto& x : result) oxenc::little_to_host_inplace(x);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -6226,25 +6225,25 @@ struct service_node_proof_serialized_old
|
|||
{
|
||||
service_node_proof_serialized_old() = default;
|
||||
service_node_proof_serialized_old(const service_nodes::proof_info &info)
|
||||
: timestamp{native_to_little(info.timestamp)},
|
||||
ip{native_to_little(info.proof->public_ip)},
|
||||
storage_https_port{native_to_little(info.proof->storage_https_port)},
|
||||
storage_omq_port{native_to_little(info.proof->storage_omq_port)},
|
||||
quorumnet_port{native_to_little(info.proof->qnet_port)},
|
||||
version{native_to_little_container(info.proof->version)},
|
||||
: timestamp{oxenc::host_to_little(info.timestamp)},
|
||||
ip{oxenc::host_to_little(info.proof->public_ip)},
|
||||
storage_https_port{oxenc::host_to_little(info.proof->storage_https_port)},
|
||||
storage_omq_port{oxenc::host_to_little(info.proof->storage_omq_port)},
|
||||
quorumnet_port{oxenc::host_to_little(info.proof->qnet_port)},
|
||||
version{host_to_little_container(info.proof->version)},
|
||||
pubkey_ed25519{info.proof->pubkey_ed25519}
|
||||
{}
|
||||
|
||||
void update(service_nodes::proof_info &info) const
|
||||
{
|
||||
info.timestamp = little_to_native(timestamp);
|
||||
info.timestamp = oxenc::little_to_host(timestamp);
|
||||
if (info.timestamp > info.effective_timestamp)
|
||||
info.effective_timestamp = info.timestamp;
|
||||
info.proof->public_ip = little_to_native(ip);
|
||||
info.proof->storage_https_port = little_to_native(storage_https_port);
|
||||
info.proof->storage_omq_port = little_to_native(storage_omq_port);
|
||||
info.proof->qnet_port = little_to_native(quorumnet_port);
|
||||
info.proof->version = little_to_native_container(version);
|
||||
info.proof->public_ip = oxenc::little_to_host(ip);
|
||||
info.proof->storage_https_port = oxenc::little_to_host(storage_https_port);
|
||||
info.proof->storage_omq_port = oxenc::little_to_host(storage_omq_port);
|
||||
info.proof->qnet_port = oxenc::little_to_host(quorumnet_port);
|
||||
info.proof->version = little_to_host_container(version);
|
||||
info.proof->storage_server_version = {0, 0, 0};
|
||||
info.proof->lokinet_version = {0, 0, 0};
|
||||
info.update_pubkey(pubkey_ed25519);
|
||||
|
@ -6271,8 +6270,8 @@ struct service_node_proof_serialized : service_node_proof_serialized_old {
|
|||
service_node_proof_serialized() = default;
|
||||
service_node_proof_serialized(const service_nodes::proof_info &info)
|
||||
: service_node_proof_serialized_old{info},
|
||||
storage_server_version{native_to_little_container(info.proof->storage_server_version)},
|
||||
lokinet_version{native_to_little_container(info.proof->lokinet_version)}
|
||||
storage_server_version{host_to_little_container(info.proof->storage_server_version)},
|
||||
lokinet_version{host_to_little_container(info.proof->lokinet_version)}
|
||||
{}
|
||||
std::array<uint16_t, 3> storage_server_version{};
|
||||
std::array<uint16_t, 3> lokinet_version{};
|
||||
|
@ -6281,8 +6280,8 @@ struct service_node_proof_serialized : service_node_proof_serialized_old {
|
|||
void update(service_nodes::proof_info& info) const {
|
||||
if (!info.proof) info.proof = std::unique_ptr<uptime_proof::Proof>(new uptime_proof::Proof());
|
||||
service_node_proof_serialized_old::update(info);
|
||||
info.proof->storage_server_version = little_to_native_container(storage_server_version);
|
||||
info.proof->lokinet_version = little_to_native_container(lokinet_version);
|
||||
info.proof->storage_server_version = little_to_host_container(storage_server_version);
|
||||
info.proof->lokinet_version = little_to_host_container(lokinet_version);
|
||||
}
|
||||
|
||||
operator service_nodes::proof_info() const
|
||||
|
|
|
@ -27,25 +27,17 @@
|
|||
|
||||
#include "db_sqlite.h"
|
||||
|
||||
#include <sodium.h>
|
||||
|
||||
#include <SQLiteCpp/SQLiteCpp.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <string>
|
||||
#include <sodium.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_core/blockchain.h"
|
||||
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include "cryptonote_basic/hardfork.h"
|
||||
|
||||
#undef OXEN_DEFAULT_LOG_CATEGORY
|
||||
|
@ -61,13 +53,7 @@ namespace cryptonote {
|
|||
create_schema();
|
||||
}
|
||||
|
||||
SQLite::Statement st {
|
||||
db,
|
||||
"SELECT height FROM batch_db_info"
|
||||
};
|
||||
while (st.executeStep()) {
|
||||
height = st.getColumn(0).getInt64();
|
||||
}
|
||||
height = prepared_get<int64_t>("SELECT height FROM batch_db_info");
|
||||
}
|
||||
|
||||
void BlockchainSQLite::create_schema() {
|
||||
|
@ -81,7 +67,10 @@ namespace cryptonote {
|
|||
CHECK(amount >= 0)
|
||||
);
|
||||
|
||||
CREATE TRIGGER batch_payments_delete_empty AFTER UPDATE ON batched_payments_accrued FOR EACH ROW WHEN NEW.amount = 0 BEGIN DELETE FROM batched_payments_accrued WHERE address = NEW.address; END;
|
||||
CREATE TRIGGER batch_payments_delete_empty AFTER UPDATE ON batched_payments_accrued
|
||||
FOR EACH ROW WHEN NEW.amount = 0 BEGIN
|
||||
DELETE FROM batched_payments_accrued WHERE address = NEW.address;
|
||||
END;
|
||||
|
||||
CREATE TABLE batched_payments_raw(
|
||||
address VARCHAR NOT NULL,
|
||||
|
@ -99,13 +88,26 @@ namespace cryptonote {
|
|||
|
||||
INSERT INTO batch_db_info(height) VALUES(0);
|
||||
|
||||
CREATE TRIGGER batch_payments_prune AFTER UPDATE ON batch_db_info FOR EACH ROW BEGIN DELETE FROM batched_payments_raw WHERE height_paid < (NEW.height - 10000); END;
|
||||
CREATE TRIGGER batch_payments_prune AFTER UPDATE ON batch_db_info
|
||||
FOR EACH ROW BEGIN
|
||||
DELETE FROM batched_payments_raw WHERE height_paid < (NEW.height - 10000);
|
||||
END;
|
||||
|
||||
CREATE VIEW batched_payments_paid AS SELECT * FROM batched_payments_raw;
|
||||
|
||||
CREATE TRIGGER make_payment INSTEAD OF INSERT ON batched_payments_paid FOR EACH ROW BEGIN UPDATE batched_payments_accrued SET amount = (amount - NEW.amount) WHERE address = NEW.address; SELECT RAISE(ABORT, 'Address not found') WHERE changes() = 0; INSERT INTO batched_payments_raw(address, amount, height_paid) VALUES(NEW.address, NEW.amount, NEW.height_paid); END;
|
||||
CREATE TRIGGER make_payment INSTEAD OF INSERT ON batched_payments_paid
|
||||
FOR EACH ROW BEGIN
|
||||
UPDATE batched_payments_accrued SET amount = (amount - NEW.amount) WHERE address = NEW.address;
|
||||
SELECT RAISE(ABORT, 'Address not found') WHERE changes() = 0;
|
||||
INSERT INTO batched_payments_raw(address, amount, height_paid) VALUES(NEW.address, NEW.amount, NEW.height_paid);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER rollback_payment INSTEAD OF DELETE ON batched_payments_paid FOR EACH ROW BEGIN DELETE FROM batched_payments_raw WHERE address = OLD.address AND height_paid = OLD.height_paid; INSERT INTO batched_payments_accrued(address, amount) VALUES(OLD.address, OLD.amount) ON CONFLICT(address) DO UPDATE SET amount = (amount + excluded.amount); END;
|
||||
CREATE TRIGGER rollback_payment INSTEAD OF DELETE ON batched_payments_paid
|
||||
FOR EACH ROW BEGIN
|
||||
DELETE FROM batched_payments_raw WHERE address = OLD.address AND height_paid = OLD.height_paid;
|
||||
INSERT INTO batched_payments_accrued(address, amount) VALUES(OLD.address, OLD.amount)
|
||||
ON CONFLICT(address) DO UPDATE SET amount = (amount + excluded.amount);
|
||||
END;
|
||||
)");
|
||||
|
||||
MDEBUG("Database setup complete");
|
||||
|
@ -132,11 +134,9 @@ namespace cryptonote {
|
|||
void BlockchainSQLite::update_height(uint64_t new_height) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with new height: " << new_height);
|
||||
height = new_height;
|
||||
SQLite::Statement update_height {
|
||||
db,
|
||||
"UPDATE batch_db_info SET height = ?"
|
||||
};
|
||||
db::exec_query(update_height, static_cast<int64_t>(height));
|
||||
prepared_exec(
|
||||
"UPDATE batch_db_info SET height = ?",
|
||||
static_cast<int64_t>(height));
|
||||
}
|
||||
|
||||
void BlockchainSQLite::increment_height() {
|
||||
|
@ -149,76 +149,73 @@ namespace cryptonote {
|
|||
update_height(height - 1);
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::add_sn_payments(std::vector<cryptonote::batch_sn_payment>& payments) {
|
||||
bool BlockchainSQLite::add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
SQLite::Statement insert_payment {
|
||||
db,
|
||||
"INSERT INTO batched_payments_accrued (address, amount) VALUES (?, ?) ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount"
|
||||
};
|
||||
auto insert_payment = prepared_st(
|
||||
"INSERT INTO batched_payments_accrued (address, amount) VALUES (?, ?)"
|
||||
" ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount");
|
||||
|
||||
for (auto& payment: payments) {
|
||||
std::string address_str = cryptonote::get_account_address_as_str(m_nettype, 0, payment.address_info.address);
|
||||
MTRACE("Adding record for SN reward contributor " << address_str << "to database with amount " << static_cast<int64_t>(payment.amount));
|
||||
auto amt = static_cast<int64_t>(payment.amount);
|
||||
MTRACE(fmt::format("Adding record for SN reward contributor {} to database with amount {}",
|
||||
address_str, amt));
|
||||
|
||||
db::exec_query(insert_payment, address_str, static_cast<int64_t>(payment.amount * 1000));
|
||||
insert_payment.reset();
|
||||
};
|
||||
db::exec_query(insert_payment, address_str, amt);
|
||||
insert_payment->reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::subtract_sn_payments(std::vector<cryptonote::batch_sn_payment>& payments) {
|
||||
bool BlockchainSQLite::subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
SQLite::Statement update_payment {
|
||||
db,
|
||||
"UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?"
|
||||
};
|
||||
auto update_payment = prepared_st(
|
||||
"UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?");
|
||||
|
||||
for (auto& payment: payments) {
|
||||
std::string address_str = cryptonote::get_account_address_as_str(m_nettype, 0, payment.address_info.address);
|
||||
auto result = db::exec_query(update_payment, static_cast<int64_t>(payment.amount * 1000), address_str);
|
||||
auto result = db::exec_query(update_payment, static_cast<int64_t>(payment.amount), address_str);
|
||||
if (!result) {
|
||||
MERROR("tried to subtract payment from an address that doesnt exist: " << address_str);
|
||||
MERROR("tried to subtract payment from an address that doesn't exist: " << address_str);
|
||||
return false;
|
||||
}
|
||||
update_payment.reset();
|
||||
};
|
||||
update_payment->reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> BlockchainSQLite::get_sn_payments(uint64_t block_height) {
|
||||
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_sn_payments(uint64_t block_height) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
|
||||
if (block_height == 0)
|
||||
return std::nullopt;
|
||||
// <= here because we might have crap in the db that we don't clear until we actually add the HF
|
||||
// block later on. (This is a pretty slim edge case that happened on devnet and is probably
|
||||
// virtually impossible on mainnet).
|
||||
if (m_nettype != cryptonote::network_type::FAKECHAIN && block_height <= cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching).first.value_or(0))
|
||||
return {};
|
||||
|
||||
const auto& conf = get_config(m_nettype);
|
||||
|
||||
SQLite::Statement select_payments {
|
||||
db,
|
||||
"SELECT address, amount FROM batched_payments_accrued WHERE amount > ? ORDER BY address ASC"
|
||||
};
|
||||
|
||||
select_payments.bind(1, static_cast<int64_t>(conf.MIN_BATCH_PAYMENT_AMOUNT * 1000));
|
||||
auto accrued_amounts = prepared_results<std::string, int64_t>(
|
||||
"SELECT address, amount FROM batched_payments_accrued WHERE amount >= ? ORDER BY address ASC",
|
||||
static_cast<int64_t>(conf.MIN_BATCH_PAYMENT_AMOUNT * BATCH_REWARD_FACTOR));
|
||||
|
||||
std::vector<cryptonote::batch_sn_payment> payments;
|
||||
|
||||
std::string address;
|
||||
uint64_t amount;
|
||||
while (select_payments.executeStep()) {
|
||||
address = select_payments.getColumn(0).getString();
|
||||
amount = static_cast<uint64_t>(select_payments.getColumn(1).getInt64() / 1000);
|
||||
for (auto [address, amount] : accrued_amounts) {
|
||||
if (cryptonote::is_valid_address(address, m_nettype)) {
|
||||
cryptonote::address_parse_info addr_info {};
|
||||
cryptonote::get_account_address_from_str(addr_info, m_nettype, address);
|
||||
uint64_t next_payout_height = addr_info.address.next_payout_height(block_height - 1, conf.BATCHING_INTERVAL);
|
||||
if (block_height == next_payout_height) {
|
||||
payments.emplace_back(address, amount, m_nettype);
|
||||
payments.emplace_back(
|
||||
std::move(address),
|
||||
amount / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR /* truncate to atomic OXEN */,
|
||||
m_nettype);
|
||||
}
|
||||
} else {
|
||||
MERROR("Invalid address returned from batching database: " << address);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,56 +263,116 @@ namespace cryptonote {
|
|||
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
|
||||
// Find out how much is due for the operator
|
||||
uint64_t operator_fee = 0;
|
||||
{
|
||||
// This calculates the operator fee using (operator_portion / max_operator_portion) * distribution_amount but using 128 bit integer math
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(sn_info.portions_for_operator, distribution_amount, &hi);
|
||||
div128_64(hi, lo, old::STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
if (resulthi > 0)
|
||||
throw std::logic_error("overflow from calculating sn operator fee");
|
||||
operator_fee = resultlo;
|
||||
}
|
||||
// Find out how much is due for the operator: fee_portions/PORTIONS * reward
|
||||
assert(sn_info.portions_for_operator <= old::STAKING_PORTIONS);
|
||||
uint64_t operator_fee = mul128_div64(sn_info.portions_for_operator, distribution_amount, old::STAKING_PORTIONS);
|
||||
|
||||
assert(operator_fee <= distribution_amount);
|
||||
|
||||
std::vector<cryptonote::batch_sn_payment> payments;
|
||||
// Pay the operator fee to the operator
|
||||
if (operator_fee > 0)
|
||||
payments.emplace_back(sn_info.operator_address, operator_fee, m_nettype);
|
||||
|
||||
// Pay the balance to all the contributors (including the operator again)
|
||||
uint64_t total_contributed_to_sn = std::accumulate(sn_info.contributors.begin(), sn_info.contributors.end(), uint64_t(0), [](auto
|
||||
const a, auto
|
||||
const b) {
|
||||
return a + b.amount;
|
||||
});
|
||||
uint64_t total_contributed_to_sn = std::accumulate(
|
||||
sn_info.contributors.begin(),
|
||||
sn_info.contributors.end(),
|
||||
uint64_t(0),
|
||||
[](auto&& a, auto&& b) { return a + b.amount; });
|
||||
|
||||
for (auto& contributor: sn_info.contributors) {
|
||||
// This calculates (contributor.amount / total_contributed_to_winner_sn) * (distribution_amount - operator_fee) but using 128 bit integer math
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(contributor.amount, distribution_amount - operator_fee, &hi);
|
||||
div128_64(hi, lo, total_contributed_to_sn, &resulthi, &resultlo);
|
||||
if (resulthi > 0)
|
||||
throw std::logic_error("overflow from calculating sn contributor reward");
|
||||
if (resultlo > 0)
|
||||
payments.emplace_back(contributor.address, resultlo, m_nettype);
|
||||
uint64_t c_reward = mul128_div64(contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn);
|
||||
if (c_reward > 0)
|
||||
payments.emplace_back(contributor.address, c_reward, m_nettype);
|
||||
}
|
||||
|
||||
return payments;
|
||||
}
|
||||
|
||||
// Calculates block rewards, then invokes either `add_sn_rewards` (if `add`) or
|
||||
// `subtract_sn_rewards` (if `!add`) to process them.
|
||||
bool BlockchainSQLite::reward_handler(
|
||||
const cryptonote::block& block,
|
||||
const service_nodes::service_node_list::state_t& service_nodes_state,
|
||||
bool add)
|
||||
{
|
||||
// The method we call do actually handle the change: either `add_sn_payments` if add is true,
|
||||
// `subtract_sn_payments` otherwise:
|
||||
bool (BlockchainSQLite::* add_or_subtract)(const std::vector<cryptonote::batch_sn_payment>&)
|
||||
= add ? &BlockchainSQLite::add_sn_rewards : &BlockchainSQLite::subtract_sn_rewards;
|
||||
|
||||
// From here on we calculate everything in milli-atomic OXEN (i.e. thousanths of an atomic
|
||||
// OXEN) so that our integer math has minimal loss from integer division.
|
||||
if (block.reward > std::numeric_limits<uint64_t>::max() / BATCH_REWARD_FACTOR)
|
||||
throw std::logic_error{"Reward distribution amount is too large"};
|
||||
|
||||
uint64_t block_reward = block.reward * BATCH_REWARD_FACTOR;
|
||||
uint64_t service_node_reward = cryptonote::service_node_reward_formula(0, block.major_version) * BATCH_REWARD_FACTOR;
|
||||
|
||||
// Step 1: Pay out the block producer their tx fees (note that, unlike the below, this applies
|
||||
// even if the SN isn't currently payable).
|
||||
if (block_reward < service_node_reward && m_nettype != cryptonote::network_type::FAKECHAIN)
|
||||
throw std::logic_error{"Invalid payment: block reward is too small"};
|
||||
|
||||
if (uint64_t tx_fees = block_reward - service_node_reward;
|
||||
tx_fees > 0
|
||||
&& block.service_node_winner_key // "service_node_winner_key" tracks the pulse winner; 0 if a mined block
|
||||
&& crypto_core_ed25519_is_valid_point(reinterpret_cast<const unsigned char *>(block.service_node_winner_key.data))
|
||||
) {
|
||||
|
||||
if (auto service_node_winner = service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
|
||||
service_node_winner != service_nodes_state.service_nodes_infos.end()) {
|
||||
auto tx_fee_payments = calculate_rewards(block.major_version, tx_fees, *service_node_winner->second);
|
||||
// Takes the block producer and adds its contributors to the batching database for the transaction fees
|
||||
if (!(this->*add_or_subtract)(tx_fee_payments))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto block_height = get_block_height(block);
|
||||
|
||||
// Step 2: Iterate over the whole service node list and pay each node 1/service_node_list fraction
|
||||
const auto payable_service_nodes = service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
|
||||
size_t total_service_nodes_payable = payable_service_nodes.size();
|
||||
for (const auto& [node_pubkey, node_info]: payable_service_nodes) {
|
||||
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
|
||||
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
|
||||
continue;
|
||||
auto snode_rewards = calculate_rewards(block.major_version, service_node_reward / total_service_nodes_payable, * payable_service_node -> second);
|
||||
// Takes the node and adds its contributors to the batching database
|
||||
if (!(this->*add_or_subtract)(snode_rewards))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Add Governance reward to the list
|
||||
if (m_nettype != cryptonote::network_type::FAKECHAIN) {
|
||||
std::vector<cryptonote::batch_sn_payment> governance_rewards;
|
||||
cryptonote::address_parse_info governance_wallet_address;
|
||||
cryptonote::get_account_address_from_str(governance_wallet_address, m_nettype,
|
||||
cryptonote::get_config(m_nettype).governance_wallet_address(block.major_version));
|
||||
uint64_t foundation_reward = cryptonote::governance_reward_formula(block.major_version);
|
||||
governance_rewards.emplace_back(governance_wallet_address.address, foundation_reward, m_nettype);
|
||||
if (!(this->*add_or_subtract)(governance_rewards))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::add_block(const cryptonote::block& block,
|
||||
const service_nodes::service_node_list::state_t& service_nodes_state) {
|
||||
auto block_height = get_block_height(block);
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " called on height: " << block_height);
|
||||
|
||||
auto hf_version = block.major_version;
|
||||
if (hf_version < hf::hf19) {
|
||||
if (hf_version < hf::hf19_reward_batching) {
|
||||
update_height(block_height);
|
||||
print_database();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19);
|
||||
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching);
|
||||
if (block_height == fork_height.first.value_or(0)) {
|
||||
MDEBUG("Batching of Service Node Rewards Begins");
|
||||
reset_database();
|
||||
|
@ -323,7 +380,7 @@ namespace cryptonote {
|
|||
}
|
||||
|
||||
if (block_height != height + 1) {
|
||||
MERROR("Block height out of sync with batching database. Block height: " << block_height << " batching db height: " << height);
|
||||
MERROR(fmt::format("Block height ({}) out of sync with batching database ({})", block_height, height));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -345,59 +402,20 @@ namespace cryptonote {
|
|||
};
|
||||
|
||||
// Goes through the miner transactions vouts checks they are right and marks them as paid in the database
|
||||
if (!validate_batch_payment(miner_tx_vouts, * calculated_rewards, block_height)) {
|
||||
if (!validate_batch_payment(miner_tx_vouts, calculated_rewards, block_height)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 1: Pay out the block producer their fees
|
||||
uint64_t service_node_reward = cryptonote::service_node_reward_formula(0, block.major_version);
|
||||
if (block.reward - service_node_reward > 0) {
|
||||
if (block.service_node_winner_key // "service_node_winner_key" tracks the pulse winner; 0 if a mined block
|
||||
&&
|
||||
crypto_core_ed25519_is_valid_point(reinterpret_cast <
|
||||
const unsigned char * > (block.service_node_winner_key.data))) {
|
||||
auto service_node_winner = service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
|
||||
if (service_node_winner != service_nodes_state.service_nodes_infos.end()) {
|
||||
std::vector<cryptonote::batch_sn_payment> block_producer_fee_payments = calculate_rewards(block.major_version, block.reward - service_node_reward, * service_node_winner -> second);
|
||||
// Takes the block producer and adds its contributors to the batching database for the transaction fees
|
||||
if (!add_sn_payments(block_producer_fee_payments))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!reward_handler(block, service_nodes_state, /*add=*/ true))
|
||||
return false;
|
||||
|
||||
// Step 2: Iterate over the whole service node list and pay each node 1/service_node_list fraction
|
||||
const auto payable_service_nodes = service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
|
||||
size_t total_service_nodes_payable = payable_service_nodes.size();
|
||||
for (const auto& [node_pubkey, node_info]: payable_service_nodes) {
|
||||
std::vector<cryptonote::batch_sn_payment> node_contributors;
|
||||
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
|
||||
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
|
||||
continue;
|
||||
std::vector<cryptonote::batch_sn_payment> node_rewards = calculate_rewards(block.major_version, service_node_reward / total_service_nodes_payable, * payable_service_node -> second);
|
||||
// Takes the node and adds its contributors to the batching database
|
||||
if (!add_sn_payments(node_rewards))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Add Governance reward to the list
|
||||
if (m_nettype != cryptonote::network_type::FAKECHAIN) {
|
||||
std::vector<cryptonote::batch_sn_payment> governance_rewards;
|
||||
cryptonote::address_parse_info governance_wallet_address;
|
||||
cryptonote::get_account_address_from_str(governance_wallet_address, m_nettype, cryptonote::get_config(m_nettype).governance_wallet_address(hf_version));
|
||||
uint64_t foundation_reward = cryptonote::governance_reward_formula(hf_version);
|
||||
governance_rewards.emplace_back(governance_wallet_address.address, foundation_reward, m_nettype);
|
||||
if (!add_sn_payments(governance_rewards))
|
||||
return false;
|
||||
}
|
||||
increment_height();
|
||||
|
||||
transaction.commit();
|
||||
} catch (std::exception& e) {
|
||||
MFATAL("Exception: " << e.what());
|
||||
MFATAL("Error adding reward payments: " << e.what());
|
||||
return false;
|
||||
}
|
||||
print_database();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -416,8 +434,8 @@ namespace cryptonote {
|
|||
}
|
||||
|
||||
const auto& conf = get_config(m_nettype);
|
||||
auto hf_version = hf{block.major_version};
|
||||
if (hf_version < hf::hf19) {
|
||||
auto hf_version = block.major_version;
|
||||
if (hf_version < hf::hf19_reward_batching) {
|
||||
decrement_height();
|
||||
return true;
|
||||
}
|
||||
|
@ -428,48 +446,8 @@ namespace cryptonote {
|
|||
SQLite::TransactionBehavior::IMMEDIATE
|
||||
};
|
||||
|
||||
// Step 1: Remove the block producers txn fees
|
||||
uint64_t service_node_reward = cryptonote::service_node_reward_formula(0, block.major_version);
|
||||
if (block.reward - service_node_reward > 0) // If true then we have tx fees to roll back
|
||||
{
|
||||
if (block.service_node_winner_key // "service_node_winner_key" tracks the pulse winner; 0 if a mined block
|
||||
&&
|
||||
crypto_core_ed25519_is_valid_point(reinterpret_cast <
|
||||
const unsigned char * > (block.service_node_winner_key.data))) {
|
||||
auto service_node_winner = service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
|
||||
if (service_node_winner != service_nodes_state.service_nodes_infos.end()) {
|
||||
std::vector<cryptonote::batch_sn_payment> block_producer_fee_payments = calculate_rewards(block.major_version, block.reward - service_node_reward, * service_node_winner -> second);
|
||||
// Takes the block producer and adds its contributors to the batching database for the transaction fees
|
||||
if (!subtract_sn_payments(block_producer_fee_payments))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Iterate over the whole service node list and subtract each node 1/service_node_list fraction
|
||||
const auto payable_service_nodes = service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
|
||||
size_t total_service_nodes_payable = payable_service_nodes.size();
|
||||
for (const auto& [node_pubkey, node_info]: payable_service_nodes) {
|
||||
std::vector<cryptonote::batch_sn_payment> node_contributors;
|
||||
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
|
||||
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
|
||||
continue;
|
||||
std::vector<cryptonote::batch_sn_payment> node_rewards = calculate_rewards(block.major_version, service_node_reward / total_service_nodes_payable, * payable_service_node -> second);
|
||||
// Takes the node and adds its contributors to the batching database
|
||||
|
||||
if (!subtract_sn_payments(node_rewards))
|
||||
return false;
|
||||
}
|
||||
// Step 3: Remove Governance reward
|
||||
if (m_nettype != cryptonote::network_type::FAKECHAIN) {
|
||||
std::vector<cryptonote::batch_sn_payment> governance_rewards;
|
||||
cryptonote::address_parse_info governance_wallet_address;
|
||||
cryptonote::get_account_address_from_str(governance_wallet_address, m_nettype, cryptonote::get_config(m_nettype).governance_wallet_address(hf_version));
|
||||
uint64_t foundation_reward = cryptonote::governance_reward_formula(hf_version);
|
||||
governance_rewards.emplace_back(governance_wallet_address.address, foundation_reward, m_nettype);
|
||||
if (!subtract_sn_payments(governance_rewards))
|
||||
return false;
|
||||
}
|
||||
if (!reward_handler(block, service_nodes_state, /*add=*/ false))
|
||||
return false;
|
||||
|
||||
// Add back to the database payments that had been made in this block
|
||||
delete_block_payments(block_height);
|
||||
|
@ -477,85 +455,94 @@ namespace cryptonote {
|
|||
decrement_height();
|
||||
transaction.commit();
|
||||
} catch (std::exception& e) {
|
||||
MFATAL("Exception: " << e.what());
|
||||
MFATAL("Error subtracting reward payments: " << e.what());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::validate_batch_payment(std::vector<std::tuple<crypto::public_key, uint64_t>> miner_tx_vouts, std::vector<cryptonote::batch_sn_payment> calculated_payments_from_batching_db, uint64_t block_height) {
|
||||
bool BlockchainSQLite::validate_batch_payment(
|
||||
const std::vector<std::tuple<crypto::public_key, uint64_t>>& miner_tx_vouts,
|
||||
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
|
||||
uint64_t block_height) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
size_t length_miner_tx_vouts = miner_tx_vouts.size();
|
||||
size_t length_calculated_payments_from_batching_db = calculated_payments_from_batching_db.size();
|
||||
|
||||
if (length_miner_tx_vouts != length_calculated_payments_from_batching_db) {
|
||||
MERROR("Length of batch paments does not match, block vouts: " << length_miner_tx_vouts << " batch size: " << length_calculated_payments_from_batching_db);
|
||||
if (miner_tx_vouts.size() != calculated_payments_from_batching_db.size()) {
|
||||
MERROR(fmt::format("Length of batch payments ({}) does not match block vouts ({})", calculated_payments_from_batching_db.size(), miner_tx_vouts.size()));
|
||||
return false;
|
||||
}
|
||||
|
||||
int8_t vout_index = 0;
|
||||
uint64_t total_oxen_payout_in_our_db = std::accumulate(calculated_payments_from_batching_db.begin(), calculated_payments_from_batching_db.end(), uint64_t(0), [](auto
|
||||
const a, auto
|
||||
const b) {
|
||||
return a + b.amount;
|
||||
});
|
||||
uint64_t total_oxen_payout_in_our_db = std::accumulate(
|
||||
calculated_payments_from_batching_db.begin(),
|
||||
calculated_payments_from_batching_db.end(),
|
||||
uint64_t(0),
|
||||
[](auto&& a, auto&& b) { return a + b.amount; });
|
||||
uint64_t total_oxen_payout_in_vouts = 0;
|
||||
std::vector<batch_sn_payment> finalised_payments;
|
||||
cryptonote::keypair
|
||||
const deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(block_height);
|
||||
for (auto& vout: miner_tx_vouts) {
|
||||
if (std::get<1>(vout) != calculated_payments_from_batching_db[vout_index].amount) {
|
||||
MERROR("Service node reward amount incorrect. Should be " << cryptonote::print_money(calculated_payments_from_batching_db[vout_index].amount) << ", is: " << cryptonote::print_money(std::get<1>(vout)));
|
||||
for (size_t vout_index = 0; vout_index < miner_tx_vouts.size(); vout_index++) {
|
||||
const auto& [pubkey, amt] = miner_tx_vouts[vout_index];
|
||||
uint64_t amount = amt * BATCH_REWARD_FACTOR;
|
||||
const auto& from_db = calculated_payments_from_batching_db[vout_index];
|
||||
if (amount != from_db.amount) {
|
||||
MERROR(fmt::format("Batched payout amount incorrect. Should be {}, not {}", from_db.amount, amount));
|
||||
return false;
|
||||
}
|
||||
crypto::public_key out_eph_public_key {};
|
||||
if (!cryptonote::get_deterministic_output_key(calculated_payments_from_batching_db[vout_index].address_info.address, deterministic_keypair, vout_index, out_eph_public_key)) {
|
||||
crypto::public_key out_eph_public_key{};
|
||||
if (!cryptonote::get_deterministic_output_key(from_db.address_info.address, deterministic_keypair, vout_index, out_eph_public_key)) {
|
||||
MERROR("Failed to generate output one-time public key");
|
||||
return false;
|
||||
}
|
||||
if (tools::view_guts(std::get<0>(vout)) != tools::view_guts(out_eph_public_key)) {
|
||||
if (tools::view_guts(pubkey) != tools::view_guts(out_eph_public_key)) {
|
||||
MERROR("Output ephemeral public key does not match");
|
||||
return false;
|
||||
}
|
||||
total_oxen_payout_in_vouts += std::get<1>(vout);
|
||||
finalised_payments.emplace_back(calculated_payments_from_batching_db[vout_index].address, std::get<1>(vout), m_nettype);
|
||||
vout_index++;
|
||||
total_oxen_payout_in_vouts += amount;
|
||||
finalised_payments.emplace_back(from_db.address, amount, m_nettype);
|
||||
}
|
||||
if (total_oxen_payout_in_vouts != total_oxen_payout_in_our_db) {
|
||||
MERROR("Total service node reward amount incorrect. Should be " << cryptonote::print_money(total_oxen_payout_in_our_db) << ", is: " << cryptonote::print_money(total_oxen_payout_in_vouts));
|
||||
MERROR(fmt::format("Total batched payout amount incorrect. Should be {}, not {}", total_oxen_payout_in_our_db, total_oxen_payout_in_vouts));
|
||||
return false;
|
||||
}
|
||||
|
||||
return save_payments(block_height, finalised_payments);
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::save_payments(uint64_t block_height, std::vector<batch_sn_payment>paid_amounts) {
|
||||
bool BlockchainSQLite::save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
|
||||
SQLite::Statement select_sum {
|
||||
db,
|
||||
"SELECT amount from batched_payments_accrued WHERE address = ?;"
|
||||
};
|
||||
auto select_sum = prepared_st(
|
||||
"SELECT amount from batched_payments_accrued WHERE address = ?");
|
||||
|
||||
SQLite::Statement update_paid {
|
||||
db,
|
||||
"INSERT INTO batched_payments_paid (address, amount, height_paid) VALUES (?,?,?);"
|
||||
};
|
||||
auto update_paid = prepared_st(
|
||||
"INSERT INTO batched_payments_paid (address, amount, height_paid) VALUES (?,?,?)");
|
||||
|
||||
for (const auto& payment: paid_amounts) {
|
||||
select_sum.bind(1, payment.address);
|
||||
while (select_sum.executeStep()) {
|
||||
uint64_t amount = static_cast<uint64_t>(select_sum.getColumn(0).getInt64());
|
||||
if (amount != payment.amount * 1000) {
|
||||
MERROR("Invalid amounts passed in to save payments for address: " << payment.address << " received " << payment.amount << " expected " << amount);
|
||||
if (auto maybe_amount = db::exec_and_maybe_get<int64_t>(select_sum, payment.address))
|
||||
{
|
||||
// Truncate the thousanths amount to an atomic OXEN:
|
||||
auto amount = static_cast<uint64_t>(*maybe_amount) / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR;
|
||||
|
||||
if (amount != payment.amount) {
|
||||
MERROR(fmt::format("Invalid amounts passed in to save payments for address {}: received {}, expected {} (truncated from {})",
|
||||
payment.address, payment.amount, amount, *maybe_amount));
|
||||
return false;
|
||||
}
|
||||
|
||||
db::exec_query(update_paid, payment.address, static_cast<int64_t>(amount), static_cast<int64_t>(block_height));
|
||||
update_paid.reset();
|
||||
update_paid->reset();
|
||||
}
|
||||
select_sum.reset();
|
||||
};
|
||||
else {
|
||||
// This shouldn't occur: we validate payout addresses much earlier in the block validation.
|
||||
MERROR(fmt::format("Internal error: Invalid amounts passed in to save payments for address {}: that address has no accrued rewards",
|
||||
payment.address));
|
||||
return false;
|
||||
}
|
||||
|
||||
select_sum->reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -563,113 +550,22 @@ namespace cryptonote {
|
|||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << block_height);
|
||||
|
||||
std::vector<cryptonote::batch_sn_payment> payments_at_height;
|
||||
SQLite::Statement st {
|
||||
db,
|
||||
"SELECT address, amount FROM batched_payments_paid WHERE height_paid = ? ORDER BY address"
|
||||
};
|
||||
st.bind(1, static_cast<int64_t>(block_height));
|
||||
while (st.executeStep()) {
|
||||
payments_at_height.emplace_back(st.getColumn(0).getString(), st.getColumn(1).getInt64(), m_nettype);
|
||||
}
|
||||
auto paid = prepared_results<std::string, int64_t>(
|
||||
"SELECT address, amount FROM batched_payments_paid WHERE height_paid = ? ORDER BY address",
|
||||
static_cast<int64_t>(block_height));
|
||||
|
||||
for (auto [addr, amt] : paid)
|
||||
payments_at_height.emplace_back(std::move(addr), static_cast<uint64_t>(amt), m_nettype);
|
||||
|
||||
return payments_at_height;
|
||||
}
|
||||
|
||||
bool BlockchainSQLite::delete_block_payments(uint64_t block_height) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << block_height);
|
||||
SQLite::Statement delete_payments {
|
||||
db,
|
||||
"DELETE FROM batched_payments_paid WHERE height_paid >= ?"
|
||||
};
|
||||
db::exec_query(delete_payments, static_cast<int64_t>(block_height));
|
||||
prepared_exec(
|
||||
"DELETE FROM batched_payments_paid WHERE height_paid >= ?",
|
||||
static_cast<int64_t>(block_height));
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlockchainSQLite::print_database()
|
||||
{
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << height);
|
||||
LOG_PRINT_L3("Print Database called with height: " << height);
|
||||
SQLite::Statement st{db, "SELECT address, amount FROM batched_payments_accrued ORDER BY address ASC"};
|
||||
while (st.executeStep()) {
|
||||
LOG_PRINT_L3(" Address: " << st.getColumn(0).getString() << " has amount: " << st.getColumn(1).getString() << " in the database");
|
||||
}
|
||||
}
|
||||
|
||||
fs::path check_if_copy_filename(std::string_view db_path) {
|
||||
return (db_path != ":memory:") ? fs::path(std::string(db_path) + "-copy") : fs::path(std::string(db_path));
|
||||
}
|
||||
|
||||
BlockchainSQLiteTest::BlockchainSQLiteTest(cryptonote::network_type nettype, fs::path db_path): BlockchainSQLite(nettype, db_path) {};
|
||||
|
||||
BlockchainSQLiteTest::BlockchainSQLiteTest(BlockchainSQLiteTest& other): BlockchainSQLiteTest(other.m_nettype, check_if_copy_filename(other.filename)) {
|
||||
std::vector<std::tuple<std::string, int64_t>> all_payments_accrued;
|
||||
SQLite::Statement st {
|
||||
other.db, "SELECT address, amount FROM batched_payments_accrued"
|
||||
};
|
||||
while (st.executeStep())
|
||||
all_payments_accrued.emplace_back(st.getColumn(0).getString(), st.getColumn(1).getInt64());
|
||||
std::vector<std::tuple<std::string, int64_t, int64_t>> all_payments_paid;
|
||||
SQLite::Statement st2 {
|
||||
other.db, "SELECT address, amount, height_paid FROM batched_payments_raw"
|
||||
};
|
||||
while (st2.executeStep())
|
||||
all_payments_paid.emplace_back(st2.getColumn(0).getString(), st2.getColumn(1).getInt64(), st2.getColumn(2).getInt64());
|
||||
|
||||
SQLite::Transaction transaction {
|
||||
db,
|
||||
SQLite::TransactionBehavior::IMMEDIATE
|
||||
};
|
||||
|
||||
SQLite::Statement insert_payment_paid {
|
||||
db,
|
||||
"INSERT INTO batched_payments_raw (address, amount, height_paid) VALUES (?, ?, ?)"
|
||||
};
|
||||
|
||||
for (auto& [address, amount, height_paid]: all_payments_paid) {
|
||||
db::exec_query(insert_payment_paid, address, amount, height_paid);
|
||||
insert_payment_paid.reset();
|
||||
};
|
||||
|
||||
SQLite::Statement insert_payment_accrued {
|
||||
db,
|
||||
"INSERT INTO batched_payments_accrued (address, amount) VALUES (?, ?)"
|
||||
};
|
||||
|
||||
for (auto& [address, amount]: all_payments_accrued) {
|
||||
db::exec_query(insert_payment_accrued, address, amount);
|
||||
insert_payment_accrued.reset();
|
||||
};
|
||||
|
||||
transaction.commit();
|
||||
|
||||
update_height(other.height);
|
||||
}
|
||||
|
||||
uint64_t BlockchainSQLiteTest::batching_count() {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
SQLite::Statement st {
|
||||
db,
|
||||
"SELECT count(*) FROM batched_payments_accrued"
|
||||
};
|
||||
uint64_t count = 0;
|
||||
while (st.executeStep()) {
|
||||
count = st.getColumn(0).getInt64();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::optional<uint64_t> BlockchainSQLiteTest::retrieve_amount_by_address(const std::string& address) {
|
||||
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
||||
SQLite::Statement st {
|
||||
db,
|
||||
"SELECT amount FROM batched_payments_accrued WHERE address = ?"
|
||||
};
|
||||
st.bind(1, address);
|
||||
std::optional<uint64_t> amount = std::nullopt;
|
||||
while (st.executeStep()) {
|
||||
assert(!amount);
|
||||
amount.emplace(st.getColumn(0).getInt64());
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
} // namespace cryptonote
|
||||
|
|
|
@ -60,11 +60,16 @@ public:
|
|||
|
||||
|
||||
// add_sn_payments/subtract_sn_payments -> passing an array of addresses and amounts. These will be added or subtracted to the database for each address specified. If the address does not exist it will be created.
|
||||
bool add_sn_payments(std::vector<cryptonote::batch_sn_payment>& payments);
|
||||
bool subtract_sn_payments(std::vector<cryptonote::batch_sn_payment>& payments);
|
||||
bool add_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
|
||||
bool subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments);
|
||||
|
||||
// get_payments -> passing a block height will return an array of payments that should be created in a coinbase transaction on that block given the current batching DB state.
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> get_sn_payments(uint64_t block_height);
|
||||
private:
|
||||
bool reward_handler(
|
||||
const cryptonote::block& block,
|
||||
const service_nodes::service_node_list::state_t& service_nodes_state,
|
||||
bool add);
|
||||
|
||||
public:
|
||||
|
||||
// get_accrued_earnings -> queries the database for the amount that has been accrued to `service_node_address` will return the atomic value in oxen that
|
||||
// the service node is owed.
|
||||
|
@ -73,7 +78,15 @@ public:
|
|||
// 2 vectors corresponding to the addresses and the atomic value in oxen that the service nodes are owed.
|
||||
std::pair<std::vector<std::string>, std::vector<uint64_t>> get_all_accrued_earnings();
|
||||
|
||||
// calculate_rewards -> takes the list of contributors from sn_info with their SN contribution amounts and will calculate how much of the block rewards should be the allocated to the contributors. The function will return a list suitable for passing to add_sn_payments
|
||||
// get_payments -> passing a block height will return an array of payments that should be created in a coinbase transaction on that block given the current batching DB state.
|
||||
std::vector<cryptonote::batch_sn_payment> get_sn_payments(uint64_t block_height);
|
||||
|
||||
// calculate_rewards -> takes the list of contributors from sn_info with their SN contribution
|
||||
// amounts and will calculate how much of the block rewards should be the allocated to the
|
||||
// contributors. The function will return a list suitable for passing to add_sn_payments
|
||||
//
|
||||
// Note that distribution_amount here is typically passed as milli-atomic OXEN for extra
|
||||
// precision.
|
||||
std::vector<cryptonote::batch_sn_payment> calculate_rewards(hf hf_version, uint64_t distribution_amount, service_nodes::service_node_info sn_info);
|
||||
|
||||
// add/pop_block -> takes a block that contains new block rewards to be batched and added to the database
|
||||
|
@ -86,17 +99,17 @@ public:
|
|||
bool pop_block(const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state);
|
||||
|
||||
// validate_batch_payment -> used to make sure that list of miner_tx_vouts is correct. Compares the miner_tx_vouts with a list previously extracted payments to make sure that the correct persons are being paid.
|
||||
bool validate_batch_payment(std::vector<std::tuple<crypto::public_key, uint64_t>> miner_tx_vouts, std::vector<cryptonote::batch_sn_payment> calculated_payments_from_batching_db, uint64_t block_height);
|
||||
bool validate_batch_payment(
|
||||
const std::vector<std::tuple<crypto::public_key, uint64_t>>& miner_tx_vouts,
|
||||
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
|
||||
uint64_t block_height);
|
||||
|
||||
// these keep track of payments made to SN operators after then payment has been made. Allows for popping blocks back and knowing who got paid in those blocks.
|
||||
// passing in a list of people to be marked as paid in the paid_amounts vector. Block height will be added to the batched_payments_paid database as height_paid.
|
||||
bool save_payments(uint64_t block_height, std::vector<batch_sn_payment> paid_amounts);
|
||||
bool save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts);
|
||||
std::vector<cryptonote::batch_sn_payment> get_block_payments(uint64_t block_height);
|
||||
bool delete_block_payments(uint64_t block_height);
|
||||
|
||||
// This is a debugging function. It prints the entire batching database to trace logs
|
||||
void print_database();
|
||||
|
||||
uint64_t height;
|
||||
|
||||
protected:
|
||||
|
@ -106,15 +119,4 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
class BlockchainSQLiteTest : public BlockchainSQLite
|
||||
{
|
||||
public:
|
||||
BlockchainSQLiteTest(cryptonote::network_type nettype, fs::path db_path);
|
||||
BlockchainSQLiteTest(BlockchainSQLiteTest &other);
|
||||
|
||||
// Helper functions, used in testing to assess the state of the database
|
||||
uint64_t batching_count();
|
||||
std::optional<uint64_t> retrieve_amount_by_address(const std::string& address);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -85,7 +85,7 @@ namespace tools
|
|||
// Copy an integer type, swapping to little-endian if needed
|
||||
template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
|
||||
void memcpy_one(char*& dest, T t) {
|
||||
boost::endian::native_to_little_inplace(t);
|
||||
oxenc::host_to_little_inplace(t);
|
||||
std::memcpy(dest, &t, sizeof(T));
|
||||
dest += sizeof(T);
|
||||
}
|
||||
|
|
|
@ -284,7 +284,7 @@ namespace crypto {
|
|||
ec_point Y;
|
||||
};
|
||||
|
||||
void generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) {
|
||||
signature generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec) {
|
||||
ge_p3 tmp3;
|
||||
ec_scalar k;
|
||||
s_comm buf;
|
||||
|
@ -300,6 +300,7 @@ namespace crypto {
|
|||
#endif
|
||||
buf.h = prefix_hash;
|
||||
buf.key = pub;
|
||||
signature sig;
|
||||
try_again:
|
||||
random_scalar(k);
|
||||
ge_scalarmult_base(&tmp3, &k);
|
||||
|
@ -311,6 +312,11 @@ namespace crypto {
|
|||
if (!sc_isnonzero((const unsigned char*)sig.r.data))
|
||||
goto try_again;
|
||||
memwipe(&k, sizeof(k));
|
||||
return sig;
|
||||
}
|
||||
|
||||
void generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) {
|
||||
sig = generate_signature(prefix_hash, pub, sec);
|
||||
}
|
||||
|
||||
static constexpr ec_point infinity = {{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
|
||||
|
|
|
@ -237,6 +237,7 @@ namespace crypto {
|
|||
* Ed25519: given signature (R, s), (unhashed) message M, pubkey A:
|
||||
* Check: sB == R + H(R||A||M)A
|
||||
*/
|
||||
signature generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec);
|
||||
void generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig);
|
||||
// See above.
|
||||
bool check_signature(const hash &prefix_hash, const public_key &pub, const signature &sig);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "cryptonote_basic.h"
|
||||
#include <oxenc/endian.h>
|
||||
|
||||
namespace cryptonote {
|
||||
|
||||
|
@ -143,7 +144,7 @@ uint64_t account_public_address::modulus(uint64_t interval) const
|
|||
{
|
||||
uint64_t address_as_integer = 0;
|
||||
std::memcpy(&address_as_integer, m_view_public_key.data, sizeof(address_as_integer));
|
||||
boost::endian::native_to_little_inplace(address_as_integer);
|
||||
oxenc::host_to_little_inplace(address_as_integer);
|
||||
return address_as_integer % interval;
|
||||
}
|
||||
|
||||
|
|
|
@ -455,7 +455,7 @@ namespace cryptonote
|
|||
throw std::invalid_argument{"too many txs in block"};
|
||||
if (b.major_version >= hf::hf16_pulse)
|
||||
field(ar, "signatures", b.signatures);
|
||||
if (b.major_version >= hf::hf19)
|
||||
if (b.major_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
field_varint(ar, "height", b.height);
|
||||
field(ar, "service_node_winner_key", b.service_node_winner_key);
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <oxenc/hex.h>
|
||||
#include <variant>
|
||||
#include "common/hex.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "epee/wipeable_string.h"
|
||||
#include "epee/string_tools.h"
|
||||
#include "common/i18n.h"
|
||||
|
@ -700,36 +701,22 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool add_service_node_register_to_tx_extra(
|
||||
std::vector<uint8_t>& tx_extra,
|
||||
const std::vector<cryptonote::account_public_address>& addresses,
|
||||
uint64_t portions_for_operator,
|
||||
const std::vector<uint64_t>& portions,
|
||||
uint64_t expiration_timestamp,
|
||||
const crypto::signature& service_node_signature)
|
||||
bool add_service_node_registration_to_tx_extra(std::vector<uint8_t>& tx_extra, const service_nodes::registration_details& reg)
|
||||
{
|
||||
if (addresses.size() != portions.size())
|
||||
tx_extra_field field;
|
||||
auto& txreg = field.emplace<tx_extra_service_node_register>();
|
||||
txreg.amounts.reserve(reg.reserved.size());
|
||||
txreg.public_spend_keys.reserve(reg.reserved.size());
|
||||
txreg.public_view_keys.reserve(reg.reserved.size());
|
||||
for (const auto& [addr, amount] : reg.reserved)
|
||||
{
|
||||
LOG_ERROR("Tried to serialize registration with more addresses than portions, this should never happen");
|
||||
return false;
|
||||
txreg.public_spend_keys.push_back(addr.m_spend_public_key);
|
||||
txreg.public_view_keys.push_back(addr.m_view_public_key);
|
||||
txreg.amounts.push_back(amount);
|
||||
}
|
||||
std::vector<crypto::public_key> public_view_keys(addresses.size());
|
||||
std::vector<crypto::public_key> public_spend_keys(addresses.size());
|
||||
for (size_t i = 0; i < addresses.size(); i++)
|
||||
{
|
||||
public_view_keys[i] = addresses[i].m_view_public_key;
|
||||
public_spend_keys[i] = addresses[i].m_spend_public_key;
|
||||
}
|
||||
// convert to variant
|
||||
tx_extra_field field =
|
||||
tx_extra_service_node_register{
|
||||
public_spend_keys,
|
||||
public_view_keys,
|
||||
portions_for_operator,
|
||||
portions,
|
||||
expiration_timestamp,
|
||||
service_node_signature
|
||||
};
|
||||
txreg.fee = reg.fee;
|
||||
txreg.hf_or_expiration = reg.hf;
|
||||
txreg.signature = reg.signature;
|
||||
|
||||
bool r = add_tx_extra_field_to_tx_extra(tx_extra, field);
|
||||
CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra registration tx");
|
||||
|
@ -873,7 +860,7 @@ namespace cryptonote
|
|||
{
|
||||
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, 0, "wrong miner tx in block: " << get_block_hash(b) << ", b.miner_tx.vin.size() != 1 (size is: " << b.miner_tx.vin.size() << ")");
|
||||
CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], txin_gen, coinbase_in, 0);
|
||||
if (b.major_version >= hf::hf19)
|
||||
if (b.major_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(coinbase_in.height == b.height, 0, "wrong miner tx in block: " << get_block_hash(b));
|
||||
}
|
||||
|
@ -1314,38 +1301,6 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool get_registration_hash(const std::vector<cryptonote::account_public_address>& addresses, uint64_t operator_portions, const std::vector<uint64_t>& portions, uint64_t expiration_timestamp, crypto::hash& hash)
|
||||
{
|
||||
if (addresses.size() != portions.size())
|
||||
{
|
||||
LOG_ERROR("get_registration_hash addresses.size() != portions.size()");
|
||||
return false;
|
||||
}
|
||||
uint64_t portions_left = old::STAKING_PORTIONS;
|
||||
for (uint64_t portion : portions)
|
||||
{
|
||||
if (portion > portions_left)
|
||||
{
|
||||
LOG_ERROR(tr("Your registration has more than ") << old::STAKING_PORTIONS << tr(" portions, this registration is invalid!"));
|
||||
return false;
|
||||
}
|
||||
portions_left -= portion;
|
||||
}
|
||||
size_t size = sizeof(uint64_t) + addresses.size() * (sizeof(cryptonote::account_public_address) + sizeof(uint64_t)) + sizeof(uint64_t);
|
||||
std::string buffer;
|
||||
buffer.reserve(size);
|
||||
buffer += tools::view_guts(operator_portions);
|
||||
for (size_t i = 0; i < addresses.size(); i++)
|
||||
{
|
||||
buffer += tools::view_guts(addresses[i]);
|
||||
buffer += tools::view_guts(portions[i]);
|
||||
}
|
||||
buffer += tools::view_guts(expiration_timestamp);
|
||||
assert(buffer.size() == size);
|
||||
crypto::cn_fast_hash(buffer.data(), buffer.size(), hash);
|
||||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------
|
||||
bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size)
|
||||
{
|
||||
if (t.is_hash_valid())
|
||||
|
|
|
@ -45,7 +45,10 @@ namespace epee
|
|||
class wipeable_string;
|
||||
}
|
||||
|
||||
namespace service_nodes { struct quorum_vote_t; }
|
||||
namespace service_nodes {
|
||||
struct quorum_vote_t;
|
||||
struct registration_details;
|
||||
}
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
|
@ -133,7 +136,7 @@ namespace cryptonote
|
|||
|
||||
bool get_service_node_pubkey_from_tx_extra(const std::vector<uint8_t>& tx_extra, crypto::public_key& pubkey);
|
||||
bool get_service_node_contributor_from_tx_extra(const std::vector<uint8_t>& tx_extra, cryptonote::account_public_address& address);
|
||||
bool add_service_node_register_to_tx_extra(std::vector<uint8_t>& tx_extra, const std::vector<cryptonote::account_public_address>& addresses, uint64_t portions_for_operator, const std::vector<uint64_t>& portions, uint64_t expiration_timestamp, const crypto::signature& signature);
|
||||
bool add_service_node_registration_to_tx_extra(std::vector<uint8_t>& tx_extra, const service_nodes::registration_details& registration);
|
||||
|
||||
bool get_tx_secret_key_from_tx_extra(const std::vector<uint8_t>& tx_extra, crypto::secret_key& key);
|
||||
void add_tx_secret_key_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::secret_key& key);
|
||||
|
@ -179,8 +182,6 @@ namespace cryptonote
|
|||
crypto::hash get_blob_hash(const std::string_view blob);
|
||||
std::string short_hash_str(const crypto::hash& h);
|
||||
|
||||
bool get_registration_hash(const std::vector<cryptonote::account_public_address>& addresses, uint64_t operator_portions, const std::vector<uint64_t>& portions, uint64_t expiration_timestamp, crypto::hash& hash);
|
||||
|
||||
crypto::hash get_transaction_hash(const transaction& t);
|
||||
bool get_transaction_hash(const transaction& t, crypto::hash& res);
|
||||
bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size);
|
||||
|
|
|
@ -65,21 +65,23 @@ static constexpr std::array testnet_hard_forks =
|
|||
hard_fork{hf::hf17, 0, 447275, 1608276840 }, // 2020-12-18 05:34 UTC
|
||||
hard_fork{hf::hf18, 0, 501750, 1616631051 }, // 2021-03-25 12:10 UTC
|
||||
hard_fork{hf::hf18, 1, 578637, 1624040400 }, // 2021-06-18 18:20 UTC
|
||||
hard_fork{hf::hf19, 0, 732355, 1650402545 },
|
||||
hard_fork{hf::hf19, 1, 751553, 1652152424 },
|
||||
hard_fork{hf::hf19_reward_batching, 0, 732355, 1650402545 },
|
||||
hard_fork{hf::hf19_reward_batching, 1, 751553, 1652152424 },
|
||||
};
|
||||
|
||||
static constexpr std::array devnet_hard_forks =
|
||||
{
|
||||
hard_fork{ hf::hf7, 0, 0, 1599848400 },
|
||||
hard_fork{ hf::hf11_infinite_staking, 0, 2, 1599848400 },
|
||||
hard_fork{ hf::hf12_checkpointing, 0, 3, 1599848400 },
|
||||
hard_fork{ hf::hf13_enforce_checkpoints, 0, 4, 1599848400 },
|
||||
hard_fork{ hf::hf15_ons, 0, 5, 1599848400 },
|
||||
hard_fork{ hf::hf16_pulse, 0, 100, 1599848400 },
|
||||
hard_fork{ hf::hf17, 0, 151, 1599848400 },
|
||||
hard_fork{ hf::hf18, 0, 152, 1599848400 },
|
||||
hard_fork{ hf::hf19, 0, 153, 1599848400 },
|
||||
hard_fork{hf::hf7, 0, 0, 1653500577},
|
||||
hard_fork{hf::hf11_infinite_staking, 0, 2, 1653500577},
|
||||
hard_fork{hf::hf12_checkpointing, 0, 3, 1653500577},
|
||||
hard_fork{hf::hf13_enforce_checkpoints, 0, 4, 1653500577},
|
||||
hard_fork{hf::hf14_blink, 0, 5, 1653500577},
|
||||
hard_fork{hf::hf15_ons, 0, 6, 1653500577},
|
||||
hard_fork{hf::hf16_pulse, 0, 100, 1653500577},
|
||||
hard_fork{hf::hf17, 0, 151, 1653500577},
|
||||
hard_fork{hf::hf18, 0, 152, 1653500577},
|
||||
hard_fork{hf::hf19_reward_batching, 0, 153, 1653500577},
|
||||
hard_fork{hf::hf19_reward_batching, 1, 154, 1653500577},
|
||||
};
|
||||
|
||||
|
||||
|
@ -166,7 +168,7 @@ get_ideal_block_version(network_type nettype, uint64_t height)
|
|||
result.first = it->version;
|
||||
result.second = it->snode_revision;
|
||||
}
|
||||
if (result.first < hf::hf19)
|
||||
if (result.first < hf::hf19_reward_batching)
|
||||
result.second = static_cast<uint8_t>(it->version);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -302,25 +302,46 @@ namespace cryptonote
|
|||
};
|
||||
|
||||
|
||||
/// Service node registration details.
|
||||
///
|
||||
/// A registration contains any reserved amounts + addresses starting with the operator, and must
|
||||
/// always have at least one (the operator).
|
||||
///
|
||||
/// Before HF19 this is limited to 4 spots per registrations, and amounts/fees are specified in
|
||||
/// portions (i.e. the numerator of a fraction with denominator 2^64 - 4).
|
||||
///
|
||||
/// Starting in HF19 we allow 10 spots per registration, reserved amounts are specified in atomic
|
||||
/// OXEN (i.e. 1000000000 = 1 OXEN), and the operator fee is specified in a per-million value
|
||||
/// (i.e. 1000000 = 100%).
|
||||
///
|
||||
/// The `hf_or_expiration` field, before HF19, is a timestamp at which the registration expires;
|
||||
/// starting in HF19, it is instead a hardfork version of the registration which must agree with
|
||||
/// the current hardfork version (e.g. if set to 23 then the registration is only accepted on
|
||||
/// HF23).
|
||||
///
|
||||
/// For crossing-the-hardfork compatibility, we allow pre-HF19 registrations (i.e. using a
|
||||
/// timestamp + portions + at most 4 reserved spots) during HF19 (but not in HF20+).
|
||||
///
|
||||
struct tx_extra_service_node_register
|
||||
{
|
||||
std::vector<crypto::public_key> m_public_spend_keys;
|
||||
std::vector<crypto::public_key> m_public_view_keys;
|
||||
uint64_t m_portions_for_operator;
|
||||
std::vector<uint64_t> m_portions; // portions *or* amounts as of HF19
|
||||
uint64_t m_expiration_timestamp;
|
||||
crypto::signature m_service_node_signature;
|
||||
std::vector<crypto::public_key> public_spend_keys; // public spend key half of the reserved wallets (at least one)
|
||||
std::vector<crypto::public_key> public_view_keys; // public view key half of the reserved wallets (at least one)
|
||||
uint64_t fee; // portions before HF19, millionths (1000000 = 100%) in HF19+.
|
||||
std::vector<uint64_t> amounts; // portions before HF19, atomic amounts after.
|
||||
uint64_t hf_or_expiration; /// registration expiration unix timestamp before HF18; the hardfork version of the registration starting in HF19
|
||||
crypto::signature signature; /// Signature of ( fee || spend[0] || view[0] || amount[0] || ... || spend[n] || view[n] || amount[n] || hf_expiration ), where integer values are little-endian encoded 8-byte strings
|
||||
|
||||
BEGIN_SERIALIZE()
|
||||
FIELD(m_public_spend_keys)
|
||||
FIELD(m_public_view_keys)
|
||||
FIELD(m_portions_for_operator)
|
||||
FIELD(m_portions)
|
||||
FIELD(m_expiration_timestamp)
|
||||
FIELD(m_service_node_signature)
|
||||
FIELD(public_spend_keys)
|
||||
FIELD(public_view_keys)
|
||||
FIELD(fee)
|
||||
FIELD(amounts)
|
||||
FIELD(hf_or_expiration)
|
||||
FIELD(signature)
|
||||
END_SERIALIZE()
|
||||
};
|
||||
|
||||
/// Service node contributor address.
|
||||
struct tx_extra_service_node_contributor
|
||||
{
|
||||
crypto::public_key m_spend_public_key;
|
||||
|
|
|
@ -111,6 +111,16 @@ namespace hashkey {
|
|||
// service node.
|
||||
using MAXIMUM_ACCEPTABLE_STAKE = std::ratio<101, 100>;
|
||||
|
||||
// In HF19+ registrations the fee amount is a relative value out of this (for older registrations
|
||||
// the fee is a portion, i.e. value out of old::STAKING_PORTIONS). For example a registration fee
|
||||
// value of 1000 corresponds to 1000/10000 = 10%. This also implicitly defines the maximum
|
||||
// precision of HF19+ registrations (i.e. to a percentage with two decimal places of precision).
|
||||
inline constexpr uint64_t STAKING_FEE_BASIS = 10'000;
|
||||
|
||||
// We calculate and store batch rewards in thousanths of atomic OXEN, to reduce the size of errors
|
||||
// from integer division of rewards.
|
||||
constexpr uint64_t BATCH_REWARD_FACTOR = 1000;
|
||||
|
||||
|
||||
// see src/cryptonote_protocol/levin_notify.cpp
|
||||
inline constexpr auto NOISE_MIN_EPOCH = 5min;
|
||||
|
@ -190,7 +200,7 @@ enum class hf : uint8_t
|
|||
hf16_pulse,
|
||||
hf17,
|
||||
hf18,
|
||||
hf19,
|
||||
hf19_reward_batching,
|
||||
|
||||
_next,
|
||||
none = 0
|
||||
|
@ -203,6 +213,10 @@ constexpr auto hf_prev(hf x) {
|
|||
return static_cast<hf>(static_cast<uint8_t>(x) - 1);
|
||||
}
|
||||
|
||||
// This is here to make sure the numeric value of the top hf enum value is correct (i.e.
|
||||
// hf19_reward_batching == 19 numerically); bump this when adding a new hf.
|
||||
static_assert(static_cast<uint8_t>(hf_max) == 19);
|
||||
|
||||
// Constants for which hardfork activates various features:
|
||||
namespace feature {
|
||||
constexpr auto PER_BYTE_FEE = hf::hf10_bulletproofs;
|
||||
|
@ -380,7 +394,7 @@ namespace config
|
|||
inline constexpr uint16_t ZMQ_RPC_DEFAULT_PORT = 38858;
|
||||
inline constexpr uint16_t QNET_DEFAULT_PORT = 38859;
|
||||
inline constexpr boost::uuids::uuid const NETWORK_ID = { {
|
||||
0xa9, 0xf7, 0x5c, 0x7d, 0x55, 0x17, 0xcb, 0x6b, 0x5b, 0xf4, 0x63, 0x79, 0x7a, 0x57, 0xab, 0xd3
|
||||
0xa9, 0xf7, 0x5c, 0x7d, 0x55, 0x17, 0xcb, 0x6b, 0x5b, 0xf4, 0x63, 0x79, 0x7a, 0x57, 0xab, 0xd4
|
||||
} };
|
||||
inline constexpr std::string_view GENESIS_TX = "04011e1e01ff00018080c9db97f4fb2702fa27e905f604faa4eb084ee675faca77b0cfea9adec1526da33cae5e286f31624201dae05bf3fa1662b7fd373c92426763d921cf3745e10ee43edb510f690c656f247200000000000000000000000000000000000000000000000000000000000000000000"sv;
|
||||
inline constexpr uint32_t GENESIS_NONCE = 12345;
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <sodium.h>
|
||||
|
||||
#include "common/rules.h"
|
||||
|
@ -309,7 +309,7 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
|
|||
uint64_t sqlite_height = 0;
|
||||
if (m_sqlite_db)
|
||||
{
|
||||
sqlite_height = std::max(hard_fork_begins(m_nettype, hf::hf19).value_or(0) - 1, m_sqlite_db->height + 1);
|
||||
sqlite_height = std::max(hard_fork_begins(m_nettype, hf::hf19_reward_batching).value_or(0) - 1, m_sqlite_db->height + 1);
|
||||
start_height_options.push_back(sqlite_height);
|
||||
} else {
|
||||
if (m_nettype != network_type::FAKECHAIN)
|
||||
|
@ -1320,7 +1320,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
|
|||
//validate reward
|
||||
uint64_t const money_in_use = get_outs_money_amount(b.miner_tx);
|
||||
if (b.miner_tx.vout.size() == 0) {
|
||||
if (b.major_version < hf::hf19) {
|
||||
if (b.major_version < hf::hf19_reward_batching) {
|
||||
MERROR_VER("miner tx has no outputs");
|
||||
return false;
|
||||
}
|
||||
|
@ -1354,7 +1354,14 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
|
|||
if (!get_oxen_block_reward(median_weight, cumulative_block_weight, already_generated_coins, version, reward_parts, block_reward_context))
|
||||
return false;
|
||||
|
||||
auto batched_sn_payments = m_sqlite_db->get_sn_payments(height);
|
||||
std::vector<cryptonote::batch_sn_payment> batched_sn_payments;
|
||||
if (m_sqlite_db)
|
||||
{
|
||||
batched_sn_payments = m_sqlite_db->get_sn_payments(height);
|
||||
} else {
|
||||
if (m_nettype != network_type::FAKECHAIN)
|
||||
throw std::logic_error("Blockchain missing SQLite Database");
|
||||
}
|
||||
cryptonote::block bl = b;
|
||||
for (ValidateMinerTxHook* hook : m_validate_miner_tx_hooks)
|
||||
{
|
||||
|
@ -1362,7 +1369,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
|
|||
return false;
|
||||
}
|
||||
|
||||
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < hf::hf19)
|
||||
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < hf::hf19_reward_batching)
|
||||
{
|
||||
if (version >= hf::hf10_bulletproofs && reward_parts.governance_paid == 0)
|
||||
{
|
||||
|
@ -1393,8 +1400,8 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
|
|||
// TODO(oxen): eliminate all floating point math in reward calculations.
|
||||
uint64_t max_base_reward = reward_parts.governance_paid + 1;
|
||||
|
||||
if (version >= hf::hf19 && batched_sn_payments.has_value()) {
|
||||
max_base_reward += std::accumulate(batched_sn_payments->begin(), batched_sn_payments->end(), uint64_t{0}, [&](auto a, auto b){return a + b.amount;});
|
||||
if (version >= hf::hf19_reward_batching) {
|
||||
max_base_reward += std::accumulate(batched_sn_payments.begin(), batched_sn_payments.end(), uint64_t{0}, [&](auto a, auto b){return a + b.amount;});
|
||||
} else {
|
||||
max_base_reward += reward_parts.base_miner + reward_parts.service_node_total;
|
||||
}
|
||||
|
@ -1408,7 +1415,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
|
|||
return false;
|
||||
}
|
||||
|
||||
if (version < hf::hf19) {
|
||||
if (version < hf::hf19_reward_batching) {
|
||||
CHECK_AND_ASSERT_MES(money_in_use >= reward_parts.miner_fee, false, "base reward calculation bug");
|
||||
base_reward = money_in_use - reward_parts.miner_fee;
|
||||
}
|
||||
|
@ -1642,8 +1649,8 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
|
|||
}
|
||||
|
||||
// This will check the batching database for who is due to be paid out in this block
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds = std::nullopt;
|
||||
if (hf_version >= hf::hf19)
|
||||
std::vector<cryptonote::batch_sn_payment> sn_rwds;
|
||||
if (hf_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
sn_rwds = m_sqlite_db->get_sn_payments(height); //Rewards to pay out
|
||||
}
|
||||
|
@ -4161,7 +4168,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block
|
|||
// this is a cheap test
|
||||
// HF19 TODO: after hardfork 19 occurs we can remove the second line of this test:
|
||||
if (auto v = get_network_version(blk_height); blk.major_version != v ||
|
||||
(v < hf::hf19 && blk.minor_version < static_cast<uint8_t>(v)))
|
||||
(v < hf::hf19_reward_batching && blk.minor_version < static_cast<uint8_t>(v)))
|
||||
{
|
||||
LOG_PRINT_L1("Block with id: " << blk_hash << ", has invalid version " << static_cast<int>(blk.major_version) << "." << +blk.minor_version <<
|
||||
"; current: " << static_cast<int>(v) << "." << static_cast<int>(v) << " for height " << blk_height);
|
||||
|
@ -4196,7 +4203,7 @@ bool Blockchain::basic_block_checks(cryptonote::block const &blk, bool alt_block
|
|||
|
||||
// HF19 TODO: after hardfork 19 occurs we can remove the second line of this test:
|
||||
if (blk.major_version != required_major_version ||
|
||||
(blk.major_version < hf::hf19 && blk.minor_version < static_cast<uint8_t>(required_major_version)))
|
||||
(blk.major_version < hf::hf19_reward_batching && blk.minor_version < static_cast<uint8_t>(required_major_version)))
|
||||
{
|
||||
MGINFO_RED("Block with id: " << blk_hash << ", has invalid version " << static_cast<int>(blk.major_version) << "." << +blk.minor_version <<
|
||||
"; current: " << static_cast<int>(required_major_version) << "." << static_cast<int>(required_major_version) << " for height " << blk_height);
|
||||
|
@ -5051,7 +5058,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar
|
|||
}
|
||||
|
||||
// Constant reward every block at HF19 and batched through service node batching
|
||||
if (hard_fork_version >= hf::hf19)
|
||||
if (hard_fork_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
reward = cryptonote::governance_reward_formula(hard_fork_version);
|
||||
return true;
|
||||
|
@ -5642,9 +5649,7 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get
|
|||
|
||||
if (checkpoints.size() > 4)
|
||||
{
|
||||
uint32_t nblocks;
|
||||
std::memcpy(&nblocks, checkpoints.data(), 4);
|
||||
boost::endian::little_to_native_inplace(nblocks);
|
||||
auto nblocks = oxenc::load_little_to_host<uint32_t>(checkpoints.data());
|
||||
if (nblocks > (std::numeric_limits<uint32_t>::max() - 4) / sizeof(hash))
|
||||
{
|
||||
MERROR("Block hash data is too large");
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include "epee/string_tools.h"
|
||||
|
||||
|
@ -2290,7 +2289,8 @@ namespace cryptonote
|
|||
|
||||
m_service_node_list.for_each_service_node_info_and_proof(sn_pks.begin(), sn_pks.end(), [&](auto& pk, auto& sni, auto& proof) {
|
||||
if (pk != m_service_keys.pub && proof.proof->public_ip == m_sn_public_ip &&
|
||||
(proof.proof->qnet_port == m_quorumnet_port || proof.proof->storage_https_port == storage_https_port() || proof.proof->storage_omq_port == storage_omq_port()))
|
||||
(proof.proof->qnet_port == m_quorumnet_port || (
|
||||
m_nettype != network_type::DEVNET && (proof.proof->storage_https_port == storage_https_port() || proof.proof->storage_omq_port == storage_omq_port()))))
|
||||
MGINFO_RED(
|
||||
"Another service node (" << pk << ") is broadcasting the same public IP and ports as this service node (" <<
|
||||
epee::string_tools::get_ip_string_from_int32(m_sn_public_ip) << ":" << proof.proof->qnet_port << "[qnet], :" <<
|
||||
|
|
|
@ -975,7 +975,7 @@ namespace cryptonote
|
|||
|
||||
/// Time point at which the storage server and lokinet last pinged us
|
||||
std::atomic<time_t> m_last_storage_server_ping, m_last_lokinet_ping;
|
||||
std::atomic<uint16_t> m_storage_https_port, m_storage_omq_port;
|
||||
std::atomic<uint16_t> m_storage_https_port{0}, m_storage_omq_port{0};
|
||||
|
||||
uint32_t sn_public_ip() const { return m_sn_public_ip; }
|
||||
uint16_t storage_https_port() const { return m_storage_https_port; }
|
||||
|
|
|
@ -152,7 +152,7 @@ namespace cryptonote
|
|||
if (height == 0)
|
||||
return false;
|
||||
|
||||
if (hard_fork_version <= hf::hf9_service_nodes || hard_fork_version >= hf::hf19)
|
||||
if (hard_fork_version <= hf::hf9_service_nodes || hard_fork_version >= hf::hf19_reward_batching)
|
||||
return true;
|
||||
|
||||
|
||||
|
@ -208,10 +208,7 @@ namespace cryptonote
|
|||
|
||||
uint64_t get_portion_of_reward(uint64_t portions, uint64_t total_service_node_reward)
|
||||
{
|
||||
uint64_t hi, lo, rewardhi, rewardlo;
|
||||
lo = mul128(total_service_node_reward, portions, &hi);
|
||||
div128_64(hi, lo, old::STAKING_PORTIONS, &rewardhi, &rewardlo);
|
||||
return rewardlo;
|
||||
return mul128_div64(total_service_node_reward, portions, old::STAKING_PORTIONS);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> distribute_reward_by_portions(const std::vector<service_nodes::payout_entry>& payout, uint64_t total_reward, bool distribute_remainder)
|
||||
|
@ -252,7 +249,7 @@ namespace cryptonote
|
|||
uint64_t fee,
|
||||
transaction& tx,
|
||||
const oxen_miner_tx_context &miner_tx_context,
|
||||
const std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds,
|
||||
const std::vector<cryptonote::batch_sn_payment>& sn_rwds,
|
||||
const std::string& extra_nonce,
|
||||
hf hard_fork_version)
|
||||
{
|
||||
|
@ -299,10 +296,13 @@ namespace cryptonote
|
|||
//
|
||||
// Each block accrues a small reward to each service node this amount
|
||||
// is essentially 16.5 (Coinbase reward for Service Nodes) divided by
|
||||
// the size of the service node list.
|
||||
// the size of the service node list. (Internally, these rewards are
|
||||
// calculated with 3 extra digits of precision to reduce integer
|
||||
// truncation errors).
|
||||
//
|
||||
// The service node list is adjusted to only accrue for nodes
|
||||
// that have been online for greater than 1 hour.
|
||||
// that have been active (i.e. without decommission or ip penalty)
|
||||
// for greater than 1 day.
|
||||
//
|
||||
// By default, when Pulse round is 0, the Block Producer is the Block
|
||||
// Leader. Transaction fees are given to the Block Leader.
|
||||
|
@ -328,8 +328,8 @@ namespace cryptonote
|
|||
// (multiple non-participation marks over the monitoring period will induce
|
||||
// a decommission) by members of the quorum.
|
||||
|
||||
std::vector<reward_payout> rewards = {};
|
||||
std::vector<batch_sn_payment> batched_rewards = {};
|
||||
std::vector<reward_payout> rewards;
|
||||
std::vector<batch_sn_payment> batched_rewards;
|
||||
const network_type nettype = miner_tx_context.nettype;
|
||||
|
||||
if (hard_fork_version >= hf::hf9_service_nodes)
|
||||
|
@ -353,27 +353,28 @@ namespace cryptonote
|
|||
else if (reward_parts.miner_fee)
|
||||
{
|
||||
// Alternative Block Producer (receives just miner fee, if there is one)
|
||||
service_nodes::payout const &producer = miner_tx_context.pulse_block_producer;
|
||||
std::vector<uint64_t> split_rewards = distribute_reward_by_portions(producer.payouts, reward_parts.miner_fee, true /*distribute_remainder*/);
|
||||
const auto& p_payouts = miner_tx_context.pulse_block_producer.payouts;
|
||||
std::vector<uint64_t> split_rewards = distribute_reward_by_portions(p_payouts, reward_parts.miner_fee, true /*distribute_remainder*/);
|
||||
|
||||
for (size_t i = 0; i < producer.payouts.size(); i++)
|
||||
if (hard_fork_version < hf::hf19)
|
||||
{
|
||||
rewards.push_back({reward_type::snode, producer.payouts[i].address, split_rewards[i]});
|
||||
} else {
|
||||
batched_rewards.emplace_back(producer.payouts[i].address, split_rewards[i], nettype);
|
||||
}
|
||||
if (hard_fork_version >= hf::hf19_reward_batching) {
|
||||
for (size_t i = 0; i < p_payouts.size(); i++)
|
||||
batched_rewards.emplace_back(p_payouts[i].address, split_rewards[i], nettype);
|
||||
} else {
|
||||
for (size_t i = 0; i < p_payouts.size(); i++)
|
||||
rewards.push_back({reward_type::snode, p_payouts[i].address, split_rewards[i]});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> split_rewards = distribute_reward_by_portions(leader.payouts, leader_reward, true /*distribute_remainder*/);
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
if (hard_fork_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
if (hard_fork_version < hf::hf19) {
|
||||
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
|
||||
} else {
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
batched_rewards.emplace_back(leader.payouts[i].address, split_rewards[i], nettype);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -384,25 +385,24 @@ namespace cryptonote
|
|||
|
||||
if (uint64_t miner_amount = reward_parts.base_miner + reward_parts.miner_fee; miner_amount)
|
||||
{
|
||||
if (hard_fork_version < hf::hf19) {
|
||||
rewards.push_back({reward_type::miner, miner_tx_context.miner_block_producer, miner_amount});
|
||||
} else {
|
||||
if (hard_fork_version >= hf::hf19_reward_batching) {
|
||||
batched_rewards.emplace_back(miner_tx_context.miner_block_producer, miner_amount, nettype);
|
||||
} else {
|
||||
rewards.push_back({reward_type::miner, miner_tx_context.miner_block_producer, miner_amount});
|
||||
}
|
||||
}
|
||||
|
||||
if (hard_fork_version >= hf::hf9_service_nodes) {
|
||||
std::vector<uint64_t> split_rewards =
|
||||
auto split_rewards =
|
||||
distribute_reward_by_portions(leader.payouts,
|
||||
reward_parts.service_node_total,
|
||||
hard_fork_version >= hf::hf16_pulse /*distribute_remainder*/);
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
{
|
||||
if (hard_fork_version < hf::hf19) {
|
||||
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
|
||||
} else {
|
||||
if (hard_fork_version >= hf::hf19_reward_batching) {
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
batched_rewards.emplace_back(leader.payouts[i].address, split_rewards[i], nettype);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < leader.payouts.size(); i++)
|
||||
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -414,29 +414,30 @@ namespace cryptonote
|
|||
{
|
||||
CHECK_AND_ASSERT_MES(hard_fork_version >= hf::hf10_bulletproofs, std::make_pair(false, block_rewards), "Governance reward can NOT be 0 before hardfork 10, hard_fork_version: " << static_cast<int>(hard_fork_version));
|
||||
}
|
||||
else
|
||||
// Governance reward paid out through SN rewards batching from HF19
|
||||
else if (hard_fork_version < hf::hf19_reward_batching)
|
||||
{
|
||||
cryptonote::address_parse_info governance_wallet_address;
|
||||
cryptonote::get_account_address_from_str(governance_wallet_address, nettype, cryptonote::get_config(nettype).governance_wallet_address(hard_fork_version));
|
||||
// Governance reward paid out through SN rewards batching after HF19
|
||||
if (hard_fork_version < hf::hf19) {
|
||||
rewards.push_back({reward_type::governance, governance_wallet_address.address, reward_parts.governance_paid});
|
||||
}
|
||||
rewards.push_back({reward_type::governance, governance_wallet_address.address, reward_parts.governance_paid});
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t total_sn_rewards = 0;
|
||||
// Add SN rewards to the block
|
||||
if (sn_rwds)
|
||||
// Add batched SN rewards to the block:
|
||||
if (!sn_rwds.empty())
|
||||
{
|
||||
for (const auto &reward : *sn_rwds)
|
||||
assert(hard_fork_version >= hf::hf19_reward_batching);
|
||||
for (const auto& reward : sn_rwds)
|
||||
{
|
||||
rewards.emplace_back(reward_type::snode, reward.address_info.address, reward.amount);
|
||||
total_sn_rewards += reward.amount;
|
||||
assert(reward.amount % BATCH_REWARD_FACTOR == 0);
|
||||
auto atomic_amt = reward.amount / BATCH_REWARD_FACTOR;
|
||||
rewards.emplace_back(reward_type::snode, reward.address_info.address, atomic_amt);
|
||||
total_sn_rewards += atomic_amt;
|
||||
}
|
||||
}
|
||||
|
||||
if (hard_fork_version < hf::hf19)
|
||||
if (hard_fork_version < hf::hf19_reward_batching)
|
||||
{
|
||||
CHECK_AND_ASSERT_MES(rewards.size() <= 9, std::make_pair(false, block_rewards), "More rewards specified than supported, number of rewards: " << rewards.size() << ", capacity: " << rewards.size());
|
||||
CHECK_AND_ASSERT_MES(rewards.size() > 0, std::make_pair(false, block_rewards), "Zero rewards are to be payed out, there should be at least 1");
|
||||
|
@ -461,10 +462,10 @@ namespace cryptonote
|
|||
return std::make_pair(false, block_rewards);
|
||||
}
|
||||
|
||||
txout_to_key tk = {};
|
||||
tk.key = out_eph_public_key;
|
||||
txout_to_key tk{};
|
||||
tk.key = out_eph_public_key;
|
||||
|
||||
tx_out out = {};
|
||||
tx_out out{};
|
||||
out.target = tk;
|
||||
out.amount = amount;
|
||||
tx.vout.push_back(out);
|
||||
|
@ -472,7 +473,7 @@ namespace cryptonote
|
|||
summary_amounts += amount;
|
||||
}
|
||||
|
||||
uint64_t expected_amount = 0;
|
||||
uint64_t expected_amount;
|
||||
if (hard_fork_version <= hf::hf15_ons)
|
||||
{
|
||||
// NOTE: Use the amount actually paid out when we split the service node
|
||||
|
@ -481,34 +482,29 @@ namespace cryptonote
|
|||
// division). This occurred prior to HF15, after that we redistribute dust
|
||||
// properly.
|
||||
expected_amount = reward_parts.base_miner + reward_parts.miner_fee + reward_parts.governance_paid;
|
||||
for (auto reward : rewards)
|
||||
{
|
||||
[[maybe_unused]] auto const &[type, address, amount] = reward;
|
||||
if (type == reward_type::snode) expected_amount += amount;
|
||||
}
|
||||
for (const auto& [type, address, amount] : rewards)
|
||||
if (type == reward_type::snode)
|
||||
expected_amount += amount;
|
||||
}
|
||||
else if (hard_fork_version < hf::hf19_reward_batching)
|
||||
expected_amount = reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total + reward_parts.governance_paid;
|
||||
else
|
||||
{
|
||||
expected_amount = 0;
|
||||
if (hard_fork_version < hf::hf19)
|
||||
expected_amount = expected_amount + reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total + reward_parts.governance_paid;
|
||||
else
|
||||
expected_amount = expected_amount + total_sn_rewards;
|
||||
}
|
||||
expected_amount = total_sn_rewards;
|
||||
|
||||
CHECK_AND_ASSERT_MES(summary_amounts == expected_amount, std::make_pair(false, block_rewards), "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal total block_reward = " << expected_amount);
|
||||
CHECK_AND_ASSERT_MES(tx.vout.size() == rewards.size(), std::make_pair(false, block_rewards), "TX output mis-match with rewards expected: " << rewards.size() << ", tx outputs: " << tx.vout.size());
|
||||
|
||||
block_rewards = std::accumulate(batched_rewards.begin(), batched_rewards.end(), uint64_t(0), [](uint64_t const x, cryptonote::batch_sn_payment const y) { return x + y.amount; });
|
||||
block_rewards = std::accumulate(
|
||||
batched_rewards.begin(),
|
||||
batched_rewards.end(),
|
||||
uint64_t{0},
|
||||
[](uint64_t x, auto&& y) { return x + y.amount; });
|
||||
|
||||
//lock
|
||||
tx.unlock_time = height + MINED_MONEY_UNLOCK_WINDOW;
|
||||
tx.vin.push_back(txin_gen{height});
|
||||
tx.invalidate_hashes();
|
||||
|
||||
//LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee)
|
||||
// << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2);
|
||||
|
||||
return std::make_pair(true, block_rewards);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,7 +129,7 @@ namespace cryptonote
|
|||
uint64_t fee,
|
||||
transaction& tx,
|
||||
const oxen_miner_tx_context &miner_context,
|
||||
const std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds,
|
||||
const std::vector<cryptonote::batch_sn_payment>& sn_rwds,
|
||||
const std::string& extra_nonce,
|
||||
hf hard_fork_version);
|
||||
|
||||
|
|
|
@ -1424,7 +1424,7 @@ round_state send_block_template(round_context &context, void *quorumnet_state, s
|
|||
cryptonote::block block = {};
|
||||
{
|
||||
uint64_t height = 0;
|
||||
service_nodes::payout block_producer_payouts = service_nodes::service_node_info_to_payout(key.pub, *info);
|
||||
service_nodes::payout block_producer_payouts = service_nodes::service_node_payout_portions(key.pub, *info);
|
||||
if (!blockchain.create_next_pulse_block_template(block,
|
||||
block_producer_payouts,
|
||||
context.prepare_for_round.round,
|
||||
|
|
|
@ -33,7 +33,11 @@
|
|||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <oxenc/endian.h>
|
||||
#include <oxenc/hex.h>
|
||||
|
||||
extern "C" {
|
||||
#include <sodium.h>
|
||||
|
@ -297,29 +301,39 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
bool reg_tx_extract_fields(const cryptonote::transaction& tx, contributor_args_t &contributor_args, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature)
|
||||
std::optional<registration_details> reg_tx_extract_fields(const cryptonote::transaction& tx)
|
||||
{
|
||||
cryptonote::tx_extra_service_node_register registration;
|
||||
if (!get_field_from_tx_extra(tx.extra, registration))
|
||||
return false;
|
||||
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, service_node_key))
|
||||
return false;
|
||||
return std::nullopt;
|
||||
|
||||
contributor_args.addresses.clear();
|
||||
contributor_args.addresses.reserve(registration.m_public_spend_keys.size());
|
||||
for (size_t i = 0; i < registration.m_public_spend_keys.size(); i++) {
|
||||
contributor_args.addresses.emplace_back();
|
||||
contributor_args.addresses.back().m_spend_public_key = registration.m_public_spend_keys[i];
|
||||
contributor_args.addresses.back().m_view_public_key = registration.m_public_view_keys[i];
|
||||
if (registration.public_spend_keys.size() != registration.public_view_keys.size() ||
|
||||
registration.amounts.size() != registration.public_spend_keys.size())
|
||||
return std::nullopt;
|
||||
|
||||
registration_details reg{};
|
||||
if (!cryptonote::get_service_node_pubkey_from_tx_extra(tx.extra, reg.service_node_pubkey))
|
||||
return std::nullopt;
|
||||
|
||||
reg.reserved.reserve(registration.public_spend_keys.size());
|
||||
for (size_t i = 0; i < registration.public_spend_keys.size(); i++) {
|
||||
auto& [addr, amount] = reg.reserved.emplace_back();
|
||||
addr.m_spend_public_key = registration.public_spend_keys[i];
|
||||
addr.m_view_public_key = registration.public_view_keys[i];
|
||||
amount = registration.amounts[i];
|
||||
}
|
||||
|
||||
contributor_args.portions_for_operator = registration.m_portions_for_operator;
|
||||
contributor_args.portions = registration.m_portions;
|
||||
contributor_args.success = true;
|
||||
reg.hf = registration.hf_or_expiration;
|
||||
if (registration.hf_or_expiration <= 255)
|
||||
reg.uses_portions = false;
|
||||
else
|
||||
// Unix timestamp, so pre-HF19 and uses portions
|
||||
reg.uses_portions = true;
|
||||
|
||||
expiration_timestamp = registration.m_expiration_timestamp;
|
||||
signature = registration.m_service_node_signature;
|
||||
return true;
|
||||
reg.fee = registration.fee;
|
||||
reg.signature = registration.signature;
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height)
|
||||
|
@ -334,43 +348,96 @@ namespace service_nodes
|
|||
return result;
|
||||
}
|
||||
|
||||
void validate_contributor_args(hf hf_version, contributor_args_t const &contributor_args)
|
||||
void validate_registration(hf hf_version, cryptonote::network_type nettype, uint64_t staking_requirement, uint64_t block_timestamp, const registration_details& reg)
|
||||
{
|
||||
const size_t max_contributors = hf_version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
if (contributor_args.portions.empty())
|
||||
throw invalid_contributions{"No portions given"};
|
||||
if (contributor_args.portions.size() != contributor_args.addresses.size())
|
||||
throw invalid_contributions{"Number of portions (" + std::to_string(contributor_args.portions.size()) + ") doesn't match the number of addresses (" + std::to_string(contributor_args.portions.size()) + ")"};
|
||||
if (contributor_args.portions.size() > max_contributors)
|
||||
throw invalid_contributions{"Too many contributors"};
|
||||
if (contributor_args.portions_for_operator > cryptonote::old::STAKING_PORTIONS)
|
||||
throw invalid_contributions{"Operator portions are too high"};
|
||||
|
||||
if (!check_service_node_portions(hf_version, contributor_args.portions))
|
||||
if (reg.uses_portions)
|
||||
{
|
||||
std::stringstream stream;
|
||||
for (size_t i = 0; i < contributor_args.portions.size(); i++)
|
||||
{
|
||||
if (i) stream << ", ";
|
||||
stream << contributor_args.portions[i];
|
||||
}
|
||||
throw invalid_contributions{"Invalid portions: {" + stream.str() + "}"};
|
||||
if (hf_version > hf::hf19_reward_batching)
|
||||
throw invalid_registration{"Portion-based registrations are not permitted after HF19"};
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not using portions then the hf value must be >= 19 and equal to the current blockchain hf:
|
||||
if (hf_version < hf::hf19_reward_batching || reg.hf != static_cast<uint8_t>(hf_version))
|
||||
throw invalid_registration{"Wrong registration hardfork " +
|
||||
std::to_string(reg.hf) + ", != current " + std::to_string(static_cast<uint8_t>(hf_version))};
|
||||
}
|
||||
|
||||
const size_t max_contributors = (hf_version >= hf::hf19_reward_batching && !reg.uses_portions)
|
||||
? oxen::MAX_CONTRIBUTORS_HF19
|
||||
: oxen::MAX_CONTRIBUTORS_V1;
|
||||
|
||||
if (reg.reserved.empty())
|
||||
throw invalid_registration{"No operator contribution given"};
|
||||
if (reg.reserved.size() > max_contributors)
|
||||
throw invalid_registration{"Too many contributors"};
|
||||
|
||||
bool valid_stakes, valid_fee;
|
||||
if (reg.uses_portions) {
|
||||
// HF18 or earlier registration
|
||||
valid_stakes = check_service_node_portions(hf_version, reg.reserved);
|
||||
valid_fee = reg.fee <= cryptonote::old::STAKING_PORTIONS;
|
||||
}
|
||||
else
|
||||
{
|
||||
valid_stakes = check_service_node_stakes(hf_version, nettype, staking_requirement, reg.reserved);
|
||||
valid_fee = reg.fee <= cryptonote::STAKING_FEE_BASIS;
|
||||
}
|
||||
|
||||
if (!valid_fee)
|
||||
throw invalid_registration{fmt::format("Operator fee is too high ({} > {})", reg.fee,
|
||||
reg.uses_portions ? cryptonote::old::STAKING_PORTIONS : cryptonote::STAKING_FEE_BASIS)};
|
||||
|
||||
if (!valid_stakes) {
|
||||
std::string amount_dump;
|
||||
amount_dump.reserve(22 * reg.reserved.size());
|
||||
for (size_t i = 0; i < reg.reserved.size(); i++)
|
||||
{
|
||||
if (i) amount_dump += ", ";
|
||||
amount_dump += std::to_string(reg.reserved[i].second);
|
||||
}
|
||||
throw invalid_registration{"Invalid "s + (reg.uses_portions ? "portions" : "amounts") + ": {" + amount_dump + "}"};
|
||||
}
|
||||
|
||||
// If using portions then `.hf` is actually the registration expiry (HF19+ registrations do not
|
||||
// expire).
|
||||
if (reg.uses_portions && reg.hf < block_timestamp)
|
||||
throw invalid_registration{"Registration expired (" + std::to_string(reg.hf) + " < " + std::to_string(block_timestamp) + ")"};
|
||||
}
|
||||
|
||||
void validate_contributor_args_signature(contributor_args_t const &contributor_args, uint64_t const expiration_timestamp, crypto::public_key const &service_node_key, crypto::signature const &signature)
|
||||
//---------------------------------------------------------------
|
||||
crypto::hash get_registration_hash(const registration_details& registration)
|
||||
{
|
||||
crypto::hash hash = {};
|
||||
if (!get_registration_hash(contributor_args.addresses, contributor_args.portions_for_operator, contributor_args.portions, expiration_timestamp, hash))
|
||||
throw invalid_contributions{"Failed to generate registration hash"};
|
||||
|
||||
if (!crypto::check_key(service_node_key))
|
||||
throw invalid_contributions{"Service Node Key was not a valid crypto key" + tools::type_to_hex(service_node_key)};
|
||||
|
||||
if (!crypto::check_signature(hash, service_node_key, signature))
|
||||
throw invalid_contributions{"Failed to validate service node with key:" + tools::type_to_hex(service_node_key) + " and hash: " + tools::type_to_hex(hash)};
|
||||
std::string buffer;
|
||||
size_t size =
|
||||
sizeof(uint64_t) + // fee
|
||||
registration.reserved.size() * (
|
||||
sizeof(cryptonote::account_public_address) + sizeof(uint64_t)) + // addr+amount for each
|
||||
sizeof(uint64_t); // expiration timestamp
|
||||
buffer.reserve(size);
|
||||
buffer += tools::view_guts(oxenc::host_to_little(registration.fee));
|
||||
for (const auto& [addr, amount] : registration.reserved)
|
||||
{
|
||||
buffer += tools::view_guts(addr);
|
||||
buffer += tools::view_guts(oxenc::host_to_little(amount));
|
||||
}
|
||||
buffer += tools::view_guts(oxenc::host_to_little(registration.hf));
|
||||
assert(buffer.size() == size);
|
||||
return crypto::cn_fast_hash(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
void validate_registration_signature(const registration_details& registration)
|
||||
{
|
||||
auto hash = get_registration_hash(registration);
|
||||
if (!crypto::check_key(registration.service_node_pubkey))
|
||||
throw invalid_registration{"Service Node Key is not a valid public key (" + tools::type_to_hex(registration.service_node_pubkey) + ")"};
|
||||
|
||||
if (!crypto::check_signature(hash, registration.service_node_pubkey, registration.signature))
|
||||
throw invalid_registration{"Registration signature verification failed for pubkey/hash: " +
|
||||
tools::type_to_hex(registration.service_node_pubkey) + "/" + tools::type_to_hex(hash)};
|
||||
}
|
||||
|
||||
|
||||
struct parsed_tx_contribution
|
||||
{
|
||||
cryptonote::account_public_address address;
|
||||
|
@ -867,37 +934,25 @@ namespace service_nodes
|
|||
|
||||
bool is_registration_tx(cryptonote::network_type nettype, hf hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info)
|
||||
{
|
||||
contributor_args_t contributor_args = {};
|
||||
crypto::public_key service_node_key;
|
||||
uint64_t expiration_timestamp{0};
|
||||
crypto::signature signature;
|
||||
|
||||
if (!reg_tx_extract_fields(tx, contributor_args, expiration_timestamp, service_node_key, signature))
|
||||
auto maybe_reg = reg_tx_extract_fields(tx);
|
||||
if (!maybe_reg)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
validate_contributor_args(hf_version, contributor_args);
|
||||
validate_contributor_args_signature(contributor_args, expiration_timestamp, service_node_key, signature);
|
||||
}
|
||||
catch (const invalid_contributions &e)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX: " << cryptonote::get_transaction_hash(tx) << ", Height: " << block_height << ". " << e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expiration_timestamp < block_timestamp)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX: Has expired. The block timestamp: " << block_timestamp <<
|
||||
" is greater than the expiration timestamp: " << expiration_timestamp <<
|
||||
" on height: " << block_height <<
|
||||
" for tx:" << cryptonote::get_transaction_hash(tx));
|
||||
return false;
|
||||
}
|
||||
|
||||
// check the initial contribution exists
|
||||
auto& reg = *maybe_reg;
|
||||
|
||||
uint64_t staking_requirement = get_staking_requirement(nettype, block_height);
|
||||
try
|
||||
{
|
||||
validate_registration(hf_version, nettype, staking_requirement, block_timestamp, reg);
|
||||
validate_registration_signature(reg);
|
||||
}
|
||||
catch (const invalid_registration &e)
|
||||
{
|
||||
LOG_PRINT_L1("Invalid registration (" << cryptonote::get_transaction_hash(tx) << " @ " << block_height << "): " << e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
// check the operator contribution exists
|
||||
|
||||
cryptonote::account_public_address address;
|
||||
|
||||
staking_components stake = {};
|
||||
|
@ -919,7 +974,7 @@ namespace service_nodes
|
|||
|
||||
// 2. the staked amount must be from the operator. (Previously there was a weird edge case where you
|
||||
// could manually construct a registration tx that stakes for someone *other* than the operator).
|
||||
if (stake.address != contributor_args.addresses[0])
|
||||
if (stake.address != reg.reserved[0].first)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX invalid: registration stake is not from the operator");
|
||||
return false;
|
||||
|
@ -938,8 +993,9 @@ namespace service_nodes
|
|||
return false;
|
||||
}
|
||||
|
||||
size_t total_num_of_addr = contributor_args.addresses.size();
|
||||
if (std::find(contributor_args.addresses.begin(), contributor_args.addresses.end(), stake.address) == contributor_args.addresses.end())
|
||||
size_t total_num_of_addr = reg.reserved.size();
|
||||
if (std::find_if(reg.reserved.begin(), reg.reserved.end(),
|
||||
[&](auto& addr_amt) { return addr_amt.first == stake.address; }) == reg.reserved.end())
|
||||
total_num_of_addr++;
|
||||
|
||||
// Don't need this check for HF16+ because the number of reserved spots is already checked in
|
||||
|
@ -956,11 +1012,16 @@ namespace service_nodes
|
|||
|
||||
// don't actually process this contribution now, do it when we fall through later.
|
||||
|
||||
key = service_node_key;
|
||||
key = reg.service_node_pubkey;
|
||||
|
||||
info.staking_requirement = staking_requirement;
|
||||
info.operator_address = reg.reserved[0].first;
|
||||
|
||||
if (reg.uses_portions)
|
||||
info.portions_for_operator = reg.fee;
|
||||
else
|
||||
info.portions_for_operator = mul128_div64(reg.fee, cryptonote::old::STAKING_PORTIONS, cryptonote::STAKING_FEE_BASIS);
|
||||
|
||||
info.staking_requirement = staking_requirement;
|
||||
info.operator_address = contributor_args.addresses[0];
|
||||
info.portions_for_operator = contributor_args.portions_for_operator;
|
||||
info.registration_height = block_height;
|
||||
info.registration_hf_version = hf_version;
|
||||
info.last_reward_block_height = block_height;
|
||||
|
@ -968,25 +1029,27 @@ namespace service_nodes
|
|||
info.swarm_id = UNASSIGNED_SWARM_ID;
|
||||
info.last_ip_change_height = block_height;
|
||||
|
||||
for (size_t i = 0; i < contributor_args.addresses.size(); i++)
|
||||
for (auto it = reg.reserved.begin(); it != reg.reserved.end(); ++it)
|
||||
{
|
||||
// Check for duplicates
|
||||
auto iter = std::find(contributor_args.addresses.begin(), contributor_args.addresses.begin() + i, contributor_args.addresses[i]);
|
||||
if (iter != contributor_args.addresses.begin() + i)
|
||||
auto& [addr, amount] = *it;
|
||||
bool dupe = false;
|
||||
for (auto it2 = std::next(it); it2 != reg.reserved.end(); ++it2)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX: There was a duplicate participant for service node on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
|
||||
return false;
|
||||
if (it2->first == addr)
|
||||
{
|
||||
LOG_PRINT_L1("Invalid registration: duplicate reserved address in registration (tx " << cryptonote::get_transaction_hash(tx) << ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(info.staking_requirement, contributor_args.portions[i], &hi);
|
||||
div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
auto& contributor = info.contributors.emplace_back();
|
||||
if (reg.uses_portions)
|
||||
contributor.reserved = mul128_div64(amount, info.staking_requirement, cryptonote::old::STAKING_PORTIONS);
|
||||
else
|
||||
contributor.reserved = amount;
|
||||
|
||||
info.contributors.emplace_back();
|
||||
auto &contributor = info.contributors.back();
|
||||
contributor.reserved = resultlo;
|
||||
contributor.address = contributor_args.addresses[i];
|
||||
info.total_reserved += resultlo;
|
||||
contributor.address = addr;
|
||||
info.total_reserved += contributor.reserved;
|
||||
}
|
||||
|
||||
// In HF16 we require that the amount staked in the registration tx be at least the amount
|
||||
|
@ -1149,7 +1212,7 @@ namespace service_nodes
|
|||
// Check node contributor counts
|
||||
{
|
||||
bool too_many_contributions = false;
|
||||
if (hf_version >= hf::hf19)
|
||||
if (hf_version >= hf::hf19_reward_batching)
|
||||
// As of HF19 we allow up to 10 stakes total
|
||||
too_many_contributions = existing_contributions + other_reservations + 1 > oxen::MAX_CONTRIBUTORS_HF19;
|
||||
else if (hf_version >= hf::hf16_pulse)
|
||||
|
@ -1165,7 +1228,7 @@ namespace service_nodes
|
|||
if (too_many_contributions)
|
||||
{
|
||||
LOG_PRINT_L1("TX: Already hit the max number of contributions: "
|
||||
<< (hf_version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1)
|
||||
<< (hf_version >= hf::hf19_reward_batching ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1)
|
||||
<< " for contributor: " << cryptonote::get_account_address_as_str(nettype, false, stake.address)
|
||||
<< " on height: " << block_height << " for tx: " << cryptonote::get_transaction_hash(tx));
|
||||
return false;
|
||||
|
@ -1652,7 +1715,7 @@ namespace service_nodes
|
|||
{
|
||||
std::array<uint32_t, (sizeof(hash) / sizeof(uint32_t)) + 1> src = {static_cast<uint32_t>(type)};
|
||||
std::memcpy(&src[1], &hash, sizeof(hash));
|
||||
for (uint32_t &val : src) boost::endian::little_to_native_inplace(val);
|
||||
for (uint32_t &val : src) oxenc::little_to_host_inplace(val);
|
||||
std::seed_seq sequence(src.begin(), src.end());
|
||||
result.seed(sequence);
|
||||
}
|
||||
|
@ -1660,7 +1723,7 @@ namespace service_nodes
|
|||
{
|
||||
uint64_t seed = 0;
|
||||
std::memcpy(&seed, hash.data, sizeof(seed));
|
||||
boost::endian::little_to_native_inplace(seed);
|
||||
oxenc::little_to_host_inplace(seed);
|
||||
seed += static_cast<uint64_t>(type);
|
||||
result.seed(seed);
|
||||
}
|
||||
|
@ -2330,7 +2393,7 @@ namespace service_nodes
|
|||
|
||||
if (key == crypto::null_pkey)
|
||||
return service_nodes::null_payout;
|
||||
return service_node_info_to_payout(key, *info);
|
||||
return service_node_payout_portions(key, *info);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
|
@ -2389,7 +2452,7 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
bool service_node_list::validate_miner_tx(cryptonote::block const &block, cryptonote::block_reward_parts const &reward_parts, std::optional<std::vector<cryptonote::batch_sn_payment>> const &batched_sn_payments) const
|
||||
bool service_node_list::validate_miner_tx(const cryptonote::block& block, const cryptonote::block_reward_parts& reward_parts, const std::optional<std::vector<cryptonote::batch_sn_payment>>& batched_sn_payments) const
|
||||
{
|
||||
const auto hf_version = block.major_version;
|
||||
if (hf_version < hf::hf9_service_nodes)
|
||||
|
@ -2465,68 +2528,61 @@ namespace service_nodes
|
|||
//
|
||||
|
||||
std::shared_ptr<const service_node_info> block_producer = nullptr;
|
||||
size_t expected_vouts_size = 0;
|
||||
|
||||
if (mode == verify_mode::pulse_block_leader_is_producer || mode == verify_mode::pulse_different_block_producer)
|
||||
{
|
||||
auto info_it = m_state.service_nodes_infos.find(block_producer_key);
|
||||
if (info_it == m_state.service_nodes_infos.end())
|
||||
{
|
||||
MGINFO_RED("The pulse block producer for round: " << +block.pulse.round << " is not currently a Service Node: " << block_producer_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
block_producer = info_it->second;
|
||||
if (mode == verify_mode::pulse_different_block_producer && reward_parts.miner_fee > 0 && block.major_version < hf::hf19)
|
||||
{
|
||||
expected_vouts_size += block_producer->contributors.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (block.major_version >= hf::hf19)
|
||||
if (block.major_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
mode = verify_mode::batched_sn_rewards;
|
||||
MDEBUG("Batched miner reward");
|
||||
}
|
||||
|
||||
size_t expected_vouts_size;
|
||||
switch (mode) {
|
||||
case verify_mode::batched_sn_rewards:
|
||||
expected_vouts_size = batched_sn_payments ? batched_sn_payments->size() : 0;
|
||||
break;
|
||||
case verify_mode::pulse_block_leader_is_producer:
|
||||
case verify_mode::pulse_different_block_producer:
|
||||
{
|
||||
auto info_it = m_state.service_nodes_infos.find(block_producer_key);
|
||||
if (info_it == m_state.service_nodes_infos.end())
|
||||
{
|
||||
MGINFO_RED("The pulse block producer for round: " << +block.pulse.round << " is not currently a Service Node: " << block_producer_key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == verify_mode::miner)
|
||||
{
|
||||
if ((reward_parts.base_miner + reward_parts.miner_fee) > 0) // (HF >= 16) this can be zero, no miner coinbase.
|
||||
{
|
||||
expected_vouts_size += 1; /*miner*/
|
||||
}
|
||||
block_producer = info_it->second;
|
||||
expected_vouts_size = mode == verify_mode::pulse_different_block_producer && reward_parts.miner_fee > 0
|
||||
? block_producer->contributors.size() : 0;
|
||||
}
|
||||
break;
|
||||
case verify_mode::miner:
|
||||
expected_vouts_size = reward_parts.base_miner + reward_parts.miner_fee > 0 // (HF >= 16) this can be zero, no miner coinbase.
|
||||
? 1 /* miner */ : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode == verify_mode::batched_sn_rewards)
|
||||
if (mode != verify_mode::batched_sn_rewards)
|
||||
{
|
||||
if (batched_sn_payments.has_value())
|
||||
expected_vouts_size += batched_sn_payments->size();
|
||||
} else {
|
||||
expected_vouts_size += block_leader.payouts.size();
|
||||
bool has_governance_output = cryptonote::height_has_governance_output(m_blockchain.nettype(), hf_version, height);
|
||||
if (has_governance_output) {
|
||||
if (has_governance_output)
|
||||
expected_vouts_size++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (miner_tx.vout.size() != expected_vouts_size)
|
||||
{
|
||||
char const *type = mode == verify_mode::miner
|
||||
? "miner"
|
||||
: mode == verify_mode::pulse_block_leader_is_producer ? "pulse" : "pulse alt round";
|
||||
auto type =
|
||||
mode == verify_mode::miner ? "miner"sv :
|
||||
mode == verify_mode::batched_sn_rewards ? "batch reward"sv :
|
||||
mode == verify_mode::pulse_block_leader_is_producer ? "pulse"sv : "pulse alt round"sv;
|
||||
MGINFO_RED("Expected " << type << " block, the miner TX specifies a different amount of outputs vs the expected: " << expected_vouts_size << ", miner tx outputs: " << miner_tx.vout.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hf_version >= hf::hf16_pulse)
|
||||
if (hf_version >= hf::hf16_pulse && reward_parts.base_miner != 0)
|
||||
{
|
||||
if (reward_parts.base_miner != 0)
|
||||
{
|
||||
MGINFO_RED("Miner reward is incorrect expected 0 reward, block specified " << cryptonote::print_money(reward_parts.base_miner));
|
||||
return false;
|
||||
}
|
||||
MGINFO_RED("Miner reward is incorrect expected 0 reward, block specified " << cryptonote::print_money(reward_parts.base_miner));
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Verify Coinbase Amounts
|
||||
|
@ -2547,7 +2603,7 @@ namespace service_nodes
|
|||
|
||||
for (size_t i = 0; i < block_leader.payouts.size(); i++)
|
||||
{
|
||||
payout_entry const &payout = block_leader.payouts[i];
|
||||
const auto& payout = block_leader.payouts[i];
|
||||
if (split_rewards[i])
|
||||
{
|
||||
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
|
||||
|
@ -2567,7 +2623,7 @@ namespace service_nodes
|
|||
size_t vout_index = 0;
|
||||
for (size_t i = 0; i < block_leader.payouts.size(); i++)
|
||||
{
|
||||
payout_entry const &payout = block_leader.payouts[i];
|
||||
const auto& payout = block_leader.payouts[i];
|
||||
if (split_rewards[i])
|
||||
{
|
||||
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
|
||||
|
@ -2582,11 +2638,11 @@ namespace service_nodes
|
|||
{
|
||||
size_t vout_index = 0;
|
||||
{
|
||||
payout block_producer_payouts = service_node_info_to_payout(block_producer_key, *block_producer);
|
||||
payout block_producer_payouts = service_node_payout_portions(block_producer_key, *block_producer);
|
||||
std::vector<uint64_t> split_rewards = cryptonote::distribute_reward_by_portions(block_producer_payouts.payouts, reward_parts.miner_fee, true /*distribute_remainder*/);
|
||||
for (size_t i = 0; i < block_producer_payouts.payouts.size(); i++)
|
||||
{
|
||||
payout_entry const &payout = block_producer_payouts.payouts[i];
|
||||
const auto& payout = block_producer_payouts.payouts[i];
|
||||
if (split_rewards[i])
|
||||
{
|
||||
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
|
||||
|
@ -2599,7 +2655,7 @@ namespace service_nodes
|
|||
std::vector<uint64_t> split_rewards = cryptonote::distribute_reward_by_portions(block_leader.payouts, reward_parts.service_node_total, true /*distribute_remainder*/);
|
||||
for (size_t i = 0; i < block_leader.payouts.size(); i++)
|
||||
{
|
||||
payout_entry const &payout = block_leader.payouts[i];
|
||||
const auto& payout = block_leader.payouts[i];
|
||||
if (split_rewards[i])
|
||||
{
|
||||
if (!verify_coinbase_tx_output(miner_tx, height, vout_index, payout.address, split_rewards[i]))
|
||||
|
@ -2612,41 +2668,57 @@ namespace service_nodes
|
|||
|
||||
case verify_mode::batched_sn_rewards:
|
||||
{
|
||||
size_t vout_index = 0;
|
||||
uint64_t total_payout_in_our_db = std::accumulate(batched_sn_payments->begin(),batched_sn_payments->end(), uint64_t{0}, [](auto const a, auto const b){return a + b.amount;});
|
||||
// NB: this amount is in milli-atomics, not atomics
|
||||
uint64_t total_payout_in_our_db = batched_sn_payments ? std::accumulate(
|
||||
batched_sn_payments->begin(),
|
||||
batched_sn_payments->end(),
|
||||
uint64_t{0},
|
||||
[](auto&& a, auto&& b) { return a + b.amount; }) : 0;
|
||||
|
||||
uint64_t total_payout_in_vouts = 0;
|
||||
cryptonote::keypair const deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(height);
|
||||
for (auto & vout : block.miner_tx.vout)
|
||||
const auto deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(height);
|
||||
for (size_t vout_index = 0; vout_index < block.miner_tx.vout.size(); vout_index++)
|
||||
{
|
||||
const auto& vout = block.miner_tx.vout[vout_index];
|
||||
const auto& batch_payment = (*batched_sn_payments)[vout_index];
|
||||
|
||||
if (!std::holds_alternative<cryptonote::txout_to_key>(vout.target))
|
||||
{
|
||||
MGINFO_RED("Service node output target type should be txout_to_key");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (vout.amount != (*batched_sn_payments)[vout_index].amount)
|
||||
constexpr uint64_t max_amount = std::numeric_limits<uint64_t>::max() / cryptonote::BATCH_REWARD_FACTOR;
|
||||
if (vout.amount > max_amount)
|
||||
{
|
||||
MERROR("Service node reward amount incorrect. Should be " << cryptonote::print_money((*batched_sn_payments)[vout_index].amount) << ", is: " << cryptonote::print_money(vout.amount));
|
||||
// We should never actually hit this limit unless someone is trying something nefarious
|
||||
MGINFO_RED("Batched reward payout invalid: exceeds maximum possible payout size");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto paid_amount = vout.amount * cryptonote::BATCH_REWARD_FACTOR;
|
||||
total_payout_in_vouts += paid_amount;
|
||||
if (paid_amount != batch_payment.amount)
|
||||
{
|
||||
MGINFO_RED(fmt::format("Batched reward payout incorrect: expected {}, not {}", batch_payment.amount, paid_amount));
|
||||
return false;
|
||||
}
|
||||
crypto::public_key out_eph_public_key{};
|
||||
if (!cryptonote::get_deterministic_output_key((*batched_sn_payments)[vout_index].address_info.address, deterministic_keypair, vout_index, out_eph_public_key))
|
||||
if (!cryptonote::get_deterministic_output_key(batch_payment.address_info.address, deterministic_keypair, vout_index, out_eph_public_key))
|
||||
{
|
||||
MERROR("Failed to generate output one-time public key");
|
||||
MGINFO_RED("Failed to generate output one-time public key");
|
||||
return false;
|
||||
}
|
||||
const auto& out_to_key = var::get<cryptonote::txout_to_key>(vout.target);
|
||||
if (tools::view_guts(out_to_key) != tools::view_guts(out_eph_public_key))
|
||||
{
|
||||
MERROR("Output Ephermeral Public Key does not match");
|
||||
MGINFO_RED("Output Ephermeral Public Key does not match (payment to wrong recipient)");
|
||||
return false;
|
||||
}
|
||||
total_payout_in_vouts += vout.amount;
|
||||
vout_index++;
|
||||
}
|
||||
if (total_payout_in_vouts != total_payout_in_our_db)
|
||||
{
|
||||
MERROR("Total service node reward amount incorrect. Should be " << cryptonote::print_money(total_payout_in_our_db) << ", is: " << cryptonote::print_money(total_payout_in_vouts));
|
||||
MGINFO_RED(fmt::format("Total service node reward amount incorrect: expected {}, not {}", total_payout_in_our_db, total_payout_in_vouts));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -3661,104 +3733,38 @@ namespace service_nodes
|
|||
return result;
|
||||
}
|
||||
|
||||
contributor_args_t convert_registration_args(cryptonote::network_type nettype,
|
||||
const std::vector<std::string> &args,
|
||||
uint64_t staking_requirement,
|
||||
hf hf_version)
|
||||
// Handles the deprecated, pre-HF19 registration parsing where values are portions rather than
|
||||
// amounts.
|
||||
// TODO: this can be deleted immediately after HF19, because this code is only used to process new
|
||||
// registration commands (and after the HF, all registration commands are HF19+ registrations with
|
||||
// raw amounts rather than portions).
|
||||
void convert_registration_portions_hf18(
|
||||
registration_details& result,
|
||||
const std::vector<std::string>& args,
|
||||
uint64_t staking_requirement,
|
||||
std::vector<std::pair<cryptonote::address_parse_info, uint64_t>>& addr_to_portions,
|
||||
hf hf_version)
|
||||
{
|
||||
contributor_args_t result = {};
|
||||
if (args.size() % 2 == 0 || args.size() < 3)
|
||||
{
|
||||
result.err_msg = tr("Usage: <operator cut> <address> <fraction> [<address> <fraction> [...]]]");
|
||||
return result;
|
||||
}
|
||||
|
||||
const size_t max_contributors = hf_version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
if (args.size() > 1 + 2 * max_contributors)
|
||||
{
|
||||
result.err_msg = tr("Exceeds the maximum number of contributors, which is ") + std::to_string(max_contributors);
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result.portions_for_operator = boost::lexical_cast<uint64_t>(args[0]);
|
||||
if (result.portions_for_operator > cryptonote::old::STAKING_PORTIONS)
|
||||
{
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(cryptonote::old::STAKING_PORTIONS);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
result.err_msg = tr("Invalid portion amount: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(cryptonote::old::STAKING_PORTIONS);
|
||||
return result;
|
||||
}
|
||||
|
||||
struct addr_to_portion_t
|
||||
{
|
||||
cryptonote::address_parse_info info;
|
||||
uint64_t portions;
|
||||
};
|
||||
|
||||
std::vector<addr_to_portion_t> addr_to_portions;
|
||||
size_t const OPERATOR_ARG_INDEX = 1;
|
||||
for (size_t i = OPERATOR_ARG_INDEX, num_contributions = 0;
|
||||
i < args.size();
|
||||
i += 2, ++num_contributions)
|
||||
{
|
||||
cryptonote::address_parse_info info;
|
||||
if (!cryptonote::get_account_address_from_str(info, nettype, args[i]))
|
||||
{
|
||||
result.err_msg = tr("Failed to parse address: ") + args[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
if (info.has_payment_id)
|
||||
{
|
||||
result.err_msg = tr("Can't use a payment id for staking tx");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (info.is_subaddress)
|
||||
{
|
||||
result.err_msg = tr("Can't use a subaddress for staking tx");
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
uint64_t num_portions = boost::lexical_cast<uint64_t>(args[i+1]);
|
||||
addr_to_portions.push_back({info, num_portions});
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
result.err_msg = tr("Invalid amount for contributor: ") + args[i] + tr(", with portion amount that could not be converted to a number: ") + args[i+1];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// FIXME(doyle): FIXME(oxen) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// This is temporary code to redistribute the insufficient portion dust
|
||||
// amounts between contributors. It should be removed in HF12.
|
||||
//
|
||||
std::array<uint64_t, oxen::MAX_CONTRIBUTORS_HF19> excess_portions;
|
||||
std::array<uint64_t, oxen::MAX_CONTRIBUTORS_HF19> min_contributions;
|
||||
std::array<uint64_t, oxen::MAX_CONTRIBUTORS_V1> excess_portions;
|
||||
std::array<uint64_t, oxen::MAX_CONTRIBUTORS_V1> min_contributions;
|
||||
{
|
||||
// NOTE: Calculate excess portions from each contributor
|
||||
uint64_t oxen_reserved = 0;
|
||||
for (size_t index = 0; index < addr_to_portions.size(); ++index)
|
||||
{
|
||||
addr_to_portion_t const &addr_to_portion = addr_to_portions[index];
|
||||
const auto& [addr, portion] = addr_to_portions[index];
|
||||
uint64_t min_contribution_portions = service_nodes::get_min_node_contribution_in_portions(hf_version, staking_requirement, oxen_reserved, index);
|
||||
uint64_t oxen_amount = service_nodes::portions_to_amount(staking_requirement, addr_to_portion.portions);
|
||||
uint64_t oxen_amount = service_nodes::portions_to_amount(staking_requirement, portion);
|
||||
oxen_reserved += oxen_amount;
|
||||
|
||||
uint64_t excess = 0;
|
||||
if (addr_to_portion.portions > min_contribution_portions)
|
||||
excess = addr_to_portion.portions - min_contribution_portions;
|
||||
if (portion > min_contribution_portions)
|
||||
excess = portion - min_contribution_portions;
|
||||
|
||||
min_contributions[index] = min_contribution_portions;
|
||||
excess_portions[index] = excess;
|
||||
|
@ -3769,15 +3775,15 @@ namespace service_nodes
|
|||
uint64_t total_reserved = 0;
|
||||
for (size_t i = 0; i < addr_to_portions.size(); ++i)
|
||||
{
|
||||
addr_to_portion_t &addr_to_portion = addr_to_portions[i];
|
||||
auto& [addr, portion] = addr_to_portions[i];
|
||||
uint64_t min_portions = get_min_node_contribution_in_portions(hf_version, staking_requirement, total_reserved, i);
|
||||
|
||||
uint64_t portions_to_steal = 0;
|
||||
if (addr_to_portion.portions < min_portions)
|
||||
if (portion < min_portions)
|
||||
{
|
||||
// NOTE: Steal dust portions from other contributor if we fall below
|
||||
// the minimum by a dust amount.
|
||||
uint64_t needed = min_portions - addr_to_portion.portions;
|
||||
uint64_t needed = min_portions - portion;
|
||||
const uint64_t FUDGE_FACTOR = 10;
|
||||
const uint64_t DUST_UNIT = cryptonote::old::STAKING_PORTIONS / staking_requirement;
|
||||
const uint64_t DUST = DUST_UNIT * FUDGE_FACTOR;
|
||||
|
@ -3791,10 +3797,10 @@ namespace service_nodes
|
|||
if (contributor_excess > 0)
|
||||
{
|
||||
portions_to_steal = std::min(needed, contributor_excess);
|
||||
addr_to_portion.portions += portions_to_steal;
|
||||
portion += portions_to_steal;
|
||||
contributor_excess -= portions_to_steal;
|
||||
needed -= portions_to_steal;
|
||||
result.portions[sub_index] -= portions_to_steal;
|
||||
addr_to_portions[sub_index].second -= portions_to_steal;
|
||||
|
||||
if (needed == 0)
|
||||
break;
|
||||
|
@ -3803,32 +3809,83 @@ namespace service_nodes
|
|||
|
||||
// NOTE: Operator is sending in the minimum amount and it falls below
|
||||
// the minimum by dust, just increase the portions so it passes
|
||||
if (needed > 0 && addr_to_portions.size() < oxen::MAX_CONTRIBUTORS_HF19)
|
||||
addr_to_portion.portions += needed;
|
||||
if (needed > 0 && addr_to_portions.size() < oxen::MAX_CONTRIBUTORS_V1)
|
||||
portion += needed;
|
||||
}
|
||||
|
||||
if (addr_to_portion.portions < min_portions || (addr_to_portion.portions - portions_to_steal) > portions_left)
|
||||
{
|
||||
result.err_msg = tr("Invalid amount for contributor: ") + args[i] + tr(", with portion amount: ") + args[i+1] + tr(". The contributors must each have at least 25%, except for the last contributor which may have the remaining amount");
|
||||
return result;
|
||||
}
|
||||
if (portion < min_portions || portion - portions_to_steal > portions_left)
|
||||
throw invalid_registration{tr("Invalid amount for contributor: ") + args[i] + tr(", with portion amount: ") +
|
||||
args[i+1] + tr(". The contributors must each have at least 25%, except for the last contributor which may have the remaining amount")};
|
||||
|
||||
if (min_portions == UINT64_MAX)
|
||||
{
|
||||
result.err_msg = tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(max_contributors) + tr(" people.");
|
||||
return result;
|
||||
}
|
||||
throw invalid_registration{
|
||||
tr("Too many contributors specified, you can only split a node with up to: ") + std::to_string(oxen::MAX_CONTRIBUTORS_V1) + tr(" people.")};
|
||||
|
||||
portions_left -= addr_to_portion.portions;
|
||||
portions_left -= portion;
|
||||
portions_left += portions_to_steal;
|
||||
result.addresses.push_back(addr_to_portion.info.address);
|
||||
result.portions.push_back(addr_to_portion.portions);
|
||||
uint64_t oxen_amount = service_nodes::portions_to_amount(addr_to_portion.portions, staking_requirement);
|
||||
total_reserved += oxen_amount;
|
||||
result.reserved.emplace_back(addr.address, portion);
|
||||
total_reserved += service_nodes::portions_to_amount(portion, staking_requirement);
|
||||
}
|
||||
}
|
||||
|
||||
registration_details convert_registration_args(
|
||||
cryptonote::network_type nettype,
|
||||
cryptonote::hf hf_version,
|
||||
const std::vector<std::string>& args,
|
||||
uint64_t staking_requirement)
|
||||
{
|
||||
registration_details result{};
|
||||
if (args.size() % 2 == 0 || args.size() < 3)
|
||||
throw invalid_registration{tr("Usage: <fee-basis-points> <address> <amount> [<address> <amount> [...]]]")};
|
||||
|
||||
const size_t max_contributors = hf_version >= hf::hf19_reward_batching ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
if (args.size() > 1 + 2 * max_contributors)
|
||||
throw invalid_registration{tr("Exceeds the maximum number of contributors") + " ("s + std::to_string(max_contributors) + ")"};
|
||||
|
||||
const uint64_t max_fee = hf_version >= hf::hf19_reward_batching ? cryptonote::STAKING_FEE_BASIS : cryptonote::old::STAKING_PORTIONS;
|
||||
if (!tools::parse_int(args[0], result.fee) || result.fee > max_fee)
|
||||
throw invalid_registration{tr("Invalid operator fee: ") + args[0] + tr(". Must be between 0 and ") + std::to_string(max_fee)};
|
||||
|
||||
std::vector<std::pair<cryptonote::address_parse_info, uint64_t>> addr_to_amounts;
|
||||
constexpr size_t OPERATOR_ARG_INDEX = 1;
|
||||
for (size_t i = OPERATOR_ARG_INDEX, num_contributions = 0;
|
||||
i < args.size();
|
||||
i += 2, ++num_contributions)
|
||||
{
|
||||
auto& [info, portion] = addr_to_amounts.emplace_back();
|
||||
if (!cryptonote::get_account_address_from_str(info, nettype, args[i]))
|
||||
throw invalid_registration{tr("Failed to parse address: ") + args[i]};
|
||||
|
||||
if (info.has_payment_id)
|
||||
throw invalid_registration{tr("Can't use a payment id for staking tx")};
|
||||
|
||||
if (info.is_subaddress)
|
||||
throw invalid_registration{tr("Can't use a subaddress for staking tx")};
|
||||
|
||||
if (!tools::parse_int(args[i+1], portion))
|
||||
throw invalid_registration{tr("Invalid amount for contributor: ") + args[i] + tr(", with portion amount that could not be converted to a number: ") + args[i+1]};
|
||||
}
|
||||
|
||||
uint64_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
if (hf_version < hf::hf19_reward_batching)
|
||||
{
|
||||
result.uses_portions = true;
|
||||
result.hf = now;
|
||||
convert_registration_portions_hf18(result, args, staking_requirement, addr_to_amounts, hf_version);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.uses_portions = false;
|
||||
result.hf = static_cast<uint8_t>(hf_version);
|
||||
// For HF19+ we just stick in the registration amounts as-is, then validate the registration to
|
||||
// make sure it looks good.
|
||||
for (const auto& [addr, amount] : addr_to_amounts)
|
||||
result.reserved.emplace_back(addr.address, amount);
|
||||
}
|
||||
|
||||
// Will throw if something is invalid:
|
||||
validate_registration(hf_version, nettype, staking_requirement, now, result);
|
||||
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -3841,59 +3898,46 @@ namespace service_nodes
|
|||
bool make_friendly)
|
||||
{
|
||||
|
||||
contributor_args_t contributor_args = convert_registration_args(nettype, args, staking_requirement, hf_version);
|
||||
if (!contributor_args.success)
|
||||
{
|
||||
MERROR(tr("Could not convert registration args, reason: ") << contributor_args.err_msg);
|
||||
registration_details reg;
|
||||
try {
|
||||
reg = convert_registration_args(nettype, hf_version, args, staking_requirement);
|
||||
} catch (const invalid_registration& e) {
|
||||
MERROR(tr("Could not parse registration arguments: ") << e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
reg.service_node_pubkey = keys.pub;
|
||||
|
||||
crypto::hash hash;
|
||||
if (reg.uses_portions)
|
||||
reg.hf = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
|
||||
bool hashed = cryptonote::get_registration_hash(contributor_args.addresses, contributor_args.portions_for_operator, contributor_args.portions, exp_timestamp, hash);
|
||||
if (!hashed)
|
||||
{
|
||||
MERROR(tr("Could not make registration hash from addresses and portions"));
|
||||
return false;
|
||||
}
|
||||
auto hash = get_registration_hash(reg);
|
||||
|
||||
crypto::signature signature;
|
||||
crypto::generate_signature(hash, keys.pub, keys.key, signature);
|
||||
reg.signature = crypto::generate_signature(hash, keys.pub, keys.key);
|
||||
|
||||
std::stringstream stream;
|
||||
cmd.clear();
|
||||
if (make_friendly)
|
||||
cmd += fmt::format("{} ({}):\n\n",
|
||||
tr("Run this command in the operator's wallet"),
|
||||
cryptonote::get_account_address_as_str(nettype, false, reg.reserved[0].first));
|
||||
|
||||
cmd += fmt::format("register_service_node {} {} {} {}",
|
||||
tools::join(" ", args),
|
||||
reg.hf,
|
||||
tools::type_to_hex(reg.service_node_pubkey),
|
||||
tools::type_to_hex(reg.signature));
|
||||
|
||||
if (make_friendly && reg.uses_portions)
|
||||
{
|
||||
stream << tr("Run this command in the operator wallet") << " (" <<
|
||||
cryptonote::get_account_address_as_str(nettype, false, contributor_args.addresses[0])
|
||||
<< "):\n\n";
|
||||
std::tm tm;
|
||||
epee::misc_utils::get_gmt_time(reg.hf, tm);
|
||||
|
||||
cmd += "\n\n";
|
||||
cmd += fmt::format(tr("This registration expires at {:%Y-%m-%d %I:%M:%S %p} UTC.\n"), tm);
|
||||
cmd += tr("This should be about 2 weeks from now; if it isn't, check this computer's clock.\n");
|
||||
cmd += tr("Please submit your registration into the blockchain before this time or it will be invalid.");
|
||||
}
|
||||
|
||||
stream << "register_service_node";
|
||||
for (size_t i = 0; i < args.size(); ++i)
|
||||
{
|
||||
stream << " " << args[i];
|
||||
}
|
||||
|
||||
stream << " " << exp_timestamp << " " << tools::type_to_hex(keys.pub) << " " << tools::type_to_hex(signature);
|
||||
|
||||
if (make_friendly)
|
||||
{
|
||||
stream << "\n\n";
|
||||
time_t tt = exp_timestamp;
|
||||
|
||||
struct tm tm;
|
||||
epee::misc_utils::get_gmt_time(tt, tm);
|
||||
|
||||
char buffer[128];
|
||||
strftime(buffer, sizeof(buffer), "%Y-%m-%d %I:%M:%S %p UTC", &tm);
|
||||
stream << tr("This registration expires at ") << buffer << tr(".\n");
|
||||
stream << tr("This should be in about 2 weeks, if it isn't, check this computer's clock.\n");
|
||||
stream << tr("Please submit your registration into the blockchain before this time or it will be invalid.");
|
||||
}
|
||||
|
||||
cmd = stream.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3966,23 +4010,21 @@ namespace service_nodes
|
|||
return true;
|
||||
}
|
||||
|
||||
payout service_node_info_to_payout(crypto::public_key const &key, service_node_info const &info)
|
||||
payout service_node_payout_portions(const crypto::public_key& key, const service_node_info& info)
|
||||
{
|
||||
service_nodes::payout result = {};
|
||||
result.key = key;
|
||||
|
||||
// Add contributors and their portions to winners.
|
||||
result.payouts.reserve(info.contributors.size());
|
||||
const uint64_t remaining_portions = cryptonote::old::STAKING_PORTIONS - info.portions_for_operator;
|
||||
const uint64_t portions_after_fee = cryptonote::old::STAKING_PORTIONS - info.portions_for_operator;
|
||||
for (const auto& contributor : info.contributors)
|
||||
{
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(contributor.amount, remaining_portions, &hi);
|
||||
div128_64(hi, lo, info.staking_requirement, &resulthi, &resultlo);
|
||||
uint64_t portion = mul128_div64(contributor.amount, portions_after_fee, info.staking_requirement);
|
||||
|
||||
if (contributor.address == info.operator_address)
|
||||
resultlo += info.portions_for_operator;
|
||||
result.payouts.push_back({contributor.address, resultlo});
|
||||
portion += info.portions_for_operator;
|
||||
result.payouts.push_back({contributor.address, portion});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -32,7 +32,9 @@
|
|||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string_view>
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
#include "cryptonote_basic/hardfork.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "serialization/serialization.h"
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "cryptonote_core/service_node_rules.h"
|
||||
|
@ -463,7 +465,7 @@ namespace service_nodes
|
|||
bool pop_batching_rewards_block(const cryptonote::block& block);
|
||||
void blockchain_detached(uint64_t height, bool by_pop_blocks) override;
|
||||
void init() override;
|
||||
bool validate_miner_tx(cryptonote::block const &block, cryptonote::block_reward_parts const &base_reward, std::optional<std::vector<cryptonote::batch_sn_payment>> const &batched_sn_payments) const override;
|
||||
bool validate_miner_tx(const cryptonote::block& block, const cryptonote::block_reward_parts& base_reward, const std::optional<std::vector<cryptonote::batch_sn_payment>>& batched_sn_payments) const override;
|
||||
bool alt_block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint) override;
|
||||
payout get_block_leader() const { std::lock_guard lock{m_sn_mutex}; return m_state.get_block_leader(); }
|
||||
bool is_service_node(const crypto::public_key& pubkey, bool require_active = true) const;
|
||||
|
@ -791,28 +793,42 @@ namespace service_nodes
|
|||
bool tx_get_staking_components (cryptonote::transaction const &tx, staking_components *contribution);
|
||||
bool tx_get_staking_components_and_amounts(cryptonote::network_type nettype, cryptonote::hf hf_version, cryptonote::transaction const &tx, uint64_t block_height, staking_components *contribution);
|
||||
|
||||
struct contributor_args_t
|
||||
{
|
||||
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
|
||||
using contribution = std::pair<cryptonote::account_public_address, uint64_t>;
|
||||
struct registration_details {
|
||||
crypto::public_key service_node_pubkey;
|
||||
std::vector<contribution> reserved;
|
||||
uint64_t fee;
|
||||
uint64_t hf; // expiration timestamp before HF19
|
||||
bool uses_portions; // if true then `hf` is a timestamp
|
||||
crypto::signature signature;
|
||||
};
|
||||
|
||||
bool is_registration_tx (cryptonote::network_type nettype, cryptonote::hf hf_version, const cryptonote::transaction& tx, uint64_t block_timestamp, uint64_t block_height, uint32_t index, crypto::public_key& key, service_node_info& info);
|
||||
bool reg_tx_extract_fields(const cryptonote::transaction& tx, contributor_args_t &contributor_args, uint64_t& expiration_timestamp, crypto::public_key& service_node_key, crypto::signature& signature);
|
||||
bool is_registration_tx(
|
||||
cryptonote::network_type nettype,
|
||||
cryptonote::hf hf_version,
|
||||
const cryptonote::transaction& tx,
|
||||
uint64_t block_timestamp,
|
||||
uint64_t block_height,
|
||||
uint32_t index,
|
||||
crypto::public_key& key,
|
||||
service_node_info& info);
|
||||
std::optional<registration_details> reg_tx_extract_fields(const cryptonote::transaction& tx);
|
||||
uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height);
|
||||
|
||||
contributor_args_t convert_registration_args(cryptonote::network_type nettype,
|
||||
const std::vector<std::string> &args,
|
||||
uint64_t staking_requirement,
|
||||
cryptonote::hf hf_version);
|
||||
// validate_registration* and convert_registration_args functions throws this on error:
|
||||
struct invalid_registration : std::invalid_argument { using std::invalid_argument::invalid_argument; };
|
||||
|
||||
// validate_contributors_* functions throws invalid_contributions exception
|
||||
struct invalid_contributions : std::invalid_argument { using std::invalid_argument::invalid_argument; };
|
||||
void validate_contributor_args(cryptonote::hf hf_version, contributor_args_t const &contributor_args);
|
||||
void validate_contributor_args_signature(contributor_args_t const &contributor_args, uint64_t const expiration_timestamp, crypto::public_key const &service_node_key, crypto::signature const &signature);
|
||||
// Converts string input values into a partially filled `registration_details`; pubkey and
|
||||
// signature will be defaulted. Throws invalid_registration on any invalid input.
|
||||
registration_details convert_registration_args(
|
||||
cryptonote::network_type nettype,
|
||||
cryptonote::hf hf_version,
|
||||
const std::vector<std::string>& args,
|
||||
uint64_t staking_requirement);
|
||||
|
||||
void validate_registration(cryptonote::hf hf_version, cryptonote::network_type nettype, uint64_t staking_requirement, uint64_t block_timestamp, const registration_details& registration);
|
||||
void validate_registration_signature(const registration_details& registration);
|
||||
crypto::hash get_registration_hash(const registration_details& registration);
|
||||
|
||||
bool make_registration_cmd(cryptonote::network_type nettype,
|
||||
cryptonote::hf hf_version,
|
||||
|
@ -836,7 +852,7 @@ namespace service_nodes
|
|||
// specified.
|
||||
std::vector<crypto::hash> get_pulse_entropy_for_next_block(cryptonote::BlockchainDB const &db, uint8_t pulse_round = 0);
|
||||
|
||||
payout service_node_info_to_payout(crypto::public_key const &key, service_node_info const &info);
|
||||
payout service_node_payout_portions(const crypto::public_key& key, const service_node_info& info);
|
||||
|
||||
const static payout_entry null_payout_entry = {cryptonote::null_address, cryptonote::old::STAKING_PORTIONS};
|
||||
const static payout null_payout = {crypto::null_pkey, {null_payout_entry}};
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
#include "common/oxen.h"
|
||||
#include "common/util.h"
|
||||
#include "epee/net/local_ip.h"
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
|
||||
#undef OXEN_DEFAULT_LOG_CATEGORY
|
||||
#define OXEN_DEFAULT_LOG_CATEGORY "quorum_cop"
|
||||
|
@ -127,7 +127,7 @@ namespace service_nodes
|
|||
}
|
||||
|
||||
// TODO: perhaps come back and make this activate on some "soft fork" height before HF19?
|
||||
if (!lokinet_reachable && hf_version >= hf::hf19)
|
||||
if (!lokinet_reachable && hf_version >= hf::hf19_reward_batching)
|
||||
{
|
||||
LOG_PRINT_L1("Service Node lokinet is not reachable for node: " << pubkey);
|
||||
result.lokinet_reachable = false;
|
||||
|
@ -748,7 +748,7 @@ namespace service_nodes
|
|||
std::memcpy(local.data(), pkdata + offset, prewrap);
|
||||
std::memcpy(local.data() + prewrap, pkdata, sizeof(uint64_t) - prewrap);
|
||||
}
|
||||
sum += boost::endian::little_to_native(*reinterpret_cast<uint64_t *>(local.data()));
|
||||
sum += oxenc::little_to_host(*reinterpret_cast<uint64_t *>(local.data()));
|
||||
++offset;
|
||||
}
|
||||
return sum;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#include "common/string_util.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "cryptonote_basic/hardfork.h"
|
||||
#include "common/oxen.h"
|
||||
#include "epee/int-util.h"
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
@ -15,6 +16,14 @@ using cryptonote::hf;
|
|||
|
||||
namespace service_nodes {
|
||||
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, hf hardfork)
|
||||
{
|
||||
assert(hardfork >= hf::hf16_pulse);
|
||||
return nettype == cryptonote::network_type::MAINNET
|
||||
? oxen::STAKING_REQUIREMENT
|
||||
: oxen::STAKING_REQUIREMENT_TESTNET;
|
||||
}
|
||||
|
||||
// TODO(oxen): Move to oxen_economy, this will also need access to oxen::exp2
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height)
|
||||
{
|
||||
|
@ -90,48 +99,80 @@ uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t heig
|
|||
|
||||
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement)
|
||||
{
|
||||
uint64_t hi, lo, resulthi, resultlo;
|
||||
lo = mul128(staking_requirement, portions, &hi);
|
||||
div128_64(hi, lo, cryptonote::old::STAKING_PORTIONS, &resulthi, &resultlo);
|
||||
return resultlo;
|
||||
return mul128_div64(staking_requirement, portions, cryptonote::old::STAKING_PORTIONS);
|
||||
}
|
||||
|
||||
bool check_service_node_portions(hf hf_version, const std::vector<uint64_t>& portions)
|
||||
bool check_service_node_portions(hf hf_version, const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& portions)
|
||||
{
|
||||
uint64_t portion_fuzz = hf_version >= hf::hf19 ? PORTION_FUZZ : 0;
|
||||
|
||||
const size_t max_contributors = hf_version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
if (portions.size() > max_contributors) {
|
||||
LOG_PRINT_L1("Registration tx rejected: too many contributors (" << portions.size() << " > " << max_contributors << ")");
|
||||
// When checking portion we always use HF18 rules, even on HF19, because a registration actually
|
||||
// generated under HF19+ won't get here.
|
||||
if (hf_version == hf::hf19_reward_batching)
|
||||
hf_version = hf::hf18;
|
||||
else if (hf_version > hf::hf19_reward_batching)
|
||||
{
|
||||
LOG_PRINT_L1("Registration tx rejected: portions-based registrations not permitted after HF19");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (portions[0] < MINIMUM_OPERATOR_PORTION - portion_fuzz)
|
||||
{
|
||||
LOG_PRINT_L1("Register TX rejected: TX does not have sufficient operator stake (" << portions[0] << " < " << MINIMUM_OPERATOR_PORTION << ")");
|
||||
if (portions.size() > oxen::MAX_CONTRIBUTORS_V1) {
|
||||
LOG_PRINT_L1("Registration tx rejected: too many contributors (" << portions.size() << " > " << oxen::MAX_CONTRIBUTORS_V1 << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t reserved = 0;
|
||||
for (auto i = 0u; i < portions.size(); ++i)
|
||||
uint64_t remaining = cryptonote::old::STAKING_PORTIONS;
|
||||
for (size_t i = 0; i < portions.size(); ++i)
|
||||
{
|
||||
|
||||
uint64_t min_portions = get_min_node_contribution(hf_version, cryptonote::old::STAKING_PORTIONS, reserved, i);
|
||||
|
||||
if (min_portions > portion_fuzz)
|
||||
min_portions -= portion_fuzz;
|
||||
|
||||
if (portions[i] < min_portions) {
|
||||
LOG_PRINT_L1("Registration tx rejected: portion " << i << " too small (" << portions[i] << " < " << min_portions << ")");
|
||||
const uint64_t min_portions = get_min_node_contribution(hf_version, cryptonote::old::STAKING_PORTIONS, reserved, i);
|
||||
if (portions[i].second < min_portions) {
|
||||
LOG_PRINT_L1("Registration tx rejected: portion " << i << " too small (" << portions[i].second << " < " << min_portions << ")");
|
||||
return false;
|
||||
}
|
||||
reserved += portions[i];
|
||||
if (portions[i].second > remaining) {
|
||||
LOG_PRINT_L1("Registration tx rejected: portion " << i << " exceeds available portions");
|
||||
return false;
|
||||
}
|
||||
|
||||
reserved += portions[i].second;
|
||||
remaining -= portions[i].second;
|
||||
}
|
||||
|
||||
if (reserved > cryptonote::old::STAKING_PORTIONS) {
|
||||
LOG_PRINT_L1("Registration tx rejected: total reserved amount too large");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_service_node_stakes(hf hf_version, cryptonote::network_type nettype, uint64_t staking_requirement, const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& stakes)
|
||||
{
|
||||
if (hf_version < hf::hf19_reward_batching) {
|
||||
LOG_PRINT_L1("Registration tx rejected: amount-based registrations not accepted before HF19");
|
||||
return false; // OXEN-based registrations not accepted before HF19
|
||||
}
|
||||
if (stakes.size() > oxen::MAX_CONTRIBUTORS_HF19) {
|
||||
LOG_PRINT_L1("Registration tx rejected: too many contributors (" << stakes.size() << " > " << oxen::MAX_CONTRIBUTORS_HF19 << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto operator_requirement = nettype == cryptonote::network_type::MAINNET
|
||||
? oxen::MINIMUM_OPERATOR_CONTRIBUTION
|
||||
: oxen::MINIMUM_OPERATOR_CONTRIBUTION_TESTNET;
|
||||
|
||||
uint64_t reserved = 0;
|
||||
uint64_t remaining = staking_requirement;
|
||||
for (size_t i = 0; i < stakes.size(); i++) {
|
||||
const uint64_t min_stake = i == 0 ? operator_requirement : get_min_node_contribution(hf_version, staking_requirement, reserved, i);
|
||||
|
||||
if (stakes[i].second < min_stake) {
|
||||
LOG_PRINT_L1("Registration tx rejected: stake " << i << " too small (" << stakes[i].second << " < " << min_stake << ")");
|
||||
return false;
|
||||
}
|
||||
if (stakes[i].second > remaining) {
|
||||
LOG_PRINT_L1("Registration tx rejected: stake " << i << " (" << stakes[i].second << ") exceeds available remaining stake (" << remaining << ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
reserved += stakes[i].second;
|
||||
remaining -= stakes[i].second;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -139,7 +180,7 @@ crypto::hash generate_request_stake_unlock_hash(uint32_t nonce)
|
|||
{
|
||||
static_assert(sizeof(crypto::hash) == 8 * sizeof(uint32_t) && alignof(crypto::hash) >= alignof(uint32_t));
|
||||
crypto::hash result;
|
||||
boost::endian::native_to_little_inplace(nonce);
|
||||
oxenc::host_to_little_inplace(nonce);
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
reinterpret_cast<uint32_t*>(result.data)[i] = nonce;
|
||||
return result;
|
||||
|
@ -172,8 +213,7 @@ uint64_t get_min_node_contribution(hf version, uint64_t staking_requirement, uin
|
|||
|
||||
const uint64_t needed = staking_requirement - total_reserved;
|
||||
|
||||
const size_t max_contributors = version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
assert(max_contributors > num_contributions);
|
||||
const size_t max_contributors = version >= hf::hf19_reward_batching ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1;
|
||||
if (max_contributors <= num_contributions) return UINT64_MAX;
|
||||
|
||||
const size_t num_contributions_remaining_avail = max_contributors - num_contributions;
|
||||
|
@ -214,24 +254,30 @@ static bool get_portions_from_percent(double cur_percent, uint64_t& portions) {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::optional<double> parse_fee_percent(std::string_view fee)
|
||||
{
|
||||
if (tools::ends_with(fee, "%"))
|
||||
fee.remove_suffix(1);
|
||||
|
||||
double percent;
|
||||
try {
|
||||
percent = boost::lexical_cast<double>(fee);
|
||||
} catch(...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (percent < 0 || percent > 100)
|
||||
return std::nullopt;
|
||||
|
||||
return percent;
|
||||
}
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions) {
|
||||
|
||||
if(!cut_str.empty() && cut_str.back() == '%')
|
||||
{
|
||||
cut_str.pop_back();
|
||||
}
|
||||
if (auto pct = parse_fee_percent(cut_str))
|
||||
return get_portions_from_percent(*pct, portions);
|
||||
|
||||
double cut_percent;
|
||||
try
|
||||
{
|
||||
cut_percent = boost::lexical_cast<double>(cut_str);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return get_portions_from_percent(cut_percent, portions);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace service_nodes
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "oxen_economy.h"
|
||||
#include "service_node_voting.h"
|
||||
#include <chrono>
|
||||
|
||||
|
@ -240,14 +241,9 @@ namespace service_nodes {
|
|||
//If the below percentage of service nodes are out of sync we will consider our clock out of sync
|
||||
inline constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80;
|
||||
|
||||
//The SN operator must contribute more than 25% of the nodes requirements
|
||||
inline constexpr uint64_t MINIMUM_OPERATOR_PORTION = cryptonote::old::STAKING_PORTIONS / oxen::MINIMUM_OPERATOR_DIVISOR;
|
||||
|
||||
// In HF19 we allow some "fuzz" in the staking portion requirements of up to this much. (These
|
||||
// are portions; with a 15k OXEN staking requirement, 1 nanoOXEN =~ 1.23 million portions). This
|
||||
// allows us to not have to worry about making slight shifts in the staked amounts to ensure the
|
||||
// staking portion doesn't fall below the required threshold.
|
||||
inline constexpr uint64_t PORTION_FUZZ = 20;
|
||||
// The SN operator must contribute at least 25% of the node's requirement, expressed as portions
|
||||
// (for pre-HF19 registrations).
|
||||
inline constexpr uint64_t MINIMUM_OPERATOR_PORTION = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_V1;
|
||||
|
||||
static_assert(cryptonote::old::STAKING_PORTIONS != UINT64_MAX, "UINT64_MAX is used as the invalid value for failing to calculate the min_node_contribution");
|
||||
// return: UINT64_MAX if (num_contributions > the max number of contributions), otherwise the amount in oxen atomic units
|
||||
|
@ -263,13 +259,30 @@ uint64_t get_min_node_contribution_in_portions(cryptonote::hf version, uint64_t
|
|||
// available contribution room, which allows slight overstaking but disallows larger overstakes.
|
||||
uint64_t get_max_node_contribution(cryptonote::hf version, uint64_t staking_requirement, uint64_t total_reserved);
|
||||
|
||||
// Returns the staking requirement at the given height; since HF16 (and always on testnet/devnet)
|
||||
// this is fixed, but before HF16 on mainnet this is height-dependent.
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, uint64_t height);
|
||||
|
||||
// Return the (fixed) staking requirement for a hardfork. This is only valid for hardfork 16+ as
|
||||
// earlier hardforks had a height-dependent staking requirement.
|
||||
uint64_t get_staking_requirement(cryptonote::network_type nettype, cryptonote::hf hardfork);
|
||||
|
||||
uint64_t portions_to_amount(uint64_t portions, uint64_t staking_requirement);
|
||||
|
||||
/// Check if portions are sufficiently large (provided the contributions
|
||||
/// are made in the specified order) and don't exceed the required amount
|
||||
bool check_service_node_portions(cryptonote::hf version, const std::vector<uint64_t>& portions);
|
||||
/// Check if portions (for pre-HF19 regisrations) are sufficiently large (provided the contributions
|
||||
/// are made in the specified order) and don't exceed the required amount. Note that this *always*
|
||||
/// enforces a limit of 4 contributors, even when under HF19+; registrations with more spots must
|
||||
/// use HF19+ registrations with amounts instead of portions.
|
||||
bool check_service_node_portions(
|
||||
cryptonote::hf version,
|
||||
const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& portions);
|
||||
|
||||
/// Check service node contribution amounts, for HF19+ registrations
|
||||
bool check_service_node_stakes(
|
||||
cryptonote::hf hf_version,
|
||||
cryptonote::network_type nettype,
|
||||
uint64_t staking_requirement,
|
||||
const std::vector<std::pair<cryptonote::account_public_address, uint64_t>>& stakes);
|
||||
|
||||
crypto::hash generate_request_stake_unlock_hash(uint32_t nonce);
|
||||
uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype, uint64_t node_register_height, uint64_t curr_height);
|
||||
|
@ -277,6 +290,8 @@ uint64_t get_locked_key_image_unlock_height(cryptonote::network_type nettype
|
|||
// Returns lowest x such that (staking_requirement * x/STAKING_PORTIONS) >= amount
|
||||
uint64_t get_portions_to_make_amount(uint64_t staking_requirement, uint64_t amount, uint64_t max_portions = cryptonote::old::STAKING_PORTIONS);
|
||||
|
||||
std::optional<double> parse_fee_percent(std::string_view fee);
|
||||
|
||||
bool get_portions_from_percent_str(std::string cut_str, uint64_t& portions);
|
||||
|
||||
}
|
||||
|
|
|
@ -280,7 +280,7 @@ namespace cryptonote
|
|||
return false;
|
||||
}
|
||||
|
||||
if (hf_version < hf::hf19)
|
||||
if (hf_version < hf::hf19_reward_batching)
|
||||
{
|
||||
if (!opts.kept_by_block && tx.is_transfer() && !m_blockchain.check_fee(tx_weight, tx.vout.size(), fee, burned, opts))
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -186,6 +186,8 @@ public:
|
|||
bool print_sr(uint64_t height);
|
||||
|
||||
bool prepare_registration(bool force_registration=false);
|
||||
// TODO FIXME: remove immediately after HF19 happens
|
||||
bool prepare_registration_hf18(cryptonote::hf hf_version, bool force_registration);
|
||||
|
||||
bool print_sn(const std::vector<std::string> &args);
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
#include "common/lock.h"
|
||||
#include "common/varint.h"
|
||||
#include <chrono>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
|
||||
#ifdef DEBUG_HWDEVICE
|
||||
#include <sodium/crypto_generichash.h>
|
||||
|
@ -434,19 +434,19 @@ namespace hw::ledger {
|
|||
}
|
||||
|
||||
void device_ledger::send_u32(uint32_t x, int& offset) {
|
||||
boost::endian::native_to_big_inplace(x);
|
||||
oxenc::host_to_big_inplace(x);
|
||||
send_bytes(&x, 4, offset);
|
||||
}
|
||||
|
||||
void device_ledger::send_u16(uint16_t x, int& offset) {
|
||||
boost::endian::native_to_big_inplace(x);
|
||||
oxenc::host_to_big_inplace(x);
|
||||
send_bytes(&x, 2, offset);
|
||||
}
|
||||
|
||||
uint32_t device_ledger::receive_u32(int& offset) {
|
||||
uint32_t x;
|
||||
receive_bytes(&x, 4, offset);
|
||||
boost::endian::big_to_native_inplace(x);
|
||||
oxenc::big_to_host_inplace(x);
|
||||
return x;
|
||||
}
|
||||
uint32_t device_ledger::receive_u32() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "io_ledger_tcp.hpp"
|
||||
#include "common/oxen.h"
|
||||
#include <array>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include "epee/misc_log_ex.h"
|
||||
|
@ -168,7 +168,7 @@ int ledger_tcp::exchange(const unsigned char* command, unsigned int cmd_len, uns
|
|||
throw std::runtime_error{"Unable to exchange data with hardware wallet: not connected"};
|
||||
|
||||
// Sending: [SIZE][DATA], where SIZE is a uint32_t in network order
|
||||
uint32_t size = boost::endian::native_to_big(cmd_len);
|
||||
uint32_t size = oxenc::host_to_big(cmd_len);
|
||||
const unsigned char* size_bytes = reinterpret_cast<const unsigned char*>(&size);
|
||||
full_write(*sockfd, size_bytes, 4);
|
||||
full_write(*sockfd, command, cmd_len);
|
||||
|
@ -178,7 +178,7 @@ int ledger_tcp::exchange(const unsigned char* command, unsigned int cmd_len, uns
|
|||
// bytes of DATA are a 2-byte, u16 status code and... therefore not... included. Good job, Ledger
|
||||
// devs.
|
||||
full_read(*sockfd, reinterpret_cast<unsigned char*>(&size), 4);
|
||||
auto data_size = boost::endian::big_to_native(size) + 2;
|
||||
auto data_size = oxenc::big_to_host(size) + 2;
|
||||
|
||||
if (data_size > max_resp_len)
|
||||
throw std::runtime_error{"Hardware wallet returned unexpectedly large response: got " +
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <common/apply_permutation.h>
|
||||
#include <common/json_util.h>
|
||||
#include <crypto/hmac-keccak.h>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/ip/udp.hpp>
|
||||
#include <oxenc/hex.h>
|
||||
|
@ -170,8 +170,8 @@ namespace trezor{
|
|||
}
|
||||
|
||||
static void serialize_message_header(void * buff, uint16_t tag, uint32_t len){
|
||||
uint16_t wire_tag = boost::endian::native_to_big(static_cast<uint16_t>(tag));
|
||||
uint32_t wire_len = boost::endian::native_to_big(static_cast<uint32_t>(len));
|
||||
uint16_t wire_tag = oxenc::host_to_big(static_cast<uint16_t>(tag));
|
||||
uint32_t wire_len = oxenc::host_to_big(static_cast<uint32_t>(len));
|
||||
memcpy(buff, (void *) &wire_tag, 2);
|
||||
memcpy((uint8_t*)buff + 2, (void *) &wire_len, 4);
|
||||
}
|
||||
|
@ -182,8 +182,8 @@ namespace trezor{
|
|||
memcpy(&wire_tag, buff, 2);
|
||||
memcpy(&wire_len, (uint8_t*)buff + 2, 4);
|
||||
|
||||
tag = boost::endian::big_to_native(wire_tag);
|
||||
len = boost::endian::big_to_native(wire_len);
|
||||
tag = oxenc::big_to_host(wire_tag);
|
||||
len = oxenc::big_to_host(wire_len);
|
||||
}
|
||||
|
||||
static void serialize_message(const google::protobuf::Message &req, size_t msg_size, uint8_t * buff, size_t buff_size) {
|
||||
|
|
|
@ -32,8 +32,7 @@
|
|||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/endian/arithmetic.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
@ -58,8 +57,8 @@ namespace socks
|
|||
{
|
||||
std::uint8_t version;
|
||||
std::uint8_t command_code;
|
||||
boost::endian::big_uint16_t port;
|
||||
boost::endian::big_uint32_t ip;
|
||||
std::uint16_t port;
|
||||
std::uint32_t ip;
|
||||
};
|
||||
|
||||
std::size_t write_domain_header(epee::span<std::uint8_t> out, const std::uint8_t command, const std::uint16_t port, std::string_view domain)
|
||||
|
@ -72,7 +71,7 @@ namespace socks
|
|||
return 0;
|
||||
|
||||
// version 4, 1 indicates invalid ip for domain extension
|
||||
const v4_header temp{4, command, port, std::uint32_t(1)};
|
||||
const v4_header temp{4, command, oxenc::host_to_little(port), oxenc::host_to_little(std::uint32_t{1})};
|
||||
std::memcpy(out.data(), std::addressof(temp), sizeof(temp));
|
||||
out.remove_prefix(sizeof(temp));
|
||||
|
||||
|
@ -243,7 +242,7 @@ namespace socks
|
|||
static_assert(0 < sizeof(buffer_), "buffer size too small for null termination");
|
||||
|
||||
// version 4
|
||||
const v4_header temp{4, v4_connect_command, address.port(), boost::endian::big_to_native(address.ip())};
|
||||
const v4_header temp{4, v4_connect_command, oxenc::host_to_big(address.port()), oxenc::host_to_big(address.ip())};
|
||||
std::memcpy(std::addressof(buffer_), std::addressof(temp), sizeof(temp));
|
||||
buffer_[sizeof(temp)] = 0;
|
||||
buffer_size_ = sizeof(temp) + 1;
|
||||
|
|
|
@ -54,8 +54,9 @@ inline constexpr size_t MAX_CONTRIBUTORS_HF19 = 10;
|
|||
// Max contributors before HF19:
|
||||
inline constexpr size_t MAX_CONTRIBUTORS_V1 = 4;
|
||||
|
||||
// Required operator contribution is 1/4 of the staking requirement
|
||||
inline constexpr uint64_t MINIMUM_OPERATOR_DIVISOR = 4;
|
||||
// Required operator contribution is 1/4 of the staking requirement:
|
||||
inline constexpr uint64_t MINIMUM_OPERATOR_CONTRIBUTION = STAKING_REQUIREMENT / 4;
|
||||
inline constexpr uint64_t MINIMUM_OPERATOR_CONTRIBUTION_TESTNET = STAKING_REQUIREMENT_TESTNET / 4;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
//
|
||||
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <future>
|
||||
#include <optional>
|
||||
#include <chrono>
|
||||
|
@ -220,7 +220,7 @@ namespace nodetool
|
|||
MERROR("Invalid ipv4:port given for --" << arg_tx_proxy.name);
|
||||
return std::nullopt;
|
||||
}
|
||||
set_proxy.address = ip::tcp::endpoint{ip::address_v4{boost::endian::native_to_big(ip)}, port};
|
||||
set_proxy.address = ip::tcp::endpoint{ip::address_v4{oxenc::host_to_big(ip)}, port};
|
||||
}
|
||||
|
||||
return proxies;
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
@ -736,12 +735,29 @@ namespace cryptonote { namespace rpc {
|
|||
void operator()(const tx_extra_service_node_pubkey& x) { entry.sn_pubkey = tools::type_to_hex(x.m_service_node_key); }
|
||||
void operator()(const tx_extra_service_node_register& x) {
|
||||
auto& reg = entry.sn_registration.emplace();
|
||||
reg.fee = microportion(x.m_portions_for_operator);
|
||||
reg.expiry = x.m_expiration_timestamp;
|
||||
for (size_t i = 0; i < x.m_portions.size(); i++) {
|
||||
auto& [wallet, portion] = reg.contributors.emplace_back();
|
||||
wallet = get_account_address_as_str(nettype, false, {x.m_public_spend_keys[i], x.m_public_view_keys[i]});
|
||||
portion = microportion(x.m_portions[i]);
|
||||
if (x.hf_or_expiration <= 255) { // hard fork value
|
||||
reg.hardfork = static_cast<hf>(x.hf_or_expiration);
|
||||
reg.fee = x.fee * 1'000'000 / STAKING_FEE_BASIS;
|
||||
} else { // timestamp
|
||||
reg.hardfork = hf::none;
|
||||
reg.expiry = x.hf_or_expiration;
|
||||
reg.fee = microportion(x.fee);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < x.amounts.size(); i++) {
|
||||
auto& [wallet, amount, portion] = reg.contributors.emplace_back();
|
||||
wallet = get_account_address_as_str(nettype, false, {x.public_spend_keys[i], x.public_view_keys[i]});
|
||||
if (reg.hardfork >= hf::hf19_reward_batching) {
|
||||
amount = x.amounts[i];
|
||||
// We aren't given info on whether this is testnet/mainnet, but we can guess by looking
|
||||
// at the operator amount, which has to be <= 100 on testnet, but >= 3750 on mainnet.
|
||||
auto nettype = x.amounts[0] > oxen::STAKING_REQUIREMENT_TESTNET
|
||||
? network_type::MAINNET : network_type::TESTNET;
|
||||
portion = std::lround(amount / (double) service_nodes::get_staking_requirement(nettype, reg.hardfork) * 1'000'000.0);
|
||||
} else {
|
||||
amount = 0;
|
||||
portion = microportion(x.amounts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
void operator()(const tx_extra_service_node_contributor& x) {
|
||||
|
|
|
@ -104,12 +104,14 @@ KV_SERIALIZE_MAP_CODE_END()
|
|||
|
||||
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::sn_reg_info::contribution)
|
||||
KV_SERIALIZE(wallet)
|
||||
if (this_ref.amount > 0) KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE(portion)
|
||||
KV_SERIALIZE_MAP_CODE_END()
|
||||
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::sn_reg_info)
|
||||
KV_SERIALIZE(contributors)
|
||||
KV_SERIALIZE(fee)
|
||||
KV_SERIALIZE(expiry)
|
||||
if (this_ref.hardfork >= hf::hf19_reward_batching) { KV_SERIALIZE_ENUM(hardfork) }
|
||||
else { KV_SERIALIZE(expiry) }
|
||||
KV_SERIALIZE_MAP_CODE_END()
|
||||
KV_SERIALIZE_MAP_CODE_BEGIN(GET_TRANSACTIONS::extra_entry::state_change)
|
||||
KV_SERIALIZE(old_dereg)
|
||||
|
|
|
@ -280,13 +280,15 @@ namespace rpc {
|
|||
struct contribution
|
||||
{
|
||||
std::string wallet; // Contributor wallet
|
||||
uint32_t portion; // Reserved portion, as the rounded nearest value out of 1'000'000 (i.e. 234567 == 23.4567%).
|
||||
uint64_t amount; // For HF19+ registrations, the atomic OXEN amount of the contribution. Omitted for older registrations.
|
||||
uint32_t portion; // Reserved portion, as the rounded nearest value out of 1'000'000 (i.e. 234567 == 23.4567%).
|
||||
KV_MAP_SERIALIZABLE
|
||||
};
|
||||
|
||||
std::vector<contribution> contributors; // Operator contribution plus any reserved contributions
|
||||
uint32_t fee; // Operator fee, as the rounded nearest value out of 1'000'000
|
||||
uint64_t expiry; // unix timestamp at which the registration expires
|
||||
uint32_t fee; // Operator fee, out of 1'000'000. For HF19+ registrations this is exact, for earlier ones this is rounded to the nearest value.
|
||||
hf hardfork; // For HF19+ registrations, this is the hard fork for which the registration is valid. Omitted for earlier registrations.
|
||||
uint64_t expiry; // For HF18 and earlier registrations, this is the unix timestamp at which the registration expires. Omitted for HF19+ registrations.
|
||||
KV_MAP_SERIALIZABLE
|
||||
};
|
||||
struct state_change
|
||||
|
@ -633,7 +635,7 @@ namespace rpc {
|
|||
bool mainnet; // States if the node is on the mainnet (`true`) or not (`false`).
|
||||
bool testnet; // States if the node is on the testnet (`true`) or not (`false`).
|
||||
bool devnet; // States if the node is on the devnet (`true`) or not (`false`).
|
||||
std::string nettype; // Nettype value used.
|
||||
std::string nettype; // Network type as a string ("mainnet", "testnet", "devnet", or "fakechain").
|
||||
std::string top_block_hash; // Hash of the highest block in the chain.
|
||||
std::string immutable_block_hash; // Hash of the highest block in the chain that can not be reorganized.
|
||||
uint64_t cumulative_difficulty; // Cumulative difficulty of all blocks in the blockchain.
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "http_server.h"
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenmq/variant.h>
|
||||
#include "common/command_line.h"
|
||||
#include "common/string_util.h"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "http_server_base.h"
|
||||
#include <oxenc/base64.h>
|
||||
#include <oxenc/hex.h>
|
||||
#include <oxenc/endian.h>
|
||||
#include "common/string_util.h"
|
||||
|
||||
// epee:
|
||||
|
@ -114,7 +115,7 @@ namespace cryptonote::rpc {
|
|||
// for example, localhost becomes `::1` instead of `0:0:0:0:0:0:0:1`).
|
||||
std::array<uint16_t, 8> a;
|
||||
std::memcpy(a.data(), addr.data(), 16);
|
||||
for (auto& x : a) boost::endian::big_to_native_inplace(x);
|
||||
for (auto& x : a) oxenc::big_to_host_inplace(x);
|
||||
|
||||
size_t zero_start = 0, zero_end = 0;
|
||||
for (size_t i = 0, start = 0, end = 0; i < a.size(); i++) {
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
#include <type_traits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
|
||||
#include "base.h"
|
||||
|
||||
|
@ -108,7 +108,7 @@ public:
|
|||
{
|
||||
stream_.read(reinterpret_cast<char*>(&v), sizeof(T));
|
||||
if constexpr (sizeof(T) > 1)
|
||||
boost::endian::little_to_native_inplace(v);
|
||||
oxenc::little_to_host_inplace(v);
|
||||
}
|
||||
|
||||
/// Serializes binary data of a given size by reading it directly into the given buffer
|
||||
|
@ -211,7 +211,7 @@ public:
|
|||
void serialize_int(T v)
|
||||
{
|
||||
if constexpr (sizeof(T) > 1)
|
||||
boost::endian::native_to_little_inplace(v);
|
||||
oxenc::host_to_little_inplace(v);
|
||||
stream_.write(reinterpret_cast<const char*>(&v), sizeof(T));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
add_library(sqlitedb
|
||||
database.cpp)
|
||||
|
||||
target_link_libraries(sqlitedb
|
||||
PUBLIC
|
||||
epee
|
||||
SQLiteCpp
|
||||
common
|
||||
PRIVATE
|
||||
SQLite::SQLite3
|
||||
fmt::fmt
|
||||
extra)
|
|
@ -0,0 +1,73 @@
|
|||
#include "database.hpp"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace db
|
||||
{
|
||||
|
||||
std::string multi_in_query(std::string_view prefix, size_t count, std::string_view suffix)
|
||||
{
|
||||
std::string query;
|
||||
query.reserve(prefix.size() + (count == 0 ? 0 : 2 * count - 1) + suffix.size());
|
||||
query += prefix;
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
query += ',';
|
||||
query += '?';
|
||||
}
|
||||
query += suffix;
|
||||
return query;
|
||||
}
|
||||
|
||||
Database::StatementWrapper Database::prepared_st(const std::string& query)
|
||||
{
|
||||
std::unordered_map<std::string, SQLite::Statement>* sts;
|
||||
{
|
||||
std::shared_lock rlock{prepared_sts_mutex};
|
||||
if (auto it = prepared_sts.find(std::this_thread::get_id()); it != prepared_sts.end())
|
||||
sts = &it->second;
|
||||
else
|
||||
{
|
||||
rlock.unlock();
|
||||
std::unique_lock wlock{prepared_sts_mutex};
|
||||
sts = &prepared_sts.try_emplace(std::this_thread::get_id()).first->second;
|
||||
}
|
||||
}
|
||||
if (auto qit = sts->find(query); qit != sts->end())
|
||||
return StatementWrapper{qit->second};
|
||||
return StatementWrapper{sts->try_emplace(query, db, query).first->second};
|
||||
}
|
||||
|
||||
Database::Database(const fs::path& db_path, const std::string_view db_password)
|
||||
: db{db_path.u8string(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE | SQLite::OPEN_FULLMUTEX, 5000/*ms*/}
|
||||
{
|
||||
// Don't fail on these because we can still work even if they fail
|
||||
if (int rc = db.tryExec("PRAGMA journal_mode = WAL"); rc != SQLITE_OK)
|
||||
MERROR("Failed to set journal mode to WAL: {}" << sqlite3_errstr(rc));
|
||||
|
||||
if (int rc = db.tryExec("PRAGMA synchronous = NORMAL"); rc != SQLITE_OK)
|
||||
MERROR("Failed to set synchronous mode to NORMAL: {}" << sqlite3_errstr(rc));
|
||||
|
||||
if (int rc = db.tryExec("PRAGMA foreign_keys = ON");
|
||||
rc != SQLITE_OK) {
|
||||
auto m = fmt::format("Failed to enable foreign keys constraints: {}", sqlite3_errstr(rc));
|
||||
MERROR(m);
|
||||
throw std::runtime_error{m};
|
||||
}
|
||||
int fk_enabled = db.execAndGet("PRAGMA foreign_keys").getInt();
|
||||
if (fk_enabled != 1) {
|
||||
MERROR("Failed to enable foreign key constraints; perhaps this sqlite3 is compiled without it?");
|
||||
throw std::runtime_error{"Foreign key support is required"};
|
||||
}
|
||||
|
||||
// FIXME: SQLite / SQLiteCPP may not have encryption available
|
||||
// so this may fail, or worse silently fail and do nothing
|
||||
if (not db_password.empty())
|
||||
{
|
||||
db.key(std::string{db_password});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace db
|
|
@ -3,15 +3,13 @@
|
|||
#include <epee/misc_log_ex.h>
|
||||
|
||||
#include <SQLiteCpp/SQLiteCpp.h>
|
||||
#include <sqlite3.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <exception>
|
||||
#include <string_view>
|
||||
#include <shared_mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <optional>
|
||||
|
||||
|
@ -22,9 +20,9 @@ namespace db
|
|||
template <typename T>
|
||||
constexpr bool is_cstr = false;
|
||||
template <size_t N>
|
||||
constexpr bool is_cstr<char[N]> = true;
|
||||
inline constexpr bool is_cstr<char[N]> = true;
|
||||
template <size_t N>
|
||||
constexpr bool is_cstr<const char[N]> = true;
|
||||
inline constexpr bool is_cstr<const char[N]> = true;
|
||||
template <>
|
||||
inline constexpr bool is_cstr<char*> = true;
|
||||
template <>
|
||||
|
@ -179,21 +177,7 @@ namespace db
|
|||
|
||||
// Takes a query prefix and suffix and places <count> ? separated by commas between them
|
||||
// Example: multi_in_query("foo(", 3, ")bar") will return "foo(?,?,?)bar"
|
||||
inline std::string
|
||||
multi_in_query(std::string_view prefix, size_t count, std::string_view suffix)
|
||||
{
|
||||
std::string query;
|
||||
query.reserve(prefix.size() + (count == 0 ? 0 : 2 * count - 1) + suffix.size());
|
||||
query += prefix;
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
query += ',';
|
||||
query += '?';
|
||||
}
|
||||
query += suffix;
|
||||
return query;
|
||||
}
|
||||
std::string multi_in_query(std::string_view prefix, size_t count, std::string_view suffix);
|
||||
|
||||
// Storage database class.
|
||||
class Database
|
||||
|
@ -213,57 +197,104 @@ namespace db
|
|||
/** Wrapper around a SQLite::Statement that calls `tryReset()` on destruction of the wrapper. */
|
||||
class StatementWrapper
|
||||
{
|
||||
protected:
|
||||
SQLite::Statement& st;
|
||||
|
||||
public:
|
||||
/// Whether we should reset on destruction; can be set to false if needed.
|
||||
bool reset_on_destruction = true;
|
||||
|
||||
explicit StatementWrapper(SQLite::Statement& st) noexcept : st{st}
|
||||
{}
|
||||
explicit StatementWrapper(SQLite::Statement& st) noexcept : st{st} {}
|
||||
|
||||
StatementWrapper(StatementWrapper&& sw) noexcept : st{sw.st}
|
||||
{
|
||||
sw.reset_on_destruction = false;
|
||||
}
|
||||
|
||||
~StatementWrapper() noexcept
|
||||
{
|
||||
if (reset_on_destruction)
|
||||
st.tryReset();
|
||||
}
|
||||
SQLite::Statement&
|
||||
operator*() noexcept
|
||||
{
|
||||
return st;
|
||||
}
|
||||
SQLite::Statement*
|
||||
operator->() noexcept
|
||||
{
|
||||
return &st;
|
||||
}
|
||||
operator SQLite::Statement&() noexcept
|
||||
{
|
||||
return st;
|
||||
}
|
||||
SQLite::Statement& operator*() noexcept { return st; }
|
||||
SQLite::Statement* operator->() noexcept { return &st; }
|
||||
operator SQLite::Statement&() noexcept { return st; }
|
||||
};
|
||||
|
||||
/** Extends the above with the ability to iterate through results. */
|
||||
template <typename... T>
|
||||
class IterableStatementWrapper : StatementWrapper
|
||||
{
|
||||
public:
|
||||
using StatementWrapper::StatementWrapper;
|
||||
|
||||
class iterator {
|
||||
IterableStatementWrapper& sw;
|
||||
bool finished;
|
||||
explicit iterator(IterableStatementWrapper& sw, bool finished = false) : sw{sw}, finished{finished}
|
||||
{
|
||||
++*this;
|
||||
}
|
||||
friend class IterableStatementWrapper;
|
||||
|
||||
public:
|
||||
iterator(const iterator&) = delete;
|
||||
iterator(iterator&&) = delete;
|
||||
void operator=(const iterator&) = delete;
|
||||
void operator=(iterator&&) = delete;
|
||||
|
||||
type_or_tuple<T...> operator*() { return get<T...>(sw); }
|
||||
|
||||
iterator& operator++()
|
||||
{
|
||||
if (!finished)
|
||||
finished = !sw->executeStep();
|
||||
return *this;
|
||||
}
|
||||
void operator++(int) { ++*this; }
|
||||
bool operator==(const iterator& other) { return &sw == &other.sw && finished == other.finished; }
|
||||
bool operator!=(const iterator& other) { return !(*this == other); }
|
||||
|
||||
using value_type = type_or_tuple<T...>;
|
||||
using reference = value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const value_type*;
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
};
|
||||
|
||||
iterator begin() { return iterator{*this}; }
|
||||
iterator end() { return iterator{*this, true}; }
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
/// Prepares a query, caching it, and returns a wrapper that automatically resets the prepared
|
||||
/// statement on destruction.
|
||||
StatementWrapper
|
||||
prepared_st(const std::string& query)
|
||||
prepared_st(const std::string& query);
|
||||
|
||||
/// Prepares (with caching) and binds a query, returning the active statement handle. Like
|
||||
/// `prepared_st` the wrapper resets the prepared statement on destruction.
|
||||
template <typename... T>
|
||||
StatementWrapper
|
||||
prepared_bind(const std::string& query, const T&... bind)
|
||||
{
|
||||
std::unordered_map<std::string, SQLite::Statement>* sts;
|
||||
{
|
||||
std::shared_lock rlock{prepared_sts_mutex};
|
||||
if (auto it = prepared_sts.find(std::this_thread::get_id()); it != prepared_sts.end())
|
||||
sts = &it->second;
|
||||
else
|
||||
{
|
||||
rlock.unlock();
|
||||
std::unique_lock wlock{prepared_sts_mutex};
|
||||
sts = &prepared_sts.try_emplace(std::this_thread::get_id()).first->second;
|
||||
}
|
||||
}
|
||||
if (auto qit = sts->find(query); qit != sts->end())
|
||||
return StatementWrapper{qit->second};
|
||||
return StatementWrapper{sts->try_emplace(query, db, query).first->second};
|
||||
auto st = prepared_st(query);
|
||||
bind_oneshot(st, bind...);
|
||||
return st;
|
||||
}
|
||||
|
||||
/// Prepares (with caching), binds parameters, then returns an object that lets you iterate
|
||||
/// through results where each row is a T or tuple<T...>:
|
||||
template <typename... T, typename... Bind, typename = std::enable_if_t<sizeof...(T) != 0>>
|
||||
IterableStatementWrapper<T...>
|
||||
prepared_results(const std::string& query, const Bind&... bind)
|
||||
{
|
||||
return IterableStatementWrapper<T...>{prepared_bind(query, bind...)};
|
||||
}
|
||||
|
||||
/// Prepares (with caching) a query and then executes it, optionally binding the given
|
||||
/// parameters when executing.
|
||||
template <typename... T>
|
||||
int
|
||||
prepared_exec(const std::string& query, const T&... bind)
|
||||
|
@ -271,6 +302,8 @@ namespace db
|
|||
return exec_query(prepared_st(query), bind...);
|
||||
}
|
||||
|
||||
/// Prepares (with caching) a query that returns a single row (with optional bind parameters),
|
||||
/// executes it, and returns the value. Throws if the query returns 0 or more than 1 rows.
|
||||
template <typename... T, typename... Bind>
|
||||
auto
|
||||
prepared_get(const std::string& query, const Bind&... bind)
|
||||
|
@ -278,6 +311,9 @@ namespace db
|
|||
return exec_and_get<T...>(prepared_st(query), bind...);
|
||||
}
|
||||
|
||||
/// Prepares (with caching) a query that returns at most a single row (with optional bind
|
||||
/// parameters), executes it, and returns the value or nullopt if the query returned no rows.
|
||||
/// Throws if the query returns more than 1 rows.
|
||||
template <typename... T, typename... Bind>
|
||||
auto
|
||||
prepared_maybe_get(const std::string& query, const Bind&... bind)
|
||||
|
@ -285,35 +321,7 @@ namespace db
|
|||
return exec_and_maybe_get<T...>(prepared_st(query), bind...);
|
||||
}
|
||||
|
||||
explicit Database(const fs::path& db_path, const std::string_view db_password)
|
||||
: db{db_path.u8string(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE | SQLite::OPEN_FULLMUTEX, 5000/*ms*/}
|
||||
{
|
||||
// Don't fail on these because we can still work even if they fail
|
||||
if (int rc = db.tryExec("PRAGMA journal_mode = WAL"); rc != SQLITE_OK)
|
||||
MERROR("Failed to set journal mode to WAL: {}" << sqlite3_errstr(rc));
|
||||
|
||||
if (int rc = db.tryExec("PRAGMA synchronous = NORMAL"); rc != SQLITE_OK)
|
||||
MERROR("Failed to set synchronous mode to NORMAL: {}" << sqlite3_errstr(rc));
|
||||
|
||||
if (int rc = db.tryExec("PRAGMA foreign_keys = ON");
|
||||
rc != SQLITE_OK) {
|
||||
auto m = fmt::format("Failed to enable foreign keys constraints: {}", sqlite3_errstr(rc));
|
||||
MERROR(m);
|
||||
throw std::runtime_error{m};
|
||||
}
|
||||
int fk_enabled = db.execAndGet("PRAGMA foreign_keys").getInt();
|
||||
if (fk_enabled != 1) {
|
||||
MERROR("Failed to enable foreign key constraints; perhaps this sqlite3 is compiled without it?");
|
||||
throw std::runtime_error{"Foreign key support is required"};
|
||||
}
|
||||
|
||||
// FIXME: SQLite / SQLiteCPP may not have encryption available
|
||||
// so this may fail, or worse silently fail and do nothing
|
||||
if (not db_password.empty())
|
||||
{
|
||||
db.key(std::string{db_password});
|
||||
}
|
||||
}
|
||||
explicit Database(const fs::path& db_path, const std::string_view db_password);
|
||||
|
||||
~Database() = default;
|
||||
};
|
||||
|
|
|
@ -110,6 +110,7 @@ if (STATIC AND BUILD_STATIC_DEPS)
|
|||
cryptonote_core
|
||||
cryptonote_basic
|
||||
cryptonote_protocol
|
||||
sqlitedb
|
||||
mnemonics
|
||||
common
|
||||
cncrypto
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <type_traits>
|
||||
#include <cpr/parameters.h>
|
||||
#include <oxenc/base64.h>
|
||||
#include <oxenc/endian.h>
|
||||
#include "common/password.h"
|
||||
#include "common/string_util.h"
|
||||
#include "cryptonote_basic/tx_extra.h"
|
||||
|
@ -8167,7 +8168,7 @@ wallet2::stake_result wallet2::check_stake_allowed(const crypto::public_key& sn_
|
|||
}
|
||||
|
||||
const bool full = snode_info.contributors.size() >= (
|
||||
*hf_version >= hf::hf19 ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1);
|
||||
*hf_version >= hf::hf19_reward_batching ? oxen::MAX_CONTRIBUTORS_HF19 : oxen::MAX_CONTRIBUTORS_V1);
|
||||
if (full && !is_preexisting_contributor)
|
||||
{
|
||||
result.status = stake_result_status::service_node_contributors_maxed;
|
||||
|
@ -8323,11 +8324,8 @@ wallet2::register_service_node_result wallet2::create_register_service_node_tx(c
|
|||
if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=")
|
||||
{
|
||||
if (!tools::parse_subaddress_indices(local_args[0], subaddr_indices))
|
||||
{
|
||||
result.status = register_service_node_result_status::subaddr_indices_parse_fail;
|
||||
result.msg = tr("Could not parse subaddress indices argument: ") + local_args[0];
|
||||
return result;
|
||||
}
|
||||
return {register_service_node_result_status::subaddr_indices_parse_fail,
|
||||
tr("Could not parse subaddress indices argument: ") + local_args[0]};
|
||||
|
||||
local_args.erase(local_args.begin());
|
||||
}
|
||||
|
@ -8336,19 +8334,13 @@ wallet2::register_service_node_result wallet2::create_register_service_node_tx(c
|
|||
local_args.erase(local_args.begin());
|
||||
|
||||
if (priority == tx_priority_blink)
|
||||
{
|
||||
result.status = register_service_node_result_status::no_blink;
|
||||
result.msg += tr("Service node registrations cannot use blink priority");
|
||||
return result;
|
||||
}
|
||||
return {register_service_node_result_status::no_blink,
|
||||
tr("Service node registrations cannot use blink priority")};
|
||||
|
||||
if (local_args.size() < 6)
|
||||
{
|
||||
result.status = register_service_node_result_status::insufficient_num_args;
|
||||
result.msg += tr("\nPrepare this command in the daemon with the prepare_registration command");
|
||||
result.msg += tr("\nThis command must be run from the daemon that will be acting as a service node");
|
||||
return result;
|
||||
}
|
||||
return {register_service_node_result_status::insufficient_num_args,
|
||||
std::string{tr("\nPrepare this command in the daemon with the prepare_registration command")}
|
||||
+ tr("\nThis command must be run from the daemon that will be acting as a service node")};
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -8356,198 +8348,166 @@ wallet2::register_service_node_result wallet2::create_register_service_node_tx(c
|
|||
//
|
||||
auto hf_version = get_hard_fork_version();
|
||||
if (!hf_version)
|
||||
{
|
||||
result.status = register_service_node_result_status::network_version_query_failed;
|
||||
result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
|
||||
return result;
|
||||
}
|
||||
return {register_service_node_result_status::network_version_query_failed,
|
||||
ERR_MSG_NETWORK_VERSION_QUERY_FAILED};
|
||||
|
||||
uint64_t staking_requirement = 0, bc_height = 0;
|
||||
service_nodes::contributor_args_t contributor_args = {};
|
||||
uint64_t bc_height;
|
||||
{
|
||||
std::string err, err2;
|
||||
bc_height = std::max(get_daemon_blockchain_height(err),
|
||||
get_daemon_blockchain_target_height(err2));
|
||||
{
|
||||
if (!err.empty() || !err2.empty())
|
||||
{
|
||||
result.msg = ERR_MSG_NETWORK_HEIGHT_QUERY_FAILED;
|
||||
result.msg += (err.empty() ? err2 : err);
|
||||
result.status = register_service_node_result_status::network_height_query_failed;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!is_synced(1))
|
||||
{
|
||||
result.status = register_service_node_result_status::wallet_not_synced;
|
||||
result.msg = tr("Wallet is not synced. Please synchronise your wallet to the blockchain");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
staking_requirement = service_nodes::get_staking_requirement(nettype(), bc_height);
|
||||
std::vector<std::string> const args(local_args.begin(), local_args.begin() + local_args.size() - 3);
|
||||
contributor_args = service_nodes::convert_registration_args(nettype(), args, staking_requirement, *hf_version);
|
||||
|
||||
if (!contributor_args.success)
|
||||
{
|
||||
result.status = register_service_node_result_status::convert_registration_args_failed;
|
||||
result.msg = tr("Could not convert registration args, reason: ") + contributor_args.err_msg;
|
||||
return result;
|
||||
}
|
||||
if (!err.empty() || !err2.empty())
|
||||
return {register_service_node_result_status::network_height_query_failed,
|
||||
ERR_MSG_NETWORK_HEIGHT_QUERY_FAILED + (err.empty() ? err2 : err)};
|
||||
}
|
||||
|
||||
cryptonote::account_public_address address = contributor_args.addresses[0];
|
||||
if (!is_synced(1))
|
||||
return {register_service_node_result_status::wallet_not_synced,
|
||||
tr("Wallet is not synced. Please synchronise your wallet to the blockchain")};
|
||||
|
||||
auto staking_requirement = service_nodes::get_staking_requirement(nettype(), bc_height);
|
||||
service_nodes::registration_details registration;
|
||||
try {
|
||||
registration = service_nodes::convert_registration_args(
|
||||
nettype(),
|
||||
*hf_version,
|
||||
std::vector<std::string>{local_args.begin(), std::prev(local_args.end(), 3)},
|
||||
staking_requirement);
|
||||
} catch (const std::exception& e) {
|
||||
return {register_service_node_result_status::convert_registration_args_failed,
|
||||
tr("Could not convert registration args: ") + std::string{e.what()}};
|
||||
}
|
||||
|
||||
auto address = registration.reserved[0].first;
|
||||
if (!contains_address(address))
|
||||
{
|
||||
result.status = register_service_node_result_status::first_address_must_be_primary_address;
|
||||
result.msg = tr(
|
||||
"The first reserved address for this registration does not belong to this wallet.\n"
|
||||
"Service node operator must specify an address owned by this wallet for service node registration."
|
||||
);
|
||||
return result;
|
||||
}
|
||||
return {register_service_node_result_status::first_address_must_be_primary_address,
|
||||
tr("The first reserved address for this registration does not belong to this wallet.\n"
|
||||
"Service node operator must specify an address owned by this wallet for service node registration.")};
|
||||
|
||||
|
||||
//
|
||||
// Parse Registration Metadata Args
|
||||
//
|
||||
size_t const timestamp_index = local_args.size() - 3;
|
||||
size_t const key_index = local_args.size() - 2;
|
||||
size_t const signature_index = local_args.size() - 1;
|
||||
const std::string &service_node_key_as_str = local_args[key_index];
|
||||
size_t const hf_index = local_args.size() - 3;
|
||||
size_t const pubkey_index = local_args.size() - 2;
|
||||
size_t const signature_index = local_args.size() - 1;
|
||||
const std::string &service_node_key_as_str = local_args[pubkey_index];
|
||||
|
||||
crypto::public_key service_node_key;
|
||||
crypto::signature signature;
|
||||
uint64_t expiration_timestamp = 0;
|
||||
uint64_t hf_or_expiration;
|
||||
if (!tools::parse_int<uint64_t>(local_args[hf_index], hf_or_expiration))
|
||||
return {register_service_node_result_status::registration_timestamp_parse_fail,
|
||||
tr("Failed to parse registration hf and/or timestamp") + " '"s + local_args[hf_index] + "'"};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if (registration.uses_portions)
|
||||
{
|
||||
try
|
||||
{
|
||||
expiration_timestamp = boost::lexical_cast<uint64_t>(local_args[timestamp_index]);
|
||||
if (expiration_timestamp <= (uint64_t)time(nullptr) + 600 /* 10 minutes */)
|
||||
{
|
||||
result.status = register_service_node_result_status::registration_timestamp_expired;
|
||||
result.msg = tr("The registration timestamp has expired.");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
result.status = register_service_node_result_status::registration_timestamp_expired;
|
||||
result.msg = tr("The registration timestamp failed to parse: ") + local_args[timestamp_index];
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!tools::hex_to_type(local_args[key_index], service_node_key))
|
||||
{
|
||||
result.status = register_service_node_result_status::service_node_key_parse_fail;
|
||||
result.msg = tr("Failed to parse service node pubkey");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!tools::hex_to_type(local_args[signature_index], signature))
|
||||
{
|
||||
result.status = register_service_node_result_status::service_node_signature_parse_fail;
|
||||
result.msg = tr("Failed to parse service node signature");
|
||||
return result;
|
||||
}
|
||||
if (static_cast<time_t>(hf_or_expiration) <= std::chrono::system_clock::to_time_t(now + 10min))
|
||||
return {register_service_node_result_status::registration_timestamp_expired,
|
||||
tr("The registration timestamp has expired.")};
|
||||
registration.hf = hf_or_expiration;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (registration.hf != hf_or_expiration)
|
||||
return {register_service_node_result_status::registration_timestamp_expired,
|
||||
tr("The registration has the wrong hard fork: ") +
|
||||
std::to_string(hf_or_expiration) + " != " + std::to_string(registration.hf)};
|
||||
}
|
||||
|
||||
if (!tools::hex_to_type(local_args[pubkey_index], registration.service_node_pubkey))
|
||||
return {register_service_node_result_status::service_node_key_parse_fail,
|
||||
tr("Failed to parse service node pubkey")};
|
||||
|
||||
if (!tools::hex_to_type(local_args[signature_index], registration.signature))
|
||||
return {register_service_node_result_status::service_node_signature_parse_fail,
|
||||
tr("Failed to parse service node signature")};
|
||||
|
||||
try
|
||||
{
|
||||
service_nodes::validate_contributor_args(*hf_version, contributor_args);
|
||||
service_nodes::validate_contributor_args_signature(contributor_args, expiration_timestamp, service_node_key, signature);
|
||||
service_nodes::validate_registration(*hf_version, nettype(), staking_requirement, std::chrono::system_clock::to_time_t(now), registration);
|
||||
service_nodes::validate_registration_signature(registration);
|
||||
}
|
||||
catch(const service_nodes::invalid_contributions &e)
|
||||
catch (const service_nodes::invalid_registration& e)
|
||||
{
|
||||
result.status = register_service_node_result_status::validate_contributor_args_fail;
|
||||
result.msg = e.what();
|
||||
return result;
|
||||
return {register_service_node_result_status::validate_registration_args_fail,
|
||||
e.what()};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> extra;
|
||||
add_service_node_contributor_to_tx_extra(extra, address);
|
||||
add_service_node_pubkey_to_tx_extra(extra, service_node_key);
|
||||
if (!add_service_node_register_to_tx_extra(extra, contributor_args.addresses, contributor_args.portions_for_operator, contributor_args.portions, expiration_timestamp, signature))
|
||||
{
|
||||
result.status = register_service_node_result_status::service_node_register_serialize_to_tx_extra_fail;
|
||||
result.msg = tr("Failed to serialize service node registration tx extra");
|
||||
return result;
|
||||
}
|
||||
add_service_node_pubkey_to_tx_extra(extra, registration.service_node_pubkey);
|
||||
if (!add_service_node_registration_to_tx_extra(extra, registration))
|
||||
return {register_service_node_result_status::service_node_register_serialize_to_tx_extra_fail,
|
||||
tr("Failed to serialize service node registration tx extra")};
|
||||
|
||||
//
|
||||
// Check service is able to be registered
|
||||
//
|
||||
refresh(false);
|
||||
{
|
||||
const auto [success, response] = get_service_nodes({service_node_key_as_str});
|
||||
if (!success)
|
||||
{
|
||||
result.status = register_service_node_result_status::service_node_list_query_failed;
|
||||
result.msg = ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (response.size() >= 1)
|
||||
{
|
||||
result.status = register_service_node_result_status::service_node_cannot_reregister;
|
||||
result.msg = tr("This service node is already registered");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (const auto [success, response] = get_service_nodes({service_node_key_as_str});
|
||||
!success)
|
||||
return {register_service_node_result_status::service_node_list_query_failed,
|
||||
ERR_MSG_NETWORK_VERSION_QUERY_FAILED};
|
||||
else if (response.size() >= 1)
|
||||
return {register_service_node_result_status::service_node_cannot_reregister,
|
||||
tr("This service node is already registered")};
|
||||
|
||||
//
|
||||
// Create Register Transaction
|
||||
//
|
||||
uint64_t amount_payable_by_operator = 0;
|
||||
if (!registration.uses_portions)
|
||||
{
|
||||
uint64_t amount_payable_by_operator = 0;
|
||||
amount_payable_by_operator = registration.reserved.at(0).second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: all of this can be deleted after HF19 because it won't be used anymore
|
||||
const uint64_t DUST = oxen::MAX_CONTRIBUTORS_V1;
|
||||
uint64_t amount_left = staking_requirement;
|
||||
for (size_t i = 0; i < registration.reserved.size(); i++)
|
||||
{
|
||||
const uint64_t DUST = oxen::MAX_CONTRIBUTORS_HF19;
|
||||
uint64_t amount_left = staking_requirement;
|
||||
for (size_t i = 0; i < contributor_args.portions.size(); i++)
|
||||
{
|
||||
uint64_t amount = service_nodes::portions_to_amount(staking_requirement, contributor_args.portions[i]);
|
||||
if (i == 0) amount_payable_by_operator += amount;
|
||||
amount_left -= amount;
|
||||
}
|
||||
|
||||
if (amount_left <= DUST)
|
||||
amount_payable_by_operator += amount_left;
|
||||
uint64_t amount = service_nodes::portions_to_amount(staking_requirement, registration.reserved[i].second);
|
||||
if (i == 0) amount_payable_by_operator += amount;
|
||||
amount_left -= amount;
|
||||
}
|
||||
|
||||
std::vector<cryptonote::tx_destination_entry> dsts;
|
||||
cryptonote::tx_destination_entry de;
|
||||
de.addr = address;
|
||||
de.is_subaddress = false;
|
||||
de.amount = amount_payable_by_operator;
|
||||
dsts.push_back(de);
|
||||
if (amount_left <= DUST)
|
||||
amount_payable_by_operator += amount_left;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// NOTE(oxen): We know the address should always be a primary address and has no payment id, so we can ignore the subaddress/payment id field here
|
||||
cryptonote::address_parse_info dest = {};
|
||||
dest.address = address;
|
||||
std::vector<cryptonote::tx_destination_entry> dsts;
|
||||
cryptonote::tx_destination_entry de;
|
||||
de.addr = address;
|
||||
de.is_subaddress = false;
|
||||
de.amount = amount_payable_by_operator;
|
||||
dsts.push_back(de);
|
||||
|
||||
oxen_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::stake, priority);
|
||||
auto ptx_vector = create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices, tx_params);
|
||||
if (ptx_vector.size() == 1)
|
||||
{
|
||||
result.status = register_service_node_result_status::success;
|
||||
result.ptx = ptx_vector[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
result.status = register_service_node_result_status::too_many_transactions_constructed;
|
||||
result.msg = ERR_MSG_TOO_MANY_TXS_CONSTRUCTED;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
try
|
||||
{
|
||||
// NOTE(oxen): We know the address should always be a primary address and has no payment id, so we can ignore the subaddress/payment id field here
|
||||
cryptonote::address_parse_info dest = {};
|
||||
dest.address = address;
|
||||
|
||||
oxen_construct_tx_params tx_params = tools::wallet2::construct_params(*hf_version, txtype::stake, priority);
|
||||
auto ptx_vector = create_transactions_2(dsts, cryptonote::TX_OUTPUT_DECOYS, 0 /* unlock_time */, priority, extra, subaddr_account, subaddr_indices, tx_params);
|
||||
if (ptx_vector.size() == 1)
|
||||
{
|
||||
result.status = register_service_node_result_status::exception_thrown;
|
||||
result.msg = ERR_MSG_EXCEPTION_THROWN;
|
||||
result.msg += e.what();
|
||||
return result;
|
||||
result.status = register_service_node_result_status::success;
|
||||
result.ptx = ptx_vector[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
result.status = register_service_node_result_status::too_many_transactions_constructed;
|
||||
result.msg = ERR_MSG_TOO_MANY_TXS_CONSTRUCTED;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
result.status = register_service_node_result_status::exception_thrown;
|
||||
result.msg = ERR_MSG_EXCEPTION_THROWN;
|
||||
result.msg += e.what();
|
||||
return result;
|
||||
}
|
||||
|
||||
assert(result.status != register_service_node_result_status::invalid);
|
||||
|
@ -13309,10 +13269,9 @@ bool wallet2::export_key_images_to_file(const fs::path& filename, bool requested
|
|||
std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images(requested_only);
|
||||
const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address;
|
||||
std::string data;
|
||||
const uint32_t offset = boost::endian::native_to_little(ski.first);
|
||||
data.reserve(sizeof(offset) + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key));
|
||||
data.resize(sizeof(offset));
|
||||
std::memcpy(&data[0], &offset, sizeof(offset));
|
||||
data.reserve(sizeof(uint32_t) + ski.second.size() * (sizeof(crypto::key_image) + sizeof(crypto::signature)) + 2 * sizeof(crypto::public_key));
|
||||
data.resize(sizeof(uint32_t));
|
||||
oxenc::write_host_as_little<uint32_t>(ski.first, data.data());
|
||||
data += tools::view_guts(keys.m_spend_public_key);
|
||||
data += tools::view_guts(keys.m_view_public_key);
|
||||
for (const auto &i: ski.second)
|
||||
|
@ -13410,9 +13369,7 @@ uint64_t wallet2::import_key_images_from_file(const fs::path& filename, uint64_t
|
|||
const size_t headerlen = 4 + 2 * sizeof(crypto::public_key);
|
||||
THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename.u8string());
|
||||
|
||||
uint32_t offset;
|
||||
std::memcpy(&offset, data.data(), sizeof(offset));
|
||||
boost::endian::little_to_native_inplace(offset);
|
||||
uint32_t offset = oxenc::load_little_to_host<uint32_t>(data.data());
|
||||
THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs");
|
||||
|
||||
// Validate embedded spend/view public keys
|
||||
|
|
|
@ -1386,7 +1386,7 @@ private:
|
|||
convert_registration_args_failed,
|
||||
registration_timestamp_expired,
|
||||
registration_timestamp_parse_fail,
|
||||
validate_contributor_args_fail,
|
||||
validate_registration_args_fail,
|
||||
service_node_key_parse_fail,
|
||||
service_node_signature_parse_fail,
|
||||
service_node_register_serialize_to_tx_extra_fail,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
#include <blockchain_db/sqlite/db_sqlite.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
inline fs::path check_if_copy_filename(std::string_view db_path) {
|
||||
return (db_path != ":memory:") ? fs::path(std::string(db_path) + "-copy") : fs::path(std::string(db_path));
|
||||
}
|
||||
|
||||
class BlockchainSQLiteTest : public cryptonote::BlockchainSQLite
|
||||
{
|
||||
public:
|
||||
BlockchainSQLiteTest(cryptonote::network_type nettype, fs::path db_path)
|
||||
: BlockchainSQLite(nettype, db_path) {};
|
||||
|
||||
|
||||
BlockchainSQLiteTest(BlockchainSQLiteTest &other)
|
||||
: BlockchainSQLiteTest(other.m_nettype, check_if_copy_filename(other.filename)) {
|
||||
auto all_payments_accrued = db::get_all<std::string, int64_t>(
|
||||
other.prepared_st("SELECT address, amount FROM batched_payments_accrued"));
|
||||
auto all_payments_paid = db::get_all<std::string, int64_t, int64_t>(
|
||||
other.prepared_st("SELECT address, amount, height_paid FROM batched_payments_raw"));
|
||||
|
||||
SQLite::Transaction transaction {
|
||||
db,
|
||||
SQLite::TransactionBehavior::IMMEDIATE
|
||||
};
|
||||
|
||||
auto insert_payment_paid = prepared_st(
|
||||
"INSERT INTO batched_payments_raw (address, amount, height_paid) VALUES (?, ?, ?)");
|
||||
|
||||
for (auto& [address, amount, height_paid]: all_payments_paid) {
|
||||
db::exec_query(insert_payment_paid, address, amount, height_paid);
|
||||
insert_payment_paid->reset();
|
||||
}
|
||||
|
||||
auto insert_payment_accrued = prepared_st(
|
||||
"INSERT INTO batched_payments_accrued (address, amount) VALUES (?, ?)");
|
||||
|
||||
for (auto& [address, amount]: all_payments_accrued) {
|
||||
db::exec_query(insert_payment_accrued, address, amount);
|
||||
insert_payment_accrued->reset();
|
||||
}
|
||||
|
||||
transaction.commit();
|
||||
|
||||
update_height(other.height);
|
||||
}
|
||||
|
||||
// Helper functions, used in testing to assess the state of the database
|
||||
uint64_t batching_count() {
|
||||
return prepared_get<int64_t>("SELECT count(*) FROM batched_payments_accrued WHERE amount >= 1000");
|
||||
}
|
||||
std::optional<uint64_t> retrieve_amount_by_address(const std::string& address) {
|
||||
if (auto maybe = prepared_maybe_get<int64_t>("SELECT amount FROM batched_payments_accrued WHERE address = ?"))
|
||||
return *maybe;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -221,9 +221,9 @@ bool gen_block_no_miner_tx::generate(std::vector<test_event_entry>& events) cons
|
|||
0, \
|
||||
TX, \
|
||||
cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner_account.get_keys().m_account_address), \
|
||||
std::nullopt, \
|
||||
{}, \
|
||||
cryptonote::hf::none); \
|
||||
{}, \
|
||||
cryptonote::hf::none); \
|
||||
if (!r) \
|
||||
return false;
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "common/hex.h"
|
||||
#include "common/varint.h"
|
||||
#include "common/median.h"
|
||||
#include "cryptonote_core/service_node_list.h"
|
||||
#include "epee/console_handler.h"
|
||||
#include "common/rules.h"
|
||||
|
||||
|
@ -168,7 +169,7 @@ std::vector<cryptonote::block> oxen_chain_generator_db::get_blocks_range(const u
|
|||
oxen_chain_generator::oxen_chain_generator(std::vector<test_event_entry>& events, const std::vector<cryptonote::hard_fork>& hard_forks, std::string first_miner_seed)
|
||||
: events_(events)
|
||||
, hard_forks_(hard_forks)
|
||||
, sqlite_db_(std::make_unique<cryptonote::BlockchainSQLiteTest>(cryptonote::network_type::FAKECHAIN, ":memory:"))
|
||||
, sqlite_db_(std::make_unique<test::BlockchainSQLiteTest>(cryptonote::network_type::FAKECHAIN, ":memory:"))
|
||||
{
|
||||
bool init = ons_db_->init(nullptr, cryptonote::network_type::FAKECHAIN, ons::init_oxen_name_system("", false /*read_only*/));
|
||||
assert(init);
|
||||
|
@ -451,61 +452,70 @@ cryptonote::transaction oxen_chain_generator::create_tx(const cryptonote::accoun
|
|||
}
|
||||
|
||||
cryptonote::transaction
|
||||
oxen_chain_generator::create_registration_tx(const cryptonote::account_base &src,
|
||||
const cryptonote::keypair &service_node_keys,
|
||||
uint64_t src_portions,
|
||||
uint64_t src_operator_cut,
|
||||
std::array<oxen_service_node_contribution, 3> const &contributions,
|
||||
int num_contributors) const
|
||||
oxen_chain_generator::create_registration_tx(const cryptonote::account_base& src,
|
||||
const cryptonote::keypair& service_node_keys,
|
||||
uint64_t operator_stake,
|
||||
uint64_t fee,
|
||||
const std::vector<service_nodes::contribution>& contributors
|
||||
) const
|
||||
{
|
||||
cryptonote::transaction result = {};
|
||||
{
|
||||
std::vector<cryptonote::account_public_address> contributors;
|
||||
std::vector<uint64_t> portions;
|
||||
uint64_t new_height = get_block_height(top().block) + 1;
|
||||
auto new_hf_version = get_hf_version_at(new_height);
|
||||
|
||||
contributors.reserve(1 + num_contributors);
|
||||
portions.reserve (1 + num_contributors);
|
||||
service_nodes::registration_details reg{};
|
||||
reg.fee = fee;
|
||||
reg.reserved.reserve(1 + contributors.size());
|
||||
reg.reserved.emplace_back(src.get_keys().m_account_address, operator_stake);
|
||||
reg.reserved.insert(reg.reserved.end(), contributors.begin(), contributors.end());
|
||||
|
||||
contributors.push_back(src.get_keys().m_account_address);
|
||||
portions.push_back(src_portions);
|
||||
for (int i = 0; i < num_contributors; i++)
|
||||
{
|
||||
oxen_service_node_contribution const &entry = contributions[i];
|
||||
contributors.push_back(entry.contributor);
|
||||
portions.push_back (entry.portions);
|
||||
if (new_hf_version >= hf::hf19_reward_batching) {
|
||||
assert(reg.reserved.size() <= oxen::MAX_CONTRIBUTORS_HF19);
|
||||
reg.hf = static_cast<uint8_t>(new_hf_version);
|
||||
} else {
|
||||
assert(reg.reserved.size() <= oxen::MAX_CONTRIBUTORS_V1);
|
||||
reg.uses_portions = true;
|
||||
reg.hf = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
assert(reg.fee <= cryptonote::STAKING_FEE_BASIS);
|
||||
reg.fee = mul128_div64(reg.fee, cryptonote::old::STAKING_PORTIONS, cryptonote::STAKING_FEE_BASIS);
|
||||
uint64_t total = 0;
|
||||
for (auto& [contrib, amount] : reg.reserved) {
|
||||
assert(amount <= oxen::STAKING_REQUIREMENT_TESTNET);
|
||||
amount = mul128_div64(amount, cryptonote::old::STAKING_PORTIONS, oxen::STAKING_REQUIREMENT_TESTNET);
|
||||
total += amount;
|
||||
}
|
||||
|
||||
uint64_t new_height = get_block_height(top().block) + 1;
|
||||
auto new_hf_version = get_hf_version_at(new_height);
|
||||
const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, new_height);
|
||||
uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement);
|
||||
assert(total <= cryptonote::old::STAKING_PORTIONS);
|
||||
|
||||
uint64_t unlock_time = 0;
|
||||
if (new_hf_version < hf::hf11_infinite_staking)
|
||||
unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN);
|
||||
|
||||
std::vector<uint8_t> extra;
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub);
|
||||
const uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
|
||||
crypto::hash hash;
|
||||
if (!cryptonote::get_registration_hash(contributors, src_operator_cut, portions, exp_timestamp, hash))
|
||||
{
|
||||
MERROR("Could not make registration hash from addresses and portions");
|
||||
return {};
|
||||
}
|
||||
|
||||
crypto::signature signature;
|
||||
crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, signature);
|
||||
add_service_node_register_to_tx_extra(extra, contributors, src_operator_cut, portions, exp_timestamp, signature);
|
||||
add_service_node_contributor_to_tx_extra(extra, contributors.at(0));
|
||||
oxen_tx_builder(events_, result, top().block, src /*from*/, src.get_keys().m_account_address /*to*/, amount, new_hf_version)
|
||||
.with_tx_type(cryptonote::txtype::stake)
|
||||
.with_unlock_time(unlock_time)
|
||||
.with_extra(extra)
|
||||
.build();
|
||||
uint64_t dust = cryptonote::old::STAKING_PORTIONS - total;
|
||||
if (dust <= reg.reserved.size())
|
||||
for (size_t i = 0; i < dust; i++)
|
||||
reg.reserved[i].second++;
|
||||
}
|
||||
|
||||
uint64_t unlock_time = 0;
|
||||
if (new_hf_version < hf::hf11_infinite_staking)
|
||||
unlock_time = new_height + service_nodes::staking_num_lock_blocks(cryptonote::network_type::FAKECHAIN);
|
||||
|
||||
std::vector<uint8_t> extra;
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub);
|
||||
|
||||
auto hash = service_nodes::get_registration_hash(reg);
|
||||
|
||||
auto block_ts = static_cast<uint64_t>(std::time(nullptr));
|
||||
const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, new_height);
|
||||
reg.service_node_pubkey = service_node_keys.pub;
|
||||
crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, reg.signature);
|
||||
|
||||
cryptonote::add_service_node_registration_to_tx_extra(extra, reg);
|
||||
add_service_node_contributor_to_tx_extra(extra, reg.reserved[0].first);
|
||||
|
||||
cryptonote::transaction result{};
|
||||
oxen_tx_builder(events_, result, top().block, src /*from*/, src.get_keys().m_account_address /*to*/, operator_stake, new_hf_version)
|
||||
.with_tx_type(cryptonote::txtype::stake)
|
||||
.with_unlock_time(unlock_time)
|
||||
.with_extra(extra)
|
||||
.build();
|
||||
|
||||
service_node_keys_[service_node_keys.pub] = service_node_keys.sec; // NOTE: Save generated key for reuse later if we need to interact with the node again
|
||||
return result;
|
||||
}
|
||||
|
@ -599,7 +609,9 @@ cryptonote::transaction oxen_chain_generator::create_state_change_tx(service_nod
|
|||
cryptonote::checkpoint_t oxen_chain_generator::create_service_node_checkpoint(uint64_t block_height, size_t num_votes) const
|
||||
{
|
||||
service_nodes::quorum const &quorum = *get_quorum(service_nodes::quorum_type::checkpointing, block_height);
|
||||
assert(num_votes < quorum.validators.size());
|
||||
if (num_votes >= quorum.validators.size())
|
||||
throw std::logic_error{"cannot create checkpoint with " + std::to_string(num_votes) +
|
||||
" votes with only " + std::to_string(quorum.validators.size()) + " validators"};
|
||||
|
||||
oxen_blockchain_entry const &entry = db_.blocks[block_height];
|
||||
crypto::hash const block_hash = cryptonote::get_block_hash(entry.block);
|
||||
|
@ -839,7 +851,6 @@ oxen_blockchain_entry oxen_chain_generator::create_genesis_block(const cryptonot
|
|||
|
||||
// TODO(doyle): Does this evaluate to 0? If so we can simplify this a lot more
|
||||
size_t target_block_weight = get_transaction_weight(blk.miner_tx);
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
@ -850,7 +861,7 @@ oxen_blockchain_entry oxen_chain_generator::create_genesis_block(const cryptonot
|
|||
0 /*total_fee*/,
|
||||
blk.miner_tx,
|
||||
cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner.get_keys().m_account_address),
|
||||
sn_rwds,
|
||||
{},
|
||||
std::string(),
|
||||
hf_version_);
|
||||
assert(constructed);
|
||||
|
@ -960,7 +971,7 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
|
|||
crypto::public_key block_producer_key = pulse_quorum.workers[0];
|
||||
auto it = params.prev.service_node_state.service_nodes_infos.find(block_producer_key);
|
||||
assert(it != params.prev.service_node_state.service_nodes_infos.end());
|
||||
block_producer = service_nodes::service_node_info_to_payout(block_producer_key, *(it->second));
|
||||
block_producer = service_nodes::service_node_payout_portions(block_producer_key, *(it->second));
|
||||
}
|
||||
|
||||
miner_tx_context = cryptonote::oxen_miner_tx_context::pulse_block(cryptonote::network_type::FAKECHAIN, block_producer, params.block_leader);
|
||||
|
@ -998,11 +1009,9 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
|
|||
}
|
||||
|
||||
size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
if (hf_version_ >= hf::hf19)
|
||||
{
|
||||
sn_rwds = sqlite_db_->get_sn_payments(height); //Rewards to pay out
|
||||
}
|
||||
auto sn_rwds = sqlite_db_->get_sn_payments(height);
|
||||
if (hf_version_ < hf::hf19_reward_batching)
|
||||
CHECK_AND_ASSERT_MES(sn_rwds.empty(), false, "batch payments should be empty before hf19");
|
||||
uint64_t block_rewards = 0;
|
||||
bool r;
|
||||
while (true)
|
||||
|
@ -1095,17 +1104,7 @@ void oxen_chain_generator::block_end(oxen_blockchain_entry &entry, oxen_create_b
|
|||
|
||||
bool oxen_chain_generator::process_registration_tx(cryptonote::transaction& tx, uint64_t block_height, hf hf_version)
|
||||
{
|
||||
service_nodes::contributor_args_t contributor_args = {};
|
||||
crypto::public_key service_node_key;
|
||||
uint64_t expiration_timestamp{0};
|
||||
crypto::signature signature;
|
||||
|
||||
if (!service_nodes::reg_tx_extract_fields(tx, contributor_args, expiration_timestamp, service_node_key, signature))
|
||||
return false;
|
||||
|
||||
uint64_t staking_requirement = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, block_height);
|
||||
|
||||
return true;
|
||||
return (bool) service_nodes::reg_tx_extract_fields(tx);
|
||||
}
|
||||
|
||||
bool oxen_chain_generator::create_block(oxen_blockchain_entry &entry,
|
||||
|
@ -1338,7 +1337,6 @@ bool test_generator::construct_block(cryptonote::block &blk,
|
|||
size_t target_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
|
||||
manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height);
|
||||
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
while (true)
|
||||
{
|
||||
auto [r, block_rewards] = construct_miner_tx(height,
|
||||
|
@ -1348,7 +1346,7 @@ bool test_generator::construct_block(cryptonote::block &blk,
|
|||
total_fee,
|
||||
blk.miner_tx,
|
||||
miner_tx_context,
|
||||
sn_rwds,
|
||||
{},
|
||||
std::string(),
|
||||
m_hf_version);
|
||||
if (!r)
|
||||
|
@ -1462,9 +1460,8 @@ bool test_generator::construct_block_manually(
|
|||
miner_tx_context.nettype = cryptonote::network_type::FAKECHAIN;
|
||||
manual_calc_batched_governance(*this, prev_id, miner_tx_context, m_hf_version, height);
|
||||
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
size_t current_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
|
||||
auto [r, block_rewards] = construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, current_block_weight, miner_fee, blk.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner_acc.get_keys().m_account_address), sn_rwds, std::string(), m_hf_version);
|
||||
auto [r, block_rewards] = construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, current_block_weight, miner_fee, blk.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, miner_acc.get_keys().m_account_address), {}, std::string(), m_hf_version);
|
||||
if (!r)
|
||||
return false;
|
||||
}
|
||||
|
@ -1499,6 +1496,12 @@ cryptonote::transaction make_registration_tx(std::vector<test_event_entry>& even
|
|||
const auto staking_requirement = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, new_height);
|
||||
uint64_t amount = service_nodes::portions_to_amount(portions[0], staking_requirement);
|
||||
|
||||
service_nodes::registration_details reg{};
|
||||
reg.uses_portions = true;
|
||||
reg.fee = operator_cut;
|
||||
reg.hf = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
reg.service_node_pubkey = service_node_keys.pub;
|
||||
|
||||
cryptonote::transaction tx;
|
||||
uint64_t unlock_time = 0;
|
||||
if (hf_version < hf::hf11_infinite_staking)
|
||||
|
@ -1506,18 +1509,10 @@ cryptonote::transaction make_registration_tx(std::vector<test_event_entry>& even
|
|||
|
||||
std::vector<uint8_t> extra;
|
||||
cryptonote::add_service_node_pubkey_to_tx_extra(extra, service_node_keys.pub);
|
||||
const uint64_t exp_timestamp = time(nullptr) + tools::to_seconds(cryptonote::old::STAKING_AUTHORIZATION_EXPIRATION_WINDOW);
|
||||
|
||||
crypto::hash hash;
|
||||
if (!cryptonote::get_registration_hash(contributors, operator_cut, portions, exp_timestamp, hash))
|
||||
{
|
||||
MERROR("Could not make registration hash from addresses and portions");
|
||||
return {};
|
||||
}
|
||||
|
||||
crypto::signature signature;
|
||||
crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, signature);
|
||||
add_service_node_register_to_tx_extra(extra, contributors, operator_cut, portions, exp_timestamp, signature);
|
||||
auto hash = service_nodes::get_registration_hash(reg);
|
||||
crypto::generate_signature(hash, service_node_keys.pub, service_node_keys.sec, reg.signature);
|
||||
cryptonote::add_service_node_registration_to_tx_extra(extra, reg);
|
||||
add_service_node_contributor_to_tx_extra(extra, contributors.at(0));
|
||||
|
||||
cryptonote::txtype tx_type = cryptonote::txtype::standard;
|
||||
|
|
|
@ -55,11 +55,14 @@
|
|||
#include "cryptonote_core/cryptonote_core.h"
|
||||
#include "cryptonote_basic/cryptonote_boost_serialization.h"
|
||||
#include "cryptonote_protocol/quorumnet.h"
|
||||
#include "oxen_economy.h"
|
||||
#include "serialization/boost_std_variant.h"
|
||||
#include "serialization/boost_std_optional.h"
|
||||
|
||||
#include "blockchain_db/testdb.h"
|
||||
|
||||
#include "../blockchain_sqlite_test.h"
|
||||
|
||||
#undef OXEN_DEFAULT_LOG_CATEGORY
|
||||
#define OXEN_DEFAULT_LOG_CATEGORY "tests.core"
|
||||
|
||||
|
@ -1379,12 +1382,6 @@ struct oxen_chain_generator_db : public cryptonote::BaseTestDB
|
|||
uint64_t height() const override { return blocks.size(); }
|
||||
};
|
||||
|
||||
struct oxen_service_node_contribution
|
||||
{
|
||||
cryptonote::account_public_address contributor;
|
||||
uint64_t portions;
|
||||
};
|
||||
|
||||
enum struct oxen_create_block_type
|
||||
{
|
||||
automatic,
|
||||
|
@ -1415,7 +1412,7 @@ struct oxen_chain_generator
|
|||
service_nodes::service_node_list::state_set state_history_;
|
||||
uint64_t last_cull_height_ = 0;
|
||||
std::shared_ptr<ons::name_system_db> ons_db_ = std::make_shared<ons::name_system_db>();
|
||||
std::unique_ptr<cryptonote::BlockchainSQLiteTest> sqlite_db_;
|
||||
std::unique_ptr<test::BlockchainSQLiteTest> sqlite_db_;
|
||||
oxen_chain_generator_db db_;
|
||||
cryptonote::hf hf_version_ = cryptonote::hf::hf7;
|
||||
std::vector<test_event_entry>& events_;
|
||||
|
@ -1424,7 +1421,7 @@ struct oxen_chain_generator
|
|||
|
||||
oxen_chain_generator(std::vector<test_event_entry>& events, const std::vector<cryptonote::hard_fork>& hard_forks, std::string first_miner_seed = "");
|
||||
oxen_chain_generator(const oxen_chain_generator &other)
|
||||
:tx_table_(other.tx_table_), service_node_keys_(other.service_node_keys_), state_history_(other.state_history_), last_cull_height_(other.last_cull_height_), sqlite_db_(std::make_unique<cryptonote::BlockchainSQLiteTest>(*other.sqlite_db_)),
|
||||
:tx_table_(other.tx_table_), service_node_keys_(other.service_node_keys_), state_history_(other.state_history_), last_cull_height_(other.last_cull_height_), sqlite_db_(std::make_unique<test::BlockchainSQLiteTest>(*other.sqlite_db_)),
|
||||
ons_db_(other.ons_db_ ), db_(other.db_), hf_version_(other.hf_version_), events_(other.events_), hard_forks_(other.hard_forks_), first_miner_(other.first_miner_) {};
|
||||
|
||||
uint64_t height() const { return cryptonote::get_block_height(db_.blocks.back().block); }
|
||||
|
@ -1469,12 +1466,11 @@ struct oxen_chain_generator
|
|||
|
||||
// NOTE: Create transactions but don't add to events_
|
||||
cryptonote::transaction create_tx(const cryptonote::account_base &src, const cryptonote::account_public_address &dest, uint64_t amount, uint64_t fee) const;
|
||||
cryptonote::transaction create_registration_tx(const cryptonote::account_base &src,
|
||||
const cryptonote::keypair &service_node_keys = cryptonote::keypair{hw::get_device("default")},
|
||||
uint64_t src_portions = cryptonote::old::STAKING_PORTIONS,
|
||||
uint64_t src_operator_cut = 0,
|
||||
std::array<oxen_service_node_contribution, 3> const &contributors = {},
|
||||
int num_contributors = 0) const;
|
||||
cryptonote::transaction create_registration_tx(const cryptonote::account_base& src,
|
||||
const cryptonote::keypair& service_node_keys = cryptonote::keypair{hw::get_device("default")},
|
||||
uint64_t operator_stake = oxen::STAKING_REQUIREMENT_TESTNET,
|
||||
uint64_t fee = cryptonote::STAKING_FEE_BASIS,
|
||||
const std::vector<service_nodes::contribution>& contributors = {}) const;
|
||||
cryptonote::transaction create_staking_tx (const crypto::public_key& pub_key, const cryptonote::account_base &src, uint64_t amount) const;
|
||||
cryptonote::transaction create_state_change_tx(service_nodes::new_state state, const crypto::public_key& pub_key, uint16_t reasons_all, uint16_t reasons_any, uint64_t height = -1, const std::vector<uint64_t>& voters = {}, uint64_t fee = 0) const;
|
||||
cryptonote::checkpoint_t create_service_node_checkpoint(uint64_t block_height, size_t num_votes) const;
|
||||
|
|
|
@ -111,7 +111,7 @@ bool gen_uint_overflow_1::generate(std::vector<test_event_entry>& events) const
|
|||
// Problem 1. Miner tx outputs overflow
|
||||
{
|
||||
oxen_blockchain_entry entry = gen.create_next_block();
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19)
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19_reward_batching)
|
||||
{
|
||||
cryptonote::transaction &miner_tx = entry.block.miner_tx;
|
||||
split_miner_tx_outs(miner_tx, oxen::MONEY_SUPPLY);
|
||||
|
@ -127,7 +127,7 @@ bool gen_uint_overflow_1::generate(std::vector<test_event_entry>& events) const
|
|||
txs.push_back(gen.create_and_add_tx(gen.first_miner_, alice.get_keys().m_account_address, MK_COINS(1), MK_COINS(100) /*fee*/, false /*kept_by_block*/));
|
||||
|
||||
oxen_blockchain_entry entry = gen.create_next_block(txs);
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19)
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19_reward_batching)
|
||||
{
|
||||
cryptonote::transaction &miner_tx = entry.block.miner_tx;
|
||||
miner_tx.vout[0].amount = 0; // Take partial block reward, fee > block_reward so ordinarly it would overflow. This should be disallowed
|
||||
|
@ -141,7 +141,7 @@ bool gen_uint_overflow_1::generate(std::vector<test_event_entry>& events) const
|
|||
txs.push_back(gen.create_and_add_tx(gen.first_miner_, alice.get_keys().m_account_address, MK_COINS(1), MK_COINS(100) /*fee*/, true /*kept_by_block*/));
|
||||
|
||||
oxen_blockchain_entry entry = gen.create_next_block(txs);
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19)
|
||||
if ( entry.block.major_version < cryptonote::hf::hf19_reward_batching)
|
||||
{
|
||||
cryptonote::transaction &miner_tx = entry.block.miner_tx;
|
||||
miner_tx.vout[0].amount = 0; // Take partial block reward, fee > block_reward so ordinarly it would overflow. This should be disallowed
|
||||
|
|
|
@ -473,7 +473,7 @@ bool oxen_core_block_reward_unpenalized_pre_pulse::generate(std::vector<test_eve
|
|||
|
||||
bool oxen_core_block_reward_unpenalized_post_pulse::generate(std::vector<test_event_entry>& events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf_prev(hf::hf19), 150 /*Proof Of Stake Delay*/);
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf_prev(hf::hf19_reward_batching), 150 /*Proof Of Stake Delay*/);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
const auto newest_hf = hard_forks.back().version;
|
||||
|
@ -2791,6 +2791,7 @@ bool oxen_service_nodes_test_rollback::generate(std::vector<test_event_entry>& e
|
|||
gen.create_and_add_next_block({tx});
|
||||
}
|
||||
|
||||
|
||||
fork.add_n_blocks(3); /// create blocks on the alt chain and trigger chain switch
|
||||
fork.add_n_blocks(15); // create a few more blocks to test winner selection
|
||||
oxen_register_callback(events, "test_registrations", [&events, deregister_index, reg_evnt_idx](cryptonote::core &c, size_t ev_index)
|
||||
|
@ -2985,10 +2986,9 @@ bool oxen_service_nodes_insufficient_contribution::generate(std::vector<test_eve
|
|||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = cryptonote::old::STAKING_PORTIONS / 2;
|
||||
uint64_t remaining_portions = cryptonote::old::STAKING_PORTIONS - operator_portions;
|
||||
uint64_t operator_amt = oxen::STAKING_REQUIREMENT_TESTNET / 2;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_amt);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
@ -3012,7 +3012,7 @@ bool oxen_service_nodes_insufficient_contribution::generate(std::vector<test_eve
|
|||
|
||||
bool oxen_service_nodes_insufficient_contribution_HF18::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf_prev(hf::hf19));
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf::hf18);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
|
@ -3023,13 +3023,12 @@ bool oxen_service_nodes_insufficient_contribution_HF18::generate(std::vector<tes
|
|||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_V1;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, hard_forks.back().height) / oxen::MAX_CONTRIBUTORS_V1;
|
||||
uint64_t remaining_portions = cryptonote::old::STAKING_PORTIONS - operator_portions;
|
||||
uint64_t single_portion_illegal_HF18_legal_HF19 = remaining_portions / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
uint64_t single_contributed_amount = (service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, hard_forks.back().height) - operator_amount) / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
uint64_t operator_amount = oxen::MINIMUM_OPERATOR_CONTRIBUTION_TESTNET;
|
||||
uint64_t remaining_amount = oxen::STAKING_REQUIREMENT_TESTNET - operator_amount;
|
||||
// This amount is too small under HF18 rules:
|
||||
uint64_t single_contributed_amount = remaining_amount / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_amount);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
@ -3038,9 +3037,9 @@ bool oxen_service_nodes_insufficient_contribution_HF18::generate(std::vector<tes
|
|||
cryptonote::transaction stake = gen.create_and_add_staking_tx(sn_keys.pub, alice, single_contributed_amount);
|
||||
gen.create_and_add_next_block({stake});
|
||||
|
||||
oxen_register_callback(events, "test_insufficient_stake_does_not_get_accepted", [sn_keys, operator_amount](cryptonote::core &c, size_t ev_index)
|
||||
oxen_register_callback(events, "test_insufficient_HF18_stake_does_not_get_accepted", [sn_keys, operator_amount](cryptonote::core &c, size_t ev_index)
|
||||
{
|
||||
DEFINE_TESTS_ERROR_CONTEXT("test_insufficient_stake_does_not_get_accepted");
|
||||
DEFINE_TESTS_ERROR_CONTEXT("test_insufficient_HF18_stake_does_not_get_accepted");
|
||||
const auto sn_list = c.get_service_node_list_state({sn_keys.pub});
|
||||
CHECK_TEST_CONDITION(sn_list.size() == 1);
|
||||
CHECK_TEST_CONDITION(sn_list[0].info->contributors.size() == 1);
|
||||
|
@ -3055,7 +3054,7 @@ bool oxen_service_nodes_insufficient_contribution_HF18::generate(std::vector<tes
|
|||
|
||||
bool oxen_service_nodes_sufficient_contribution_HF19::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf::hf19);
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf::hf19_reward_batching);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
|
@ -3066,14 +3065,13 @@ bool oxen_service_nodes_sufficient_contribution_HF19::generate(std::vector<test_
|
|||
gen.create_and_add_next_block({tx0});
|
||||
gen.add_transfer_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_V1;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, hard_forks.back().height) / oxen::MAX_CONTRIBUTORS_V1;
|
||||
uint64_t remaining_portions = cryptonote::old::STAKING_PORTIONS - operator_portions;
|
||||
uint64_t single_portion_illegal_HF18_legal_HF19 = remaining_portions / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
uint64_t single_contributed_amount = (service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, hard_forks.back().height) - operator_amount) / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
uint64_t operator_amount = oxen::MINIMUM_OPERATOR_CONTRIBUTION_TESTNET;
|
||||
uint64_t remaining_amount = oxen::STAKING_REQUIREMENT_TESTNET - operator_amount;
|
||||
// This amount is too small under HF18 rules, but is accepted under HF19:
|
||||
uint64_t single_contributed_amount = remaining_amount / (oxen::MAX_CONTRIBUTORS_HF19 - 1);
|
||||
uint64_t total_amount = operator_amount + single_contributed_amount;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_amount);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
|
||||
|
@ -3098,16 +3096,15 @@ bool oxen_service_nodes_sufficient_contribution_HF19::generate(std::vector<test_
|
|||
|
||||
bool oxen_service_nodes_insufficient_operator_contribution_HF19::generate(std::vector<test_event_entry> &events)
|
||||
{
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf::hf19);
|
||||
auto hard_forks = oxen_generate_hard_fork_table(cryptonote::hf::hf19_reward_batching);
|
||||
oxen_chain_generator gen(events, hard_forks);
|
||||
|
||||
gen.add_blocks_until_version(hard_forks.back().version);
|
||||
gen.add_mined_money_unlock_blocks();
|
||||
|
||||
uint64_t operator_portions = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_HF19;
|
||||
uint64_t operator_amount = service_nodes::get_staking_requirement(cryptonote::network_type::FAKECHAIN, hard_forks.back().height) / oxen::MAX_CONTRIBUTORS_HF19;
|
||||
uint64_t operator_amount = oxen::MINIMUM_OPERATOR_CONTRIBUTION_TESTNET - 1;
|
||||
cryptonote::keypair sn_keys{hw::get_device("default")};
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_portions);
|
||||
cryptonote::transaction register_tx = gen.create_registration_tx(gen.first_miner_, sn_keys, operator_amount);
|
||||
gen.add_tx(register_tx);
|
||||
gen.create_and_add_next_block({register_tx});
|
||||
|
||||
|
@ -3722,7 +3719,7 @@ bool oxen_batch_sn_rewards_pop_blocks::generate(std::vector<test_event_entry> &e
|
|||
CHECK_EQ((*records).size(), 1);
|
||||
// Check that the database has a lower amount that does not include the popped block
|
||||
batched_rewards_earned = MK_COINS(1) * 16.5 * (more_blocks - conf.SERVICE_NODE_PAYABLE_AFTER_BLOCKS - 1);
|
||||
CHECK_EQ((*records)[0].amount, batched_rewards_earned);
|
||||
CHECK_EQ((*records)[0].amount, batched_rewards_earned * cryptonote::BATCH_REWARD_FACTOR);
|
||||
CHECK_EQ(tools::view_guts((*records)[0].address_info.address), tools::view_guts(alice.get_keys().m_account_address));
|
||||
}
|
||||
else
|
||||
|
@ -3797,20 +3794,20 @@ bool oxen_batch_sn_rewards_pop_blocks_after_big_cycle::generate(std::vector<test
|
|||
cryptonote::Blockchain& blockchain = c.get_blockchain_storage();
|
||||
uint64_t curr_height = blockchain.get_current_blockchain_height();
|
||||
auto sqliteDB = blockchain.sqlite_db();
|
||||
CHECK_EQ((*sqliteDB).height, curr_height - 1);
|
||||
CHECK_EQ(sqliteDB->height, curr_height - 1);
|
||||
|
||||
blockchain.pop_blocks(conf.BATCHING_INTERVAL * 3 + 1);
|
||||
|
||||
|
||||
CHECK_EQ((*sqliteDB).height + 1, blockchain.get_current_blockchain_height());
|
||||
CHECK_EQ((*sqliteDB).height + 1, curr_height - conf.BATCHING_INTERVAL * 3 - 1);
|
||||
CHECK_EQ(sqliteDB->height + 1, blockchain.get_current_blockchain_height());
|
||||
CHECK_EQ(sqliteDB->height + 1, curr_height - conf.BATCHING_INTERVAL * 3 - 1);
|
||||
|
||||
curr_height = blockchain.get_current_blockchain_height();
|
||||
|
||||
auto records = (*sqliteDB).get_sn_payments(curr_height);
|
||||
CHECK_EQ((*records).size(), 1);
|
||||
CHECK_EQ((*records)[0].amount, amount);
|
||||
CHECK_EQ(tools::view_guts((*records)[0].address_info.address), tools::view_guts(alice.get_keys().m_account_address));
|
||||
auto records = sqliteDB->get_sn_payments(curr_height);
|
||||
CHECK_EQ(records.size(), 1);
|
||||
CHECK_EQ(records[0].amount, amount * cryptonote::BATCH_REWARD_FACTOR);
|
||||
CHECK_EQ(tools::view_guts(records[0].address_info.address), tools::view_guts(alice.get_keys().m_account_address));
|
||||
|
||||
return true;
|
||||
});
|
||||
|
|
|
@ -58,18 +58,17 @@ bool test_transaction_generation_and_ring_signature()
|
|||
account_base rv_acc2;
|
||||
rv_acc2.generate();
|
||||
transaction tx_mine_1;
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
construct_miner_tx(0, 0, 0, 10, 0, tx_mine_1, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc1.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 10, 0, tx_mine_1, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc1.get_keys().m_account_address),{},{},hf_max);
|
||||
transaction tx_mine_2;
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_2, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc2.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_2, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc2.get_keys().m_account_address),{},{},hf_max);
|
||||
transaction tx_mine_3;
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_3, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc3.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_3, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc3.get_keys().m_account_address),{},{},hf_max);
|
||||
transaction tx_mine_4;
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_4, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc4.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_4, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc4.get_keys().m_account_address),{},{},hf_max);
|
||||
transaction tx_mine_5;
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_5, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc5.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_5, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc5.get_keys().m_account_address),{},{},hf_max);
|
||||
transaction tx_mine_6;
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_6, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc6.get_keys().m_account_address),sn_rwds,{},hf_max);
|
||||
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_6, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, miner_acc6.get_keys().m_account_address),{},{},hf_max);
|
||||
|
||||
//fill inputs entry
|
||||
typedef tx_source_entry::output_entry tx_output_entry;
|
||||
|
@ -140,9 +139,8 @@ bool test_block_creation()
|
|||
bool r = get_account_address_from_str(info, network_type::MAINNET, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2");
|
||||
CHECK_AND_ASSERT_MES(r, false, "failed to import");
|
||||
block b;
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
uint64_t block_rewards = 0;
|
||||
std::tie(r, block_rewards) = construct_miner_tx(90, tools::median(std::move(szs)), 3553616528562147, 33094, 10000000, b.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, info.address), sn_rwds, {}, hf_max);
|
||||
std::tie(r, block_rewards) = construct_miner_tx(90, tools::median(std::move(szs)), 3553616528562147, 33094, 10000000, b.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, info.address), {}, {}, hf_max);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,14 +51,13 @@ public:
|
|||
using namespace cryptonote;
|
||||
|
||||
std::vector<tx_source_entry::output_entry> output_entries;
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
for (size_t i = 0; i < ring_size; ++i)
|
||||
{
|
||||
m_miners[i].generate();
|
||||
|
||||
uint64_t block_rewards = 0;
|
||||
bool r;
|
||||
std::tie(r, block_rewards) = construct_miner_tx(0, 0, 0, 2, 0, m_miner_txs[1], cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, m_miners[i].get_keys().m_account_address), sn_rwds, {}, hf::none);
|
||||
std::tie(r, block_rewards) = construct_miner_tx(0, 0, 0, 2, 0, m_miner_txs[1], cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, m_miners[i].get_keys().m_account_address), {}, {}, hf::none);
|
||||
if (!r)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -44,12 +44,11 @@ public:
|
|||
m_bob.generate();
|
||||
|
||||
oxen_miner_tx_context miner_tx_context = {};
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
miner_tx_context.miner_block_producer = m_bob.get_keys().m_account_address;
|
||||
|
||||
uint64_t block_rewards = 0;
|
||||
bool r;
|
||||
std::tie(r, block_rewards) = construct_miner_tx(0, 0, 0, 2, 0, m_tx, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, m_bob.get_keys().m_account_address), sn_rwds, {}, hf::none);
|
||||
std::tie(r, block_rewards) = construct_miner_tx(0, 0, 0, 2, 0, m_tx, cryptonote::oxen_miner_tx_context::miner_block(network_type::FAKECHAIN, m_bob.get_keys().m_account_address), {}, {}, hf::none);
|
||||
if (!r)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <boost/predef/other/endian.h>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <oxenc/endian.h>
|
||||
#include <boost/range/algorithm/equal.hpp>
|
||||
#include <boost/range/algorithm_ext/iota.hpp>
|
||||
#include <cstdint>
|
||||
|
@ -460,9 +460,9 @@ TEST(NetUtils, IPv4NetworkAddress)
|
|||
{
|
||||
static_assert(epee::net_utils::ipv4_network_address::get_type_id() == epee::net_utils::address_type::ipv4, "bad ipv4 type id");
|
||||
|
||||
const auto ip1 = boost::endian::native_to_big(0x330012FFu);
|
||||
const auto ip_loopback = boost::endian::native_to_big(0x7F000001u);
|
||||
const auto ip_local = boost::endian::native_to_big(0x0A000000u);
|
||||
const auto ip1 = oxenc::host_to_big(0x330012FFu);
|
||||
const auto ip_loopback = oxenc::host_to_big(0x7F000001u);
|
||||
const auto ip_local = oxenc::host_to_big(0x0A000000u);
|
||||
|
||||
epee::net_utils::ipv4_network_address address1{ip1, 65535};
|
||||
CHECK_EQUAL(address1, address1);
|
||||
|
@ -527,9 +527,9 @@ TEST(NetUtils, IPv4NetworkAddress)
|
|||
|
||||
TEST(NetUtils, NetworkAddress)
|
||||
{
|
||||
const auto ip1 = boost::endian::native_to_big(0x330012FFu);
|
||||
const auto ip_loopback = boost::endian::native_to_big(0x7F000001u);
|
||||
const auto ip_local = boost::endian::native_to_big(0x0A000000u);
|
||||
const auto ip1 = oxenc::host_to_big(0x330012FFu);
|
||||
const auto ip_loopback = oxenc::host_to_big(0x7F000001u);
|
||||
const auto ip_local = oxenc::host_to_big(0x0A000000u);
|
||||
|
||||
struct custom_address {
|
||||
constexpr static bool equal(const custom_address&) noexcept { return false; }
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <boost/endian/conversion.hpp>
|
||||
#include <boost/range/adaptor/sliced.hpp>
|
||||
#include <boost/range/combine.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
|
|
@ -330,19 +330,28 @@ TEST(service_nodes, tx_extra_state_change_validation)
|
|||
}
|
||||
}
|
||||
|
||||
static auto fake_portions(std::initializer_list<uint64_t> portions_in) {
|
||||
std::vector<std::pair<cryptonote::account_public_address, uint64_t>> portions_out;
|
||||
portions_out.reserve(portions_in.size());
|
||||
cryptonote::account_public_address null_addr{};
|
||||
for (auto& p : portions_in)
|
||||
portions_out.emplace_back(null_addr, p);
|
||||
return portions_out;
|
||||
}
|
||||
|
||||
TEST(service_nodes, min_portions)
|
||||
{
|
||||
|
||||
auto hf_version = cryptonote::hf::hf9_service_nodes;
|
||||
// Test new contributors can *NOT* stake to a registration with under 25% of the total stake if there is more than 25% available.
|
||||
{
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, cryptonote::old::STAKING_PORTIONS}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({0, cryptonote::old::STAKING_PORTIONS})));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_HF19 - 1;
|
||||
const auto small = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_V1 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({small, rest})));
|
||||
}
|
||||
|
||||
constexpr auto MIN_PORTIONS_HF19 = cryptonote::old::STAKING_PORTIONS / oxen::MAX_CONTRIBUTORS_HF19;
|
||||
|
@ -351,13 +360,13 @@ TEST(service_nodes, min_portions)
|
|||
/// TODO: fix this test
|
||||
const auto small = MIN_PORTIONS_HF19 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small - cryptonote::old::STAKING_PORTIONS / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {cryptonote::old::STAKING_PORTIONS / 2, small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({cryptonote::old::STAKING_PORTIONS / 2, small, rest})));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS_HF19 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small - 2 * MIN_PORTIONS_HF19;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS_HF19, MIN_PORTIONS_HF19, small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({MIN_PORTIONS_HF19, MIN_PORTIONS_HF19, small, rest})));
|
||||
}
|
||||
|
||||
// Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available.
|
||||
|
@ -366,7 +375,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto large = 4 * (cryptonote::old::STAKING_PORTIONS / 5);
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - large;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({large, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -374,7 +383,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto half = cryptonote::old::STAKING_PORTIONS / 2 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - 2 * half;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {half, half, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({half, half, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -382,7 +391,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto third = cryptonote::old::STAKING_PORTIONS / 3 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - 3 * third;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {third, third, third, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({third, third, third, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -390,25 +399,25 @@ TEST(service_nodes, min_portions)
|
|||
hf_version = cryptonote::hf::hf11_infinite_staking;
|
||||
|
||||
{
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {0, cryptonote::old::STAKING_PORTIONS}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({0, cryptonote::old::STAKING_PORTIONS})));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS_HF19 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({small, rest})));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = cryptonote::old::STAKING_PORTIONS / 8;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small - cryptonote::old::STAKING_PORTIONS / 2;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {cryptonote::old::STAKING_PORTIONS / 2, small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({cryptonote::old::STAKING_PORTIONS / 2, small, rest})));
|
||||
}
|
||||
|
||||
{
|
||||
const auto small = MIN_PORTIONS_HF19 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - small - 2 * MIN_PORTIONS_HF19;
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, {MIN_PORTIONS_HF19, MIN_PORTIONS_HF19, small, rest}));
|
||||
ASSERT_FALSE(service_nodes::check_service_node_portions(hf_version, fake_portions({MIN_PORTIONS_HF19, MIN_PORTIONS_HF19, small, rest})));
|
||||
}
|
||||
|
||||
// Test new contributors *CAN* stake as the last person with under 25% if there is less than 25% available.
|
||||
|
@ -417,7 +426,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto large = 4 * (cryptonote::old::STAKING_PORTIONS / 5);
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - large;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({large, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -425,7 +434,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto half = cryptonote::old::STAKING_PORTIONS / 2 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - 2 * half;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {half, half, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({half, half, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -433,7 +442,7 @@ TEST(service_nodes, min_portions)
|
|||
{
|
||||
const auto third = cryptonote::old::STAKING_PORTIONS / 3 - 1;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - 3 * third;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {third, third, third, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({third, third, third, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
@ -443,7 +452,7 @@ TEST(service_nodes, min_portions)
|
|||
const auto small_1 = cryptonote::old::STAKING_PORTIONS / 6;
|
||||
const auto small_2 = cryptonote::old::STAKING_PORTIONS / 6;
|
||||
const auto rest = cryptonote::old::STAKING_PORTIONS - large - small_1 - small_2;
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, {large, small_1, small_2, rest});
|
||||
bool result = service_nodes::check_service_node_portions(hf_version, fake_portions({large, small_1, small_2, rest}));
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,69 +30,68 @@
|
|||
|
||||
#include "blockchain_db/sqlite/db_sqlite.h"
|
||||
|
||||
#include "../blockchain_sqlite_test.h"
|
||||
|
||||
TEST(SQLITE, AddressModulus)
|
||||
{
|
||||
cryptonote::address_parse_info wallet_address;
|
||||
cryptonote::get_account_address_from_str(wallet_address, cryptonote::network_type::TESTNET, "T6TzkJb5EiASaCkcH7idBEi1HSrpSQJE1Zq3aL65ojBMPZvqHNYPTL56i3dncGVNEYCG5QG5zrBmRiVwcg6b1cRM1SRNqbp44");
|
||||
|
||||
EXPECT_TRUE(wallet_address.address.modulus(10) == 0);
|
||||
EXPECT_TRUE(wallet_address.address.modulus(100) == 90);
|
||||
EXPECT_EQ(wallet_address.address.modulus(10), 0);
|
||||
EXPECT_EQ(wallet_address.address.modulus(100), 90);
|
||||
|
||||
EXPECT_TRUE(wallet_address.address.next_payout_height(50, 100) == 90);
|
||||
EXPECT_TRUE(wallet_address.address.next_payout_height(100, 100) == 190);
|
||||
EXPECT_EQ(wallet_address.address.next_payout_height(50, 100), 90);
|
||||
EXPECT_EQ(wallet_address.address.next_payout_height(100, 100), 190);
|
||||
}
|
||||
|
||||
TEST(SQLITE, AddSNRewards)
|
||||
{
|
||||
cryptonote::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::TESTNET, ":memory:");
|
||||
test::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::FAKECHAIN, ":memory:");
|
||||
|
||||
std::cout << "in memory db opened" << std::endl;
|
||||
|
||||
EXPECT_TRUE(sqliteDB.batching_count() == 0);
|
||||
EXPECT_EQ(sqliteDB.batching_count(), 0);
|
||||
|
||||
std::vector<cryptonote::batch_sn_payment> t1;
|
||||
|
||||
cryptonote::address_parse_info wallet_address;
|
||||
|
||||
cryptonote::get_account_address_from_str(wallet_address, cryptonote::network_type::TESTNET, "T6TzkJb5EiASaCkcH7idBEi1HSrpSQJE1Zq3aL65ojBMPZvqHNYPTL56i3dncGVNEYCG5QG5zrBmRiVwcg6b1cRM1SRNqbp44");
|
||||
cryptonote::get_account_address_from_str(wallet_address, cryptonote::network_type::FAKECHAIN, "LCFxT37LAogDn1jLQKf4y7aAqfi21DjovX9qyijaLYQSdrxY1U5VGcnMJMjWrD9RhjeK5Lym67wZ73uh9AujXLQ1RKmXEyL");
|
||||
|
||||
t1.emplace_back(wallet_address.address, 16500000000/2, cryptonote::network_type::TESTNET);
|
||||
t1.emplace_back(wallet_address.address, 16500000001'789/2, cryptonote::network_type::FAKECHAIN);
|
||||
|
||||
bool success = false;
|
||||
success = sqliteDB.add_sn_payments(t1);
|
||||
bool success = false;
|
||||
success = sqliteDB.add_sn_rewards(t1);
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
EXPECT_TRUE(sqliteDB.batching_count() == 1);
|
||||
EXPECT_EQ(sqliteDB.batching_count(), 1);
|
||||
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> p1;
|
||||
std::vector<cryptonote::batch_sn_payment> p1;
|
||||
const auto expected_payout = wallet_address.address.next_payout_height(0, cryptonote::config::BATCHING_INTERVAL);
|
||||
p1 = sqliteDB.get_sn_payments(expected_payout - 1);
|
||||
EXPECT_TRUE(p1.has_value());
|
||||
EXPECT_TRUE((*p1).size() == 0);
|
||||
EXPECT_EQ(p1.size(), 0);
|
||||
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> p2;
|
||||
std::vector<cryptonote::batch_sn_payment> p2;
|
||||
p2 = sqliteDB.get_sn_payments(expected_payout);
|
||||
EXPECT_TRUE(p2.has_value());
|
||||
EXPECT_TRUE((*p2).size() == 1);
|
||||
uint64_t expected_amount = (16500000000/2);
|
||||
EXPECT_TRUE((*p2)[0].amount == expected_amount);
|
||||
EXPECT_EQ(p2.size(), 1);
|
||||
// We shouldn't get a fractional atomic OXEN amount in the payment amount:
|
||||
uint64_t expected_amount = 8250000000'000;
|
||||
EXPECT_EQ(p2[0].amount, expected_amount);
|
||||
|
||||
// Pay an amount less than the database expects and test for failure
|
||||
std::vector<cryptonote::batch_sn_payment> t2;
|
||||
t2.emplace_back(wallet_address.address, expected_amount - 1, cryptonote::network_type::TESTNET);
|
||||
t2.emplace_back(wallet_address.address, expected_amount - 1000, cryptonote::network_type::FAKECHAIN);
|
||||
EXPECT_FALSE(sqliteDB.save_payments(expected_payout, t2));
|
||||
|
||||
// Pay the amount back out and expect the database to be empty
|
||||
std::vector<cryptonote::batch_sn_payment> t3;
|
||||
t3.emplace_back(wallet_address.address, expected_amount, cryptonote::network_type::TESTNET);
|
||||
success = sqliteDB.save_payments(expected_payout, t3);
|
||||
t3.emplace_back(wallet_address.address, expected_amount, cryptonote::network_type::FAKECHAIN);
|
||||
success = sqliteDB.save_payments(expected_payout, t3);
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_TRUE(sqliteDB.batching_count() == 0);
|
||||
EXPECT_EQ(sqliteDB.batching_count(), 0);
|
||||
}
|
||||
|
||||
TEST(SQLITE, CalculateRewards)
|
||||
{
|
||||
cryptonote::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::TESTNET, ":memory:");
|
||||
test::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::TESTNET, ":memory:");
|
||||
|
||||
cryptonote::block block;
|
||||
block.reward = 200;
|
||||
|
@ -121,9 +120,9 @@ TEST(SQLITE, CalculateRewards)
|
|||
multiple_contributors.contributors.back().amount = 34;
|
||||
auto multiple_rewards = sqliteDB.calculate_rewards(block.major_version, block.reward, multiple_contributors);
|
||||
|
||||
EXPECT_TRUE(multiple_rewards[0].amount == 66);
|
||||
EXPECT_TRUE(multiple_rewards[1].amount == 66);
|
||||
EXPECT_TRUE(multiple_rewards[2].amount == 68);
|
||||
EXPECT_EQ(multiple_rewards[0].amount, 66);
|
||||
EXPECT_EQ(multiple_rewards[1].amount, 66);
|
||||
EXPECT_EQ(multiple_rewards[2].amount, 68);
|
||||
|
||||
// Check that 3 contributors receives their portion of the block reward when the operator takes a 10% fee
|
||||
multiple_contributors.portions_for_operator = cryptonote::old::STAKING_PORTIONS/10;
|
||||
|
|
|
@ -141,10 +141,9 @@ TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed)
|
|||
cryptonote::account_base acc;
|
||||
acc.generate();
|
||||
std::string b = "dsdsdfsdfsf";
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
uint64_t block_rewards = 0;
|
||||
bool r;
|
||||
std::tie(r, block_rewards) = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, acc.get_keys().m_account_address), sn_rwds, b, cryptonote::hf::none);
|
||||
std::tie(r, block_rewards) = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, acc.get_keys().m_account_address), {}, b, cryptonote::hf::none);
|
||||
ASSERT_TRUE(r);
|
||||
crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(tx);
|
||||
ASSERT_NE(tx_pub_key, crypto::null_pkey);
|
||||
|
@ -154,11 +153,10 @@ TEST(parse_and_validate_tx_extra, fails_on_big_extra_nonce)
|
|||
cryptonote::transaction tx{};
|
||||
cryptonote::account_base acc;
|
||||
acc.generate();
|
||||
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
|
||||
std::string b(cryptonote::TX_EXTRA_NONCE_MAX_COUNT + 1, 0);
|
||||
uint64_t block_rewards = 0;
|
||||
bool r;
|
||||
std::tie(r,block_rewards) = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, acc.get_keys().m_account_address), sn_rwds, b, cryptonote::hf::none);
|
||||
std::tie(r,block_rewards) = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::network_type::FAKECHAIN, acc.get_keys().m_account_address), {}, b, cryptonote::hf::none);
|
||||
ASSERT_FALSE(r);
|
||||
}
|
||||
TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce)
|
||||
|
|
Loading…
Reference in New Issue