Iterative amount/balance visitor (#1257)

* Iterative amount/balance visitor

* Factor out epilogue
This commit is contained in:
cryptocode 2018-11-08 20:26:57 +01:00 committed by Lee Bousfield
commit 35844371ab
4 changed files with 267 additions and 165 deletions

View file

@ -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)

View file

@ -1,171 +1,246 @@
#include <queue>
#include <rai/node/common.hpp>
#include <rai/node/wallet.hpp>
#include <rai/secure/blockstore.hpp>
#include <boost/polymorphic_cast.hpp>
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<rai::uint128_t>::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<rai::uint128_t>::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),

View file

@ -1,52 +1,82 @@
#pragma once
#include <rai/secure/common.hpp>
#include <stack>
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<frame> 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;
};
/**

View file

@ -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