mirror of https://github.com/oxen-io/oxen-core.git
Merge pull request #1450 from darcys22/batch-sn-payments
Batch sn payments
This commit is contained in:
commit
3ac7e55056
|
@ -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 [
|
||||
|
@ -325,13 +326,13 @@ local gui_wallet_step_darwin = {
|
|||
'mkdir -p build/{arm64,sim64}',
|
||||
'cd build/arm64',
|
||||
'cmake ../.. -G Ninja ' +
|
||||
'-DCMAKE_TOOLCHAIN_FILE=../../cmake/ios.toolchain.cmake -DPLATFORM=OS -DDEPLOYMENT_TARGET=11 -DENABLE_VISIBILITY=ON -DENABLE_BITCODE=OFF ' +
|
||||
'-DCMAKE_TOOLCHAIN_FILE=../../cmake/ios.toolchain.cmake -DPLATFORM=OS -DDEPLOYMENT_TARGET=13 -DENABLE_VISIBILITY=ON -DENABLE_BITCODE=OFF ' +
|
||||
'-DSTATIC=ON -DBUILD_STATIC_DEPS=ON -DUSE_LTO=OFF -DCMAKE_BUILD_TYPE=Release ' +
|
||||
'-DRANDOMX_ENABLE_JIT=OFF -DCMAKE_CXX_FLAGS=-fcolor-diagnostics',
|
||||
'ninja -j6 -v wallet_merged',
|
||||
'cd ../sim64',
|
||||
'cmake ../.. -G Ninja ' +
|
||||
'-DCMAKE_TOOLCHAIN_FILE=../../cmake/ios.toolchain.cmake -DPLATFORM=SIMULATOR64 -DDEPLOYMENT_TARGET=11 -DENABLE_VISIBILITY=ON -DENABLE_BITCODE=OFF ' +
|
||||
'-DCMAKE_TOOLCHAIN_FILE=../../cmake/ios.toolchain.cmake -DPLATFORM=SIMULATOR64 -DDEPLOYMENT_TARGET=13 -DENABLE_VISIBILITY=ON -DENABLE_BITCODE=OFF ' +
|
||||
'-DSTATIC=ON -DBUILD_STATIC_DEPS=ON -DUSE_LTO=OFF -DCMAKE_BUILD_TYPE=Release ' +
|
||||
'-DRANDOMX_ENABLE_JIT=OFF -DCMAKE_CXX_FLAGS=-fcolor-diagnostics',
|
||||
'ninja -j6 -v wallet_merged',
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit beb2b2964036f7ec87394a0d7f32db170d4bcdfe
|
||||
Subproject commit ce6dd9b82234e0427801380286d87f5bae417cca
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
|
@ -34,6 +34,7 @@ target_link_libraries(blockchain_tools_common_libs INTERFACE
|
|||
version
|
||||
filesystem
|
||||
Boost::program_options
|
||||
SQLiteCpp
|
||||
extra)
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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{};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -65,6 +65,8 @@ 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 },
|
||||
hard_fork{19, 1, 751553, 1652152424 },
|
||||
};
|
||||
|
||||
static constexpr std::array devnet_hard_forks =
|
||||
|
@ -74,7 +76,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 },
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -55,6 +55,7 @@ target_link_libraries(cryptonote_core
|
|||
SQLite::SQLite3
|
||||
PRIVATE
|
||||
Boost::program_options
|
||||
SQLiteCpp
|
||||
systemd
|
||||
extra)
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ? ®test_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 ? ®test_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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -41,4 +41,5 @@ target_link_libraries(cryptonote_protocol
|
|||
p2p
|
||||
PRIVATE
|
||||
easylogging
|
||||
SQLiteCpp
|
||||
extra)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,5 +47,6 @@ target_link_libraries(daemon
|
|||
version
|
||||
filesystem
|
||||
Boost::program_options
|
||||
SQLiteCpp
|
||||
systemd
|
||||
extra)
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -40,6 +40,7 @@ target_link_libraries(device
|
|||
ringct_basic
|
||||
Boost::serialization
|
||||
PRIVATE
|
||||
SQLiteCpp
|
||||
version
|
||||
extra)
|
||||
|
||||
|
|
|
@ -42,4 +42,5 @@ target_link_libraries(p2p
|
|||
Boost::program_options
|
||||
filesystem
|
||||
Boost::serialization
|
||||
SQLiteCpp
|
||||
extra)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -32,6 +32,7 @@ target_link_libraries(block_weight
|
|||
PRIVATE
|
||||
cryptonote_core
|
||||
blockchain_db
|
||||
SQLiteCpp
|
||||
extra)
|
||||
|
||||
add_test(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -37,6 +37,7 @@ target_link_libraries(core_proxy
|
|||
version
|
||||
epee
|
||||
Boost::program_options
|
||||
SQLiteCpp
|
||||
extra)
|
||||
set_property(TARGET core_proxy
|
||||
PROPERTY
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -57,6 +57,7 @@ target_link_libraries(core_tests
|
|||
device
|
||||
wallet
|
||||
Boost::program_options
|
||||
SQLiteCpp
|
||||
extra)
|
||||
set_property(TARGET core_tests
|
||||
PROPERTY
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ¶ms,
|
||||
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();
|
||||
|
||||
|
|
|
@ -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 ¶ms, const std::vector<cryptonote::transaction> &tx_list) const;
|
||||
void block_fill_pulse_data(oxen_blockchain_entry &entry, oxen_create_block_params const ¶ms, uint8_t round) const;
|
||||
void block_end(oxen_blockchain_entry &entry, oxen_create_block_params const ¶ms) 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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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); };
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
testdata
|
||||
__pycache__
|
||||
*.pyc
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
@ -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))
|
|
@ -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))
|
||||
|
||||
|
|
@ -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']))
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue