Batching of service node rewards

This updates the coinbase transactions to reward service nodes
periodically rather than every block. If you recieve a service node
reward this reward will be delayed x blocks, if you receive another
reward to the same wallet before those blocks have been completed it
will be added to your total and all will be paid out after those x
blocks has passed.

For example if our batching interval is 2 blocks:

Block 1 - Address A receives reward of 10 oxen - added to batch
Block 2 - Address A receives reward of 10 oxen - added to batch
Block 3 - Address A is paid out 20 oxen.

Batching accumulates a small reward for all nodes every block

The batching of service node rewards allows us to drip feed rewards
to service nodes. Rather than accruing each service node 16.5 oxen every
time they are pulse block leader we now reward every node the 16.5 /
num_service_nodes every block and pay each wallet the full amount that
has been accrued after a period of time (Likely 3.5 days).

To spread each payment evenly we now pay the rewards based on the
address of the recipient. This modulus of their address determines which
block the address should be paid and by setting the interval to our
service_node_batching interval we can guarantee they will be paid out
regularly and evenly distribute the payments for all wallets over this
This commit is contained in:
Sean Darcy 2022-04-29 09:51:14 +10:00
parent 0d266a4e18
commit 866691d9d8
77 changed files with 2748 additions and 776 deletions

View File

@ -134,9 +134,10 @@ local mac_builder(name,
'mkdir build',
'cd build',
'cmake .. -G Ninja -DCMAKE_CXX_FLAGS=-fcolor-diagnostics -DCMAKE_BUILD_TYPE=' + build_type + ' ' +
'-DLOCAL_MIRROR=https://builds.lokinet.dev/deps '
+ cmake_options({ USE_LTO: lto, WARNINGS_AS_ERRORS: werror, BUILD_TESTS: build_tests || run_tests })
+ cmake_extra,
'-DLOCAL_MIRROR=https://builds.lokinet.dev/deps -DUSE_LTO=' + (if lto then 'ON ' else 'OFF ') +
(if werror then '-DWARNINGS_AS_ERRORS=ON ' else '') +
(if build_tests || run_tests then '-DBUILD_TESTS=ON ' else '') +
cmake_extra,
'ninja -j' + jobs + ' -v',
] + (
if run_tests then [

View File

@ -288,6 +288,7 @@ if(NOT MANUAL_SUBMODULES)
check_submodule(external/trezor-common)
check_submodule(external/randomx)
check_submodule(external/loki-mq cppzmq)
check_submodule(external/SQLiteCpp)
if(BUILD_TESTS)
check_submodule(external/googletest)
endif()

2
external/SQLiteCpp vendored

@ -1 +1 @@
Subproject commit beb2b2964036f7ec87394a0d7f32db170d4bcdfe
Subproject commit ce6dd9b82234e0427801380286d87f5bae417cca

View File

@ -30,9 +30,12 @@
add_library(blockchain_db
blockchain_db.cpp
lmdb/db_lmdb.cpp
sqlite/db_sqlite.cpp
)
target_link_libraries(blockchain_db
PUBLIC
SQLiteCpp
PRIVATE
common
ringct

View File

@ -291,7 +291,9 @@ bool BlockchainDB::get_pruned_tx(const crypto::hash& h, cryptonote::transaction
if (!get_pruned_tx_blob(h, bd))
return false;
if (!parse_and_validate_tx_base_from_blob(bd, tx))
{
throw DB_ERROR("Failed to parse transaction base from blob retrieved from the db");
}
return true;
}

View File

@ -2542,6 +2542,7 @@ T BlockchainLMDB::get_and_convert_block_blob_from_height(uint64_t height) const
// header, not the full-block (of which a good chunk is thrown away because we
// only want the header).
LOG_PRINT_L3("BlockchainLMDB::" << __func__);
check_open();
TXN_PREFIX_RDONLY();
@ -3619,7 +3620,9 @@ bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&
if (pruned)
{
if (!parse_and_validate_tx_base_from_blob(bd, tx))
{
throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db"));
}
}
else
{

View File

@ -0,0 +1,636 @@
// Copyright (c) 2021, The Oxen Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "db_sqlite.h"
#include <sodium.h>
#include <SQLiteCpp/SQLiteCpp.h>
#include <sqlite3.h>
#include <string>
#include <iostream>
#include <cassert>
#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
#define OXEN_DEFAULT_LOG_CATEGORY "blockchain.db.sqlite"
namespace cryptonote {
BlockchainSQLite::BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path): db::Database(db_path, ""), m_nettype(nettype), filename {db_path.u8string()} {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
height = 0;
if (!db.tableExists("batched_payments_accrued") || !db.tableExists("batched_payments_raw") || !db.tableExists("batch_db_info")) {
create_schema();
}
SQLite::Statement st {
db,
"SELECT height FROM batch_db_info"
};
while (st.executeStep()) {
height = st.getColumn(0).getInt64();
}
}
void BlockchainSQLite::create_schema() {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
db.exec(R"(
CREATE TABLE batched_payments_accrued(
address VARCHAR NOT NULL,
amount BIGINT NOT NULL,
PRIMARY KEY(address),
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 TABLE batched_payments_raw(
address VARCHAR NOT NULL,
amount BIGINT NOT NULL,
height_paid BIGINT NOT NULL,
PRIMARY KEY(address, height_paid),
CHECK(amount >= 0)
);
CREATE INDEX batched_payments_raw_height_idx ON batched_payments_raw(height_paid);
CREATE TABLE batch_db_info(
height BIGINT NOT NULL
);
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 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 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");
}
void BlockchainSQLite::reset_database() {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
db.exec(R"(
DROP TABLE IF EXISTS batched_payments_accrued;
DROP VIEW IF EXISTS batched_payments_paid;
DROP TABLE IF EXISTS batched_payments_raw;
DROP TABLE IF EXISTS batch_db_info;
)");
create_schema();
MDEBUG("Database reset complete");
}
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));
}
void BlockchainSQLite::increment_height() {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << height + 1);
update_height(height + 1);
}
void BlockchainSQLite::decrement_height() {
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << height - 1);
update_height(height - 1);
}
bool BlockchainSQLite::add_sn_payments(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"
};
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));
db::exec_query(insert_payment, address_str, static_cast<int64_t>(payment.amount * 1000));
insert_payment.reset();
};
return true;
}
bool BlockchainSQLite::subtract_sn_payments(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 = ?"
};
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);
if (!result) {
MERROR("tried to subtract payment from an address that doesnt exist: " << address_str);
return false;
}
update_payment.reset();
};
return true;
}
std::optional<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;
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));
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);
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);
}
} else {
MERROR("Invalid address returned from batching database: " << address);
return std::nullopt;
}
}
return payments;
}
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::calculate_rewards(uint8_t hf_version, uint64_t distribution_amount, service_nodes::service_node_info sn_info) {
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, STAKING_PORTIONS, &resulthi, &resultlo);
if (resulthi > 0)
throw std::logic_error("overflow from calculating sn operator fee");
operator_fee = resultlo;
}
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;
});
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);
}
return payments;
}
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 < cryptonote::network_version_19) {
update_height(block_height);
print_database();
return true;
}
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, cryptonote::network_version_19);
if (block_height == fork_height.first.value_or(0)) {
MDEBUG("Batching of Service Node Rewards Begins");
reset_database();
update_height(block_height - 1);
}
if (block_height != height + 1) {
MERROR("Block height out of sync with batching database. Block height: " << block_height << " batching db height: " << height);
return false;
}
// We query our own database as a source of truth to verify the blocks payments against. The calculated_rewards
// variable contains a known good list of who should have been paid in this block
auto calculated_rewards = get_sn_payments(block_height);
// We iterate through the block's coinbase payments and build a copy of our own list of the payments
// miner_tx_vouts this will be compared against calculated_rewards and if they match we know the block is
// paying the correct people only.
std::vector<std::tuple<crypto::public_key, uint64_t>> miner_tx_vouts;
for (auto & vout: block.miner_tx.vout)
miner_tx_vouts.emplace_back(var::get<txout_to_key>(vout.target).key, vout.amount);
try {
SQLite::Transaction transaction {
db,
SQLite::TransactionBehavior::IMMEDIATE
};
// 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)) {
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;
}
}
}
// 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::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());
return false;
}
print_database();
return true;
}
bool BlockchainSQLite::pop_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);
if (height < block_height) {
MDEBUG("Block above batching DB height skipping pop");
return true;
}
if (block_height != height) {
MERROR("Block height out of sync with batching database");
return false;
}
const auto& conf = get_config(m_nettype);
auto hf_version = block.major_version;
if (hf_version < cryptonote::network_version_19) {
decrement_height();
return true;
}
try {
SQLite::Transaction transaction {
db,
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::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;
}
// Add back to the database payments that had been made in this block
delete_block_payments(block_height);
decrement_height();
transaction.commit();
} catch (std::exception& e) {
MFATAL("Exception: " << 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) {
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);
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_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)));
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)) {
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)) {
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++;
}
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));
return false;
}
return save_payments(block_height, finalised_payments);
}
bool BlockchainSQLite::save_payments(uint64_t block_height, 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 = ?;"
};
SQLite::Statement update_paid {
db,
"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);
return false;
}
db::exec_query(update_paid, payment.address, static_cast<int64_t>(amount), static_cast<int64_t>(block_height));
update_paid.reset();
}
select_sum.reset();
};
return true;
}
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_block_payments(uint64_t block_height) {
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);
}
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));
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

@ -0,0 +1,113 @@
// Copyright (c) 2021, The Oxen Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <string>
#include <filesystem>
#include "epee/misc_log_ex.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "sqlitedb/database.hpp"
#include "common/fs.h"
#include <SQLiteCpp/SQLiteCpp.h>
namespace cryptonote
{
fs::path check_if_copy_filename(std::string_view db_path);
class BlockchainSQLite : public db::Database
{
public:
explicit BlockchainSQLite(cryptonote::network_type nettype, fs::path db_path);
BlockchainSQLite(const BlockchainSQLite&) = delete;
// Database management functions. Should be called on creation of BlockchainSQLite
void create_schema();
void reset_database();
// The batching database maintains a height variable to know if it gets out of sync with the mainchain. Calling increment and decrement is the primary method of interacting with this height variable
void update_height(uint64_t new_height);
void increment_height();
void decrement_height();
// 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);
// 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);
// 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
std::vector<cryptonote::batch_sn_payment> calculate_rewards(uint8_t 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
// and/or batching payments that need to be subtracted from the database, in addition it takes a reference to
// the service node state which it will use to calculate the individual payouts.
// The function will then process this block add and subtracting to the batching DB appropriately.
// This is the primary entry point for the blockchain to add to the batching database.
// Each accepted block should call this passing in the SN list structure.
bool add_block(const cryptonote::block& block, const service_nodes::service_node_list::state_t& service_nodes_state);
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);
// 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);
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:
cryptonote::network_type m_nettype;
std::string filename;
};
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

@ -34,6 +34,7 @@ target_link_libraries(blockchain_tools_common_libs INTERFACE
version
filesystem
Boost::program_options
SQLiteCpp
extra)

View File

@ -458,7 +458,7 @@ int main(int argc, char* argv[])
LOG_PRINT_L0("Error opening database: " << e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK");

View File

@ -143,7 +143,7 @@ int main(int argc, char* argv[])
LOG_PRINT_L0("Error opening database: " << e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK");

View File

@ -142,7 +142,7 @@ int main(int argc, char* argv[])
LOG_PRINT_L0("Error opening database: " << e.what());
return 1;
}
r = core_storage->init(db, nullptr, opt_testnet ? cryptonote::TESTNET : opt_devnet ? cryptonote::DEVNET : cryptonote::MAINNET);
r = core_storage->init(db, nullptr, nullptr, opt_testnet ? cryptonote::TESTNET : opt_devnet ? cryptonote::DEVNET : cryptonote::MAINNET);
if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat)
{

View File

@ -144,7 +144,7 @@ int main(int argc, char* argv[])
LOG_PRINT_L0("Error opening database: " << e.what());
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK");

View File

@ -172,7 +172,7 @@ int main(int argc, char* argv[])
return 1;
}
r = core_storage->init(db, nullptr /*ons_db*/, net_type);
r = core_storage->init(db, nullptr /*ons_db*/, nullptr, net_type);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage");
LOG_PRINT_L0("Source blockchain storage initialized OK");

View File

@ -313,7 +313,8 @@ namespace cryptonote
for (size_t i = 0; i < oxen::array_count(HARDCODED_MAINNET_CHECKPOINTS); ++i)
{
height_to_hash const &checkpoint = HARDCODED_MAINNET_CHECKPOINTS[i];
ADD_CHECKPOINT(checkpoint.height, checkpoint.hash);
bool added = add_checkpoint(checkpoint.height, checkpoint.hash);
CHECK_AND_ASSERT(added, false);
}
}

View File

@ -38,11 +38,11 @@
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "common/fs.h"
#define ADD_CHECKPOINT(h, hash) CHECK_AND_ASSERT(add_checkpoint(h, hash), false);
#define JSON_HASH_FILE_NAME "checkpoints.json"
namespace cryptonote
{
constexpr std::string_view JSON_HASH_FILE_NAME = "checkpoints.json"sv;
enum struct checkpoint_type
{
hardcoded,

View File

@ -82,7 +82,10 @@ block::block(const block& b) :
block_header(b),
miner_tx{b.miner_tx},
tx_hashes{b.tx_hashes},
signatures{b.signatures}
signatures{b.signatures},
height{b.height},
service_node_winner_key{b.service_node_winner_key},
reward{b.reward}
{
copy_hash(b);
}
@ -91,7 +94,10 @@ block::block(block&& b) :
block_header(std::move(b)),
miner_tx{std::move(b.miner_tx)},
tx_hashes{std::move(b.tx_hashes)},
signatures{std::move(b.signatures)}
signatures{std::move(b.signatures)},
height{std::move(b.height)},
service_node_winner_key{std::move(b.service_node_winner_key)},
reward{std::move(b.reward)}
{
copy_hash(b);
}
@ -102,6 +108,9 @@ block& block::operator=(const block& b)
miner_tx = b.miner_tx;
tx_hashes = b.tx_hashes;
signatures = b.signatures;
height = b.height;
service_node_winner_key = b.service_node_winner_key;
reward = b.reward;
copy_hash(b);
return *this;
}
@ -111,6 +120,9 @@ block& block::operator=(block&& b)
miner_tx = std::move(b.miner_tx);
tx_hashes = std::move(b.tx_hashes);
signatures = std::move(b.signatures);
height = std::move(b.height);
service_node_winner_key = std::move(b.service_node_winner_key);
reward = std::move(b.reward);
copy_hash(b);
return *this;
}
@ -124,4 +136,23 @@ void block::set_hash_valid(bool v) const
hash_valid.store(v,std::memory_order_release);
}
// Convert the address to an integer and then performs (address % interval)
// it does this by taking the first 64 bits of the public_view_key and converting to an integer
// This is used to determine when an address gets paid their batching reward.
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);
return address_as_integer % interval;
}
uint64_t account_public_address::next_payout_height(uint64_t current_height, uint64_t interval) const
{
uint64_t next_payout_height = current_height + (modulus(interval) - current_height % interval);
if (next_payout_height <= current_height)
next_payout_height += interval;
return next_payout_height;
}
}

View File

@ -208,7 +208,9 @@ namespace cryptonote
FIELD(vin)
FIELD(vout)
if (version >= txversion::v3_per_output_unlock_times && vout.size() != output_unlock_times.size())
{
throw std::invalid_argument{"v3 tx without correct unlock times"};
}
FIELD(extra)
if (version >= txversion::v4_tx_types)
ENUM_FIELD_N("type", type, type < txtype::_count);
@ -428,6 +430,9 @@ namespace cryptonote
void set_hash_valid(bool v) const;
transaction miner_tx;
size_t height;
crypto::public_key service_node_winner_key;
uint64_t reward = 0;
std::vector<crypto::hash> tx_hashes;
// hash cache
@ -445,6 +450,12 @@ namespace cryptonote
throw std::invalid_argument{"too many txs in block"};
if (major_version >= cryptonote::network_version_16_pulse)
FIELD(signatures)
if (major_version >= cryptonote::network_version_19)
{
VARINT_FIELD(height)
FIELD(service_node_winner_key)
FIELD(reward)
}
END_SERIALIZE()
};
@ -477,6 +488,10 @@ namespace cryptonote
{
return !(*this == rhs);
}
uint64_t modulus(uint64_t interval) const;
uint64_t next_payout_height(uint64_t current_height, uint64_t interval) const;
};
inline constexpr account_public_address null_address{};

