mirror of https://github.com/oxen-io/oxen-core.git
572 lines
23 KiB
C++
572 lines
23 KiB
C++
// 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 <sqlite3.h>
|
|
#include <sodium.h>
|
|
#include <fmt/core.h>
|
|
|
|
#include <iostream>
|
|
#include <cassert>
|
|
|
|
#include "cryptonote_config.h"
|
|
#include "cryptonote_core/blockchain.h"
|
|
#include "cryptonote_core/service_node_list.h"
|
|
#include "common/string_util.h"
|
|
#include "cryptonote_basic/hardfork.h"
|
|
|
|
#undef OXEN_DEFAULT_LOG_CATEGORY
|
|
#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();
|
|
}
|
|
|
|
height = prepared_get<int64_t>("SELECT height FROM batch_db_info");
|
|
}
|
|
|
|
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;
|
|
prepared_exec(
|
|
"UPDATE batch_db_info SET 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_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
auto insert_payment = prepared_st(
|
|
"INSERT INTO batched_payments_accrued (address, amount) VALUES (?, ?)"
|
|
" ON CONFLICT (address) DO UPDATE SET amount = amount + excluded.amount");
|
|
|
|
for (auto& payment: payments) {
|
|
std::string address_str = cryptonote::get_account_address_as_str(m_nettype, 0, payment.address_info.address);
|
|
auto amt = static_cast<int64_t>(payment.amount);
|
|
MTRACE(fmt::format("Adding record for SN reward contributor {} to database with amount {}",
|
|
address_str, amt));
|
|
|
|
db::exec_query(insert_payment, address_str, amt);
|
|
insert_payment->reset();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BlockchainSQLite::subtract_sn_rewards(const std::vector<cryptonote::batch_sn_payment>& payments) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
auto update_payment = prepared_st(
|
|
"UPDATE batched_payments_accrued SET amount = (amount - ?) WHERE address = ?");
|
|
|
|
for (auto& payment: payments) {
|
|
std::string address_str = cryptonote::get_account_address_as_str(m_nettype, 0, payment.address_info.address);
|
|
auto result = db::exec_query(update_payment, static_cast<int64_t>(payment.amount), address_str);
|
|
if (!result) {
|
|
MERROR("tried to subtract payment from an address that doesn't exist: " << address_str);
|
|
return false;
|
|
}
|
|
update_payment->reset();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::get_sn_payments(uint64_t block_height) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
|
|
// <= here because we might have crap in the db that we don't clear until we actually add the HF
|
|
// block later on. (This is a pretty slim edge case that happened on devnet and is probably
|
|
// virtually impossible on mainnet).
|
|
if (m_nettype != cryptonote::network_type::FAKECHAIN && block_height <= cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching).first.value_or(0))
|
|
return {};
|
|
|
|
const auto& conf = get_config(m_nettype);
|
|
|
|
auto accrued_amounts = prepared_results<std::string, int64_t>(
|
|
"SELECT address, amount FROM batched_payments_accrued WHERE amount >= ? ORDER BY address ASC",
|
|
static_cast<int64_t>(conf.MIN_BATCH_PAYMENT_AMOUNT * BATCH_REWARD_FACTOR));
|
|
|
|
std::vector<cryptonote::batch_sn_payment> payments;
|
|
|
|
for (auto [address, amount] : accrued_amounts) {
|
|
if (cryptonote::is_valid_address(address, m_nettype)) {
|
|
cryptonote::address_parse_info addr_info {};
|
|
cryptonote::get_account_address_from_str(addr_info, m_nettype, address);
|
|
uint64_t next_payout_height = addr_info.address.next_payout_height(block_height - 1, conf.BATCHING_INTERVAL);
|
|
if (block_height == next_payout_height) {
|
|
payments.emplace_back(
|
|
std::move(address),
|
|
amount / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR /* truncate to atomic OXEN */,
|
|
m_nettype);
|
|
}
|
|
} else {
|
|
MERROR("Invalid address returned from batching database: " << address);
|
|
}
|
|
}
|
|
|
|
return payments;
|
|
}
|
|
|
|
|
|
uint64_t BlockchainSQLite::get_accrued_earnings(const std::string& address) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
|
|
SQLite::Statement select_earnings {
|
|
db,
|
|
"SELECT amount FROM batched_payments_accrued WHERE address = ?;"
|
|
};
|
|
select_earnings.bind(1, address);
|
|
|
|
uint64_t amount{};
|
|
while (select_earnings.executeStep()) {
|
|
amount = static_cast<uint64_t>(select_earnings.getColumn(0).getInt64() / 1000);
|
|
}
|
|
|
|
return amount;
|
|
}
|
|
|
|
std::pair<std::vector<std::string>, std::vector<uint64_t>> BlockchainSQLite::get_all_accrued_earnings() {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
|
|
SQLite::Statement select_earnings {
|
|
db,
|
|
"SELECT address, amount FROM batched_payments_accrued;"
|
|
};
|
|
|
|
std::vector<uint64_t> amounts;
|
|
std::vector<std::string> addresses;
|
|
while (select_earnings.executeStep()) {
|
|
addresses.emplace_back(select_earnings.getColumn(0).getString());
|
|
amounts.emplace_back(static_cast<uint64_t>(select_earnings.getColumn(1).getInt64() / 1000));
|
|
}
|
|
|
|
return std::make_pair(addresses, amounts);
|
|
}
|
|
|
|
|
|
std::vector<cryptonote::batch_sn_payment> BlockchainSQLite::calculate_rewards(hf 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: fee_portions/PORTIONS * reward
|
|
assert(sn_info.portions_for_operator <= old::STAKING_PORTIONS);
|
|
uint64_t operator_fee = mul128_div64(sn_info.portions_for_operator, distribution_amount, old::STAKING_PORTIONS);
|
|
|
|
assert(operator_fee <= distribution_amount);
|
|
|
|
std::vector<cryptonote::batch_sn_payment> payments;
|
|
// Pay the operator fee to the operator
|
|
if (operator_fee > 0)
|
|
payments.emplace_back(sn_info.operator_address, operator_fee, m_nettype);
|
|
|
|
// Pay the balance to all the contributors (including the operator again)
|
|
uint64_t total_contributed_to_sn = std::accumulate(
|
|
sn_info.contributors.begin(),
|
|
sn_info.contributors.end(),
|
|
uint64_t(0),
|
|
[](auto&& a, auto&& b) { return a + b.amount; });
|
|
|
|
for (auto& contributor: sn_info.contributors) {
|
|
// This calculates (contributor.amount / total_contributed_to_winner_sn) * (distribution_amount - operator_fee) but using 128 bit integer math
|
|
uint64_t c_reward = mul128_div64(contributor.amount, distribution_amount - operator_fee, total_contributed_to_sn);
|
|
if (c_reward > 0)
|
|
payments.emplace_back(contributor.address, c_reward, m_nettype);
|
|
}
|
|
|
|
return payments;
|
|
}
|
|
|
|
// Calculates block rewards, then invokes either `add_sn_rewards` (if `add`) or
|
|
// `subtract_sn_rewards` (if `!add`) to process them.
|
|
bool BlockchainSQLite::reward_handler(
|
|
const cryptonote::block& block,
|
|
const service_nodes::service_node_list::state_t& service_nodes_state,
|
|
bool add)
|
|
{
|
|
// The method we call do actually handle the change: either `add_sn_payments` if add is true,
|
|
// `subtract_sn_payments` otherwise:
|
|
bool (BlockchainSQLite::* add_or_subtract)(const std::vector<cryptonote::batch_sn_payment>&)
|
|
= add ? &BlockchainSQLite::add_sn_rewards : &BlockchainSQLite::subtract_sn_rewards;
|
|
|
|
// From here on we calculate everything in milli-atomic OXEN (i.e. thousanths of an atomic
|
|
// OXEN) so that our integer math has minimal loss from integer division.
|
|
if (block.reward > std::numeric_limits<uint64_t>::max() / BATCH_REWARD_FACTOR)
|
|
throw std::logic_error{"Reward distribution amount is too large"};
|
|
|
|
uint64_t block_reward = block.reward * BATCH_REWARD_FACTOR;
|
|
uint64_t service_node_reward = cryptonote::service_node_reward_formula(0, block.major_version) * BATCH_REWARD_FACTOR;
|
|
|
|
// Step 1: Pay out the block producer their tx fees (note that, unlike the below, this applies
|
|
// even if the SN isn't currently payable).
|
|
if (block_reward < service_node_reward && m_nettype != cryptonote::network_type::FAKECHAIN)
|
|
throw std::logic_error{"Invalid payment: block reward is too small"};
|
|
|
|
if (uint64_t tx_fees = block_reward - service_node_reward;
|
|
tx_fees > 0
|
|
&& block.service_node_winner_key // "service_node_winner_key" tracks the pulse winner; 0 if a mined block
|
|
&& crypto_core_ed25519_is_valid_point(reinterpret_cast<const unsigned char *>(block.service_node_winner_key.data))
|
|
) {
|
|
|
|
if (auto service_node_winner = service_nodes_state.service_nodes_infos.find(block.service_node_winner_key);
|
|
service_node_winner != service_nodes_state.service_nodes_infos.end()) {
|
|
auto tx_fee_payments = calculate_rewards(block.major_version, tx_fees, *service_node_winner->second);
|
|
// Takes the block producer and adds its contributors to the batching database for the transaction fees
|
|
if (!(this->*add_or_subtract)(tx_fee_payments))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
auto block_height = get_block_height(block);
|
|
|
|
// Step 2: Iterate over the whole service node list and pay each node 1/service_node_list fraction
|
|
const auto payable_service_nodes = service_nodes_state.payable_service_nodes_infos(block_height, m_nettype);
|
|
size_t total_service_nodes_payable = payable_service_nodes.size();
|
|
for (const auto& [node_pubkey, node_info]: payable_service_nodes) {
|
|
auto payable_service_node = service_nodes_state.service_nodes_infos.find(node_pubkey);
|
|
if (payable_service_node == service_nodes_state.service_nodes_infos.end())
|
|
continue;
|
|
auto snode_rewards = calculate_rewards(block.major_version, service_node_reward / total_service_nodes_payable, * payable_service_node -> second);
|
|
// Takes the node and adds its contributors to the batching database
|
|
if (!(this->*add_or_subtract)(snode_rewards))
|
|
return false;
|
|
}
|
|
|
|
// Step 3: Add Governance reward to the list
|
|
if (m_nettype != cryptonote::network_type::FAKECHAIN) {
|
|
std::vector<cryptonote::batch_sn_payment> governance_rewards;
|
|
cryptonote::address_parse_info governance_wallet_address;
|
|
cryptonote::get_account_address_from_str(governance_wallet_address, m_nettype,
|
|
cryptonote::get_config(m_nettype).governance_wallet_address(block.major_version));
|
|
uint64_t foundation_reward = cryptonote::governance_reward_formula(block.major_version) * BATCH_REWARD_FACTOR;
|
|
governance_rewards.emplace_back(governance_wallet_address.address, foundation_reward, m_nettype);
|
|
if (!(this->*add_or_subtract)(governance_rewards))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BlockchainSQLite::add_block(const cryptonote::block& block,
|
|
const service_nodes::service_node_list::state_t& service_nodes_state) {
|
|
auto block_height = get_block_height(block);
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " called on height: " << block_height);
|
|
|
|
auto hf_version = block.major_version;
|
|
if (hf_version < hf::hf19_reward_batching) {
|
|
update_height(block_height);
|
|
return true;
|
|
}
|
|
|
|
auto fork_height = cryptonote::get_hard_fork_heights(m_nettype, hf::hf19_reward_batching);
|
|
if (block_height == fork_height.first.value_or(0)) {
|
|
MDEBUG("Batching of Service Node Rewards Begins");
|
|
reset_database();
|
|
update_height(block_height - 1);
|
|
}
|
|
|
|
if (block_height != height + 1) {
|
|
MERROR(fmt::format("Block height ({}) out of sync with batching database ({})", block_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;
|
|
}
|
|
|
|
if (!reward_handler(block, service_nodes_state, /*add=*/ true))
|
|
return false;
|
|
|
|
increment_height();
|
|
|
|
transaction.commit();
|
|
} catch (std::exception& e) {
|
|
MFATAL("Error adding reward payments: " << e.what());
|
|
return false;
|
|
}
|
|
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 < hf::hf19_reward_batching) {
|
|
decrement_height();
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
SQLite::Transaction transaction {
|
|
db,
|
|
SQLite::TransactionBehavior::IMMEDIATE
|
|
};
|
|
|
|
if (!reward_handler(block, service_nodes_state, /*add=*/ false))
|
|
return false;
|
|
|
|
// Add back to the database payments that had been made in this block
|
|
delete_block_payments(block_height);
|
|
|
|
decrement_height();
|
|
transaction.commit();
|
|
} catch (std::exception& e) {
|
|
MFATAL("Error subtracting reward payments: " << e.what());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BlockchainSQLite::validate_batch_payment(
|
|
const std::vector<std::tuple<crypto::public_key, uint64_t>>& miner_tx_vouts,
|
|
const std::vector<cryptonote::batch_sn_payment>& calculated_payments_from_batching_db,
|
|
uint64_t block_height) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
|
|
if (miner_tx_vouts.size() != calculated_payments_from_batching_db.size()) {
|
|
MERROR(fmt::format("Length of batch payments ({}) does not match block vouts ({})", calculated_payments_from_batching_db.size(), miner_tx_vouts.size()));
|
|
return false;
|
|
}
|
|
|
|
int8_t vout_index = 0;
|
|
uint64_t total_oxen_payout_in_our_db = std::accumulate(
|
|
calculated_payments_from_batching_db.begin(),
|
|
calculated_payments_from_batching_db.end(),
|
|
uint64_t(0),
|
|
[](auto&& a, auto&& b) { return a + b.amount; });
|
|
uint64_t total_oxen_payout_in_vouts = 0;
|
|
std::vector<batch_sn_payment> finalised_payments;
|
|
cryptonote::keypair
|
|
const deterministic_keypair = cryptonote::get_deterministic_keypair_from_height(block_height);
|
|
for (size_t vout_index = 0; vout_index < miner_tx_vouts.size(); vout_index++) {
|
|
const auto& [pubkey, amt] = miner_tx_vouts[vout_index];
|
|
uint64_t amount = amt * BATCH_REWARD_FACTOR;
|
|
const auto& from_db = calculated_payments_from_batching_db[vout_index];
|
|
if (amount != from_db.amount) {
|
|
MERROR(fmt::format("Batched payout amount incorrect. Should be {}, not {}", from_db.amount, amount));
|
|
return false;
|
|
}
|
|
crypto::public_key out_eph_public_key{};
|
|
if (!cryptonote::get_deterministic_output_key(from_db.address_info.address, deterministic_keypair, vout_index, out_eph_public_key)) {
|
|
MERROR("Failed to generate output one-time public key");
|
|
return false;
|
|
}
|
|
if (tools::view_guts(pubkey) != tools::view_guts(out_eph_public_key)) {
|
|
MERROR("Output ephemeral public key does not match");
|
|
return false;
|
|
}
|
|
total_oxen_payout_in_vouts += amount;
|
|
finalised_payments.emplace_back(from_db.address, amount, m_nettype);
|
|
}
|
|
if (total_oxen_payout_in_vouts != total_oxen_payout_in_our_db) {
|
|
MERROR(fmt::format("Total batched payout amount incorrect. Should be {}, not {}", total_oxen_payout_in_our_db, total_oxen_payout_in_vouts));
|
|
return false;
|
|
}
|
|
|
|
return save_payments(block_height, finalised_payments);
|
|
}
|
|
|
|
bool BlockchainSQLite::save_payments(uint64_t block_height, const std::vector<batch_sn_payment>& paid_amounts) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__);
|
|
|
|
auto select_sum = prepared_st(
|
|
"SELECT amount from batched_payments_accrued WHERE address = ?");
|
|
|
|
auto update_paid = prepared_st(
|
|
"INSERT INTO batched_payments_paid (address, amount, height_paid) VALUES (?,?,?)");
|
|
|
|
for (const auto& payment: paid_amounts) {
|
|
if (auto maybe_amount = db::exec_and_maybe_get<int64_t>(select_sum, payment.address))
|
|
{
|
|
// Truncate the thousanths amount to an atomic OXEN:
|
|
auto amount = static_cast<uint64_t>(*maybe_amount) / BATCH_REWARD_FACTOR * BATCH_REWARD_FACTOR;
|
|
|
|
if (amount != payment.amount) {
|
|
MERROR(fmt::format("Invalid amounts passed in to save payments for address {}: received {}, expected {} (truncated from {})",
|
|
payment.address, payment.amount, amount, *maybe_amount));
|
|
return false;
|
|
}
|
|
|
|
db::exec_query(update_paid, payment.address, static_cast<int64_t>(amount), static_cast<int64_t>(block_height));
|
|
update_paid->reset();
|
|
}
|
|
else {
|
|
// This shouldn't occur: we validate payout addresses much earlier in the block validation.
|
|
MERROR(fmt::format("Internal error: Invalid amounts passed in to save payments for address {}: that address has no accrued rewards",
|
|
payment.address));
|
|
return false;
|
|
}
|
|
|
|
select_sum->reset();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
auto paid = prepared_results<std::string, int64_t>(
|
|
"SELECT address, amount FROM batched_payments_paid WHERE height_paid = ? ORDER BY address",
|
|
static_cast<int64_t>(block_height));
|
|
|
|
for (auto [addr, amt] : paid)
|
|
payments_at_height.emplace_back(std::move(addr), static_cast<uint64_t>(amt), m_nettype);
|
|
|
|
return payments_at_height;
|
|
}
|
|
|
|
bool BlockchainSQLite::delete_block_payments(uint64_t block_height) {
|
|
LOG_PRINT_L3("BlockchainDB_SQLITE::" << __func__ << " Called with height: " << block_height);
|
|
prepared_exec(
|
|
"DELETE FROM batched_payments_paid WHERE height_paid >= ?",
|
|
static_cast<int64_t>(block_height));
|
|
return true;
|
|
}
|
|
|
|
} // namespace cryptonote
|