diff --git a/rai/node/lmdb.cpp b/rai/node/lmdb.cpp index 5dfdfe45..dd995cac 100644 --- a/rai/node/lmdb.cpp +++ b/rai/node/lmdb.cpp @@ -1076,9 +1076,8 @@ void rai::mdb_store::clear (MDB_dbi db_a) rai::uint128_t rai::mdb_store::block_balance (rai::transaction const & transaction_a, rai::block_hash const & hash_a) { - balance_visitor visitor (transaction_a, *this); - visitor.compute (hash_a); - return visitor.balance; + summation_visitor visitor (transaction_a, *this); + return visitor.compute_balance (hash_a); } rai::epoch rai::mdb_store::block_version (rai::transaction const & transaction_a, rai::block_hash const & hash_a) diff --git a/rai/secure/blockstore.cpp b/rai/secure/blockstore.cpp index 41749f16..a4efaab3 100644 --- a/rai/secure/blockstore.cpp +++ b/rai/secure/blockstore.cpp @@ -1,171 +1,246 @@ -#include #include #include #include #include -rai::amount_visitor::amount_visitor (rai::transaction const & transaction_a, rai::block_store & store_a) : +rai::summation_visitor::summation_visitor (rai::transaction const & transaction_a, rai::block_store & store_a) : transaction (transaction_a), -store (store_a), -current_amount (0), -current_balance (0), -amount (0) +store (store_a) { } -void rai::amount_visitor::send_block (rai::send_block const & block_a) +void rai::summation_visitor::send_block (rai::send_block const & block_a) { - current_balance = block_a.hashables.previous; - amount = block_a.hashables.balance.number (); - current_amount = 0; -} - -void rai::amount_visitor::receive_block (rai::receive_block const & block_a) -{ - current_amount = block_a.hashables.source; -} - -void rai::amount_visitor::open_block (rai::open_block const & block_a) -{ - if (block_a.hashables.source != rai::genesis_account) + assert (current->type != summation_type::invalid && current != nullptr); + if (current->type == summation_type::amount) { - current_amount = block_a.hashables.source; + sum_set (block_a.hashables.balance.number ()); + current->balance_hash = block_a.hashables.previous; + current->amount_hash = 0; } else { - amount = rai::genesis_amount; - current_amount = 0; + sum_add (block_a.hashables.balance.number ()); + current->balance_hash = 0; } } -void rai::amount_visitor::state_block (rai::state_block const & block_a) +void rai::summation_visitor::state_block (rai::state_block const & block_a) { - current_balance = block_a.hashables.previous; - amount = block_a.hashables.balance.number (); - current_amount = 0; -} - -void rai::amount_visitor::change_block (rai::change_block const & block_a) -{ - amount = 0; - current_amount = 0; -} - -void rai::amount_visitor::compute (rai::block_hash const & block_hash) -{ - current_amount = block_hash; - while (!current_amount.is_zero () || !current_balance.is_zero ()) + assert (current->type != summation_type::invalid && current != nullptr); + sum_set (block_a.hashables.balance.number ()); + if (current->type == summation_type::amount) { - if (!current_amount.is_zero ()) + current->balance_hash = block_a.hashables.previous; + current->amount_hash = 0; + } + else + { + current->balance_hash = 0; + } +} + +void rai::summation_visitor::receive_block (rai::receive_block const & block_a) +{ + assert (current->type != summation_type::invalid && current != nullptr); + if (current->type == summation_type::amount) + { + current->amount_hash = block_a.hashables.source; + } + else + { + rai::block_info block_info; + if (!store.block_info_get (transaction, block_a.hash (), block_info)) { - auto block (store.block_get (transaction, current_amount)); - if (block != nullptr) + sum_add (block_info.balance.number ()); + current->balance_hash = 0; + } + else + { + current->amount_hash = block_a.hashables.source; + current->balance_hash = block_a.hashables.previous; + } + } +} + +void rai::summation_visitor::open_block (rai::open_block const & block_a) +{ + assert (current->type != summation_type::invalid && current != nullptr); + if (current->type == summation_type::amount) + { + if (block_a.hashables.source != rai::genesis_account) + { + current->amount_hash = block_a.hashables.source; + } + else + { + sum_set (rai::genesis_amount); + current->amount_hash = 0; + } + } + else + { + current->amount_hash = block_a.hashables.source; + current->balance_hash = 0; + } +} + +void rai::summation_visitor::change_block (rai::change_block const & block_a) +{ + assert (current->type != summation_type::invalid && current != nullptr); + if (current->type == summation_type::amount) + { + sum_set (0); + current->amount_hash = 0; + } + else + { + rai::block_info block_info; + if (!store.block_info_get (transaction, block_a.hash (), block_info)) + { + sum_add (block_info.balance.number ()); + current->balance_hash = 0; + } + else + { + current->balance_hash = block_a.hashables.previous; + } + } +} + +rai::summation_visitor::frame rai::summation_visitor::push (rai::summation_visitor::summation_type type_a, rai::block_hash const & hash_a) +{ + frames.emplace (type_a, type_a == summation_type::balance ? hash_a : 0, type_a == summation_type::amount ? hash_a : 0); + return frames.top (); +} + +void rai::summation_visitor::sum_add (rai::uint128_t addend_a) +{ + current->sum += addend_a; + result = current->sum; +} + +void rai::summation_visitor::sum_set (rai::uint128_t value_a) +{ + current->sum = value_a; + result = current->sum; +} + +rai::uint128_t rai::summation_visitor::compute_internal (rai::summation_visitor::summation_type type_a, rai::block_hash const & hash_a) +{ + push (type_a, hash_a); + + /* + Invocation loop representing balance and amount computations calling each other. + This is usually better done by recursion or something like boost::coroutine2, but + segmented stacks are not supported on all platforms so we do it manually to avoid + stack overflow (the mutual calls are not tail-recursive so we cannot rely on the + compiler optimizing that into a loop, though a future alternative is to do a + CPS-style implementation to enforce tail calls.) + */ + while (frames.size () > 0) + { + current = &frames.top (); + assert (current->type != summation_type::invalid && current != nullptr); + + if (current->type == summation_type::balance) + { + if (current->awaiting_result) { - block->visit (*this); + sum_add (current->incoming_result); + current->awaiting_result = false; } - else + + while (!current->awaiting_result && (!current->balance_hash.is_zero () || !current->amount_hash.is_zero ())) { - if (block_hash == rai::genesis_account) + if (!current->amount_hash.is_zero ()) { - amount = std::numeric_limits::max (); - current_amount = 0; + // Compute amount + current->awaiting_result = true; + push (summation_type::amount, current->amount_hash); + current->amount_hash = 0; } else { - assert (false); - amount = 0; - current_amount = 0; + auto block (store.block_get (transaction, current->balance_hash)); + assert (block != nullptr); + block->visit (*this); } } + + epilogue (); } - else + else if (current->type == summation_type::amount) { - balance_visitor prev (transaction, store); - prev.compute (current_balance); - amount = amount < prev.balance ? prev.balance - amount : amount - prev.balance; - current_balance = 0; + if (current->awaiting_result) + { + sum_set (current->sum < current->incoming_result ? current->incoming_result - current->sum : current->sum - current->incoming_result); + current->awaiting_result = false; + } + + while (!current->awaiting_result && (!current->amount_hash.is_zero () || !current->balance_hash.is_zero ())) + { + if (!current->amount_hash.is_zero ()) + { + auto block (store.block_get (transaction, current->amount_hash)); + if (block != nullptr) + { + block->visit (*this); + } + else + { + if (current->amount_hash == rai::genesis_account) + { + sum_set (std::numeric_limits::max ()); + current->amount_hash = 0; + } + else + { + assert (false); + sum_set (0); + current->amount_hash = 0; + } + } + } + else + { + // Compute balance + current->awaiting_result = true; + push (summation_type::balance, current->balance_hash); + current->balance_hash = 0; + } + } + + epilogue (); } } + + return result; } -rai::balance_visitor::balance_visitor (rai::transaction const & transaction_a, rai::block_store & store_a) : -transaction (transaction_a), -store (store_a), -current_balance (0), -current_amount (0), -balance (0) +void rai::summation_visitor::epilogue () { -} - -void rai::balance_visitor::send_block (rai::send_block const & block_a) -{ - balance += block_a.hashables.balance.number (); - current_balance = 0; -} - -void rai::balance_visitor::receive_block (rai::receive_block const & block_a) -{ - rai::block_info block_info; - if (!store.block_info_get (transaction, block_a.hash (), block_info)) + if (!current->awaiting_result) { - balance += block_info.balance.number (); - current_balance = 0; - } - else - { - current_amount = block_a.hashables.source; - current_balance = block_a.hashables.previous; - } -} - -void rai::balance_visitor::open_block (rai::open_block const & block_a) -{ - current_amount = block_a.hashables.source; - current_balance = 0; -} - -void rai::balance_visitor::change_block (rai::change_block const & block_a) -{ - rai::block_info block_info; - if (!store.block_info_get (transaction, block_a.hash (), block_info)) - { - balance += block_info.balance.number (); - current_balance = 0; - } - else - { - current_balance = block_a.hashables.previous; - } -} - -void rai::balance_visitor::state_block (rai::state_block const & block_a) -{ - balance = block_a.hashables.balance.number (); - current_balance = 0; -} - -void rai::balance_visitor::compute (rai::block_hash const & block_hash) -{ - current_balance = block_hash; - while (!current_balance.is_zero () || !current_amount.is_zero ()) - { - if (!current_amount.is_zero ()) + frames.pop (); + if (frames.size () > 0) { - amount_visitor source (transaction, store); - source.compute (current_amount); - balance += source.amount; - current_amount = 0; - } - else - { - auto block (store.block_get (transaction, current_balance)); - assert (block != nullptr); - block->visit (*this); + frames.top ().incoming_result = current->sum; } } } +rai::uint128_t rai::summation_visitor::compute_amount (rai::block_hash const & block_hash) +{ + return compute_internal (summation_type::amount, block_hash); +} + +rai::uint128_t rai::summation_visitor::compute_balance (rai::block_hash const & block_hash) +{ + return compute_internal (summation_type::balance, block_hash); +} + rai::representative_visitor::representative_visitor (rai::transaction const & transaction_a, rai::block_store & store_a) : transaction (transaction_a), store (store_a), diff --git a/rai/secure/blockstore.hpp b/rai/secure/blockstore.hpp index 79a7c996..9fb87966 100644 --- a/rai/secure/blockstore.hpp +++ b/rai/secure/blockstore.hpp @@ -1,52 +1,82 @@ #pragma once #include +#include namespace rai { class transaction; class block_store; -/** - * Determine the balance as of this block - */ -class balance_visitor : public rai::block_visitor -{ -public: - balance_visitor (rai::transaction const &, rai::block_store &); - virtual ~balance_visitor () = default; - void compute (rai::block_hash const &); - void send_block (rai::send_block const &) override; - void receive_block (rai::receive_block const &) override; - void open_block (rai::open_block const &) override; - void change_block (rai::change_block const &) override; - void state_block (rai::state_block const &) override; - rai::transaction const & transaction; - rai::block_store & store; - rai::block_hash current_balance; - rai::block_hash current_amount; - rai::uint128_t balance; -}; /** - * Determine the amount delta resultant from this block + * Summation visitor for blocks, supporting amount and balance computations. These + * computations are mutually dependant. The natural solution is to use mutual recursion + * between balance and amount visitors, but this leads to very deep stacks. Hence, the + * summation visitor uses an iterative approach. */ -class amount_visitor : public rai::block_visitor +class summation_visitor : public rai::block_visitor { + enum summation_type + { + invalid = 0, + balance = 1, + amount = 2 + }; + + /** Represents an invocation frame */ + class frame + { + public: + frame (summation_type type_a, rai::block_hash balance_hash_a, rai::block_hash amount_hash_a) : + type (type_a), balance_hash (balance_hash_a), amount_hash (amount_hash_a) + { + } + + /** The summation type guides the block visitor handlers */ + summation_type type{ invalid }; + /** Accumulated balance or amount */ + rai::uint128_t sum{ 0 }; + /** The current balance hash */ + rai::block_hash balance_hash{ 0 }; + /** The current amount hash */ + rai::block_hash amount_hash{ 0 }; + /** If true, this frame is awaiting an invocation result */ + bool awaiting_result{ false }; + /** Set by the invoked frame, representing the return value */ + rai::uint128_t incoming_result{ 0 }; + }; + public: - amount_visitor (rai::transaction const &, rai::block_store &); - virtual ~amount_visitor () = default; - void compute (rai::block_hash const &); + summation_visitor (rai::transaction const &, rai::block_store &); + virtual ~summation_visitor () = default; + /** Computes the balance as of \p block_hash */ + rai::uint128_t compute_balance (rai::block_hash const & block_hash); + /** Computes the amount delta between \p block_hash and its predecessor */ + rai::uint128_t compute_amount (rai::block_hash const & block_hash); + +protected: + rai::transaction const & transaction; + rai::block_store & store; + + /** The final result */ + rai::uint128_t result{ 0 }; + /** The current invocation frame */ + frame * current{ nullptr }; + /** Invocation frames */ + std::stack frames; + /** Push a copy of \p hash of the given summation \p type */ + rai::summation_visitor::frame push (rai::summation_visitor::summation_type type, rai::block_hash const & hash); + void sum_add (rai::uint128_t addend_a); + void sum_set (rai::uint128_t value_a); + /** The epilogue yields the result to previous frame, if any */ + void epilogue (); + + rai::uint128_t compute_internal (rai::summation_visitor::summation_type type, rai::block_hash const &); void send_block (rai::send_block const &) override; void receive_block (rai::receive_block const &) override; void open_block (rai::open_block const &) override; void change_block (rai::change_block const &) override; void state_block (rai::state_block const &) override; - void from_send (rai::block_hash const &); - rai::transaction const & transaction; - rai::block_store & store; - rai::block_hash current_amount; - rai::block_hash current_balance; - rai::uint128_t amount; }; /** diff --git a/rai/secure/ledger.cpp b/rai/secure/ledger.cpp index 78e050e3..f382ac61 100644 --- a/rai/secure/ledger.cpp +++ b/rai/secure/ledger.cpp @@ -623,9 +623,8 @@ epoch_signer (epoch_signer_a) // Balance for account containing hash rai::uint128_t rai::ledger::balance (rai::transaction const & transaction_a, rai::block_hash const & hash_a) { - rai::balance_visitor visitor (transaction_a, store); - visitor.compute (hash_a); - return visitor.balance; + rai::summation_visitor visitor (transaction_a, store); + return visitor.compute_balance (hash_a); } // Balance for an account by account number @@ -828,9 +827,8 @@ rai::account rai::ledger::account (rai::transaction const & transaction_a, rai:: // Return amount decrease or increase for block rai::uint128_t rai::ledger::amount (rai::transaction const & transaction_a, rai::block_hash const & hash_a) { - amount_visitor amount (transaction_a, store); - amount.compute (hash_a); - return amount.amount; + summation_visitor amount (transaction_a, store); + return amount.compute_amount (hash_a); } // Return latest block for account