View File

@ -164,6 +164,17 @@ namespace cryptonote {
return true;
}
//------------------------------------------------------------------------------------
batch_sn_payment::batch_sn_payment(std::string addr, uint64_t amt, cryptonote::network_type nettype):address(addr),amount(amt){
cryptonote::get_account_address_from_str(address_info, nettype, address);
};
batch_sn_payment::batch_sn_payment(cryptonote::address_parse_info& addr_info, uint64_t amt, cryptonote::network_type nettype):address_info(addr_info),amount(amt){
address = cryptonote::get_account_address_as_str(nettype, address_info.is_subaddress, address_info.address);
};
batch_sn_payment::batch_sn_payment(const cryptonote::account_public_address& addr, uint64_t amt, cryptonote::network_type nettype):amount(amt){
address_info = cryptonote::address_parse_info{addr,0};
address = cryptonote::get_account_address_as_str(nettype, address_info.is_subaddress, address_info.address);
};
//------------------------------------------------------------------------------------
uint8_t get_account_address_checksum(const public_address_outer_blob& bl)
{
const unsigned char* pbuf = reinterpret_cast<const unsigned char*>(&bl);

View File

@ -54,10 +54,32 @@ namespace cryptonote {
virtual void init() = 0;
};
struct address_parse_info
{
account_public_address address;
bool is_subaddress;
bool has_payment_id;
crypto::hash8 payment_id;
std::string as_str(network_type nettype) const;
KV_MAP_SERIALIZABLE
};
struct batch_sn_payment {
std::string address;
cryptonote::address_parse_info address_info;
uint64_t amount;
batch_sn_payment() = default;
batch_sn_payment(std::string addr, uint64_t amt, cryptonote::network_type nettype);
batch_sn_payment(cryptonote::address_parse_info& addr_info, uint64_t amt, cryptonote::network_type nettype);
batch_sn_payment(const cryptonote::account_public_address& addr, uint64_t amt, cryptonote::network_type nettype);
};
class ValidateMinerTxHook
{
public:
virtual bool validate_miner_tx(cryptonote::block const &block, struct block_reward_parts const &reward_parts) const = 0;
virtual bool validate_miner_tx(cryptonote::block const &block, struct block_reward_parts const &reward_parts, std::optional<std::vector<cryptonote::batch_sn_payment>> const &batched_sn_payments) const = 0;
};
class AltBlockAddedHook
@ -89,17 +111,7 @@ namespace cryptonote {
return addresses[0];
}
struct address_parse_info
{
account_public_address address;
bool is_subaddress;
bool has_payment_id;
crypto::hash8 payment_id;
std::string as_str(network_type nettype) const;
KV_MAP_SERIALIZABLE
};
/************************************************************************/
/* Cryptonote helper functions */

View File

@ -215,6 +215,11 @@ namespace boost
//------------------
a & b.miner_tx;
a & b.tx_hashes;
if (ver < 19)
return;
a & b.height;
a & b.service_node_winner_key;
a & b.reward;
}
template <class Archive>

View File

@ -868,9 +868,19 @@ namespace cryptonote
//---------------------------------------------------------------
uint64_t get_block_height(const block& b)
{
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);
return coinbase_in.height;
cryptonote::block bl = b;
if (b.miner_tx.vout.size() > 0)
{
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 >= cryptonote::network_version_19)
{
CHECK_AND_ASSERT_MES(coinbase_in.height == b.height, 0, "wrong miner tx in block: " << get_block_hash(b));
}
return coinbase_in.height;
} else {
return b.height;
}
}
//---------------------------------------------------------------
bool check_inputs_types_supported(const transaction& tx)
@ -880,7 +890,6 @@ namespace cryptonote
CHECK_AND_ASSERT_MES(std::holds_alternative<txin_to_key>(in), false, "wrong variant type: "
<< tools::type_name(tools::variant_type(in)) << ", expected " << tools::type_name<txin_to_key>()
<< ", in transaction id=" << get_transaction_hash(tx));
}
return true;
}

View File

@ -65,6 +65,7 @@ static constexpr std::array testnet_hard_forks =
hard_fork{17, 0, 447275, 1608276840 }, // 2020-12-18 05:34 UTC
hard_fork{18, 0, 501750, 1616631051 }, // 2021-03-25 12:10 UTC
hard_fork{18, 1, 578637, 1624040400 }, // 2021-06-18 18:20 UTC
hard_fork{19, 0, 732355, 1650402545 },
};
static constexpr std::array devnet_hard_forks =
@ -74,7 +75,10 @@ static constexpr std::array devnet_hard_forks =
hard_fork{ 12, 0, 3, 1599848400 },
hard_fork{ 13, 0, 4, 1599848400 },
hard_fork{ 15, 0, 5, 1599848400 },
hard_fork{ 16, 0, 99, 1599848400 },
hard_fork{ 16, 0, 100, 1599848400 },
hard_fork{ 17, 0, 151, 1599848400 },
hard_fork{ 18, 0, 152, 1599848400 },
hard_fork{ 19, 0, 153, 1599848400 },
};

View File

@ -257,6 +257,13 @@ namespace config
inline constexpr std::string_view HASH_KEY_CLSAG_AGG_0 = "CLSAG_agg_0"sv;
inline constexpr std::string_view HASH_KEY_CLSAG_AGG_1 = "CLSAG_agg_1"sv;
//Batching SN Rewards
inline constexpr uint64_t BATCHING_INTERVAL = 2520;
inline constexpr uint64_t MIN_BATCH_PAYMENT_AMOUNT = 1'000'000'000; // 1 OXEN (in atomic units)
inline constexpr uint64_t LIMIT_BATCH_OUTPUTS = 15;
// If a node has been online for this amount of blocks they will receive SN rewards
inline constexpr uint64_t SERVICE_NODE_PAYABLE_AFTER_BLOCKS = 720;
namespace testnet
{
inline constexpr uint64_t HEIGHT_ESTIMATE_HEIGHT = 339767;
@ -284,6 +291,8 @@ namespace config
// Testnet uptime proofs are 6x faster than mainnet (devnet config also uses these)
inline constexpr auto UPTIME_PROOF_FREQUENCY = 10min;
inline constexpr auto UPTIME_PROOF_VALIDITY = 21min;
inline constexpr uint64_t BATCHING_INTERVAL = 20;
inline constexpr uint64_t SERVICE_NODE_PAYABLE_AFTER_BLOCKS = 4;
}
namespace devnet
@ -390,6 +399,12 @@ namespace cryptonote
std::chrono::seconds UPTIME_PROOF_FREQUENCY;
std::chrono::seconds UPTIME_PROOF_VALIDITY;
uint64_t BATCHING_INTERVAL;
uint64_t MIN_BATCH_PAYMENT_AMOUNT;
uint64_t LIMIT_BATCH_OUTPUTS;
uint64_t SERVICE_NODE_PAYABLE_AFTER_BLOCKS;
inline constexpr std::string_view governance_wallet_address(int hard_fork_version) const {
const auto wallet_switch =
(NETWORK_TYPE == MAINNET || NETWORK_TYPE == FAKECHAIN)
@ -419,6 +434,10 @@ namespace cryptonote
config::UPTIME_PROOF_CHECK_INTERVAL,
config::UPTIME_PROOF_FREQUENCY,
config::UPTIME_PROOF_VALIDITY,
config::BATCHING_INTERVAL,
config::MIN_BATCH_PAYMENT_AMOUNT,
config::LIMIT_BATCH_OUTPUTS,
config::SERVICE_NODE_PAYABLE_AFTER_BLOCKS,
};
inline constexpr network_config testnet_config{
TESTNET,
@ -441,6 +460,10 @@ namespace cryptonote
config::UPTIME_PROOF_CHECK_INTERVAL,
config::testnet::UPTIME_PROOF_FREQUENCY,
config::testnet::UPTIME_PROOF_VALIDITY,
::config::testnet::BATCHING_INTERVAL,
config::MIN_BATCH_PAYMENT_AMOUNT,
config::LIMIT_BATCH_OUTPUTS,
::config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS,
};
inline constexpr network_config devnet_config{
DEVNET,
@ -463,6 +486,10 @@ namespace cryptonote
config::UPTIME_PROOF_CHECK_INTERVAL,
config::testnet::UPTIME_PROOF_FREQUENCY,
config::testnet::UPTIME_PROOF_VALIDITY,
::config::testnet::BATCHING_INTERVAL,
config::MIN_BATCH_PAYMENT_AMOUNT,
config::LIMIT_BATCH_OUTPUTS,
::config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS,
};
inline constexpr network_config fakenet_config{
FAKECHAIN,
@ -485,6 +512,10 @@ namespace cryptonote
config::fakechain::UPTIME_PROOF_CHECK_INTERVAL,
config::fakechain::UPTIME_PROOF_FREQUENCY,
config::fakechain::UPTIME_PROOF_VALIDITY,
::config::testnet::BATCHING_INTERVAL,
config::MIN_BATCH_PAYMENT_AMOUNT,
config::LIMIT_BATCH_OUTPUTS,
::config::testnet::SERVICE_NODE_PAYABLE_AFTER_BLOCKS,
};
inline constexpr const network_config& get_config(network_type nettype)

View File

@ -55,6 +55,7 @@ target_link_libraries(cryptonote_core
SQLite::SQLite3
PRIVATE
Boost::program_options
SQLiteCpp
systemd
extra)

View File

