dncurrency/nano/secure/ledger.cpp
Wesley Shillingford 0a64feb49e
Various consistency changes (#3058)
* Various consistency changes

* Colin comments
2021-02-17 15:36:27 +00:00

1540 lines
65 KiB
C++

#include <nano/lib/rep_weights.hpp>
#include <nano/lib/stats.hpp>
#include <nano/lib/utility.hpp>
#include <nano/lib/work.hpp>
#include <nano/secure/blockstore.hpp>
#include <nano/secure/common.hpp>
#include <nano/secure/ledger.hpp>
#include <crypto/cryptopp/words.h>
namespace
{
/**
* Roll back the visited block
*/
class rollback_visitor : public nano::block_visitor
{
public:
rollback_visitor (nano::write_transaction const & transaction_a, nano::ledger & ledger_a, std::vector<std::shared_ptr<nano::block>> & list_a) :
transaction (transaction_a),
ledger (ledger_a),
list (list_a)
{
}
virtual ~rollback_visitor () = default;
void send_block (nano::send_block const & block_a) override
{
auto hash (block_a.hash ());
nano::pending_info pending;
nano::pending_key key (block_a.hashables.destination, hash);
while (!error && ledger.store.pending_get (transaction, key, pending))
{
error = ledger.rollback (transaction, ledger.latest (transaction, block_a.hashables.destination), list);
}
if (!error)
{
nano::account_info info;
[[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, info));
debug_assert (!error);
ledger.store.pending_del (transaction, key);
ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ());
nano::account_info new_info (block_a.hashables.previous, info.representative, info.open_block, ledger.balance (transaction, block_a.hashables.previous), nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0);
ledger.update_account (transaction, pending.source, info, new_info);
ledger.store.block_del (transaction, hash);
ledger.store.frontier_del (transaction, hash);
ledger.store.frontier_put (transaction, block_a.hashables.previous, pending.source);
ledger.store.block_successor_clear (transaction, block_a.hashables.previous);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::send);
}
}
void receive_block (nano::receive_block const & block_a) override
{
auto hash (block_a.hash ());
auto amount (ledger.amount (transaction, hash));
auto destination_account (ledger.account (transaction, hash));
// Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing
[[maybe_unused]] bool is_pruned (false);
auto source_account (ledger.account_safe (transaction, block_a.hashables.source, is_pruned));
nano::account_info info;
[[maybe_unused]] auto error (ledger.store.account_get (transaction, destination_account, info));
debug_assert (!error);
ledger.cache.rep_weights.representation_add (info.representative, 0 - amount);
nano::account_info new_info (block_a.hashables.previous, info.representative, info.open_block, ledger.balance (transaction, block_a.hashables.previous), nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0);
ledger.update_account (transaction, destination_account, info, new_info);
ledger.store.block_del (transaction, hash);
ledger.store.pending_put (transaction, nano::pending_key (destination_account, block_a.hashables.source), { source_account, amount, nano::epoch::epoch_0 });
ledger.store.frontier_del (transaction, hash);
ledger.store.frontier_put (transaction, block_a.hashables.previous, destination_account);
ledger.store.block_successor_clear (transaction, block_a.hashables.previous);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::receive);
}
void open_block (nano::open_block const & block_a) override
{
auto hash (block_a.hash ());
auto amount (ledger.amount (transaction, hash));
auto destination_account (ledger.account (transaction, hash));
// Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing
[[maybe_unused]] bool is_pruned (false);
auto source_account (ledger.account_safe (transaction, block_a.hashables.source, is_pruned));
ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - amount);
nano::account_info new_info;
ledger.update_account (transaction, destination_account, new_info, new_info);
ledger.store.block_del (transaction, hash);
ledger.store.pending_put (transaction, nano::pending_key (destination_account, block_a.hashables.source), { source_account, amount, nano::epoch::epoch_0 });
ledger.store.frontier_del (transaction, hash);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::open);
}
void change_block (nano::change_block const & block_a) override
{
auto hash (block_a.hash ());
auto rep_block (ledger.representative (transaction, block_a.hashables.previous));
auto account (ledger.account (transaction, block_a.hashables.previous));
nano::account_info info;
[[maybe_unused]] auto error (ledger.store.account_get (transaction, account, info));
debug_assert (!error);
auto balance (ledger.balance (transaction, block_a.hashables.previous));
auto block = ledger.store.block_get (transaction, rep_block);
release_assert (block != nullptr);
auto representative = block->representative ();
ledger.cache.rep_weights.representation_add_dual (block_a.representative (), 0 - balance, representative, balance);
ledger.store.block_del (transaction, hash);
nano::account_info new_info (block_a.hashables.previous, representative, info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0);
ledger.update_account (transaction, account, info, new_info);
ledger.store.frontier_del (transaction, hash);
ledger.store.frontier_put (transaction, block_a.hashables.previous, account);
ledger.store.block_successor_clear (transaction, block_a.hashables.previous);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::change);
}
void state_block (nano::state_block const & block_a) override
{
auto hash (block_a.hash ());
nano::block_hash rep_block_hash (0);
if (!block_a.hashables.previous.is_zero ())
{
rep_block_hash = ledger.representative (transaction, block_a.hashables.previous);
}
auto balance (ledger.balance (transaction, block_a.hashables.previous));
auto is_send (block_a.hashables.balance < balance);
nano::account representative{ 0 };
if (!rep_block_hash.is_zero ())
{
// Move existing representation & add in amount delta
auto block (ledger.store.block_get (transaction, rep_block_hash));
debug_assert (block != nullptr);
representative = block->representative ();
ledger.cache.rep_weights.representation_add_dual (representative, balance, block_a.representative (), 0 - block_a.hashables.balance.number ());
}
else
{
// Add in amount delta only
ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - block_a.hashables.balance.number ());
}
nano::account_info info;
auto error (ledger.store.account_get (transaction, block_a.hashables.account, info));
if (is_send)
{
nano::pending_key key (block_a.hashables.link.as_account (), hash);
while (!error && !ledger.store.pending_exists (transaction, key))
{
error = ledger.rollback (transaction, ledger.latest (transaction, block_a.hashables.link.as_account ()), list);
}
ledger.store.pending_del (transaction, key);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::send);
}
else if (!block_a.hashables.link.is_zero () && !ledger.is_epoch_link (block_a.hashables.link))
{
// Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing
[[maybe_unused]] bool is_pruned (false);
auto source_account (ledger.account_safe (transaction, block_a.hashables.link.as_block_hash (), is_pruned));
nano::pending_info pending_info (source_account, block_a.hashables.balance.number () - balance, block_a.sideband ().source_epoch);
ledger.store.pending_put (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()), pending_info);
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::receive);
}
debug_assert (!error);
auto previous_version (ledger.store.block_version (transaction, block_a.hashables.previous));
nano::account_info new_info (block_a.hashables.previous, representative, info.open_block, balance, nano::seconds_since_epoch (), info.block_count - 1, previous_version);
ledger.update_account (transaction, block_a.hashables.account, info, new_info);
auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
if (previous != nullptr)
{
ledger.store.block_successor_clear (transaction, block_a.hashables.previous);
if (previous->type () < nano::block_type::state)
{
ledger.store.frontier_put (transaction, block_a.hashables.previous, block_a.hashables.account);
}
}
else
{
ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::open);
}
ledger.store.block_del (transaction, hash);
}
nano::write_transaction const & transaction;
nano::ledger & ledger;
std::vector<std::shared_ptr<nano::block>> & list;
bool error{ false };
};
class ledger_processor : public nano::mutable_block_visitor
{
public:
ledger_processor (nano::ledger &, nano::write_transaction const &, nano::signature_verification = nano::signature_verification::unknown);
virtual ~ledger_processor () = default;
void send_block (nano::send_block &) override;
void receive_block (nano::receive_block &) override;
void open_block (nano::open_block &) override;
void change_block (nano::change_block &) override;
void state_block (nano::state_block &) override;
void state_block_impl (nano::state_block &);
void epoch_block_impl (nano::state_block &);
nano::ledger & ledger;
nano::write_transaction const & transaction;
nano::signature_verification verification;
nano::process_return result;
private:
bool validate_epoch_block (nano::state_block const & block_a);
};
// Returns true if this block which has an epoch link is correctly formed.
bool ledger_processor::validate_epoch_block (nano::state_block const & block_a)
{
debug_assert (ledger.is_epoch_link (block_a.hashables.link));
nano::amount prev_balance (0);
if (!block_a.hashables.previous.is_zero ())
{
result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? nano::process_result::progress : nano::process_result::gap_previous;
if (result.code == nano::process_result::progress)
{
prev_balance = ledger.balance (transaction, block_a.hashables.previous);
}
else if (result.verified == nano::signature_verification::unknown)
{
// Check for possible regular state blocks with epoch link (send subtype)
if (validate_message (block_a.hashables.account, block_a.hash (), block_a.signature))
{
// Is epoch block signed correctly
if (validate_message (ledger.epoch_signer (block_a.link ()), block_a.hash (), block_a.signature))
{
result.verified = nano::signature_verification::invalid;
result.code = nano::process_result::bad_signature;
}
else
{
result.verified = nano::signature_verification::valid_epoch;
}
}
else
{
result.verified = nano::signature_verification::valid;
}
}
}
return (block_a.hashables.balance == prev_balance);
}
void ledger_processor::state_block (nano::state_block & block_a)
{
result.code = nano::process_result::progress;
auto is_epoch_block = false;
if (ledger.is_epoch_link (block_a.hashables.link))
{
// This function also modifies the result variable if epoch is mal-formed
is_epoch_block = validate_epoch_block (block_a);
}
if (result.code == nano::process_result::progress)
{
if (is_epoch_block)
{
epoch_block_impl (block_a);
}
else
{
state_block_impl (block_a);
}
}
}
void ledger_processor::state_block_impl (nano::state_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Unambiguous)
if (result.code == nano::process_result::progress)
{
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid)
{
result.code = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is this block signed correctly (Unambiguous)
}
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
result.code = block_a.hashables.account.is_zero () ? nano::process_result::opened_burn_account : nano::process_result::progress; // Is this for the burn account? (Unambiguous)
if (result.code == nano::process_result::progress)
{
nano::epoch epoch (nano::epoch::epoch_0);
nano::epoch source_epoch (nano::epoch::epoch_0);
nano::account_info info;
nano::amount amount (block_a.hashables.balance);
auto is_send (false);
auto is_receive (false);
auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info));
if (!account_error)
{
// Account already exists
epoch = info.epoch ();
result.previous_balance = info.balance;
result.code = block_a.hashables.previous.is_zero () ? nano::process_result::fork : nano::process_result::progress; // Has this account already been opened? (Ambigious)
if (result.code == nano::process_result::progress)
{
result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? nano::process_result::progress : nano::process_result::gap_previous; // Does the previous block exist in the ledger? (Unambigious)
if (result.code == nano::process_result::progress)
{
is_send = block_a.hashables.balance < info.balance;
is_receive = !is_send && !block_a.hashables.link.is_zero ();
amount = is_send ? (info.balance.number () - amount.number ()) : (amount.number () - info.balance.number ());
result.code = block_a.hashables.previous == info.head ? nano::process_result::progress : nano::process_result::fork; // Is the previous block the account's head block? (Ambigious)
}
}
}
else
{
// Account does not yet exists
result.previous_balance = 0;
result.code = block_a.previous ().is_zero () ? nano::process_result::progress : nano::process_result::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious)
if (result.code == nano::process_result::progress)
{
is_receive = true;
result.code = !block_a.hashables.link.is_zero () ? nano::process_result::progress : nano::process_result::gap_source; // Is the first block receiving from a send ? (Unambigious)
}
}
if (result.code == nano::process_result::progress)
{
if (!is_send)
{
if (!block_a.hashables.link.is_zero ())
{
result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.link.as_block_hash ()) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless)
if (result.code == nano::process_result::progress)
{
nano::pending_key key (block_a.hashables.account, block_a.hashables.link.as_block_hash ());
nano::pending_info pending;
result.code = ledger.store.pending_get (transaction, key, pending) ? nano::process_result::unreceivable : nano::process_result::progress; // Has this source already been received (Malformed)
if (result.code == nano::process_result::progress)
{
result.code = amount == pending.amount ? nano::process_result::progress : nano::process_result::balance_mismatch;
source_epoch = pending.epoch;
epoch = std::max (epoch, source_epoch);
}
}
}
else
{
// If there's no link, the balance must remain the same, only the representative can change
result.code = amount.is_zero () ? nano::process_result::progress : nano::process_result::balance_mismatch;
}
}
}
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (epoch, is_send, is_receive, false);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block);
block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, source_epoch));
ledger.store.block_put (transaction, hash, block_a);
if (!info.head.is_zero ())
{
// Move existing representation & add in amount delta
ledger.cache.rep_weights.representation_add_dual (info.representative, 0 - info.balance.number (), block_a.representative (), block_a.hashables.balance.number ());
}
else
{
// Add in amount delta only
ledger.cache.rep_weights.representation_add (block_a.representative (), block_a.hashables.balance.number ());
}
if (is_send)
{
nano::pending_key key (block_a.hashables.link.as_account (), hash);
nano::pending_info info (block_a.hashables.account, amount.number (), epoch);
ledger.store.pending_put (transaction, key, info);
}
else if (!block_a.hashables.link.is_zero ())
{
ledger.store.pending_del (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()));
}
nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch);
ledger.update_account (transaction, block_a.hashables.account, info, new_info);
if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
{
ledger.store.frontier_del (transaction, info.head);
}
}
}
}
}
}
}
void ledger_processor::epoch_block_impl (nano::state_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Unambiguous)
if (result.code == nano::process_result::progress)
{
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid_epoch)
{
result.code = validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is this block signed correctly (Unambiguous)
}
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature));
result.verified = nano::signature_verification::valid_epoch;
result.code = block_a.hashables.account.is_zero () ? nano::process_result::opened_burn_account : nano::process_result::progress; // Is this for the burn account? (Unambiguous)
if (result.code == nano::process_result::progress)
{
nano::account_info info;
auto account_error (ledger.store.account_get (transaction, block_a.hashables.account, info));
if (!account_error)
{
// Account already exists
result.previous_balance = info.balance;
result.code = block_a.hashables.previous.is_zero () ? nano::process_result::fork : nano::process_result::progress; // Has this account already been opened? (Ambigious)
if (result.code == nano::process_result::progress)
{
result.code = block_a.hashables.previous == info.head ? nano::process_result::progress : nano::process_result::fork; // Is the previous block the account's head block? (Ambigious)
if (result.code == nano::process_result::progress)
{
result.code = block_a.hashables.representative == info.representative ? nano::process_result::progress : nano::process_result::representative_mismatch;
}
}
}
else
{
result.previous_balance = 0;
result.code = block_a.hashables.representative.is_zero () ? nano::process_result::progress : nano::process_result::representative_mismatch;
// Non-exisitng account should have pending entries
if (result.code == nano::process_result::progress)
{
bool pending_exists = ledger.store.pending_any (transaction, block_a.hashables.account);
result.code = pending_exists ? nano::process_result::progress : nano::process_result::block_position;
}
}
if (result.code == nano::process_result::progress)
{
auto epoch = ledger.network_params.ledger.epochs.epoch (block_a.hashables.link);
// Must be an epoch for an unopened account or the epoch upgrade must be sequential
auto is_valid_epoch_upgrade = account_error ? static_cast<std::underlying_type_t<nano::epoch>> (epoch) > 0 : nano::epochs::is_sequential (info.epoch (), epoch);
result.code = is_valid_epoch_upgrade ? nano::process_result::progress : nano::process_result::block_position;
if (result.code == nano::process_result::progress)
{
result.code = block_a.hashables.balance == info.balance ? nano::process_result::progress : nano::process_result::balance_mismatch;
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (epoch, false, false, true);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block);
block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
ledger.store.block_put (transaction, hash, block_a);
nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch);
ledger.update_account (transaction, block_a.hashables.account, info, new_info);
if (!ledger.store.frontier_get (transaction, info.head).is_zero ())
{
ledger.store.frontier_del (transaction, info.head);
}
}
}
}
}
}
}
}
}
void ledger_processor::change_block (nano::change_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Harmless)
if (result.code == nano::process_result::progress)
{
auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
result.code = previous != nullptr ? nano::process_result::progress : nano::process_result::gap_previous; // Have we seen the previous block already? (Harmless)
if (result.code == nano::process_result::progress)
{
result.code = block_a.valid_predecessor (*previous) ? nano::process_result::progress : nano::process_result::block_position;
if (result.code == nano::process_result::progress)
{
auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous));
result.code = account.is_zero () ? nano::process_result::fork : nano::process_result::progress;
if (result.code == nano::process_result::progress)
{
nano::account_info info;
auto latest_error (ledger.store.account_get (transaction, account, info));
(void)latest_error;
debug_assert (!latest_error);
debug_assert (info.head == block_a.hashables.previous);
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid)
{
result.code = validate_message (account, hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is this block signed correctly (Malformed)
}
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
block_a.sideband_set (nano::block_sideband (account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
ledger.store.block_put (transaction, hash, block_a);
auto balance (ledger.balance (transaction, block_a.hashables.previous));
ledger.cache.rep_weights.representation_add_dual (block_a.representative (), balance, info.representative, 0 - balance);
nano::account_info new_info (hash, block_a.representative (), info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0);
ledger.update_account (transaction, account, info, new_info);
ledger.store.frontier_del (transaction, block_a.hashables.previous);
ledger.store.frontier_put (transaction, hash, account);
result.previous_balance = info.balance;
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change);
}
}
}
}
}
}
}
void ledger_processor::send_block (nano::send_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Harmless)
if (result.code == nano::process_result::progress)
{
auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
result.code = previous != nullptr ? nano::process_result::progress : nano::process_result::gap_previous; // Have we seen the previous block already? (Harmless)
if (result.code == nano::process_result::progress)
{
result.code = block_a.valid_predecessor (*previous) ? nano::process_result::progress : nano::process_result::block_position;
if (result.code == nano::process_result::progress)
{
auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous));
result.code = account.is_zero () ? nano::process_result::fork : nano::process_result::progress;
if (result.code == nano::process_result::progress)
{
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid)
{
result.code = validate_message (account, hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is this block signed correctly (Malformed)
}
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
nano::account_info info;
auto latest_error (ledger.store.account_get (transaction, account, info));
(void)latest_error;
debug_assert (!latest_error);
debug_assert (info.head == block_a.hashables.previous);
result.code = info.balance.number () >= block_a.hashables.balance.number () ? nano::process_result::progress : nano::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious)
if (result.code == nano::process_result::progress)
{
auto amount (info.balance.number () - block_a.hashables.balance.number ());
ledger.cache.rep_weights.representation_add (info.representative, 0 - amount);
block_a.sideband_set (nano::block_sideband (account, 0, block_a.hashables.balance /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
ledger.store.block_put (transaction, hash, block_a);
nano::account_info new_info (hash, info.representative, info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0);
ledger.update_account (transaction, account, info, new_info);
ledger.store.pending_put (transaction, nano::pending_key (block_a.hashables.destination, hash), { account, amount, nano::epoch::epoch_0 });
ledger.store.frontier_del (transaction, block_a.hashables.previous);
ledger.store.frontier_put (transaction, hash, account);
result.previous_balance = info.balance;
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send);
}
}
}
}
}
}
}
}
void ledger_processor::receive_block (nano::receive_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block already? (Harmless)
if (result.code == nano::process_result::progress)
{
auto previous (ledger.store.block_get (transaction, block_a.hashables.previous));
result.code = previous != nullptr ? nano::process_result::progress : nano::process_result::gap_previous;
if (result.code == nano::process_result::progress)
{
result.code = block_a.valid_predecessor (*previous) ? nano::process_result::progress : nano::process_result::block_position;
if (result.code == nano::process_result::progress)
{
auto account (ledger.store.frontier_get (transaction, block_a.hashables.previous));
result.code = account.is_zero () ? nano::process_result::gap_previous : nano::process_result::progress; //Have we seen the previous block? No entries for account at all (Harmless)
if (result.code == nano::process_result::progress)
{
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid)
{
result.code = validate_message (account, hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is the signature valid (Malformed)
}
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless)
if (result.code == nano::process_result::progress)
{
nano::account_info info;
ledger.store.account_get (transaction, account, info);
result.code = info.head == block_a.hashables.previous ? nano::process_result::progress : nano::process_result::gap_previous; // Block doesn't immediately follow latest block (Harmless)
if (result.code == nano::process_result::progress)
{
nano::pending_key key (account, block_a.hashables.source);
nano::pending_info pending;
result.code = ledger.store.pending_get (transaction, key, pending) ? nano::process_result::unreceivable : nano::process_result::progress; // Has this source already been received (Malformed)
if (result.code == nano::process_result::progress)
{
result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed)
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
auto new_balance (info.balance.number () + pending.amount.number ());
#ifdef NDEBUG
if (ledger.store.block_exists (transaction, block_a.hashables.source))
{
nano::account_info source_info;
[[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, source_info));
debug_assert (!error);
}
#endif
ledger.store.pending_del (transaction, key);
block_a.sideband_set (nano::block_sideband (account, 0, new_balance, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
ledger.store.block_put (transaction, hash, block_a);
nano::account_info new_info (hash, info.representative, info.open_block, new_balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0);
ledger.update_account (transaction, account, info, new_info);
ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ());
ledger.store.frontier_del (transaction, block_a.hashables.previous);
ledger.store.frontier_put (transaction, hash, account);
result.previous_balance = info.balance;
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive);
}
}
}
}
}
}
}
else
{
result.code = ledger.store.block_exists (transaction, block_a.hashables.previous) ? nano::process_result::fork : nano::process_result::gap_previous; // If we have the block but it's not the latest we have a signed fork (Malicious)
}
}
}
}
}
void ledger_processor::open_block (nano::open_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.block_or_pruned_exists (transaction, hash));
result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block already? (Harmless)
if (result.code == nano::process_result::progress)
{
// Validate block if not verified outside of ledger
if (result.verified != nano::signature_verification::valid)
{
result.code = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::process_result::bad_signature : nano::process_result::progress; // Is the signature valid (Malformed)
}
if (result.code == nano::process_result::progress)
{
debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block? (Harmless)
if (result.code == nano::process_result::progress)
{
nano::account_info info;
result.code = ledger.store.account_get (transaction, block_a.hashables.account, info) ? nano::process_result::progress : nano::process_result::fork; // Has this account already been opened? (Malicious)
if (result.code == nano::process_result::progress)
{
nano::pending_key key (block_a.hashables.account, block_a.hashables.source);
nano::pending_info pending;
result.code = ledger.store.pending_get (transaction, key, pending) ? nano::process_result::unreceivable : nano::process_result::progress; // Has this source already been received (Malformed)
if (result.code == nano::process_result::progress)
{
result.code = block_a.hashables.account == ledger.network_params.ledger.burn_account ? nano::process_result::opened_burn_account : nano::process_result::progress; // Is it burning 0 account? (Malicious)
if (result.code == nano::process_result::progress)
{
result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed)
if (result.code == nano::process_result::progress)
{
nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */);
result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed)
if (result.code == nano::process_result::progress)
{
#ifdef NDEBUG
if (ledger.store.block_exists (transaction, block_a.hashables.source))
{
nano::account_info source_info;
[[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, source_info));
debug_assert (!error);
}
#endif
ledger.store.pending_del (transaction, key);
block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
ledger.store.block_put (transaction, hash, block_a);
nano::account_info new_info (hash, block_a.representative (), hash, pending.amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0);
ledger.update_account (transaction, block_a.hashables.account, info, new_info);
ledger.cache.rep_weights.representation_add (block_a.representative (), pending.amount.number ());
ledger.store.frontier_put (transaction, hash, block_a.hashables.account);
result.previous_balance = 0;
ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open);
}
}
}
}
}
}
}
}
}
ledger_processor::ledger_processor (nano::ledger & ledger_a, nano::write_transaction const & transaction_a, nano::signature_verification verification_a) :
ledger (ledger_a),
transaction (transaction_a),
verification (verification_a)
{
result.verified = verification;
}
} // namespace
nano::ledger::ledger (nano::block_store & store_a, nano::stat & stat_a, nano::generate_cache const & generate_cache_a) :
store (store_a),
stats (stat_a),
check_bootstrap_weights (true)
{
if (!store.init_error ())
{
initialize (generate_cache_a);
}
}
void nano::ledger::initialize (nano::generate_cache const & generate_cache_a)
{
if (generate_cache_a.reps || generate_cache_a.account_count || generate_cache_a.block_count)
{
store.accounts_for_each_par (
[this](nano::read_transaction const & /*unused*/, nano::store_iterator<nano::account, nano::account_info> i, nano::store_iterator<nano::account, nano::account_info> n) {
uint64_t block_count_l{ 0 };
uint64_t account_count_l{ 0 };
decltype (this->cache.rep_weights) rep_weights_l;
for (; i != n; ++i)
{
nano::account_info const & info (i->second);
block_count_l += info.block_count;
++account_count_l;
rep_weights_l.representation_add (info.representative, info.balance.number ());
}
this->cache.block_count += block_count_l;
this->cache.account_count += account_count_l;
this->cache.rep_weights.copy_from (rep_weights_l);
});
}
if (generate_cache_a.cemented_count)
{
store.confirmation_height_for_each_par (
[this](nano::read_transaction const & /*unused*/, nano::store_iterator<nano::account, nano::confirmation_height_info> i, nano::store_iterator<nano::account, nano::confirmation_height_info> n) {
uint64_t cemented_count_l (0);
for (; i != n; ++i)
{
cemented_count_l += i->second.height;
}
this->cache.cemented_count += cemented_count_l;
});
}
auto transaction (store.tx_begin_read ());
cache.pruned_count = store.pruned_count (transaction);
}
// Balance for account containing hash
nano::uint128_t nano::ledger::balance (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const
{
return hash_a.is_zero () ? 0 : store.block_balance (transaction_a, hash_a);
}
nano::uint128_t nano::ledger::balance_safe (nano::transaction const & transaction_a, nano::block_hash const & hash_a, bool & error_a) const
{
nano::uint128_t result (0);
if (pruning && !hash_a.is_zero () && !store.block_exists (transaction_a, hash_a))
{
error_a = true;
result = 0;
}
else
{
result = balance (transaction_a, hash_a);
}
return result;
}
// Balance for an account by account number
nano::uint128_t nano::ledger::account_balance (nano::transaction const & transaction_a, nano::account const & account_a, bool only_confirmed_a)
{
nano::uint128_t result (0);
if (only_confirmed_a)
{
nano::confirmation_height_info info;
if (!store.confirmation_height_get (transaction_a, account_a, info))
{
result = balance (transaction_a, info.frontier);
}
}
else
{
nano::account_info info;
auto none (store.account_get (transaction_a, account_a, info));
if (!none)
{
result = info.balance.number ();
}
}
return result;
}
nano::uint128_t nano::ledger::account_pending (nano::transaction const & transaction_a, nano::account const & account_a, bool only_confirmed_a)
{
nano::uint128_t result (0);
nano::account end (account_a.number () + 1);
for (auto i (store.pending_begin (transaction_a, nano::pending_key (account_a, 0))), n (store.pending_begin (transaction_a, nano::pending_key (end, 0))); i != n; ++i)
{
nano::pending_info const & info (i->second);
if (only_confirmed_a)
{
if (block_confirmed (transaction_a, i->first.hash) || (pruning && store.pruned_exists (transaction_a, i->first.hash)))
{
result += info.amount.number ();
}
}
else
{
result += info.amount.number ();
}
}
return result;
}
nano::process_return nano::ledger::process (nano::write_transaction const & transaction_a, nano::block & block_a, nano::signature_verification verification)
{
debug_assert (!nano::work_validate_entry (block_a) || network_params.network.is_dev_network ());
ledger_processor processor (*this, transaction_a, verification);
block_a.visit (processor);
if (processor.result.code == nano::process_result::progress)
{
++cache.block_count;
}
return processor.result;
}
nano::block_hash nano::ledger::representative (nano::transaction const & transaction_a, nano::block_hash const & hash_a)
{
auto result (representative_calculated (transaction_a, hash_a));
debug_assert (result.is_zero () || store.block_exists (transaction_a, result));
return result;
}
nano::block_hash nano::ledger::representative_calculated (nano::transaction const & transaction_a, nano::block_hash const & hash_a)
{
representative_visitor visitor (transaction_a, store);
visitor.compute (hash_a);
return visitor.result;
}
bool nano::ledger::block_exists (nano::block_hash const & hash_a) const
{
return store.block_exists (store.tx_begin_read (), hash_a);
}
bool nano::ledger::block_or_pruned_exists (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const
{
return pruning ? store.block_or_pruned_exists (transaction_a, hash_a) : store.block_exists (transaction_a, hash_a);
}
bool nano::ledger::block_or_pruned_exists (nano::block_hash const & hash_a) const
{
return block_or_pruned_exists (store.tx_begin_read (), hash_a);
}
std::string nano::ledger::block_text (char const * hash_a)
{
return block_text (nano::block_hash (hash_a));
}
std::string nano::ledger::block_text (nano::block_hash const & hash_a)
{
std::string result;
auto transaction (store.tx_begin_read ());
auto block (store.block_get (transaction, hash_a));
if (block != nullptr)
{
block->serialize_json (result);
}
return result;
}
bool nano::ledger::is_send (nano::transaction const & transaction_a, nano::state_block const & block_a) const
{
/*
* if block_a does not have a sideband, then is_send()
* requires that the previous block exists in the database.
* This is because it must retrieve the balance of the previous block.
*/
debug_assert (block_a.has_sideband () || block_a.hashables.previous.is_zero () || store.block_exists (transaction_a, block_a.hashables.previous));
bool result (false);
if (block_a.has_sideband ())
{
result = block_a.sideband ().details.is_send;
}
else
{
nano::block_hash previous (block_a.hashables.previous);
if (!previous.is_zero ())
{
if (block_a.hashables.balance < balance (transaction_a, previous))
{
result = true;
}
}
}
return result;
}
nano::account const & nano::ledger::block_destination (nano::transaction const & transaction_a, nano::block const & block_a)
{
nano::send_block const * send_block (dynamic_cast<nano::send_block const *> (&block_a));
nano::state_block const * state_block (dynamic_cast<nano::state_block const *> (&block_a));
if (send_block != nullptr)
{
return send_block->hashables.destination;
}
else if (state_block != nullptr && is_send (transaction_a, *state_block))
{
return state_block->hashables.link.as_account ();
}
static nano::account result (0);
return result;
}
nano::block_hash nano::ledger::block_source (nano::transaction const & transaction_a, nano::block const & block_a)
{
/*
* block_source() requires that the previous block of the block
* passed in exist in the database. This is because it will try
* to check account balances to determine if it is a send block.
*/
debug_assert (block_a.previous ().is_zero () || store.block_exists (transaction_a, block_a.previous ()));
// If block_a.source () is nonzero, then we have our source.
// However, universal blocks will always return zero.
nano::block_hash result (block_a.source ());
nano::state_block const * state_block (dynamic_cast<nano::state_block const *> (&block_a));
if (state_block != nullptr && !is_send (transaction_a, *state_block))
{
result = state_block->hashables.link.as_block_hash ();
}
return result;
}
std::pair<nano::block_hash, nano::block_hash> nano::ledger::hash_root_random (nano::transaction const & transaction_a) const
{
nano::block_hash hash (0);
nano::root root (0);
if (!pruning)
{
auto block (store.block_random (transaction_a));
hash = block->hash ();
root = block->root ();
}
else
{
uint64_t count (cache.block_count);
release_assert (std::numeric_limits<CryptoPP::word32>::max () > count);
auto region = static_cast<size_t> (nano::random_pool::generate_word32 (0, static_cast<CryptoPP::word32> (count - 1)));
// Pruned cache cannot guarantee that pruned blocks are already commited
if (region < cache.pruned_count)
{
hash = store.pruned_random (transaction_a);
}
if (hash.is_zero ())
{
auto block (store.block_random (transaction_a));
hash = block->hash ();
root = block->root ();
}
}
return std::make_pair (hash, root.as_block_hash ());
}
// Vote weight of an account
nano::uint128_t nano::ledger::weight (nano::account const & account_a)
{
if (check_bootstrap_weights.load ())
{
if (cache.block_count < bootstrap_weight_max_blocks)
{
auto weight = bootstrap_weights.find (account_a);
if (weight != bootstrap_weights.end ())
{
return weight->second;
}
}
else
{
check_bootstrap_weights = false;
}
}
return cache.rep_weights.representation_get (account_a);
}
// Rollback blocks until `block_a' doesn't exist or it tries to penetrate the confirmation height
bool nano::ledger::rollback (nano::write_transaction const & transaction_a, nano::block_hash const & block_a, std::vector<std::shared_ptr<nano::block>> & list_a)
{
debug_assert (store.block_exists (transaction_a, block_a));
auto account_l (account (transaction_a, block_a));
auto block_account_height (store.block_account_height (transaction_a, block_a));
rollback_visitor rollback (transaction_a, *this, list_a);
nano::account_info account_info;
auto error (false);
while (!error && store.block_exists (transaction_a, block_a))
{
nano::confirmation_height_info confirmation_height_info;
store.confirmation_height_get (transaction_a, account_l, confirmation_height_info);
if (block_account_height > confirmation_height_info.height)
{
auto latest_error = store.account_get (transaction_a, account_l, account_info);
debug_assert (!latest_error);
auto block (store.block_get (transaction_a, account_info.head));
list_a.push_back (block);
block->visit (rollback);
error = rollback.error;
if (!error)
{
--cache.block_count;
}
}
else
{
error = true;
}
}
return error;
}
bool nano::ledger::rollback (nano::write_transaction const & transaction_a, nano::block_hash const & block_a)
{
std::vector<std::shared_ptr<nano::block>> rollback_list;
return rollback (transaction_a, block_a, rollback_list);
}
// Return account containing hash
nano::account nano::ledger::account (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const
{
return store.block_account (transaction_a, hash_a);
}
nano::account nano::ledger::account_safe (nano::transaction const & transaction_a, nano::block_hash const & hash_a, bool & error_a) const
{
if (!pruning)
{
return store.block_account (transaction_a, hash_a);
}
else
{
auto block (store.block_get (transaction_a, hash_a));
if (block != nullptr)
{
return store.block_account_calculated (*block);
}
else
{
error_a = true;
return 0;
}
}
}
// Return amount decrease or increase for block
nano::uint128_t nano::ledger::amount (nano::transaction const & transaction_a, nano::account const & account_a)
{
release_assert (account_a == network_params.ledger.genesis_account);
return network_params.ledger.genesis_amount;
}
nano::uint128_t nano::ledger::amount (nano::transaction const & transaction_a, nano::block_hash const & hash_a)
{
auto block (store.block_get (transaction_a, hash_a));
auto block_balance (balance (transaction_a, hash_a));
auto previous_balance (balance (transaction_a, block->previous ()));
return block_balance > previous_balance ? block_balance - previous_balance : previous_balance - block_balance;
}
nano::uint128_t nano::ledger::amount_safe (nano::transaction const & transaction_a, nano::block_hash const & hash_a, bool & error_a) const
{
auto block (store.block_get (transaction_a, hash_a));
debug_assert (block);
auto block_balance (balance (transaction_a, hash_a));
auto previous_balance (balance_safe (transaction_a, block->previous (), error_a));
return error_a ? 0 : block_balance > previous_balance ? block_balance - previous_balance : previous_balance - block_balance;
}
// Return latest block for account
nano::block_hash nano::ledger::latest (nano::transaction const & transaction_a, nano::account const & account_a)
{
nano::account_info info;
auto latest_error (store.account_get (transaction_a, account_a, info));
return latest_error ? 0 : info.head;
}
// Return latest root for account, account number if there are no blocks for this account.
nano::root nano::ledger::latest_root (nano::transaction const & transaction_a, nano::account const & account_a)
{
nano::account_info info;
if (store.account_get (transaction_a, account_a, info))
{
return account_a;
}
else
{
return info.head;
}
}
void nano::ledger::dump_account_chain (nano::account const & account_a, std::ostream & stream)
{
auto transaction (store.tx_begin_read ());
auto hash (latest (transaction, account_a));
while (!hash.is_zero ())
{
auto block (store.block_get (transaction, hash));
debug_assert (block != nullptr);
stream << hash.to_string () << std::endl;
hash = block->previous ();
}
}
bool nano::ledger::could_fit (nano::transaction const & transaction_a, nano::block const & block_a) const
{
auto dependencies (dependent_blocks (transaction_a, block_a));
return std::all_of (dependencies.begin (), dependencies.end (), [this, &transaction_a](nano::block_hash const & hash_a) {
return hash_a.is_zero () || store.block_exists (transaction_a, hash_a);
});
}
bool nano::ledger::dependents_confirmed (nano::transaction const & transaction_a, nano::block const & block_a) const
{
auto dependencies (dependent_blocks (transaction_a, block_a));
return std::all_of (dependencies.begin (), dependencies.end (), [this, &transaction_a](nano::block_hash const & hash_a) {
auto result (hash_a.is_zero ());
if (!result)
{
result = block_confirmed (transaction_a, hash_a);
}
return result;
});
}
bool nano::ledger::is_epoch_link (nano::link const & link_a) const
{
return network_params.ledger.epochs.is_epoch_link (link_a);
}
class dependent_block_visitor : public nano::block_visitor
{
public:
dependent_block_visitor (nano::ledger const & ledger_a, nano::transaction const & transaction_a) :
ledger (ledger_a),
transaction (transaction_a),
result ({ 0, 0 })
{
}
void send_block (nano::send_block const & block_a) override
{
result[0] = block_a.previous ();
}
void receive_block (nano::receive_block const & block_a) override
{
result[0] = block_a.previous ();
result[1] = block_a.source ();
}
void open_block (nano::open_block const & block_a) override
{
if (block_a.source () != ledger.network_params.ledger.genesis_account)
{
result[0] = block_a.source ();
}
}
void change_block (nano::change_block const & block_a) override
{
result[0] = block_a.previous ();
}
void state_block (nano::state_block const & block_a) override
{
result[0] = block_a.hashables.previous;
result[1] = block_a.hashables.link.as_block_hash ();
// ledger.is_send will check the sideband first, if block_a has a loaded sideband the check that previous block exists can be skipped
if (ledger.is_epoch_link (block_a.hashables.link) || ((block_a.has_sideband () || ledger.store.block_exists (transaction, block_a.hashables.previous)) && ledger.is_send (transaction, block_a)))
{
result[1].clear ();
}
}
nano::ledger const & ledger;
nano::transaction const & transaction;
std::array<nano::block_hash, 2> result;
};
std::array<nano::block_hash, 2> nano::ledger::dependent_blocks (nano::transaction const & transaction_a, nano::block const & block_a) const
{
dependent_block_visitor visitor (*this, transaction_a);
block_a.visit (visitor);
return visitor.result;
}
nano::account const & nano::ledger::epoch_signer (nano::link const & link_a) const
{
return network_params.ledger.epochs.signer (network_params.ledger.epochs.epoch (link_a));
}
nano::link const & nano::ledger::epoch_link (nano::epoch epoch_a) const
{
return network_params.ledger.epochs.link (epoch_a);
}
void nano::ledger::update_account (nano::write_transaction const & transaction_a, nano::account const & account_a, nano::account_info const & old_a, nano::account_info const & new_a)
{
if (!new_a.head.is_zero ())
{
if (old_a.head.is_zero () && new_a.open_block == new_a.head)
{
++cache.account_count;
}
if (!old_a.head.is_zero () && old_a.epoch () != new_a.epoch ())
{
// store.account_put won't erase existing entries if they're in different tables
store.account_del (transaction_a, account_a);
}
store.account_put (transaction_a, account_a, new_a);
}
else
{
debug_assert (!store.confirmation_height_exists (transaction_a, account_a));
store.account_del (transaction_a, account_a);
debug_assert (cache.account_count > 0);
--cache.account_count;
}
}
std::shared_ptr<nano::block> nano::ledger::successor (nano::transaction const & transaction_a, nano::qualified_root const & root_a)
{
nano::block_hash successor (0);
auto get_from_previous = false;
if (root_a.previous ().is_zero ())
{
nano::account_info info;
if (!store.account_get (transaction_a, root_a.root ().as_account (), info))
{
successor = info.open_block;
}
else
{
get_from_previous = true;
}
}
else
{
get_from_previous = true;
}
if (get_from_previous)
{
successor = store.block_successor (transaction_a, root_a.previous ());
}
std::shared_ptr<nano::block> result;
if (!successor.is_zero ())
{
result = store.block_get (transaction_a, successor);
}
debug_assert (successor.is_zero () || result != nullptr);
return result;
}
std::shared_ptr<nano::block> nano::ledger::forked_block (nano::transaction const & transaction_a, nano::block const & block_a)
{
debug_assert (!store.block_exists (transaction_a, block_a.hash ()));
auto root (block_a.root ());
debug_assert (store.block_exists (transaction_a, root.as_block_hash ()) || store.account_exists (transaction_a, root.as_account ()));
auto result (store.block_get (transaction_a, store.block_successor (transaction_a, root.as_block_hash ())));
if (result == nullptr)
{
nano::account_info info;
auto error (store.account_get (transaction_a, root.as_account (), info));
(void)error;
debug_assert (!error);
result = store.block_get (transaction_a, info.open_block);
debug_assert (result != nullptr);
}
return result;
}
bool nano::ledger::block_confirmed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const
{
auto confirmed (false);
auto block = store.block_get (transaction_a, hash_a);
if (block)
{
nano::confirmation_height_info confirmation_height_info;
store.confirmation_height_get (transaction_a, block->account ().is_zero () ? block->sideband ().account : block->account (), confirmation_height_info);
confirmed = (confirmation_height_info.height >= block->sideband ().height);
}
return confirmed;
}
uint64_t nano::ledger::pruning_action (nano::write_transaction & transaction_a, nano::block_hash const & hash_a, uint64_t const batch_size_a)
{
uint64_t pruned_count (0);
nano::block_hash hash (hash_a);
while (!hash.is_zero () && hash != network_params.ledger.genesis_hash)
{
auto block (store.block_get (transaction_a, hash));
if (block != nullptr)
{
store.block_del (transaction_a, hash);
store.pruned_put (transaction_a, hash);
hash = block->previous ();
++pruned_count;
++cache.pruned_count;
if (pruned_count % batch_size_a == 0)
{
transaction_a.commit ();
transaction_a.renew ();
}
}
else if (store.pruned_exists (transaction_a, hash))
{
hash = 0;
}
else
{
hash = 0;
release_assert (false && "Error finding block for pruning");
}
}
return pruned_count;
}
std::multimap<uint64_t, nano::uncemented_info, std::greater<>> nano::ledger::unconfirmed_frontiers () const
{
nano::locked<std::multimap<uint64_t, nano::uncemented_info, std::greater<>>> result;
using result_t = decltype (result)::value_type;
store.accounts_for_each_par ([this, &result](nano::read_transaction const & transaction_a, nano::store_iterator<nano::account, nano::account_info> i, nano::store_iterator<nano::account, nano::account_info> n) {
result_t unconfirmed_frontiers_l;
for (; i != n; ++i)
{
auto const & account (i->first);
auto const & account_info (i->second);
nano::confirmation_height_info conf_height_info;
this->store.confirmation_height_get (transaction_a, account, conf_height_info);
if (account_info.block_count != conf_height_info.height)
{
// Always output as no confirmation height has been set on the account yet
auto height_delta = account_info.block_count - conf_height_info.height;
auto const & frontier = account_info.head;
auto const & cemented_frontier = conf_height_info.frontier;
unconfirmed_frontiers_l.emplace (std::piecewise_construct, std::forward_as_tuple (height_delta), std::forward_as_tuple (cemented_frontier, frontier, i->first));
}
}
// Merge results
auto result_locked = result.lock ();
result_locked->insert (unconfirmed_frontiers_l.begin (), unconfirmed_frontiers_l.end ());
});
return result;
}
// A precondition is that the store is an LMDB store
bool nano::ledger::migrate_lmdb_to_rocksdb (boost::filesystem::path const & data_path_a) const
{
boost::system::error_code error_chmod;
nano::set_secure_perm_directory (data_path_a, error_chmod);
auto rockdb_data_path = data_path_a / "rocksdb";
boost::filesystem::remove_all (rockdb_data_path);
nano::logger_mt logger;
auto error (false);
// Open rocksdb database
nano::rocksdb_config rocksdb_config;
rocksdb_config.enable = true;
auto rocksdb_store = nano::make_store (logger, data_path_a, false, true, rocksdb_config);
if (!rocksdb_store->init_error ())
{
store.blocks_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::blocks }));
std::vector<uint8_t> vector;
{
nano::vectorstream stream (vector);
nano::serialize_block (stream, *i->second.block);
i->second.sideband.serialize (stream, i->second.block->type ());
}
rocksdb_store->block_raw_put (rocksdb_transaction, vector, i->first);
}
});
store.unchecked_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::unchecked }));
rocksdb_store->unchecked_put (rocksdb_transaction, i->first, i->second);
}
});
store.pending_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::pending }));
rocksdb_store->pending_put (rocksdb_transaction, i->first, i->second);
}
});
store.confirmation_height_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::confirmation_height }));
rocksdb_store->confirmation_height_put (rocksdb_transaction, i->first, i->second);
}
});
store.accounts_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::accounts }));
rocksdb_store->account_put (rocksdb_transaction, i->first, i->second);
}
});
store.frontiers_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::frontiers }));
rocksdb_store->frontier_put (rocksdb_transaction, i->first, i->second);
}
});
store.pruned_for_each_par (
[&rocksdb_store](nano::read_transaction const & /*unused*/, auto i, auto n) {
for (; i != n; ++i)
{
auto rocksdb_transaction (rocksdb_store->tx_begin_write ({}, { nano::tables::pruned }));
rocksdb_store->pruned_put (rocksdb_transaction, i->first);
}
});
auto lmdb_transaction (store.tx_begin_read ());
auto version = store.version_get (lmdb_transaction);
auto rocksdb_transaction (rocksdb_store->tx_begin_write ());
rocksdb_store->version_put (rocksdb_transaction, version);
for (auto i (store.online_weight_begin (lmdb_transaction)), n (store.online_weight_end ()); i != n; ++i)
{
rocksdb_store->online_weight_put (rocksdb_transaction, i->first, i->second);
}
for (auto i (store.peers_begin (lmdb_transaction)), n (store.peers_end ()); i != n; ++i)
{
rocksdb_store->peer_put (rocksdb_transaction, i->first);
}
// Compare counts
error |= store.unchecked_count (lmdb_transaction) != rocksdb_store->unchecked_count (rocksdb_transaction);
error |= store.peer_count (lmdb_transaction) != rocksdb_store->peer_count (rocksdb_transaction);
error |= store.pruned_count (lmdb_transaction) != rocksdb_store->pruned_count (rocksdb_transaction);
error |= store.online_weight_count (lmdb_transaction) != rocksdb_store->online_weight_count (rocksdb_transaction);
error |= store.version_get (lmdb_transaction) != rocksdb_store->version_get (rocksdb_transaction);
// For large tables a random key is used instead and makes sure it exists
auto random_block (store.block_random (lmdb_transaction));
error |= rocksdb_store->block_get (rocksdb_transaction, random_block->hash ()) == nullptr;
auto account = random_block->account ().is_zero () ? random_block->sideband ().account : random_block->account ();
nano::account_info account_info;
error |= rocksdb_store->account_get (rocksdb_transaction, account, account_info);
// If confirmation height exists in the lmdb ledger for this account it should exist in the rocksdb ledger
nano::confirmation_height_info confirmation_height_info;
if (!store.confirmation_height_get (lmdb_transaction, account, confirmation_height_info))
{
error |= rocksdb_store->confirmation_height_get (rocksdb_transaction, account, confirmation_height_info);
}
}
else
{
error = true;
}
return error;
}
nano::uncemented_info::uncemented_info (nano::block_hash const & cemented_frontier, nano::block_hash const & frontier, nano::account const & account) :
cemented_frontier (cemented_frontier), frontier (frontier), account (account)
{
}
std::unique_ptr<nano::container_info_component> nano::collect_container_info (ledger & ledger, std::string const & name)
{
auto count = ledger.bootstrap_weights_size.load ();
auto sizeof_element = sizeof (decltype (ledger.bootstrap_weights)::value_type);
auto composite = std::make_unique<container_info_composite> (name);
composite->add_component (std::make_unique<container_info_leaf> (container_info{ "bootstrap_weights", count, sizeof_element }));
composite->add_component (collect_container_info (ledger.cache.rep_weights, "rep_weights"));
return composite;
}