Merge pull request #1550 from jagerman/batch1000

Batching thousanths
This commit is contained in:
Sean 2022-05-27 11:15:09 +10:00 committed by GitHub
commit e9a69b6a78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2224 additions and 1707 deletions

View File

@ -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")

View File

@ -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))

View File

@ -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>

View File

@ -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 {

View File

@ -65,6 +65,7 @@ target_link_libraries(epee
PUBLIC
easylogging
oxenmq::oxenmq
oxenc::oxenc
PRIVATE
filesystem
Boost::thread

View File

@ -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

View File

@ -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)

View File

@ -35,7 +35,7 @@ add_library(blockchain_db
target_link_libraries(blockchain_db
PUBLIC
SQLiteCpp
sqlitedb
PRIVATE
common
ringct

View File

@ -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

View File

@ -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

View File

@ -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);
};
}

View File

@ -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);
}

View File

@ -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}};

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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())

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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");

View File

@ -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], :" <<

View File

@ -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; }

View File

@ -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);
}

View File

@ -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);

View File

@ -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,

View File

@ -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;

View File

@ -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}};

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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() {

View File

@ -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 " +

View File

@ -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>

View File

@ -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) {

View File

@ -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;

View File

@ -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;
// -------------------------------------------------------------------------------------------------

View File

@ -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;

View File

@ -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) {

View File

@ -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)

View File

@ -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.

View File

@ -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"

View File

@ -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++) {

View File

@ -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));
}

View File

@ -0,0 +1,13 @@
add_library(sqlitedb
database.cpp)
target_link_libraries(sqlitedb
PUBLIC
epee
SQLiteCpp
common
PRIVATE
SQLite::SQLite3
fmt::fmt
extra)

73
src/sqlitedb/database.cpp Normal file
View File

@ -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

View File

@ -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;
};

View File

@ -110,6 +110,7 @@ if (STATIC AND BUILD_STATIC_DEPS)
cryptonote_core
cryptonote_basic
cryptonote_protocol
sqlitedb
mnemonics
common
cncrypto

View File

@ -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

View File

@ -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,

View File

@ -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;
}
};
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;
});

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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; }

View File

@ -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>

View File

@ -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);
}

View File

@ -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;

View File

@ -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)