@ -33,6 +33,7 @@
#include <chrono>
#include <cstdio>
#include <boost/endian/conversion.hpp>
#include <sodium.h>
#include "common/rules.h"
#include "common/hex.h"
@ -300,22 +301,35 @@ uint64_t Blockchain::get_current_blockchain_height(bool lock) const
//------------------------------------------------------------------
bool Blockchain::load_missing_blocks_into_oxen_subsystems()
{
uint64_t const snl_height = std::max(hard_fork_begins(m_nettype, network_version_9_service_nodes).value_or(0), m_service_node_list.height() + 1);
uint64_t const ons_height = std::max(hard_fork_begins(m_nettype, network_version_15_ons).value_or(0), m_ons_db.height() + 1);
uint64_t const end_height = m_db->height();
uint64_t const start_height = std::min(end_height, std::min(ons_height, snl_height));
std::vector<uint64_t> start_height_options;
uint64_t const snl_height = std::max(hard_fork_begins(m_nettype, network_version_9_service_nodes).value_or(0), m_service_node_list.height() + 1);
start_height_options.push_back(snl_height);
uint64_t const ons_height = std::max(hard_fork_begins(m_nettype, network_version_15_ons).value_or(0), m_ons_db.height() + 1);
start_height_options.push_back(ons_height);
uint64_t sqlite_height = 0;
if (m_sqlite_db)
{
sqlite_height = std::max(hard_fork_begins(m_nettype, network_version_19).value_or(0) - 1, m_sqlite_db->height + 1);
start_height_options.push_back(sqlite_height);
} else {
if (m_nettype != FAKECHAIN)
throw std::logic_error("Blockchain missing SQLite Database");
}
uint64_t const end_height = m_db->height();
start_height_options.push_back(end_height);
uint64_t const start_height = *std::min_element(start_height_options.begin(), start_height_options.end());
int64_t const total_blocks = static_cast<int64_t>(end_height) - static_cast<int64_t>(start_height);
if (total_blocks <= 0) return true;
if (total_blocks > 1)
MGINFO("Loading blocks into oxen subsystems, scanning blockchain from height: " << start_height << " to: " << end_height << " (snl: " << snl_height << ", ons: " << ons_height << ")");
MGINFO("Loading blocks into oxen subsystems, scanning blockchain from height: " << start_height << " to: " << end_height << " (snl: " << snl_height << ", ons: " << ons_height << ", sqlite: " << sqlite_height << ")");
using clock = std::chrono::steady_clock;
using work_time = std::chrono::duration<float>;
int64_t constexpr BLOCK_COUNT = 1000;
auto work_start = clock::now();
auto scan_start = work_start;
work_time ons_duration{}, snl_duration{}, ons_iteration_duration{}, snl_iteration_duration{};
work_time ons_duration{}, snl_duration{}, sqlite_duration{}, ons_iteration_duration{}, snl_iteration_duration{}, sqlite_iteration_duration{};
std::vector<cryptonote::block> blocks;
std::vector<cryptonote::transaction> txs;
@ -330,7 +344,7 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
{
m_service_node_list.store();
auto duration = work_time{clock::now() - work_start};
MGINFO("... scanning height " << start_height + (index * BLOCK_COUNT) << " (" << duration.count() << "s) (snl: " << snl_iteration_duration.count() << "s; ons: " << ons_iteration_duration.count() << "s)");
MGINFO("... scanning height " << start_height + (index * BLOCK_COUNT) << " (" << duration.count() << "s) (snl: " << snl_iteration_duration.count() << "s; ons: " << ons_iteration_duration.count() << "s; sqlite: " << sqlite_iteration_duration.count() << "s)");
#ifdef ENABLE_SYSTEMD
// Tell systemd that we're doing something so that it should let us continue starting up
// (giving us 120s until we have to send the next notification):
@ -340,6 +354,7 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
ons_duration += ons_iteration_duration;
snl_duration += snl_iteration_duration;
sqlite_duration += sqlite_iteration_duration;
ons_iteration_duration = snl_iteration_duration = {};
}
@ -390,13 +405,24 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
}
ons_iteration_duration += clock::now() - ons_start;
}
if (m_sqlite_db && (block_height >= sqlite_height))
{
auto sqlite_start = clock::now();
if (!m_service_node_list.process_batching_rewards(blk))
{
MFATAL("Unable to process block for updating SQLite DB: " << cryptonote::get_block_hash(blk));
return false;
}
sqlite_iteration_duration += clock::now() - sqlite_start;
}
}
}
if (total_blocks > 1)
{
auto duration = work_time{clock::now() - scan_start};
MGINFO("Done recalculating oxen subsystems (" << duration.count() << "s) (snl: " << snl_duration.count() << "s; ons: " << ons_duration.count() << "s)");
MGINFO("Done recalculating oxen subsystems (" << duration.count() << "s) (snl: " << snl_duration.count() << "s; ons: " << ons_duration.count() << "s)" << "s; sqlite: " << sqlite_duration.count() << "s)");
}
if (total_blocks > 0)
@ -407,7 +433,8 @@ bool Blockchain::load_missing_blocks_into_oxen_subsystems()
//------------------------------------------------------------------
//FIXME: possibly move this into the constructor, to avoid accidentally
// dereferencing a null BlockchainDB pointer
bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty, const GetCheckpointsCallback& get_checkpoints/* = nullptr*/)
bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, std::shared_ptr<cryptonote::BlockchainSQLite> sqlite_db, const network_type nettype, bool offline, const cryptonote::test_options *test_options, difficulty_type fixed_difficulty, const GetCheckpointsCallback& get_checkpoints/* = nullptr*/)
{
LOG_PRINT_L3("Blockchain::" << __func__);
@ -439,6 +466,13 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, const network_type nett
if (test_options) // Fakechain mode
fakechain_hardforks = test_options->hard_forks;
if (sqlite_db)
{
m_sqlite_db = std::move(sqlite_db);
} else {
if (m_nettype != FAKECHAIN)
throw std::logic_error("Blockchain missing SQLite Database");
}
// if the blockchain is new, add the genesis block
// this feels kinda kludgy to do it this way, but can be looked at later.
@ -511,6 +545,11 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, const network_type nett
try
{
m_db->pop_block(popped_block, popped_txs);
if (!m_service_node_list.pop_batching_rewards_block(popped_block))
{
LOG_ERROR("Failed to pop to batch rewards DB. throwing");
throw;
}
}
// anything that could cause this to throw is likely catastrophic,
// so we re-throw
@ -550,6 +589,7 @@ bool Blockchain::init(BlockchainDB* db, sqlite3 *ons_db, const network_type nett
return false;
}
hook_block_added(m_checkpoints);
hook_blockchain_detached(m_checkpoints);
for (InitHook* hook : m_init_hooks)
@ -692,6 +732,7 @@ block Blockchain::pop_block_from_blockchain()
CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block");
try
{
m_db->pop_block(popped_block, popped_txs);
@ -708,6 +749,11 @@ block Blockchain::pop_block_from_blockchain()
LOG_ERROR("Error popping block from blockchain, throwing!");
throw;
}
if (!m_service_node_list.pop_batching_rewards_block(popped_block))
{
LOG_ERROR("Failed to pop to batch rewards DB. throwing");
throw;
}
m_ons_db.block_detach(*this, m_db->height());
@ -1219,46 +1265,49 @@ difficulty_type Blockchain::get_difficulty_for_alternative_chain(const std::list
bool Blockchain::prevalidate_miner_transaction(const block& b, uint64_t height, uint8_t hf_version)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs");
CHECK_AND_ASSERT_MES(std::holds_alternative<txin_gen>(b.miner_tx.vin[0]), false, "coinbase transaction in the block has the wrong type");
if (var::get<txin_gen>(b.miner_tx.vin[0]).height != height)
if (b.miner_tx.vout.size() > 0)
{
MWARNING("The miner transaction in block has invalid height: " << var::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height);
return false;
}
MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx));
CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
if (hf_version >= cryptonote::network_version_12_checkpointing)
{
if (b.miner_tx.type != txtype::standard)
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() == 1, false, "coinbase transaction in the block has no inputs");
CHECK_AND_ASSERT_MES(std::holds_alternative<txin_gen>(b.miner_tx.vin[0]), false, "coinbase transaction in the block has the wrong type");
if (var::get<txin_gen>(b.miner_tx.vin[0]).height != height)
{
MERROR("Coinbase invalid transaction type for coinbase transaction.");
MWARNING("The miner transaction in block has invalid height: " << var::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height);
return false;
}
MDEBUG("Miner tx hash: " << get_transaction_hash(b.miner_tx));
CHECK_AND_ASSERT_MES(b.miner_tx.unlock_time == height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "coinbase transaction transaction has the wrong unlock time=" << b.miner_tx.unlock_time << ", expected " << height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW);
txversion min_version = transaction::get_max_version_for_hf(hf_version);
txversion max_version = transaction::get_min_version_for_hf(hf_version);
if (b.miner_tx.version < min_version || b.miner_tx.version > max_version)
if (hf_version >= cryptonote::network_version_12_checkpointing)
{
MERROR_VER("Coinbase invalid version: " << b.miner_tx.version << " for hardfork: " << hf_version << " min/max version: " << min_version << "/" << max_version);
if (b.miner_tx.type != txtype::standard)
{
MERROR("Coinbase invalid transaction type for coinbase transaction.");
return false;
}
txversion min_version = transaction::get_max_version_for_hf(hf_version);
txversion max_version = transaction::get_min_version_for_hf(hf_version);
if (b.miner_tx.version < min_version || b.miner_tx.version > max_version)
{
MERROR_VER("Coinbase invalid version: " << b.miner_tx.version << " for hardfork: " << hf_version << " min/max version: " << min_version << "/" << max_version);
return false;
}
}
if (hf_version >= HF_VERSION_REJECT_SIGS_IN_COINBASE) // Enforce empty rct signatures for miner transactions,
CHECK_AND_ASSERT_MES(b.miner_tx.rct_signatures.type == rct::RCTType::Null, false, "RingCT signatures not allowed in coinbase transactions");
//check outs overflow
//NOTE: not entirely sure this is necessary, given that this function is
// designed simply to make sure the total amount for a transaction
// does not overflow a uint64_t, and this transaction *is* a uint64_t...
if(!check_outs_overflow(b.miner_tx))
{
MERROR("miner transaction has money overflow in block " << get_block_hash(b));
return false;
}
}
if (hf_version >= HF_VERSION_REJECT_SIGS_IN_COINBASE) // Enforce empty rct signatures for miner transactions,
CHECK_AND_ASSERT_MES(b.miner_tx.rct_signatures.type == rct::RCTType::Null, false, "RingCT signatures not allowed in coinbase transactions");
//check outs overflow
//NOTE: not entirely sure this is necessary, given that this function is
// designed simply to make sure the total amount for a transaction
// does not overflow a uint64_t, and this transaction *is* a uint64_t...
if(!check_outs_overflow(b.miner_tx))
{
MERROR("miner transaction has money overflow in block " << get_block_hash(b));
return false;
}
return true;
}
//------------------------------------------------------------------
@ -1269,8 +1318,10 @@ 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) {
MERROR_VER("miner tx has no outputs");
return false;
if (b.major_version < cryptonote::network_version_19) {
MERROR_VER("miner tx has no outputs");
return false;
}
}
uint64_t median_weight;
@ -1296,21 +1347,20 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
return false;
}
block_reward_parts reward_parts;
block_reward_parts reward_parts{0};
if (!get_oxen_block_reward(median_weight, cumulative_block_weight, already_generated_coins, version, reward_parts, block_reward_context))
{
MERROR_VER("block weight " << cumulative_block_weight << " is bigger than allowed for this blockchain");
return false;
}
auto batched_sn_payments = m_sqlite_db->get_sn_payments(height);
cryptonote::block bl = b;
for (ValidateMinerTxHook* hook : m_validate_miner_tx_hooks)
{
if (!hook->validate_miner_tx(b, reward_parts))
if (!hook->validate_miner_tx(b, reward_parts, batched_sn_payments))
return false;
}
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b))
if (already_generated_coins != 0 && block_has_governance_output(nettype(), b) && version < cryptonote::network_version_19)
{
if (version >= network_version_10_bulletproofs && reward_parts.governance_paid == 0)
{
@ -1324,6 +1374,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
return false;
}
if (!validate_governance_reward_key(
m_db->height(),
cryptonote::get_config(m_nettype).governance_wallet_address(version),
@ -1338,8 +1389,16 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
// +1 here to allow a 1 atomic unit error in the calculation (which can happen because of floating point errors or rounding)
// TODO(oxen): eliminate all floating point math in reward calculations.
uint64_t max_base_reward = reward_parts.base_miner + reward_parts.governance_paid + reward_parts.service_node_total + 1;
uint64_t max_base_reward = reward_parts.governance_paid + 1;
if (version >= network_version_19 && 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;});
} else {
max_base_reward += reward_parts.base_miner + reward_parts.service_node_total;
}
uint64_t max_money_in_use = max_base_reward + reward_parts.miner_fee;
if (money_in_use > max_money_in_use)
{
MERROR_VER("coinbase transaction spends too much money (" << print_money(money_in_use) << "). Maximum block reward is "
@ -1347,8 +1406,17 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
return false;
}
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;
if (version < network_version_19) {
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;
}
if (b.reward > reward_parts.base_miner + reward_parts.miner_fee + reward_parts.service_node_total)
{
MERROR_VER("block reward to be batched spends too much money (" << print_money(b.reward) << "). Maximum block reward is "
<< print_money(max_money_in_use) << " (= " << print_money(max_base_reward) << " base + " << print_money(reward_parts.miner_fee) << " fees)");
return false;
}
return true;
}
@ -1465,6 +1533,7 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
invalidate_block_template_cache();
}
// from_block is usually nullptr, used to build altchains
if (from_block)
{
//build alternative subchain, front -> mainchain, back -> alternative head
@ -1524,6 +1593,7 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
}
else
{
// Creates the block template for next block on main chain
height = m_db->height();
auto [maj, min] = get_ideal_block_version(m_nettype, height);
b.major_version = maj;
@ -1543,8 +1613,11 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead.");
uint8_t hf_version = b.major_version;
size_t txs_weight;
uint64_t fee;
// Add transactions in mempool to block
if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, b.major_version, height))
{
return false;
@ -1556,7 +1629,6 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight
*/
//make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight
uint8_t hf_version = b.major_version;
auto miner_tx_context =
info.is_miner
? oxen_miner_tx_context::miner_block(m_nettype, info.miner_address, m_service_node_list.get_block_leader())
@ -1567,13 +1639,22 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
return false;
}
bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, b.miner_tx, miner_tx_context, ex_nonce, hf_version);
// 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 >= cryptonote::network_version_19)
{
sn_rwds = m_sqlite_db->get_sn_payments(height); //Rewards to pay out
}
uint64_t block_rewards = 0;
bool r;
std::tie(r, block_rewards) = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, b.miner_tx, miner_tx_context, sn_rwds, ex_nonce, hf_version);
CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance");
size_t cumulative_weight = txs_weight + get_transaction_weight(b.miner_tx);
for (size_t try_count = 0; try_count != 10; ++try_count)
{
r = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, b.miner_tx, miner_tx_context, ex_nonce, hf_version);
std::tie(r, block_rewards) = construct_miner_tx(height, median_weight, already_generated_coins, cumulative_weight, fee, b.miner_tx, miner_tx_context, sn_rwds, ex_nonce, hf_version);
CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, second chance");
size_t coinbase_weight = get_transaction_weight(b.miner_tx);
@ -1606,6 +1687,14 @@ bool Blockchain::create_block_template_internal(block& b, const crypto::hash *fr
if (!from_block)
cache_block_template(b, info.miner_address, ex_nonce, diffic, height, expected_reward, pool_cookie);
if (miner_tx_context.pulse)
b.service_node_winner_key = miner_tx_context.pulse_block_producer.key;
else
b.service_node_winner_key = {0};
b.reward = block_rewards;
b.height = height;
return true;
}
LOG_ERROR("Failed to create_block_template with " << 10 << " tries");
@ -2857,6 +2946,7 @@ size_t Blockchain::get_total_transactions() const
// m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as
// well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must
// lock if it is otherwise needed.
return m_db->get_tx_count();
}
//------------------------------------------------------------------
@ -3100,9 +3190,12 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const
LOG_PRINT_L3("Blockchain::" << __func__);
for (const txin_v& in: tx.vin)
{
CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, in_to_key, true);
if(have_tx_keyimg_as_spent(in_to_key.k_image))
return true;
if (!std::holds_alternative<txin_gen>(in))
{
CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, in_to_key, true);
if(have_tx_keyimg_as_spent(in_to_key.k_image))
return true;
}
}
return false;
}
@ -3221,7 +3314,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
if (tx.is_transfer())
{
if (tx.type != txtype::oxen_name_system && hf_version >= HF_VERSION_MIN_2_OUTPUTS && tx.vout.size() < 2)
if (tx.type != txtype::oxen_name_system && !std::holds_alternative<txin_gen>(tx.vin[0]) && hf_version >= HF_VERSION_MIN_2_OUTPUTS && tx.vout.size() < 2)
{
MERROR_VER("Tx " << get_transaction_hash(tx) << " has fewer than two outputs, which is not allowed as of hardfork " << +HF_VERSION_MIN_2_OUTPUTS);
tvc.m_too_few_outputs = true;
@ -3338,8 +3431,12 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
{
case rct::RCTType::Null: {
// we only accept no signatures for coinbase txes
MERROR_VER("Null rct signature on non-coinbase tx");
return false;
if (!std::holds_alternative<txin_gen>(tx.vin[0]))
{
MERROR_VER("Null rct signature on non-coinbase tx");
return false;
}
break;
}
case rct::RCTType::Simple:
case rct::RCTType::Bulletproof:
@ -4224,6 +4321,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
TIME_MEASURE_START(aa);
// XXX old code does not check whether tx exists
if (m_db->tx_exists(tx_id))
{
MGINFO_RED("Block with id: " << id << " attempting to add transaction already in blockchain with id: " << tx_id);
@ -4407,6 +4505,18 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
MGINFO_RED("Failed to add block to ONS DB.");
bvc.m_verifivation_failed = true;
return false;
}
if (m_sqlite_db) {
if (!m_service_node_list.process_batching_rewards(bl))
{
MERROR("Failed to add block to batch rewards DB.");
bvc.m_verifivation_failed = true;
return false;
}
} else {
if (m_nettype != FAKECHAIN)
throw std::logic_error("Blockchain missing SQLite Database");
}
for (BlockAddedHook* hook : m_block_added_hooks)
@ -4938,6 +5048,13 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar
return true;
}
// Constant reward every block at HF19 and batched through service node batching
if (hard_fork_version >= cryptonote::network_version_19)
{
reward = cryptonote::governance_reward_formula(hard_fork_version);
return true;
}
// Ignore governance reward and payout instead the last
// GOVERNANCE_BLOCK_REWARD_INTERVAL number of blocks governance rewards. We
// come back for this height's rewards in the next interval. The reward is
@ -4957,8 +5074,7 @@ bool Blockchain::calc_batched_governance_reward(uint64_t height, uint64_t &rewar
}
uint64_t start_height = height - num_blocks;
if (height < num_blocks)
{
if (height < num_blocks) {
start_height = 0;
num_blocks = height;
}
@ -5193,14 +5309,18 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
// get all amounts from tx.vin(s)
for (const auto &txin : tx.vin)
{
const auto& in_to_key = var::get<txin_to_key>(txin);
// check for duplicate
auto it = its->second.find(in_to_key.k_image);
if (it != its->second.end())
SCAN_TABLE_QUIT("Duplicate key_image found from incoming blocks.");
if (!std::holds_alternative<txin_gen>(txin))
{
const auto& in_to_key = var::get<txin_to_key>(txin);
amounts.push_back(in_to_key.amount);
// check for duplicate
auto it = its->second.find(in_to_key.k_image);
if (it != its->second.end())
SCAN_TABLE_QUIT("Duplicate key_image found from incoming blocks.");
amounts.push_back(in_to_key.amount);
}
}
// sort and remove duplicate amounts from amounts list
@ -5221,11 +5341,14 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
// add new absolute_offsets to offset_map
for (const auto &txin : tx.vin)
{
const auto& in_to_key = var::get<txin_to_key>(txin);
// no need to check for duplicate here.
auto absolute_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets);
for (const auto & offset : absolute_offsets)
offset_map[in_to_key.amount].push_back(offset);
if (!std::holds_alternative<txin_gen>(txin))
{
const auto& in_to_key = var::get<txin_to_key>(txin);
// no need to check for duplicate here.
auto absolute_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets);
for (const auto & offset : absolute_offsets)
offset_map[in_to_key.amount].push_back(offset);
}
}
}
@ -5287,33 +5410,36 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
for (const auto &txin : tx.vin)
{
const txin_to_key &in_to_key = var::get<txin_to_key>(txin);
auto needed_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets);
std::vector<output_data_t> outputs;
for (const uint64_t & offset_needed : needed_offsets)
if (!std::holds_alternative<txin_gen>(txin))
{
size_t pos = 0;
bool found = false;
const txin_to_key &in_to_key = var::get<txin_to_key>(txin);
auto needed_offsets = relative_output_offsets_to_absolute(in_to_key.key_offsets);
for (const uint64_t &offset_found : offset_map[in_to_key.amount])
std::vector<output_data_t> outputs;
for (const uint64_t & offset_needed : needed_offsets)
{
if (offset_needed == offset_found)
size_t pos = 0;
bool found = false;
for (const uint64_t &offset_found : offset_map[in_to_key.amount])
{
found = true;
break;
if (offset_needed == offset_found)
{
found = true;
break;
}
++pos;
}
++pos;
if (found && pos < tx_map[in_to_key.amount].size())
outputs.push_back(tx_map[in_to_key.amount].at(pos));
else
break;
}
if (found && pos < tx_map[in_to_key.amount].size())
outputs.push_back(tx_map[in_to_key.amount].at(pos));
else
break;
its->second.emplace(in_to_key.k_image, outputs);
}
its->second.emplace(in_to_key.k_image, outputs);
}
}
}

View File

