[Pruning] Accept pruned-related blocks in blocks processing & rollback (#2974)

* [Pruning] Accept pruned-related blocks in blocks processing & rollback

* Fix open block pruned source rollback & add test (Guilherme review)
* Add state block comment
* Fix processing open/receive blocks with pruned source after rollback
* Use C++17 [[maybe_unused]]
This commit is contained in:
Sergey Kroshnin 2020-10-03 04:50:33 +03:00 committed by GitHub
commit 27c1365d5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 213 additions and 29 deletions

View file

@ -3420,6 +3420,18 @@ TEST (ledger, pruning_action)
ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ()));
ASSERT_TRUE (store->block_exists (transaction, genesis.hash ()));
ASSERT_TRUE (store->block_exists (transaction, send2.hash ()));
// Receiving pruned block
nano::state_block receive1 (nano::genesis_account, send2.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code);
ASSERT_TRUE (store->block_exists (transaction, receive1.hash ()));
auto receive1_stored (store->block_get (transaction, receive1.hash ()));
ASSERT_NE (nullptr, receive1_stored);
ASSERT_EQ (receive1, *receive1_stored);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
ASSERT_EQ (4, receive1_stored->sideband ().height);
ASSERT_FALSE (receive1_stored->sideband ().details.is_send);
ASSERT_TRUE (receive1_stored->sideband ().details.is_receive);
ASSERT_FALSE (receive1_stored->sideband ().details.is_epoch);
// Middle block pruning
ASSERT_TRUE (store->block_exists (transaction, send2.hash ()));
ASSERT_EQ (1, ledger.pruning_action (transaction, send2.hash (), 1));
@ -3467,6 +3479,166 @@ TEST (ledger, pruning_large_chain)
ASSERT_EQ (1, store->block_count (transaction)); // Genesis
}
TEST (ledger, pruning_source_rollback)
{
nano::logger_mt logger;
auto store = nano::make_store (logger, nano::unique_path ());
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats);
ledger.pruning = true;
nano::genesis genesis;
auto transaction (store->tx_begin_write ());
store->initialize (transaction, genesis, ledger.cache);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::state_block epoch1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_1), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch1).code);
nano::state_block send1 (nano::genesis_account, epoch1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (epoch1.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code);
ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
nano::state_block send2 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send2).code);
ASSERT_TRUE (store->block_exists (transaction, send2.hash ()));
// Pruning action
ASSERT_EQ (2, ledger.pruning_action (transaction, send1.hash (), 1));
ASSERT_FALSE (store->block_exists (transaction, send1.hash ()));
ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ()));
ASSERT_FALSE (store->block_exists (transaction, epoch1.hash ()));
ASSERT_TRUE (store->pruned_exists (transaction, epoch1.hash ()));
ASSERT_TRUE (store->block_exists (transaction, genesis.hash ()));
nano::pending_info info;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info));
ASSERT_EQ (nano::genesis_account, info.source);
ASSERT_EQ (nano::Gxrb_ratio, info.amount.number ());
ASSERT_EQ (nano::epoch::epoch_1, info.epoch);
// Receiving pruned block
nano::state_block receive1 (nano::genesis_account, send2.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (5, ledger.cache.block_count);
// Rollback receive block
ASSERT_FALSE (ledger.rollback (transaction, receive1.hash ()));
nano::pending_info info2;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info2));
ASSERT_NE (nano::genesis_account, info2.source); // Tradeoff to not store pruned blocks accounts
ASSERT_EQ (nano::Gxrb_ratio, info2.amount.number ());
ASSERT_EQ (nano::epoch::epoch_1, info2.epoch);
// Process receive block again
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (5, ledger.cache.block_count);
}
TEST (ledger, pruning_source_rollback_legacy)
{
nano::logger_mt logger;
auto store = nano::make_store (logger, nano::unique_path ());
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats);
ledger.pruning = true;
nano::genesis genesis;
auto transaction (store->tx_begin_write ());
store->initialize (transaction, genesis, ledger.cache);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::send_block send1 (genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code);
ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
nano::keypair key1;
nano::send_block send2 (send1.hash (), key1.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send2).code);
ASSERT_TRUE (store->block_exists (transaction, send2.hash ()));
ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ())));
nano::send_block send3 (send2.hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send3).code);
ASSERT_TRUE (store->block_exists (transaction, send3.hash ()));
ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send3.hash ())));
// Pruning action
ASSERT_EQ (2, ledger.pruning_action (transaction, send2.hash (), 1));
ASSERT_FALSE (store->block_exists (transaction, send2.hash ()));
ASSERT_TRUE (store->pruned_exists (transaction, send2.hash ()));
ASSERT_FALSE (store->block_exists (transaction, send1.hash ()));
ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ()));
ASSERT_TRUE (store->block_exists (transaction, genesis.hash ()));
nano::pending_info info1;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info1));
ASSERT_EQ (nano::genesis_account, info1.source);
ASSERT_EQ (nano::Gxrb_ratio, info1.amount.number ());
ASSERT_EQ (nano::epoch::epoch_0, info1.epoch);
nano::pending_info info2;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (key1.pub, send2.hash ()), info2));
ASSERT_EQ (nano::genesis_account, info2.source);
ASSERT_EQ (nano::Gxrb_ratio, info2.amount.number ());
ASSERT_EQ (nano::epoch::epoch_0, info2.epoch);
// Receiving pruned block
nano::receive_block receive1 (send3.hash (), send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send3.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (5, ledger.cache.block_count);
// Rollback receive block
ASSERT_FALSE (ledger.rollback (transaction, receive1.hash ()));
nano::pending_info info3;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info3));
ASSERT_NE (nano::genesis_account, info3.source); // Tradeoff to not store pruned blocks accounts
ASSERT_EQ (nano::Gxrb_ratio, info3.amount.number ());
ASSERT_EQ (nano::epoch::epoch_0, info3.epoch);
// Process receive block again
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (5, ledger.cache.block_count);
// Receiving pruned block (open)
nano::open_block open1 (send2.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *pool.generate (key1.pub));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (6, ledger.cache.block_count);
// Rollback open block
ASSERT_FALSE (ledger.rollback (transaction, open1.hash ()));
nano::pending_info info4;
ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (key1.pub, send2.hash ()), info4));
ASSERT_NE (nano::genesis_account, info4.source); // Tradeoff to not store pruned blocks accounts
ASSERT_EQ (nano::Gxrb_ratio, info4.amount.number ());
ASSERT_EQ (nano::epoch::epoch_0, info4.epoch);
// Process open block again
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code);
ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ())));
ASSERT_EQ (2, ledger.cache.pruned_count);
ASSERT_EQ (6, ledger.cache.block_count);
}
TEST (ledger, pruning_process_error)
{
nano::logger_mt logger;
auto store = nano::make_store (logger, nano::unique_path ());
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats);
ledger.pruning = true;
nano::genesis genesis;
auto transaction (store->tx_begin_write ());
store->initialize (transaction, genesis, ledger.cache);
nano::work_pool pool (std::numeric_limits<unsigned>::max ());
nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ()));
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code);
ASSERT_EQ (0, ledger.cache.pruned_count);
ASSERT_EQ (2, ledger.cache.block_count);
// Pruning action for latest block (not valid action)
ASSERT_EQ (1, ledger.pruning_action (transaction, send1.hash (), 1));
ASSERT_FALSE (store->block_exists (transaction, send1.hash ()));
ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ()));
// Attempt to process pruned block again
ASSERT_EQ (nano::process_result::old, ledger.process (transaction, send1).code);
// Attept to process new block after pruned
nano::state_block send2 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ()));
ASSERT_EQ (nano::process_result::gap_previous, ledger.process (transaction, send2).code);
ASSERT_EQ (1, ledger.cache.pruned_count);
ASSERT_EQ (2, ledger.cache.block_count);
}
TEST (ledger, pruning_legacy_blocks)
{
nano::logger_mt logger;
@ -3598,4 +3770,4 @@ TEST (ledger, hash_root_random)
done = (root_hash.first == send2.hash ()) && (root_hash.second == send2.root ().as_block_hash ());
ASSERT_LE (iteration, 1000);
}
}
}