@ -63,6 +63,7 @@
#include "crypto/hash.h"
#include "checkpoints/checkpoints.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_db/sqlite/db_sqlite.h"
#include "cryptonote_core/oxen_name_system.h"
#include "pulse.h"
@ -149,7 +150,7 @@ namespace cryptonote
*
* @return true on success, false if any initialization steps fail
*/
bool init(BlockchainDB* db, sqlite3 *ons_db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = nullptr, difficulty_type fixed_difficulty = 0, const GetCheckpointsCallback& get_checkpoints = nullptr);
bool init(BlockchainDB* db, sqlite3 *ons_db, std::shared_ptr<cryptonote::BlockchainSQLite> sqlite_db, const network_type nettype = MAINNET, bool offline = false, const cryptonote::test_options *test_options = nullptr, difficulty_type fixed_difficulty = 0, const GetCheckpointsCallback& get_checkpoints = nullptr);
/**
* @brief Uninitializes the blockchain state
@ -1009,6 +1010,8 @@ namespace cryptonote
const ons::name_system_db &name_system_db() const { return m_ons_db; }
std::shared_ptr<cryptonote::BlockchainSQLite> sqlite_db() { return m_sqlite_db; }
/**
* @brief flush the invalid blocks set
*/
@ -1056,6 +1059,7 @@ namespace cryptonote
tx_memory_pool& m_tx_pool;
service_nodes::service_node_list& m_service_node_list;
ons::name_system_db m_ons_db;
std::shared_ptr<cryptonote::BlockchainSQLite> m_sqlite_db;
mutable std::recursive_mutex m_blockchain_lock; // TODO: add here reader/writer lock

View File

@ -64,6 +64,7 @@ extern "C" {
#include "checkpoints/checkpoints.h"
#include "ringct/rctTypes.h"
#include "blockchain_db/blockchain_db.h"
#include "blockchain_db/sqlite/db_sqlite.h"
#include "ringct/rctSigs.h"
#include "common/notify.h"
#include "version.h"
@ -608,6 +609,12 @@ namespace cryptonote
if(fs::exists(folder / "lns.db"))
ons_db_file_path = folder / "lns.db";
auto sqlite_db_file_path = folder / "sqlite.db";
if (m_nettype == FAKECHAIN)
{
sqlite_db_file_path = ":memory:";
}
auto sqliteDB = std::make_shared<cryptonote::BlockchainSQLite>(m_nettype, sqlite_db_file_path);
folder /= db->get_db_name();
MGINFO("Loading blockchain from folder " << folder << " ...");
@ -779,10 +786,11 @@ namespace cryptonote
sqlite3 *ons_db = ons::init_oxen_name_system(ons_db_file_path, db->is_read_only());
if (!ons_db) return false;
init_oxenmq(vm);
const difficulty_type fixed_difficulty = command_line::get_arg(vm, arg_fixed_difficulty);
r = m_blockchain_storage.init(db.release(), ons_db, m_nettype, m_offline, regtest ? &regtest_test_options : test_options, fixed_difficulty, get_checkpoints);
r = m_blockchain_storage.init(db.release(), ons_db, std::move(sqliteDB), m_nettype, m_offline, regtest ? &regtest_test_options : test_options, fixed_difficulty, get_checkpoints);
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage");
r = m_mempool.init(max_txpool_weight);
@ -1253,11 +1261,11 @@ namespace cryptonote
}
}
//-----------------------------------------------------------------------------------------------
std::vector<core::tx_verification_batch_info> core::parse_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
std::vector<cryptonote::tx_verification_batch_info> core::parse_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
{
// Caller needs to do this around both this *and* handle_parsed_txs
//auto lock = incoming_tx_lock();
std::vector<tx_verification_batch_info> tx_info(tx_blobs.size());
std::vector<cryptonote::tx_verification_batch_info> tx_info(tx_blobs.size());
tools::threadpool& tpool = tools::threadpool::getInstance();
tools::threadpool::waiter waiter;
@ -1347,7 +1355,7 @@ namespace cryptonote
return ok;
}
//-----------------------------------------------------------------------------------------------
std::vector<core::tx_verification_batch_info> core::handle_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
std::vector<cryptonote::tx_verification_batch_info> core::handle_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
{
auto lock = incoming_tx_lock();
auto parsed = parse_incoming_txs(tx_blobs, opts);
@ -2062,6 +2070,7 @@ namespace cryptonote
MERROR("Block found, but failed to prepare to add");
return false;
}
// add_new_block will verify block and set bvc.m_verification_failed accordingly
add_new_block(b, bvc, nullptr /*checkpoint*/);
cleanup_handle_incoming_blocks(true);
m_miner.on_block_chain_update();
@ -2250,8 +2259,10 @@ namespace cryptonote
{
std::vector<service_nodes::service_node_pubkey_info> const states = get_service_node_list_state({ m_service_keys.pub });
// wait one block before starting uptime proofs.
if (!states.empty() && (states[0].info->registration_height + 1) < get_current_blockchain_height())
// wait one block before starting uptime proofs (but not on testnet/devnet, where we sometimes
// have mass registrations/deregistrations where the waiting causes problems).
uint64_t delay_blocks = m_nettype == MAINNET ? 1 : 0;
if (!states.empty() && (states[0].info->registration_height + delay_blocks) < get_current_blockchain_height())
{
m_check_uptime_proof_interval.do_call([this]() {
// This timer is not perfectly precise and can leak seconds slightly, so send the uptime

View File

@ -182,22 +182,6 @@ namespace cryptonote
*/
bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, const tx_pool_options &opts);
/**
* Returned type of parse_incoming_txs() that provides details about which transactions failed
* and why. This is passed on to handle_parsed_txs() (potentially after modification such as
* setting `approved_blink`) to handle_parsed_txs() to actually insert the transactions.
*/
struct tx_verification_batch_info {
tx_verification_context tvc{}; // Verification information
bool parsed = false; // Will be true if we were able to at least parse the transaction
bool result = false; // Indicates that the transaction was parsed and passed some basic checks
bool already_have = false; // Indicates that the tx was found to already exist (in mempool or blockchain)
bool approved_blink = false; // Can be set between the parse and handle calls to make this a blink tx (that replaces conflicting non-blink txes)
const blobdata *blob = nullptr; // Will be set to a pointer to the incoming blobdata (i.e. string). caller must keep it alive!
crypto::hash tx_hash; // The transaction hash (only set if `parsed`)
transaction tx; // The parsed transaction (only set if `parsed`)
};
/// Returns an RAII unique lock holding the incoming tx mutex.
auto incoming_tx_lock() { return std::unique_lock{m_incoming_tx_lock}; }
@ -217,7 +201,7 @@ namespace cryptonote
*
* @return vector of tx_verification_batch_info structs for the given transactions.
*/
std::vector<tx_verification_batch_info> parse_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts);
std::vector<cryptonote::tx_verification_batch_info> parse_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts);
/**
* @brief handles parsed incoming transactions

View File

@ -38,6 +38,7 @@
#include "blockchain.h"
#include "cryptonote_basic/miner.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
#include "ringct/rctSigs.h"
@ -121,6 +122,8 @@ namespace cryptonote
cryptonote::get_account_address_from_str(governance_wallet_address, nettype, governance_wallet_address_str);
crypto::public_key correct_key;
if (!get_deterministic_output_key(governance_wallet_address.address, gov_key, output_index, correct_key))
{
MERROR("Failed to generate deterministic output key for governance wallet output validation");
@ -130,7 +133,7 @@ namespace cryptonote
return correct_key == output_key;
}
uint64_t governance_reward_formula(uint64_t base_reward, uint8_t hf_version)
uint64_t governance_reward_formula(uint8_t hf_version, uint64_t base_reward)
{
return hf_version >= network_version_17 ? FOUNDATION_REWARD_HF17 :
hf_version >= network_version_16_pulse ? FOUNDATION_REWARD_HF15 + CHAINFLIP_LIQUIDITY_HF16 :
@ -149,9 +152,10 @@ namespace cryptonote
if (height == 0)
return false;
if (hard_fork_version <= network_version_9_service_nodes)
if (hard_fork_version <= network_version_9_service_nodes || hard_fork_version >= cryptonote::network_version_19)
return true;
if (height % cryptonote::get_config(nettype).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS != 0)
{
return false;
@ -163,7 +167,7 @@ namespace cryptonote
uint64_t derive_governance_from_block_reward(network_type nettype, const cryptonote::block &block, uint8_t hf_version)
{
if (hf_version >= 15)
return governance_reward_formula(0, hf_version);
return governance_reward_formula(hf_version);
uint64_t result = 0;
uint64_t snode_reward = 0;
@ -179,7 +183,7 @@ namespace cryptonote
}
uint64_t base_reward = snode_reward * 2; // pre-HF15, SN reward = half of base reward
uint64_t governance = governance_reward_formula(base_reward, hf_version);
uint64_t governance = governance_reward_formula(hf_version, base_reward);
uint64_t block_reward = base_reward - governance;
uint64_t actual_reward = 0; // sanity check
@ -240,22 +244,7 @@ namespace cryptonote
return reward;
}
enum struct reward_type
{
miner,
snode,
governance
};
struct reward_payout
{
reward_type type;
account_public_address address;
uint64_t amount;
bool operator==(service_nodes::payout_entry const &other) const { return address == other.address; }
};
bool construct_miner_tx(
std::pair<bool, uint64_t> construct_miner_tx(
size_t height,
size_t median_weight,
uint64_t already_generated_coins,
@ -263,6 +252,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 blobdata& extra_nonce,
uint8_t hard_fork_version)
{
@ -276,22 +266,22 @@ namespace cryptonote
keypair const txkey{hw::get_device("default")};
keypair const gov_key = get_deterministic_keypair_from_height(height); // NOTE: Always need since we use same key for service node
uint64_t block_rewards = 0;
// NOTE: TX Extra
add_tx_extra<tx_extra_pub_key>(tx, txkey.pub);
if(!extra_nonce.empty())
{
if(!add_extra_nonce_to_tx_extra(tx.extra, extra_nonce))
return false;
return std::make_pair(false, block_rewards);
}
// TODO(doyle): We don't need to do this. It's a deterministic key.
if (already_generated_coins != 0)
add_tx_extra<tx_extra_pub_key>(tx, gov_key.pub);
add_service_node_winner_to_tx_extra(tx.extra, miner_tx_context.block_leader.key);
oxen_block_reward_context block_reward_context = {};
block_reward_context.fee = fee;
block_reward_context.height = height;
@ -302,33 +292,26 @@ namespace cryptonote
if(!get_oxen_block_reward(median_weight, current_block_weight, already_generated_coins, hard_fork_version, reward_parts, block_reward_context))
{
LOG_PRINT_L0("Failed to calculate block reward");
return false;
return std::make_pair(false, block_rewards);
}
// TODO(doyle): Batching awards
// NOTE: Batched Pulse Block Payment Details
//
// NOTE: Summarise rewards to payout (up to 9 payout entries/outputs)
//
// Miner Block
// - 1 | Miner
// - Up To 4 | Block Leader (Queued node at the top of the Service Node List)
// - Up To 1 | Governance
//
// Pulse Block
// - Up to 4 | Block Producer (0-3 for Pooled Service Node)
// - Up To 4 | Block Leader (Queued node at the top of the Service Node List)
// - Up To 1 | Governance (When a block is at the Governance payout interval)
//
// NOTE: Pulse Block Payment Details
// 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 service node list is adjusted to only accrue for nodes
// that have been online for greater than 1 hour.
//
// By default, when Pulse round is 0, the Block Producer is the Block
// Leader. Coinbase and transaction fees are given to the Block Leader.
// This is the common case, and in that instance we avoid generating
// duplicate outputs and payment occurs in 1 output.
// Leader. Transaction fees are given to the Block Leader.
// This is the common case, and the transaction fees incentivise the
// block producer to produce the block and not stall the network.
//
// On alternative rounds, transaction fees are given to the alternative
// block producer (which is now different from the Block Leader). The
// original block producer still receives the coinbase reward. A Pulse
// original block producer still accrues their share of the coinbase. A Pulse
// round's failure is determined by the non-participation of the members of
// the quorum, so failing a round's onus is not always on the original block
// producer (it could be the validators colluding) hence why they still
@ -345,19 +328,22 @@ namespace cryptonote
// (multiple non-participation marks over the monitoring period will induce
// a decommission) by members of the quorum.
size_t rewards_length = 0;
std::array<reward_payout, 9> 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 >= cryptonote::network_version_9_service_nodes)
CHECK_AND_ASSERT_MES(miner_tx_context.block_leader.payouts.size(), false, "Constructing a block leader reward for block but no payout entries specified");
CHECK_AND_ASSERT_MES(miner_tx_context.block_leader.payouts.size(), std::make_pair(false, block_rewards), "Constructing a block leader reward for block but no payout entries specified");
// NOTE: Add Block Producer Reward
service_nodes::payout const &leader = miner_tx_context.block_leader;
if (miner_tx_context.pulse)
{
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.payouts.size(), false, "Constructing a reward for block produced by pulse but no payout entries specified");
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.key, false, "Null Key given for Pulse Block Producer");
CHECK_AND_ASSERT_MES(hard_fork_version >= cryptonote::network_version_16_pulse, false, "Pulse Block Producer is not valid until HF16, current HF" << hard_fork_version);
// PULSE BLOCKS
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.payouts.size(), std::make_pair(false, block_rewards), "Constructing a reward for block produced by pulse but no payout entries specified");
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.key, std::make_pair(false, block_rewards), "Null Key given for Pulse Block Producer");
CHECK_AND_ASSERT_MES(hard_fork_version >= cryptonote::network_version_16_pulse, std::make_pair(false, block_rewards), "Pulse Block Producer is not valid until HF16, current HF" << hard_fork_version);
uint64_t leader_reward = reward_parts.service_node_total;
if (miner_tx_context.block_leader.key == miner_tx_context.pulse_block_producer.key)
@ -371,29 +357,53 @@ namespace cryptonote
std::vector<uint64_t> split_rewards = distribute_reward_by_portions(producer.payouts, reward_parts.miner_fee, true /*distribute_remainder*/);
for (size_t i = 0; i < producer.payouts.size(); i++)
rewards[rewards_length++] = {reward_type::snode, producer.payouts[i].address, split_rewards[i]};
if (hard_fork_version < cryptonote::network_version_19)
{
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);
}
}
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++)
rewards[rewards_length++] = {reward_type::snode, leader.payouts[i].address, split_rewards[i]};
{
if (hard_fork_version < cryptonote::network_version_19) {
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
} else {
batched_rewards.emplace_back(leader.payouts[i].address, split_rewards[i], nettype);
}
}
}
else
{
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.payouts.empty(), false, "Constructing a reward for block produced by miner but payout entries specified");
// MINED BLOCKS
CHECK_AND_ASSERT_MES(miner_tx_context.pulse_block_producer.payouts.empty(), std::make_pair(false, block_rewards), "Constructing a reward for block produced by miner but payout entries specified");
if (uint64_t miner_amount = reward_parts.base_miner + reward_parts.miner_fee; miner_amount)
rewards[rewards_length++] = {reward_type::miner, miner_tx_context.miner_block_producer, miner_amount};
if (hard_fork_version >= cryptonote::network_version_9_service_nodes)
{
if (hard_fork_version < cryptonote::network_version_19) {
rewards.push_back({reward_type::miner, miner_tx_context.miner_block_producer, miner_amount});
} else {
batched_rewards.emplace_back(miner_tx_context.miner_block_producer, miner_amount, nettype);
}
}
if (hard_fork_version >= cryptonote::network_version_9_service_nodes) {
std::vector<uint64_t> split_rewards =
distribute_reward_by_portions(leader.payouts,
reward_parts.service_node_total,
hard_fork_version >= cryptonote::network_version_16_pulse /*distribute_remainder*/);
for (size_t i = 0; i < leader.payouts.size(); i++)
rewards[rewards_length++] = {reward_type::snode, leader.payouts[i].address, split_rewards[i]};
{
if (hard_fork_version < cryptonote::network_version_19) {
rewards.push_back({reward_type::snode, leader.payouts[i].address, split_rewards[i]});
} else {
batched_rewards.emplace_back(leader.payouts[i].address, split_rewards[i], nettype);
}
}
}
}
@ -402,24 +412,40 @@ namespace cryptonote
{
if (reward_parts.governance_paid == 0)
{
CHECK_AND_ASSERT_MES(hard_fork_version >= network_version_10_bulletproofs, false, "Governance reward can NOT be 0 before hardfork 10, hard_fork_version: " << hard_fork_version);
CHECK_AND_ASSERT_MES(hard_fork_version >= network_version_10_bulletproofs, std::make_pair(false, block_rewards), "Governance reward can NOT be 0 before hardfork 10, hard_fork_version: " << hard_fork_version);
}
else
{
const network_type nettype = miner_tx_context.nettype;
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));
rewards[rewards_length++] = {reward_type::governance, governance_wallet_address.address, reward_parts.governance_paid};
// Governance reward paid out through SN rewards batching after HF19
if (hard_fork_version < cryptonote::network_version_19) {
rewards.push_back({reward_type::governance, governance_wallet_address.address, reward_parts.governance_paid});
}
}
}
CHECK_AND_ASSERT_MES(rewards_length <= rewards.size(), false, "More rewards specified than supported, number of rewards: " << rewards_length << ", capacity: " << rewards.size());
CHECK_AND_ASSERT_MES(rewards_length > 0, false, "Zero rewards are to be payed out, there should be at least 1");
uint64_t total_sn_rewards = 0;
// Add SN rewards to the block
if (sn_rwds)
{
for (const auto &reward : *sn_rwds)
{
rewards.emplace_back(reward_type::snode, reward.address_info.address, reward.amount);
total_sn_rewards += reward.amount;
}
}
if (hard_fork_version < cryptonote::network_version_19)
{
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");
}
// NOTE: Make TX Outputs
uint64_t summary_amounts = 0;
for (size_t reward_index = 0; reward_index < rewards_length; reward_index++)
{
auto const &[type, address, amount] = rewards[reward_index];
for (auto it = rewards.begin(); it != rewards.end(); ++it) {
auto const &[type, address, amount] = *it;
assert(amount > 0);
crypto::public_key out_eph_public_key{};
@ -427,14 +453,14 @@ namespace cryptonote
// TODO(doyle): I don't think txkey is necessary, just use the governance key?
keypair const &derivation_pair = (type == reward_type::miner) ? txkey : gov_key;
crypto::key_derivation derivation{};
if (!get_deterministic_output_key(address, derivation_pair, reward_index, out_eph_public_key))
if (!get_deterministic_output_key(address, derivation_pair, it - rewards.begin(), out_eph_public_key))
{
MERROR("Failed to generate output one-time public key");
return false;
return std::make_pair(false, block_rewards);
}
txout_to_key tk = {};
tk.key = out_eph_public_key;
@ -455,19 +481,25 @@ 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 (size_t reward_index = 0; reward_index < rewards_length; reward_index++)
for (auto reward : rewards)
{
[[maybe_unused]] auto const &[type, address, amount] = rewards[reward_index];
[[maybe_unused]] auto const &[type, address, amount] = reward;
if (type == reward_type::snode) expected_amount += amount;
}
}
else
{
expected_amount = reward_parts.base_miner + reward_parts.miner_fee + reward_parts.governance_paid + reward_parts.service_node_total;
expected_amount = 0;
if (hard_fork_version < cryptonote::network_version_19)
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;
}
CHECK_AND_ASSERT_MES(summary_amounts == expected_amount, false, "Failed to construct miner tx, summary_amounts = " << summary_amounts << " not equal total block_reward = " << expected_amount);
CHECK_AND_ASSERT_MES(tx.vout.size() == rewards_length, false, "TX output mis-match with rewards expected: " << rewards_length << ", tx outputs: " << tx.vout.size());
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; });
//lock
tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
@ -476,7 +508,8 @@ namespace cryptonote
//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 true;
return std::make_pair(true, block_rewards);
}
bool get_oxen_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, int hard_fork_version, block_reward_parts &result, const oxen_block_reward_context &oxen_context)
@ -509,7 +542,7 @@ namespace cryptonote
// There is a goverance fee due every block. Beginning in hardfork 10 this is still subtracted
// from the block reward as if it was paid, but the actual payments get batched into rare, large
// accumulated payments. (Before hardfork 10 they are included in every block, unbatched).
result.governance_due = governance_reward_formula(result.original_base_reward, hard_fork_version);
result.governance_due = governance_reward_formula(hard_fork_version, result.original_base_reward);
result.governance_paid = hard_fork_version >= network_version_10_bulletproofs
? oxen_context.batched_governance
: result.governance_due;

View File

@ -34,6 +34,7 @@
#include <boost/serialization/utility.hpp>
#include "ringct/rctOps.h"
#include "cryptonote_core/service_node_list.h"
#include "cryptonote_basic/verification_context.h"
namespace cryptonote
{
@ -42,7 +43,7 @@ namespace cryptonote
bool get_deterministic_output_key (const account_public_address& address, const keypair& tx_key, size_t output_index, crypto::public_key& output_key);
bool validate_governance_reward_key (uint64_t height, std::string_view governance_wallet_address_str, size_t output_index, const crypto::public_key& output_key, const cryptonote::network_type nettype);
uint64_t governance_reward_formula (uint64_t base_reward, uint8_t hf_version);
uint64_t governance_reward_formula (uint8_t hf_version, uint64_t base_reward = 0);
bool block_has_governance_output (network_type nettype, cryptonote::block const &block);
bool height_has_governance_output (network_type nettype, uint8_t hard_fork_version, uint64_t height);
uint64_t derive_governance_from_block_reward (network_type nettype, const cryptonote::block &block, uint8_t hf_version);
@ -51,6 +52,22 @@ namespace cryptonote
uint64_t get_portion_of_reward (uint64_t portions, uint64_t total_service_node_reward);
uint64_t service_node_reward_formula (uint64_t base_reward, uint8_t hard_fork_version);
/**
* Returned type of parse_incoming_txs() that provides details about which transactions failed
* and why. This is passed on to handle_parsed_txs() (potentially after modification such as
* setting `approved_blink`) to handle_parsed_txs() to actually insert the transactions.
*/
struct tx_verification_batch_info {
cryptonote::tx_verification_context tvc{}; // Verification information
bool parsed = false; // Will be true if we were able to at least parse the transaction
bool result = false; // Indicates that the transaction was parsed and passed some basic checks
bool already_have = false; // Indicates that the tx was found to already exist (in mempool or blockchain)
bool approved_blink = false; // Can be set between the parse and handle calls to make this a blink tx (that replaces conflicting non-blink txes)
const blobdata *blob = nullptr; // Will be set to a pointer to the incoming blobdata (i.e. string). caller must keep it alive!
crypto::hash tx_hash; // The transaction hash (only set if `parsed`)
transaction tx; // The parsed transaction (only set if `parsed`)
};
struct oxen_miner_tx_context
{
static oxen_miner_tx_context miner_block(network_type nettype,
@ -86,7 +103,25 @@ namespace cryptonote
uint64_t batched_governance; // NOTE: 0 until hardfork v10, then use blockchain::calc_batched_governance_reward
};
bool construct_miner_tx(
enum struct reward_type
{
miner,
snode,
governance
};
struct reward_payout
{
reward_type type;
account_public_address address;
uint64_t amount;
bool operator==(service_nodes::payout_entry const &other) const { return address == other.address; }
reward_payout() = default;
reward_payout(reward_type t, account_public_address addr, uint64_t amt): type{t}, address{std::move(addr)}, amount{amt} {}
};
std::pair<bool, uint64_t> construct_miner_tx(
size_t height,
size_t median_weight,
uint64_t already_generated_coins,
@ -94,6 +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 blobdata& extra_nonce = blobdata(),
uint8_t hard_fork_version = 1);

View File

@ -133,6 +133,10 @@ namespace service_nodes
return sort_and_filter(service_nodes_infos, [](const service_node_info &info) { return info.is_decommissioned() && info.is_fully_funded(); }, /*reserve=*/ false);
}
std::vector<pubkey_and_sninfo> service_node_list::state_t::payable_service_nodes_infos(uint64_t height, cryptonote::network_type nettype) const {
return sort_and_filter(service_nodes_infos, [height, nettype](const service_node_info &info) { return info.is_payable(height, nettype); }, /*reserve=*/ true);
}
std::shared_ptr<const quorum> service_node_list::get_quorum(quorum_type type, uint64_t height, bool include_old, std::vector<std::shared_ptr<const quorum>> *alt_quorums) const
{
height = offset_testing_quorum_height(type, height);
@ -1284,6 +1288,7 @@ namespace service_nodes
if (miner_block)
{
if (cryptonote::block_has_pulse_components(block))
{
if (log_errors) MGINFO("Pulse " << block_type << "received but only miner blocks are permitted\n" << dump_pulse_block_data(block, pulse_quorum.get()));
@ -1624,6 +1629,15 @@ namespace service_nodes
return result;
}
bool service_node_list::process_batching_rewards(const cryptonote::block& block)
{
return m_blockchain.sqlite_db()->add_block(block, m_state);
}
bool service_node_list::pop_batching_rewards_block(const cryptonote::block& block)
{
return m_blockchain.sqlite_db()->pop_block(block, m_state);
}
static std::mt19937_64 quorum_rng(uint8_t hf_version, crypto::hash const &hash, quorum_type type)
{
std::mt19937_64 result;
@ -2368,7 +2382,7 @@ namespace service_nodes
return true;
}
bool service_node_list::validate_miner_tx(cryptonote::block const &block, cryptonote::block_reward_parts const &reward_parts) const
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
{
uint8_t const hf_version = block.major_version;
if (hf_version < cryptonote::network_version_9_service_nodes)
@ -2398,6 +2412,7 @@ namespace service_nodes
miner,
pulse_block_leader_is_producer,
pulse_different_block_producer,
batched_sn_rewards,
};
verify_mode mode = verify_mode::miner;
@ -2443,7 +2458,8 @@ namespace service_nodes
//
std::shared_ptr<const service_node_info> block_producer = nullptr;
size_t expected_vouts_size = 0;
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);
@ -2454,17 +2470,39 @@ namespace service_nodes
}
block_producer = info_it->second;
if (mode == verify_mode::pulse_different_block_producer && reward_parts.miner_fee > 0)
if (mode == verify_mode::pulse_different_block_producer && reward_parts.miner_fee > 0 && block.major_version < cryptonote::network_version_19)
{
expected_vouts_size += block_producer->contributors.size();
}
else
{
if ((reward_parts.base_miner + reward_parts.miner_fee) > 0) // (HF >= 16) this can be zero, no miner coinbase.
expected_vouts_size += 1; /*miner*/
}
}
if (block.major_version >= cryptonote::network_version_19)
{
mode = verify_mode::batched_sn_rewards;
MDEBUG("Batched miner reward");
}
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*/
}
}
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) {
expected_vouts_size++;
}
}
expected_vouts_size += block_leader.payouts.size();
expected_vouts_size += static_cast<size_t>(cryptonote::height_has_governance_output(m_blockchain.nettype(), hf_version, height));
if (miner_tx.vout.size() != expected_vouts_size)
{
@ -2564,6 +2602,48 @@ namespace service_nodes
}
}
break;
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;});
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)
{
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)
{
MERROR("Service node reward amount incorrect. Should be " << cryptonote::print_money((*batched_sn_payments)[vout_index].amount) << ", is: " << cryptonote::print_money(vout.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))
{
MERROR("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");
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));
return false;
}
}
break;
}
return true;
@ -3013,7 +3093,7 @@ namespace service_nodes
REJECT_PROOF("timestamp is too far from now");
for (auto const &min : MIN_UPTIME_PROOF_VERSIONS) {
if (vers >= min.hardfork_revision) {
if (vers >= min.hardfork_revision && m_blockchain.nettype() != cryptonote::DEVNET) {
if (proof->version < min.oxend)
REJECT_PROOF("v" << tools::join(".", min.oxend) << "+ oxend version is required for v" << +vers.first << "." << +vers.second << "+ network proofs");
if (proof->lokinet_version < min.lokinet)

View File

@ -311,6 +311,10 @@ namespace service_nodes
bool is_fully_funded() const { return total_contributed >= staking_requirement; }
bool is_decommissioned() const { return active_since_height < 0; }
bool is_active() const { return is_fully_funded() && !is_decommissioned(); }
bool is_payable(uint64_t at_height, cryptonote::network_type nettype) const {
auto& netconf = get_config(nettype);
return is_active() && at_height >= active_since_height + netconf.SERVICE_NODE_PAYABLE_AFTER_BLOCKS;
}
bool can_transition_to_state(uint8_t hf_version, uint64_t block_height, new_state proposed_state) const;
bool can_be_voted_on (uint64_t block_height) const;
@ -454,9 +458,11 @@ namespace service_nodes
service_node_list &operator=(const service_node_list &) = delete;
bool block_added(const cryptonote::block& block, const std::vector<cryptonote::transaction>& txs, cryptonote::checkpoint_t const *checkpoint) override;
bool process_batching_rewards(const cryptonote::block& block);
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) const 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 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;
@ -690,6 +696,8 @@ namespace service_nodes
std::vector<pubkey_and_sninfo> active_service_nodes_infos() const;
std::vector<pubkey_and_sninfo> decommissioned_service_nodes_infos() const; // return: All nodes that are fully funded *and* decommissioned.
std::vector<pubkey_and_sninfo> payable_service_nodes_infos(uint64_t height, cryptonote::network_type nettype) const; // return: All nodes that are active and have been online for a period greater than SERVICE_NODE_PAYABLE_AFTER_BLOCKS
std::vector<crypto::public_key> get_expired_nodes(cryptonote::BlockchainDB const &db, cryptonote::network_type nettype, uint8_t hf_version, uint64_t block_height) const;
void update_from_block(
cryptonote::BlockchainDB const &db,
@ -792,7 +800,7 @@ namespace service_nodes
};
bool is_registration_tx (cryptonote::network_type nettype, uint8_t 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, crypto::public_key& tx_pub_key);
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);
uint64_t offset_testing_quorum_height(quorum_type type, uint64_t height);
contributor_args_t convert_registration_args(cryptonote::network_type nettype,

View File

@ -240,6 +240,7 @@ namespace service_nodes {
//If the below percentage of service nodes are out of sync we will consider our clock out of sync
constexpr uint8_t MAXIMUM_EXTERNAL_OUT_OF_SYNC = 80;
static_assert(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
uint64_t get_min_node_contribution (uint8_t version, uint64_t staking_requirement, uint64_t total_reserved, size_t num_contributions);

View File

@ -260,7 +260,7 @@ namespace cryptonote
return false;
}
if(!check_inputs_types_supported(tx))
if (!check_inputs_types_supported(tx))
{
tvc.m_verifivation_failed = true;
tvc.m_invalid_input = true;
@ -279,11 +279,14 @@ namespace cryptonote
return false;
}
if (!opts.kept_by_block && tx.is_transfer() && !m_blockchain.check_fee(tx_weight, tx.vout.size(), fee, burned, opts))
if (hf_version < cryptonote::network_version_19)
{
tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true;
return false;
if (!opts.kept_by_block && tx.is_transfer() && !m_blockchain.check_fee(tx_weight, tx.vout.size(), fee, burned, opts))
{
tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true;
return false;
}
}
size_t tx_weight_limit = get_transaction_weight_limit(hf_version);

View File

@ -41,4 +41,5 @@ target_link_libraries(cryptonote_protocol
p2p
PRIVATE
easylogging
SQLiteCpp
extra)

View File

@ -557,6 +557,7 @@ namespace cryptonote
int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context)
{
MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)");
if(context.m_state != cryptonote_connection_context::state_normal)
return 1;
if(!is_synchronized() || m_no_sync) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks
@ -568,7 +569,6 @@ namespace cryptonote
m_core.pause_mine();
block new_block;
transaction miner_tx;
if(parse_and_validate_block_from_blob(arg.b.block, new_block))
{
// This is a second notification, we must have asked for some missing tx

View File

@ -47,5 +47,6 @@ target_link_libraries(daemon
version
filesystem
Boost::program_options
SQLiteCpp
systemd
extra)

View File

@ -1929,28 +1929,6 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
!invoke<GET_SERVICE_KEYS>({}, kres, "Failed to retrieve service node keys"))
return false;
if (!res.service_node)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon is not running in --service-node mode";
return false;
}
else if (auto last_lokinet_ping = static_cast<std::time_t>(res.last_lokinet_ping.value_or(0));
last_lokinet_ping < (time(nullptr) - 60) && !force_registration)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from lokinet "
<< (res.last_lokinet_ping == 0 ? "yet" : "since " + get_human_time_ago(last_lokinet_ping, std::time(nullptr)));
return false;
}
else if (auto last_storage_server_ping = static_cast<std::time_t>(res.last_storage_server_ping.value_or(0));
last_storage_server_ping < (time(nullptr) - 60) && !force_registration)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from the storage server "
<< (res.last_storage_server_ping == 0 ? "yet" : "since " + get_human_time_ago(last_storage_server_ping, std::time(nullptr)));
return false;
}
uint64_t block_height = std::max(res.height, res.target_height);
uint8_t hf_version = hf_res.version;
cryptonote::network_type const nettype =
res.mainnet ? cryptonote::MAINNET :
res.devnet ? cryptonote::DEVNET :
@ -1958,6 +1936,32 @@ bool rpc_command_executor::prepare_registration(bool force_registration)
res.nettype == "fakechain" ? cryptonote::FAKECHAIN :
cryptonote::UNDEFINED;
if (!res.service_node)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon is not running in --service-node mode";
return false;
}
if (nettype != cryptonote::DEVNET)
{
if (auto last_lokinet_ping = static_cast<std::time_t>(res.last_lokinet_ping.value_or(0));
last_lokinet_ping < (time(nullptr) - 60) && !force_registration && nettype != cryptonote::DEVNET)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from lokinet "
<< (res.last_lokinet_ping == 0 ? "yet" : "since " + get_human_time_ago(last_lokinet_ping, std::time(nullptr)));
return false;
}
if (auto last_storage_server_ping = static_cast<std::time_t>(res.last_storage_server_ping.value_or(0));
last_storage_server_ping < (time(nullptr) - 60) && !force_registration)
{
tools::fail_msg_writer() << "Unable to prepare registration: this daemon has not received a ping from the storage server "
<< (res.last_storage_server_ping == 0 ? "yet" : "since " + get_human_time_ago(last_storage_server_ping, std::time(nullptr)));
return false;
}
}
uint64_t block_height = std::max(res.height, res.target_height);
uint8_t hf_version = hf_res.version;
// Query the latest block we've synced and check that the timestamp is sensible, issue a warning if not
{
GET_LAST_BLOCK_HEADER::response res{};

View File

@ -40,6 +40,7 @@ target_link_libraries(device
ringct_basic
Boost::serialization
PRIVATE
SQLiteCpp
version
extra)

View File

@ -42,4 +42,5 @@ target_link_libraries(p2p
Boost::program_options
filesystem
Boost::serialization
SQLiteCpp
extra)

View File

@ -75,6 +75,7 @@ target_link_libraries(rpc
cryptonote_protocol
Boost::thread
Boost::program_options
SQLiteCpp
extra)
target_link_libraries(daemon_rpc_server
@ -82,6 +83,7 @@ target_link_libraries(daemon_rpc_server
rpc_server_base
rpc
Boost::thread
SQLiteCpp
extra)
target_link_libraries(rpc_http_client

View File

@ -1826,15 +1826,20 @@ namespace cryptonote { namespace rpc {
response.difficulty = m_core.get_blockchain_storage().block_difficulty(height);
response.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height);
response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height);
response.reward = get_block_reward(blk);
response.miner_reward = blk.miner_tx.vout[0].amount;
response.reward = (blk.reward > 0) ? blk.reward : get_block_reward(blk);
response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height);
response.num_txes = blk.tx_hashes.size();
if (fill_pow_hash)
response.pow_hash = tools::type_to_hex(get_block_longhash_w_blockchain(m_core.get_nettype(), &(m_core.get_blockchain_storage()), blk, height, 0));
response.long_term_weight = m_core.get_blockchain_storage().get_db().get_block_long_term_weight(height);
response.miner_tx_hash = tools::type_to_hex(cryptonote::get_transaction_hash(blk.miner_tx));
response.service_node_winner = tools::type_to_hex(cryptonote::get_service_node_winner_from_tx_extra(blk.miner_tx.extra));
response.service_node_winner = (tools::type_to_hex(blk.service_node_winner_key) == "") ? tools::type_to_hex(cryptonote::get_service_node_winner_from_tx_extra(blk.miner_tx.extra)) : tools::type_to_hex(blk.service_node_winner_key);
if (blk.miner_tx.vout.size() > 0)
{
response.miner_reward = blk.miner_tx.vout[0].amount;
response.miner_tx_hash = tools::type_to_hex(cryptonote::get_transaction_hash(blk.miner_tx));
} else {
response.miner_reward = get_block_reward(blk);
}
if (get_tx_hashes)
{
response.tx_hashes.reserve(blk.tx_hashes.size());
@ -2068,6 +2073,7 @@ namespace cryptonote { namespace rpc {
block_hash = get_block_hash(blk);
block_height = req.height;
}
fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash && context.admin, false /*tx hashes*/);
res.tx_hashes.reserve(blk.tx_hashes.size());
for (const auto& tx_hash : blk.tx_hashes)

View File

@ -26,9 +26,9 @@ namespace db
template <size_t N>
constexpr bool is_cstr<const char[N]> = true;
template <>
constexpr bool is_cstr<char*> = true;
inline constexpr bool is_cstr<char*> = true;
template <>
constexpr bool is_cstr<const char*> = true;
inline constexpr bool is_cstr<const char*> = true;
// Simple wrapper class that can be used to bind a blob through the templated binding code below.
// E.g. `exec_query(st, 100, 42, blob_binder{data})` binds the third parameter using no-copy blob
@ -286,7 +286,7 @@ namespace db
}
explicit Database(const fs::path& db_path, const std::string_view db_password)
: db{db_path.u8path(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE | SQLite::OPEN_FULLMUTEX, 5000/*ms*/}
: 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)

View File

@ -126,6 +126,7 @@ if (STATIC AND BUILD_STATIC_DEPS)
Boost::program_options Boost::serialization Boost::system Boost::thread
zlib
SQLite::SQLite3
SQLiteCpp
${merged_protobuf}
sodium
libzmq
@ -136,6 +137,7 @@ if (STATIC AND BUILD_STATIC_DEPS)
randomx
uSockets
cpr
fmt
)
if(IOS)

View File

@ -100,7 +100,6 @@ namespace string_tools = epee::string_tools;
#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "wallet.wallet2"
namespace {
constexpr std::string_view UNSIGNED_TX_PREFIX = "Loki unsigned tx set\004"sv;
@ -2828,15 +2827,17 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
};
txidx = 0;
for (size_t i = 0; i < blocks.size(); ++i)
for (size_t i = 0; i < parsed_blocks.size(); ++i)
{
cryptonote::block blk = parsed_blocks[i].block;
if (should_skip_block(parsed_blocks[i].block, start_height + i))
{
txidx += 1 + parsed_blocks[i].block.tx_hashes.size();
continue;
}
if (m_refresh_type != RefreshType::RefreshNoCoinbase)
if (m_refresh_type != RefreshType::RefreshNoCoinbase && parsed_blocks[i].block.miner_tx.vout.size() > 0)
{
THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range");
const size_t n_vouts = m_refresh_type == RefreshType::RefreshOptimizeCoinbase ? 1 : parsed_blocks[i].block.miner_tx.vout.size();
@ -2847,7 +2848,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
{
THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range");
tpool.submit(&waiter, [&, i, j, txidx](){ geniod(parsed_blocks[i].txes[j], parsed_blocks[i].txes[j].vout.size(), txidx); }, true);
++txidx;
++txidx;
}
}
THROW_WALLET_EXCEPTION_IF(txidx != tx_cache_data.size(), error::wallet_internal_error, "txidx did not reach expected value");

View File

@ -32,6 +32,7 @@ target_link_libraries(block_weight
PRIVATE
cryptonote_core
blockchain_db
SQLiteCpp
extra)
add_test(

View File

@ -129,11 +129,12 @@ static void test(test_t t, uint64_t blocks)
const cryptonote::test_options test_options{hard_forks, 5000};
auto& bc = bc_objects.m_blockchain;
if (!bc.init(new TestDB(), nullptr, cryptonote ::FAKECHAIN, true, &test_options, 0)) {
if (!bc.init(new TestDB(), nullptr /*ons_db*/, nullptr /*sqlite_db*/, cryptonote ::FAKECHAIN, true, &test_options, 0, NULL)) {
fprintf(stderr, "Failed to init blockchain\n");
exit(1);
};
for (uint64_t h = 0; h < LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h)
{
cryptonote::block b;

View File

@ -37,6 +37,7 @@ target_link_libraries(core_proxy
version
epee
Boost::program_options
SQLiteCpp
extra)
set_property(TARGET core_proxy
PROPERTY

View File

@ -45,6 +45,7 @@
//#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.inl"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "core_proxy.h"
#include "version.h"
@ -158,9 +159,9 @@ string tx2str(const cryptonote::transaction& tx, const cryptonote::hash256& tx_h
return ss.str();
}*/
std::vector<cryptonote::core::tx_verification_batch_info> tests::proxy_core::parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const tx_pool_options &opts) {
std::vector<cryptonote::tx_verification_batch_info> tests::proxy_core::parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const tx_pool_options &opts) {
std::vector<cryptonote::core::tx_verification_batch_info> tx_info(tx_blobs.size());
std::vector<cryptonote::tx_verification_batch_info> tx_info(tx_blobs.size());
for (size_t i = 0; i < tx_blobs.size(); i++) {
auto &txi = tx_info[i];
@ -186,7 +187,7 @@ std::vector<cryptonote::core::tx_verification_batch_info> tests::proxy_core::par
return tx_info;
}
bool tests::proxy_core::handle_parsed_txs(std::vector<cryptonote::core::tx_verification_batch_info> &parsed_txs, const tx_pool_options &opts, uint64_t *blink_rollback_height) {
bool tests::proxy_core::handle_parsed_txs(std::vector<cryptonote::tx_verification_batch_info> &parsed_txs, const tx_pool_options &opts, uint64_t *blink_rollback_height) {
if (blink_rollback_height) *blink_rollback_height = 0;
@ -197,7 +198,7 @@ bool tests::proxy_core::handle_parsed_txs(std::vector<cryptonote::core::tx_verif
return ok;
}
std::vector<core::tx_verification_batch_info> tests::proxy_core::handle_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
std::vector<tx_verification_batch_info> tests::proxy_core::handle_incoming_txs(const std::vector<blobdata>& tx_blobs, const tx_pool_options &opts)
{
auto parsed = parse_incoming_txs(tx_blobs, opts);
handle_parsed_txs(parsed, opts);

View File

@ -36,6 +36,7 @@
#include "cryptonote_basic/verification_context.h"
#include "cryptonote_core/service_node_voting.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "cryptonote_core/tx_blink.h"
#include <unordered_map>
@ -78,9 +79,9 @@ namespace tests
bool have_block(const crypto::hash& id);
void get_blockchain_top(uint64_t& height, crypto::hash& top_id);
bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, const cryptonote::tx_pool_options &opts);
std::vector<cryptonote::core::tx_verification_batch_info> parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts);
bool handle_parsed_txs(std::vector<cryptonote::core::tx_verification_batch_info> &parsed_txs, const cryptonote::tx_pool_options &opts, uint64_t *blink_rollback_height = nullptr);
std::vector<cryptonote::core::tx_verification_batch_info> handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts);
std::vector<cryptonote::tx_verification_batch_info> parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts);
bool handle_parsed_txs(std::vector<cryptonote::tx_verification_batch_info> &parsed_txs, const cryptonote::tx_pool_options &opts, uint64_t *blink_rollback_height = nullptr);
std::vector<cryptonote::tx_verification_batch_info> handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts);
std::pair<std::vector<std::shared_ptr<cryptonote::blink_tx>>, std::unordered_set<crypto::hash>> parse_incoming_blinks(const std::vector<cryptonote::serializable_blink_metadata> &blinks);
int add_blinks(const std::vector<std::shared_ptr<cryptonote::blink_tx>> &blinks) { return 0; }
bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, cryptonote::checkpoint_t *checkpoint, bool update_miner_blocktemplate = true);

View File

@ -57,6 +57,7 @@ target_link_libraries(core_tests
device
wallet
Boost::program_options
SQLiteCpp
extra)
set_property(TARGET core_tests
PROPERTY

View File

@ -43,7 +43,8 @@ namespace
const account_public_address& miner_address, std::vector<uint64_t>& block_weights, size_t target_tx_weight,
size_t target_block_weight, uint64_t fee = 0)
{
if (!construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, target_block_weight, fee, miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, mminer_address)))
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
if (!construct_miner_tx(height, tools::median(block_weights.begin(), block_weights.end()), already_generated_coins, target_block_weight, fee, miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, mminer_address), sn_rwds))
return false;
size_t current_weight = get_transaction_weight(miner_tx);

View File

@ -448,7 +448,7 @@ static bool construct_miner_tx_with_extra_output(cryptonote::transaction& tx,
uint64_t governance_reward = 0;
if (already_generated_coins != 0) {
governance_reward = governance_reward_formula(block_reward, hard_fork_version);
governance_reward = governance_reward_formula(hard_fork_version, block_reward);
block_reward -= governance_reward;
}

View File

@ -40,6 +40,7 @@
#include <fstream>
#include "common/string_util.h"
#include "common/hex.h"
#include "common/varint.h"
#include "common/median.h"
#include "epee/console_handler.h"
@ -164,14 +165,22 @@ std::vector<cryptonote::block> oxen_chain_generator_db::get_blocks_range(const u
return result;
}
oxen_chain_generator::oxen_chain_generator(std::vector<test_event_entry> &events, const std::vector<cryptonote::hard_fork>& hard_forks)
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::FAKECHAIN, ":memory:"))
{
bool init = ons_db_->init(nullptr, cryptonote::FAKECHAIN, ons::init_oxen_name_system("", false /*read_only*/));
assert(init);
first_miner_.generate();
if (first_miner_seed == "") {
first_miner_.generate();
} else {
crypto::secret_key seeded_secret_key;
tools::hex_to_type<crypto::secret_key>(first_miner_seed, seeded_secret_key);
first_miner_.generate(seeded_secret_key, true);
}
oxen_blockchain_entry genesis = oxen_chain_generator::create_genesis_block(first_miner_, 1338224400);
events_.push_back(genesis.block);
db_.blocks.push_back(genesis);
@ -241,6 +250,8 @@ oxen_blockchain_entry &oxen_chain_generator::add_block(oxen_blockchain_entry con
ons_db_->add_block(entry.block, entry.txs);
}
sqlite_db_->add_block(entry.block, entry.service_node_state);
// TODO(oxen): State history culling and alt states
state_history_.emplace_hint(state_history_.end(), result.service_node_state);
@ -257,6 +268,9 @@ oxen_blockchain_entry &oxen_chain_generator::add_block(oxen_blockchain_entry con
events_.push_back(oxen_blockchain_addable<cryptonote::block>(result.block, can_be_added_to_blockchain, fail_msg));
}
cryptonote::block sopthing = entry.block;
return result;
}
@ -825,16 +839,20 @@ 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;
uint64_t block_rewards = 0;
while (true)
{
bool constructed = construct_miner_tx(height,
bool constructed = false;
std::tie(constructed, block_rewards) = construct_miner_tx(height,
0 /*median_weight*/,
0 /*already_generated_coins*/,
target_block_weight,
0 /*total_fee*/,
blk.miner_tx,
cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner.get_keys().m_account_address),
sn_rwds,
cryptonote::blobdata(),
hf_version_);
assert(constructed);
@ -893,6 +911,7 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
cryptonote::block &blk = entry.block;
blk.major_version = params.hf_version;
blk.minor_version = params.hf_version;
blk.height = height;
blk.timestamp = params.timestamp;
blk.prev_id = get_block_hash(params.prev.block);
@ -947,10 +966,12 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
}
miner_tx_context = cryptonote::oxen_miner_tx_context::pulse_block(cryptonote::FAKECHAIN, block_producer, params.block_leader);
blk.service_node_winner_key = miner_tx_context.pulse_block_producer.key;
}
else
{
miner_tx_context = cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, params.miner_acc.get_keys().m_account_address, params.block_leader);
blk.service_node_winner_key = miner_tx_context.block_leader.key;
}
if (blk.major_version >= cryptonote::network_version_10_bulletproofs &&
@ -959,13 +980,11 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
constexpr uint64_t num_blocks = cryptonote::get_config(cryptonote::FAKECHAIN).GOVERNANCE_REWARD_INTERVAL_IN_BLOCKS;
uint64_t start_height = height - num_blocks;
static_assert(cryptonote::network_version_count == cryptonote::network_version_19 + 1,
"The code below needs to be updated to support higher hard fork versions");
if (blk.major_version == cryptonote::network_version_15_ons)
miner_tx_context.batched_governance = FOUNDATION_REWARD_HF15 * num_blocks;
else if (blk.major_version == cryptonote::network_version_16_pulse)
miner_tx_context.batched_governance = (FOUNDATION_REWARD_HF15 + CHAINFLIP_LIQUIDITY_HF16) * num_blocks;
else if (blk.major_version >= cryptonote::network_version_17 && blk.major_version <= cryptonote::network_version_19)
else if (blk.major_version >= cryptonote::network_version_17)
miner_tx_context.batched_governance = FOUNDATION_REWARD_HF17 * num_blocks;
else
{
@ -981,18 +1000,27 @@ 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_ >= cryptonote::network_version_19)
{
sn_rwds = sqlite_db_->get_sn_payments(height); //Rewards to pay out
}
uint64_t block_rewards = 0;
bool r;
while (true)
{
if (!construct_miner_tx(height,
tools::median(params.block_weights.begin(), params.block_weights.end()),
params.prev.already_generated_coins,
target_block_weight,
total_fee,
blk.miner_tx,
miner_tx_context,
cryptonote::blobdata(),
blk.major_version
))
std::tie(r, block_rewards) = construct_miner_tx(height,
tools::median(params.block_weights.begin(), params.block_weights.end()),
params.prev.already_generated_coins,
target_block_weight,
total_fee,
blk.miner_tx,
miner_tx_context,
sn_rwds,
cryptonote::blobdata(),
blk.major_version
);
if (!r)
return false;
entry.block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
@ -1033,6 +1061,7 @@ bool oxen_chain_generator::block_begin(oxen_blockchain_entry &entry, oxen_create
}
}
blk.reward = block_rewards;
entry.txs = tx_list;
uint64_t block_reward, block_reward_unpenalized;
cryptonote::get_base_block_reward(tools::median(params.block_weights.begin(), params.block_weights.end()), entry.block_weight, params.prev.already_generated_coins, block_reward, block_reward_unpenalized, params.hf_version, height);
@ -1066,6 +1095,21 @@ void oxen_chain_generator::block_end(oxen_blockchain_entry &entry, oxen_create_b
entry.service_node_state.update_from_block(db_, cryptonote::FAKECHAIN, state_history_, {} /*state_archive*/, {} /*alt_states*/, entry.block, entry.txs, nullptr);
}
bool oxen_chain_generator::process_registration_tx(cryptonote::transaction& tx, uint64_t block_height, uint8_t 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::FAKECHAIN, block_height);
return true;
}
bool oxen_chain_generator::create_block(oxen_blockchain_entry &entry,
oxen_create_block_params &params,
const std::vector<cryptonote::transaction> &tx_list) const
@ -1229,7 +1273,7 @@ static void manual_calc_batched_governance(const test_generator &generator,
if (hard_fork_version >= cryptonote::network_version_15_ons)
{
miner_tx_context.batched_governance = num_blocks * cryptonote::governance_reward_formula(0, hard_fork_version);
miner_tx_context.batched_governance = num_blocks * cryptonote::governance_reward_formula(hard_fork_version);
return;
}
@ -1296,17 +1340,22 @@ 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);
uint64_t block_rewards = 0;
bool r;
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds;
while (true)
{
if (!construct_miner_tx(height,
tools::median(block_weights.begin(), block_weights.end()),
already_generated_coins,
target_block_weight,
total_fee,
blk.miner_tx,
miner_tx_context,
cryptonote::blobdata(),
m_hf_version))
std::tie(r, block_rewards) = construct_miner_tx(height,
tools::median(block_weights.begin(), block_weights.end()),
already_generated_coins,
target_block_weight,
total_fee,
blk.miner_tx,
miner_tx_context,
sn_rwds,
cryptonote::blobdata(),
m_hf_version);
if (!r)
return false;
size_t actual_block_weight = txs_weight + get_transaction_weight(blk.miner_tx);
@ -1402,6 +1451,7 @@ bool test_generator::construct_block_manually(
blk.tx_hashes = actual_params & bf_tx_hashes ? tx_hashes : std::vector<crypto::hash>();
size_t height = get_block_height(prev_block) + 1;
blk.height = height;
uint64_t already_generated_coins = get_already_generated_coins(prev_block);
std::vector<uint64_t> block_weights;
get_last_n_block_weights(block_weights, get_block_hash(prev_block), CRYPTONOTE_REWARD_BLOCKS_WINDOW);
@ -1416,8 +1466,12 @@ bool test_generator::construct_block_manually(
miner_tx_context.nettype = cryptonote::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);
if (!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::FAKECHAIN, miner_acc.get_keys().m_account_address), cryptonote::blobdata(), m_hf_version))
uint64_t block_rewards = 0;
bool r;
std::tie(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::FAKECHAIN, miner_acc.get_keys().m_account_address), sn_rwds, cryptonote::blobdata(), m_hf_version);
if (!r)
return false;
}
@ -1781,6 +1835,7 @@ void fill_tx_sources_and_multi_destinations(const std::vector<test_event_entry>&
bool always_add_change_ouput,
uint64_t *change_amount)
{
sources.clear();
destinations.clear();

View File

@ -1134,15 +1134,20 @@ inline bool do_replay_file(const std::string& filename)
#define MAKE_MINER_TX_MANUALLY(TX, BLK) \
transaction TX; \
if (!construct_miner_tx(get_block_height(BLK) + 1, \
std::optional<std::vector<cryptonote::batch_sn_payment>> sn_rwds; \
uint64_t block_rewards = 0; \
bool r; \
std::tie(r, block_rewards) = construct_miner_tx(get_block_height(BLK) + 1, \
0, \
generator.get_already_generated_coins(BLK), \
0, \
0, \
TX, \
cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_account.get_keys().m_account_address), \
{}, \
7)) \
sn_rwds, \
{} \
); \
if (!r) \
return false;
#define MAKE_TX_LIST_START_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \
@ -1380,6 +1385,7 @@ struct oxen_blockchain_entry
cryptonote::checkpoint_t checkpoint;
};
struct oxen_chain_generator_db : public cryptonote::BaseTestDB
{
std::vector<oxen_blockchain_entry> blocks;
@ -1431,13 +1437,17 @@ 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_;
oxen_chain_generator_db db_;
uint8_t hf_version_ = cryptonote::network_version_7;
std::vector<test_event_entry>& events_;
const std::vector<cryptonote::hard_fork> hard_forks_;
cryptonote::account_base first_miner_;
oxen_chain_generator(std::vector<test_event_entry> &events, const std::vector<cryptonote::hard_fork>& hard_forks);
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_)),
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); }
uint64_t chain_height() const { return height() + 1; }
@ -1505,8 +1515,15 @@ struct oxen_chain_generator
bool block_begin(oxen_blockchain_entry &entry, oxen_create_block_params &params, const std::vector<cryptonote::transaction> &tx_list) const;
void block_fill_pulse_data(oxen_blockchain_entry &entry, oxen_create_block_params const &params, uint8_t round) const;
void block_end(oxen_blockchain_entry &entry, oxen_create_block_params const &params) const;
bool process_registration_tx(cryptonote::transaction& tx, uint64_t block_height, uint8_t hf_version);
uint8_t get_hf_version_at(uint64_t height) const;
std::vector<uint64_t> last_n_block_weights(uint64_t height, uint64_t num) const;
const cryptonote::account_base& first_miner() const { return first_miner_; }
oxen_chain_generator& operator=(const oxen_chain_generator& other)
{
new(this) oxen_chain_generator(other);
return *this;
}
};