View file

@ -35,8 +35,7 @@ public:
if (!error)
{
nano::account_info info;
auto error (ledger.store.account_get (transaction, pending.source, info));
(void)error;
[[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 ());
@ -52,12 +51,13 @@ public:
void receive_block (nano::receive_block const & block_a) override
{
auto hash (block_a.hash ());
auto amount (ledger.amount (transaction, block_a.hashables.source));
auto amount (ledger.amount (transaction, hash));
auto destination_account (ledger.account (transaction, hash));
auto source_account (ledger.account (transaction, block_a.hashables.source));
// 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;
auto error (ledger.store.account_get (transaction, destination_account, info));
(void)error;
[[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);
@ -72,9 +72,11 @@ public:
void open_block (nano::open_block const & block_a) override
{
auto hash (block_a.hash ());
auto amount (ledger.amount (transaction, block_a.hashables.source));
auto amount (ledger.amount (transaction, hash));
auto destination_account (ledger.account (transaction, hash));
auto source_account (ledger.account (transaction, block_a.hashables.source));
// 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);
@ -89,8 +91,7 @@ public:
auto rep_block (ledger.representative (transaction, block_a.hashables.previous));
auto account (ledger.account (transaction, block_a.hashables.previous));
nano::account_info info;
auto error (ledger.store.account_get (transaction, account, info));
(void)error;
[[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);
@ -145,7 +146,10 @@ public:
}
else if (!block_a.hashables.link.is_zero () && !ledger.is_epoch_link (block_a.hashables.link))
{
nano::pending_info pending_info (ledger.account (transaction, block_a.hashables.link.as_block_hash ()), block_a.hashables.balance.number () - balance, block_a.sideband ().source_epoch);
// 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);
}
@ -260,7 +264,7 @@ void ledger_processor::state_block (nano::state_block & block_a)
void ledger_processor::state_block_impl (nano::state_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -318,7 +322,7 @@ void ledger_processor::state_block_impl (nano::state_block & block_a)
{
if (!block_a.hashables.link.is_zero ())
{
result.code = ledger.store.block_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)
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 ());
@ -387,7 +391,7 @@ void ledger_processor::state_block_impl (nano::state_block & block_a)
void ledger_processor::epoch_block_impl (nano::state_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -477,7 +481,7 @@ void ledger_processor::epoch_block_impl (nano::state_block & block_a)
void ledger_processor::change_block (nano::change_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -531,7 +535,7 @@ void ledger_processor::change_block (nano::change_block & block_a)
void ledger_processor::send_block (nano::send_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -590,7 +594,7 @@ void ledger_processor::send_block (nano::send_block & block_a)
void ledger_processor::receive_block (nano::receive_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -614,7 +618,7 @@ void ledger_processor::receive_block (nano::receive_block & block_a)
{
debug_assert (!validate_message (account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless)
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;
@ -635,10 +639,14 @@ void ledger_processor::receive_block (nano::receive_block & block_a)
if (result.code == nano::process_result::progress)
{
auto new_balance (info.balance.number () + pending.amount.number ());
nano::account_info source_info;
auto error (ledger.store.account_get (transaction, pending.source, source_info));
(void)error;
debug_assert (!error);
#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);
@ -668,7 +676,7 @@ void ledger_processor::receive_block (nano::receive_block & block_a)
void ledger_processor::open_block (nano::open_block & block_a)
{
auto hash (block_a.hash ());
auto existing (ledger.store.block_exists (transaction, 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)
{
@ -681,7 +689,7 @@ void ledger_processor::open_block (nano::open_block & block_a)
{
debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature));
result.verified = nano::signature_verification::valid;
result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block? (Harmless)
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;
@ -703,10 +711,14 @@ void ledger_processor::open_block (nano::open_block & block_a)
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)
{
nano::account_info source_info;
auto error (ledger.store.account_get (transaction, pending.source, source_info));
(void)error;
debug_assert (!error);
#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);