View File

@ -147,6 +147,11 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(oxen_pulse_fallback_to_pow_and_back);
GENERATE_AND_PLAY(oxen_pulse_chain_split);
GENERATE_AND_PLAY(oxen_pulse_chain_split_with_no_checkpoints);
GENERATE_AND_PLAY(oxen_batch_sn_rewards);
GENERATE_AND_PLAY(oxen_batch_sn_rewards_bad_amount);
GENERATE_AND_PLAY(oxen_batch_sn_rewards_bad_address);
GENERATE_AND_PLAY(oxen_batch_sn_rewards_pop_blocks);
GENERATE_AND_PLAY(oxen_batch_sn_rewards_pop_blocks_after_big_cycle);
// NOTE: Monero Tests
GENERATE_AND_PLAY(gen_simple_chain_001);

View File

@ -111,9 +111,12 @@ 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();
cryptonote::transaction &miner_tx = entry.block.miner_tx;
split_miner_tx_outs(miner_tx, MONEY_SUPPLY);
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We purposely overflow miner tx by MONEY_SUPPLY in the miner tx");
if ( entry.block.major_version < cryptonote::network_version_19)
{
cryptonote::transaction &miner_tx = entry.block.miner_tx;
split_miner_tx_outs(miner_tx, MONEY_SUPPLY);
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We purposely overflow miner tx by MONEY_SUPPLY in the miner tx");
}
}
// Problem 2. block_reward overflow
@ -124,9 +127,12 @@ 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);
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
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We should not be able to add TX because the fee is greater than the base miner reward");
if ( entry.block.major_version < cryptonote::network_version_19)
{
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
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We should not be able to add TX because the fee is greater than the base miner reward");
}
}
{
@ -135,9 +141,12 @@ 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);
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
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We should not be able to add TX because the fee is greater than the base miner reward even if kept_by_block is true");
if ( entry.block.major_version < cryptonote::network_version_19)
{
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
gen.add_block(entry, false /*can_be_added_to_blockchain*/, "We should not be able to add TX because the fee is greater than the base miner reward even if kept_by_block is true");
}
}
}
return true;

File diff suppressed because it is too large Load Diff

View File

@ -88,6 +88,10 @@ struct oxen_pulse_out_of_order_voters
struct oxen_pulse_reject_miner_block : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_pulse_generate_blocks : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_pulse_fallback_to_pow_and_back : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_pulse_chain_split : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_pulse_chain_split_with_no_checkpoints : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_pulse_chain_split : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); }; struct oxen_pulse_chain_split_with_no_checkpoints : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_batch_sn_rewards : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_batch_sn_rewards_bad_amount : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_batch_sn_rewards_bad_address : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_batch_sn_rewards_pop_blocks : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct oxen_batch_sn_rewards_pop_blocks_after_big_cycle : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };

View File

@ -58,17 +58,18 @@ bool test_transaction_generation_and_ring_signature()
account_base rv_acc2;
rv_acc2.generate();
transaction tx_mine_1;
construct_miner_tx(0, 0, 0, 10, 0, tx_mine_1, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc1.get_keys().m_account_address));
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(cryptonote::FAKECHAIN, miner_acc1.get_keys().m_account_address),sn_rwds);
transaction tx_mine_2;
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_2, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc2.get_keys().m_account_address));
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_2, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc2.get_keys().m_account_address),sn_rwds);
transaction tx_mine_3;
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_3, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc3.get_keys().m_account_address));
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_3, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc3.get_keys().m_account_address),sn_rwds);
transaction tx_mine_4;
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_4, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc4.get_keys().m_account_address));
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_4, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc4.get_keys().m_account_address),sn_rwds);
transaction tx_mine_5;
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_5, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc5.get_keys().m_account_address));
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_5, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc5.get_keys().m_account_address),sn_rwds);
transaction tx_mine_6;
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_6, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc6.get_keys().m_account_address));
construct_miner_tx(0, 0, 0, 0, 0, tx_mine_6, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, miner_acc6.get_keys().m_account_address),sn_rwds);
//fill inputs entry
typedef tx_source_entry::output_entry tx_output_entry;
@ -139,7 +140,9 @@ bool test_block_creation()
bool r = get_account_address_from_str(info, MAINNET, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2");
CHECK_AND_ASSERT_MES(r, false, "failed to import");
block b;
r = construct_miner_tx(90, tools::median(std::move(szs)), 3553616528562147, 33094, 10000000, b.miner_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, info.address), blobdata());
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(cryptonote::FAKECHAIN, info.address), sn_rwds, blobdata());
return r;
}

View File

@ -51,11 +51,15 @@ 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();
if (!construct_miner_tx(0, 0, 0, 2, 0, m_miner_txs[1], cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, m_miners[i].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_miner_txs[1], cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, m_miners[i].get_keys().m_account_address), sn_rwds);
if (!r)
return false;
txout_to_key tx_out = var::get<txout_to_key>(m_miner_txs[i].vout[0].target);

View File

@ -44,8 +44,13 @@ 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;
if (!construct_miner_tx(0, 0, 0, 2, 0, m_tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, 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(cryptonote::FAKECHAIN, m_bob.get_keys().m_account_address), sn_rwds);
if (!r)
return false;
m_tx_pub_key = get_tx_pub_key_from_extra(m_tx);

View File

@ -73,6 +73,7 @@ add_executable(unit_tests
service_nodes.cpp
service_nodes_swarm.cpp
sha256.cpp
sqlite.cpp
string_util.cpp
subaddress.cpp
test_tx_utils.cpp
@ -101,6 +102,7 @@ target_link_libraries(unit_tests
wallet
p2p
version
SQLiteCpp
Boost::thread
gtest
extra)

View File

@ -34,6 +34,7 @@
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/uptime_proof.h"
#include "blockchain_utilities/blockchain_objects.h"
#include "blockchain_db/sqlite/db_sqlite.h"
#include "blockchain_db/testdb.h"
#define TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW 5000
@ -117,7 +118,8 @@ static uint32_t lcg()
window, \
}; \
cryptonote::Blockchain *bc = &bc_objects.m_blockchain; \
bool r = bc->init(new ::TestDB(), nullptr /*ons_db*/, cryptonote::FAKECHAIN, true, &test_options, 0); \
auto sqliteDB = std::make_shared<cryptonote::BlockchainSQLite>(cryptonote::FAKECHAIN, ":memory:"); \
bool r = bc->init(new ::TestDB(), nullptr /*ons_db*/, sqliteDB /*sqlite_db*/, cryptonote::FAKECHAIN, true, &test_options, 0); \
ASSERT_TRUE(r)
#define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW)

View File

@ -32,6 +32,8 @@
#include "epee/misc_log_ex.h"
#include "epee/memwipe.h"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
// Probably won't catch the optimized out case, but at least we test
// it works in the normal case
static void test(bool wipe)

View File

@ -35,6 +35,7 @@
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.inl"
#include "cryptonote_core/blockchain.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#define MAKE_IPV4_ADDRESS(a,b,c,d) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),0}
#define MAKE_IPV4_ADDRESS_PORT(a,b,c,d,e) epee::net_utils::ipv4_network_address{MAKE_IP(a,b,c,d),e}
@ -56,9 +57,9 @@ public:
bool get_short_chain_history(std::list<crypto::hash>& ids) const { return true; }
bool have_block(const crypto::hash& id) const {return true;}
void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;}
std::vector<cryptonote::core::tx_verification_batch_info> parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts) { return {}; }
bool handle_parsed_txs(std::vector<cryptonote::core::tx_verification_batch_info> &parsed_txs, const cryptonote::tx_pool_options &opts, uint64_t *blink_rollback_height = nullptr) { if (blink_rollback_height) *blink_rollback_height = 0; return true; }
std::vector<cryptonote::core::tx_verification_batch_info> handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts) { return {}; }
std::vector<cryptonote::tx_verification_batch_info> parse_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts) { return {}; }
bool handle_parsed_txs(std::vector<cryptonote::tx_verification_batch_info> &parsed_txs, const cryptonote::tx_pool_options &opts, uint64_t *blink_rollback_height = nullptr) { if (blink_rollback_height) *blink_rollback_height = 0; return true; }
std::vector<cryptonote::tx_verification_batch_info> handle_incoming_txs(const std::vector<cryptonote::blobdata>& tx_blobs, const cryptonote::tx_pool_options &opts) { return {}; }
bool handle_incoming_tx(const cryptonote::blobdata& tx_blob, cryptonote::tx_verification_context& tvc, const cryptonote::tx_pool_options &opts) { return true; }
std::pair<std::vector<std::shared_ptr<cryptonote::blink_tx>>, std::unordered_set<crypto::hash>> parse_incoming_blinks(const std::vector<cryptonote::serializable_blink_metadata> &blinks) { return {}; }
int add_blinks(const std::vector<std::shared_ptr<cryptonote::blink_tx>> &blinks) { return 0; }

View File

@ -89,7 +89,7 @@ bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64
};
} opts;
cryptonote::Blockchain *blockchain = &bc.m_blockchain;
bool r = blockchain->init(new TestDB(test_distribution_size), nullptr /*ons_db*/, cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL);
bool r = blockchain->init(new TestDB(test_distribution_size), nullptr /*ons_db*/, nullptr /*sqlite_db*/, cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL);
return r && blockchain->get_output_distribution(amount, from, to, start_height, distribution, base);
}

143
tests/unit_tests/sqlite.cpp Normal file
View File

@ -0,0 +1,143 @@
// Copyright (c) 2021, The Oxen Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <gtest/gtest.h>
#include "blockchain_db/sqlite/db_sqlite.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_TRUE(wallet_address.address.next_payout_height(50, 100) == 90);
EXPECT_TRUE(wallet_address.address.next_payout_height(100, 100) == 190);
}
TEST(SQLITE, AddSNRewards)
{
cryptonote::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::TESTNET, ":memory:");
std::cout << "in memory db opened" << std::endl;
EXPECT_TRUE(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");
t1.emplace_back(wallet_address.address, 16500000000/2, cryptonote::network_type::TESTNET);
bool success = false;
success = sqliteDB.add_sn_payments(t1);
EXPECT_TRUE(success);
EXPECT_TRUE(sqliteDB.batching_count() == 1);
std::optional<std::vector<cryptonote::batch_sn_payment>> p1;
const auto expected_payout = wallet_address.address.next_payout_height(0, config::BATCHING_INTERVAL);
p1 = sqliteDB.get_sn_payments(expected_payout - 1);
EXPECT_TRUE(p1.has_value());
EXPECT_TRUE((*p1).size() == 0);
std::optional<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);
// 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);
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);
EXPECT_TRUE(success);
EXPECT_TRUE(sqliteDB.batching_count() == 0);
}
TEST(SQLITE, CalculateRewards)
{
cryptonote::BlockchainSQLiteTest sqliteDB(cryptonote::network_type::TESTNET, ":memory:");
cryptonote::block block;
block.reward = 200;
// Check that a single contributor receives 100% of the block reward
service_nodes::service_node_info single_contributor{};
single_contributor.portions_for_operator = 0;
cryptonote::address_parse_info first_address{};
cryptonote::get_account_address_from_str(first_address, cryptonote::network_type::TESTNET, "T6TzkJb5EiASaCkcH7idBEi1HSrpSQJE1Zq3aL65ojBMPZvqHNYPTL56i3dncGVNEYCG5QG5zrBmRiVwcg6b1cRM1SRNqbp44");
single_contributor.contributors.emplace_back(0, first_address.address);
single_contributor.contributors.back().amount = block.reward;
auto rewards = sqliteDB.calculate_rewards(block.major_version, block.reward, single_contributor);
auto hf_version = block.major_version;
// Check that 3 contributor receives their portion of the block reward
service_nodes::service_node_info multiple_contributors{};
multiple_contributors.contributors.emplace_back(0, first_address.address);
multiple_contributors.contributors.back().amount = 33;
cryptonote::address_parse_info second_address{};
cryptonote::get_account_address_from_str(second_address, cryptonote::network_type::TESTNET, "T6SjALssDNvPZnTnV7vr459SX632c4X5qjLKfHfzvS32RPuhH3vnJmP9fyiD6ZiMu4XPk8ofH95mNRDg5bUPWkmq1LGAnyP3B");
multiple_contributors.contributors.emplace_back(0, second_address.address);
multiple_contributors.contributors.back().amount = 33;
cryptonote::address_parse_info third_address{};
cryptonote::get_account_address_from_str(third_address, cryptonote::network_type::TESTNET, "T6SkkovCyLWViVDMgeJoF7X4vFrHnKX5jXyktaoGmRuNTdoFEx1xXu1joXdmeH9mx2LLNPq998fKKcsAHwdRJWhk126SapptR");
multiple_contributors.contributors.emplace_back(0, third_address.address);
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);
// Check that 3 contributors receives their portion of the block reward when the operator takes a 10% fee
multiple_contributors.portions_for_operator = STAKING_PORTIONS/10;
multiple_contributors.operator_address = first_address.address;
block.reward = 1000;
auto multiple_rewards_with_fee = sqliteDB.calculate_rewards(block.major_version, block.reward, multiple_contributors);
// Operator gets 10%
EXPECT_TRUE(multiple_rewards_with_fee[0].amount == 99);
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[0].address_info.address) == tools::view_guts(first_address.address));
// Contributors (including operator) receive the balance
EXPECT_TRUE(multiple_rewards_with_fee[1].amount == 297);
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[1].address_info.address) == tools::view_guts(first_address.address));
EXPECT_TRUE(multiple_rewards_with_fee[2].amount == 297);
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[2].address_info.address) == tools::view_guts(second_address.address));
EXPECT_TRUE(multiple_rewards_with_fee[3].amount == 306);
EXPECT_TRUE(tools::view_guts(multiple_rewards_with_fee[3].address_info.address) == tools::view_guts(third_address.address));
}

View File

@ -141,7 +141,11 @@ TEST(parse_and_validate_tx_extra, is_valid_tx_extra_parsed)
cryptonote::account_base acc;
acc.generate();
cryptonote::blobdata b = "dsdsdfsdfsf";
ASSERT_TRUE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, acc.get_keys().m_account_address), b));
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::FAKECHAIN, acc.get_keys().m_account_address), sn_rwds, b);
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);
}
@ -150,8 +154,12 @@ 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;
cryptonote::blobdata b(cryptonote::TX_EXTRA_NONCE_MAX_COUNT + 1, 0);
ASSERT_FALSE(cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, TEST_FEE, tx, cryptonote::oxen_miner_tx_context::miner_block(cryptonote::FAKECHAIN, acc.get_keys().m_account_address), b));
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::FAKECHAIN, acc.get_keys().m_account_address), sn_rwds, b);
ASSERT_FALSE(r);
}
TEST(parse_and_validate_tx_extra, fails_on_wrong_size_in_extra_nonce)
{

View File

@ -1 +1,3 @@
testdata
__pycache__
*.pyc

View File

@ -13,6 +13,7 @@ def instruct_daemon(method, params):
headers = {'content-type': "application/json"}
try:
response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers)
# response = requests.request("POST", "http://"+config.listen_ip+":1165/json_rpc", data=payload, headers=headers)
return json.loads(response.text)
except requests.exceptions.RequestException as e:
print(e)

View File

@ -0,0 +1,41 @@
#!/usr/bin/python3
import sys
sys.path.append('../testdata')
import config
import requests
import argparse
import json
from datetime import datetime
def instruct_daemon(method, params):
payload = json.dumps({"method": method, "params": params}, skipkeys=False)
# print(payload)
headers = {'content-type': "application/json"}
try:
response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers)
return json.loads(response.text)
except requests.exceptions.RequestException as e:
print(e)
except:
print('No response from daemon, check daemon is running on this machine')
params = {}
answer = instruct_daemon('get_last_block_header', params)
# print(json.dumps(answer['result']['block_header']['timestamp'], indent=4, sort_keys=True))
blockheight = answer['result']['block_header']['height']
print("Block height: " + str(blockheight))
blocktime = datetime.fromtimestamp(answer['result']['block_header']['timestamp'])
print("Block time: " + str(blocktime))
now = datetime.now()
print("Current time: " + str(now))
diff = now - blocktime
print(str(diff.seconds) + " seconds ago")

View File

@ -0,0 +1,25 @@
#!/usr/bin/python3
import sys
sys.path.append('../testdata')
import config
import requests
import json
def instruct_daemon(method, params):
payload = json.dumps({"method": method, "params": params})
headers = {'content-type': "application/json"}
try:
response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers)
return json.loads(response.text)
except requests.exceptions.RequestException as e:
print(e)
except:
print('No response from daemon, check daemon is running on this machine')
# $ curl http://127.0.0.1:18081/get_transaction_pool -H 'Content-Type: application/json'
answer = instruct_daemon('get_transaction_pool', [])
print(json.dumps(answer, indent=4, sort_keys=True))

View File

@ -0,0 +1,35 @@
#!/usr/bin/python3
import sys
sys.path.append('../testdata')
import config
import requests
import json
def instruct_daemon(method, params):
payload = json.dumps({"method": method, "params": params}, skipkeys=False)
# print(payload)
headers = {'content-type': "application/json"}
try:
response = requests.request("POST", "http://"+config.listen_ip+":"+config.listen_port+"/json_rpc", data=payload, headers=headers)
return json.loads(response.text)
except requests.exceptions.RequestException as e:
print(e)
except:
print('No response from daemon, check daemon is running on this machine')
service_node_pubkeys = []
answer = instruct_daemon('get_service_nodes', [])
# Transform json input to python objects
# sn.json_rpc("get_n_service_nodes", {"fields":{"quorumnet_port":True}}).json()['result']['service_node_states'])
# Filter python objects with list comprehensions
output_dict = [x['quorumnet_port'] for x in answer['result']['service_node_states']]
print("Quorumnet Ports:")
print(json.dumps(output_dict, indent=4, sort_keys=True))

View File

@ -14,8 +14,8 @@ LISTEN_IP, NEXT_PORT = (
if sys.platform == 'linux' else
('127.0.0.1', random.randint(5000, 20000)))
verbose = True
# verbose = False
# verbose = True
verbose = False
def next_port():
global NEXT_PORT
@ -84,7 +84,7 @@ class RPCDaemon:
return self.args
def json_rpc(self, method, params=None, *, timeout=10):
def json_rpc(self, method, params=None, *, timeout=100):
"""Sends a json_rpc request to the rpc port. Returns the response object."""
if not self.proc:
raise RuntimeError("Cannot make rpc request before calling start()")
@ -139,7 +139,7 @@ class Daemon(RPCDaemon):
name=None,
datadir=None,
service_node=False,
log_level=2,
log_level=3,
peers=()):
self.rpc_port = rpc_port or next_port()
if name is None:
@ -151,6 +151,7 @@ class Daemon(RPCDaemon):
self.qnet_port = qnet_port or next_port()
self.ss_port = ss_port or next_port()
self.peers = []
self.service_node_key = None
self.args = [oxend] + list(self.__class__.base_args)
self.args += (
@ -235,6 +236,15 @@ class Daemon(RPCDaemon):
"""Triggers a p2p resync to happen soon (i.e. at the next p2p idle loop)."""
self.json_rpc("test_trigger_p2p_resync")
def sn_key(self):
if not self.service_node_key:
self.service_node_key = self.json_rpc("get_service_keys").json()["result"]["service_node_pubkey"]
return self.service_node_key
def sn_status(self):
return self.json_rpc("get_service_node_status").json()["result"]
class Wallet(RPCDaemon):
@ -250,7 +260,7 @@ class Wallet(RPCDaemon):
datadir=None,
listen_ip=None,
rpc_port=None,
log_level=2):
log_level=3):
self.listen_ip = listen_ip or LISTEN_IP
self.rpc_port = rpc_port or next_port()
@ -363,3 +373,25 @@ class Wallet(RPCDaemon):
r = self.json_rpc("register_service_node", {"register_service_node_str": cmd}).json()
if 'error' in r:
raise RuntimeError("Failed to submit service node registration tx: {}".format(r['error']['message']))
def register_sn_for_contributions(self, sn):
r = sn.json_rpc("get_service_node_registration_cmd", {
"operator_cut": "10",
"contributions": [{"address": self.address(), "amount": 50000000000}],
"staking_requirement": 100000000000
}).json()
if 'error' in r:
raise RuntimeError("Registration cmd generation failed: {}".format(r['error']['message']))
cmd = r['result']['registration_cmd']
r = self.json_rpc("register_service_node", {"register_service_node_str": cmd}).json()
if 'error' in r:
raise RuntimeError("Failed to submit service node registration tx: {}".format(r['error']['message']))
def contribute_to_sn(self, sn):
r = self.json_rpc("stake", {
"destination": self.address(),
"amount": 50000000000,
"service_node_key": sn.sn_key(),
}).json()
if 'error' in r:
raise RuntimeError("Failed to submit stake tx: {}".format(r['error']['message']))

View File

@ -79,6 +79,7 @@ class SNNetwork:
if i != k:
self.all_nodes[i].add_peer(self.all_nodes[k])
vprint("Starting new oxend service nodes with RPC on {} ports".format(self.sns[0].listen_ip), end="")
for sn in self.sns:
vprint(" {}".format(sn.rpc_port), end="", flush=True, timestamp=False)
@ -107,6 +108,10 @@ class SNNetwork:
for w in self.wallets:
w.wait_for_json_rpc("refresh")
configfile=self.datadir+'config.py'
with open(configfile, 'w') as filetowrite:
filetowrite.write('#!/usr/bin/python3\n# -*- coding: utf-8 -*-\nlisten_ip=\"{}\"\nlisten_port=\"{}\"\nwallet_listen_ip=\"{}\"\nwallet_listen_port=\"{}\"\nwallet_address=\"{}\"\nexternal_address=\"{}\"'.format(self.sns[0].listen_ip,self.sns[0].rpc_port,self.mike.listen_ip,self.mike.rpc_port,self.mike.address(),self.bob.address()))
# Mine some blocks; we need 100 per SN registration, and we can nearly 600 on fakenet before
# it hits HF16 and kills mining rewards. This lets us submit the first 5 SN registrations a
# SN (at height 40, which is the earliest we can submit them without getting an occasional
@ -128,7 +133,8 @@ class SNNetwork:
self.mine(6*len(self.sns))
self.print_wallet_balances()
self.mike.transfer(self.alice, 150000000000)
self.mike.transfer(self.bob, 150000000000)
vprint("Submitting more service node registrations: ", end="", flush=True)
for sn in self.sns[5:-1]:
self.mike.register_sn(sn)
@ -139,7 +145,7 @@ class SNNetwork:
self.print_wallet_balances()
vprint("Mining 40 blocks (registrations + blink quorum lag) and waiting for nodes to sync")
self.sync_nodes(self.mine(40))
self.sync_nodes(self.mine(40), timeout=120)
self.print_wallet_balances()
@ -155,10 +161,20 @@ class SNNetwork:
wait_for(lambda: all_service_nodes_proofed(sn), timeout=120)
vprint(".", end="", flush=True, timestamp=False)
vprint(timestamp=False)
for sn in self.sns[-1:]:
self.mike.register_sn(sn)
vprint(".", end="", flush=True, timestamp=False)
self.sync_nodes(self.mine(1))
# This commented out code will register the last SN through Mikes wallet (Has done every other SN)
# for sn in self.sns[-1:]:
# self.mike.register_sn(sn)
# vprint(".", end="", flush=True, timestamp=False)
# This commented out code will register the last SN through Bobs wallet (Has not done any others)
# self.bob.register_sn(self.sns[-1])
# This commented out code will register the last SN through Bobs wallet (Has not done any others)
# and also get alice to contribute 50% of the node with a 10% operator fee
self.bob.register_sn_for_contributions(self.sns[-1])
self.sync_nodes(self.mine(5), timeout=120)
self.alice.contribute_to_sn(self.sns[-1])
self.sync_nodes(self.mine(2), timeout=120)
time.sleep(10)
for sn in self.sns:
sn.send_uptime_proof()
@ -166,12 +182,6 @@ class SNNetwork:
vprint("Local Devnet SN network setup complete!")
vprint("Communicate with daemon on ip: {} port: {}".format(self.sns[0].listen_ip,self.sns[0].rpc_port))
configfile=self.datadir+'config.py'
with open(configfile, 'w') as filetowrite:
filetowrite.write('#!/usr/bin/python3\n# -*- coding: utf-8 -*-\nlisten_ip=\"{}\"\nlisten_port=\"{}\"\nwallet_listen_ip=\"{}\"\nwallet_listen_port=\"{}\"\nwallet_address=\"{}\"\nexternal_address=\"{}\"'.format(self.sns[0].listen_ip,self.sns[0].rpc_port,self.mike.listen_ip,self.mike.rpc_port,self.mike.address(),self.bob.address()))
def refresh_wallets(self, *, extra=[]):
vprint("Refreshing wallets")