Add iterators for receivable entries to nano::ledger.

This commit is contained in:
Colin LeMahieu 2024-03-16 16:08:09 +00:00
commit b7888ac265
No known key found for this signature in database
GPG key ID: 43708520C8DFB938
5 changed files with 209 additions and 0 deletions

View file

@ -5559,3 +5559,98 @@ TEST (ledger, head_block)
auto tx = store.tx_begin_read ();
ASSERT_EQ (*nano::dev::genesis, *ledger.head_block (tx, nano::dev::genesis_key.pub));
}
// Test that nullopt can be returned when there are no receivable entries
TEST (ledger_receivable, upper_bound_account_none)
{
auto ctx = nano::test::context::ledger_empty ();
ASSERT_EQ (ctx.ledger ().receivable_end (), ctx.ledger ().receivable_upper_bound (ctx.store ().tx_begin_read (), 0));
}
// Test behavior of ledger::receivable_upper_bound when there are receivable entries for multiple accounts
TEST (ledger_receivable, upper_bound_account_key)
{
auto ctx = nano::test::context::ledger_empty ();
nano::block_builder builder;
nano::keypair key;
auto send1 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*ctx.pool ().generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, ctx.ledger ().process (ctx.store ().tx_begin_write (), send1));
auto send2 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*ctx.pool ().generate (send1->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, ctx.ledger ().process (ctx.store ().tx_begin_write (), send2));
auto tx = ctx.store ().tx_begin_read ();
auto & ledger = ctx.ledger ();
auto next1 = ledger.receivable_upper_bound (tx, nano::dev::genesis_key.pub);
auto next2 = ledger.receivable_upper_bound (tx, key.pub);
// Depending on which is greater but only one should have a value
ASSERT_TRUE (next1 == ledger.receivable_end () xor next2 == ledger.receivable_end ());
// The account returned should be after the one we searched for
ASSERT_TRUE (next1 == ledger.receivable_end () || next1->first.account == key.pub);
ASSERT_TRUE (next2 == ledger.receivable_end () || next2->first.account == nano::dev::genesis_key.pub);
auto next3 = ledger.receivable_upper_bound (tx, nano::dev::genesis_key.pub, 0);
auto next4 = ledger.receivable_upper_bound (tx, key.pub, 0);
// Neither account has more than one receivable
ASSERT_TRUE (next3 != ledger.receivable_end () && next4 != ledger.receivable_end ());
auto next5 = ledger.receivable_upper_bound (tx, next3->first.account, next3->first.hash);
auto next6 = ledger.receivable_upper_bound (tx, next4->first.account, next4->first.hash);
ASSERT_TRUE (next5 == ledger.receivable_end () && next6 == ledger.receivable_end ());
ASSERT_EQ (ledger.receivable_end (), ++next3);
ASSERT_EQ (ledger.receivable_end (), ++next4);
}
// Test that multiple receivable entries for the same account
TEST (ledger_receivable, key_two)
{
auto ctx = nano::test::context::ledger_empty ();
nano::block_builder builder;
nano::keypair key;
auto send1 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*ctx.pool ().generate (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, ctx.ledger ().process (ctx.store ().tx_begin_write (), send1));
auto send2 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*ctx.pool ().generate (send1->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, ctx.ledger ().process (ctx.store ().tx_begin_write (), send2));
auto tx = ctx.store ().tx_begin_read ();
auto & ledger = ctx.ledger ();
auto next1 = ledger.receivable_upper_bound (tx, key.pub, 0);
ASSERT_TRUE (next1 != ledger.receivable_end () && next1->first.account == key.pub);
auto next2 = ledger.receivable_upper_bound (tx, key.pub, next1->first.hash);
ASSERT_TRUE (next2 != ledger.receivable_end () && next2->first.account == key.pub);
ASSERT_NE (next1->first.hash, next2->first.hash);
ASSERT_EQ (next2, ++next1);
ASSERT_EQ (ledger.receivable_end (), ++next1);
ASSERT_EQ (ledger.receivable_end (), ++next2);
}

View file

@ -1545,6 +1545,36 @@ uint64_t nano::ledger::height (store::transaction const & transaction, nano::blo
return block_l->sideband ().height;
}
std::optional<std::pair<nano::pending_key, nano::pending_info>> nano::ledger::receivable_lower_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const
{
auto result = store.pending.begin (tx, { account, hash });
if (result == store.pending.end ())
{
return std::nullopt;
}
return *result;
}
nano::receivable_iterator nano::ledger::receivable_end () const
{
return nano::receivable_iterator{};
}
nano::receivable_iterator nano::ledger::receivable_upper_bound (store::transaction const & tx, nano::account const & account) const
{
return receivable_iterator{ *this, tx, receivable_lower_bound (tx, account.number () + 1, 0) };
}
nano::receivable_iterator nano::ledger::receivable_upper_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const
{
auto result = receivable_lower_bound (tx, account, hash.number () + 1);
if (!result || result.value ().first.account != account)
{
return nano::receivable_iterator{ *this, tx, std::nullopt };
}
return nano::receivable_iterator{ *this, tx, result };
}
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)
{

View file

@ -5,6 +5,7 @@
#include <nano/secure/account_info.hpp>
#include <nano/secure/generate_cache_flags.hpp>
#include <nano/secure/ledger_cache.hpp>
#include <nano/secure/pending_info.hpp>
#include <map>
@ -36,6 +37,8 @@ public:
class ledger final
{
friend class receivable_iterator;
public:
ledger (nano::store::component &, nano::stats &, nano::ledger_constants & constants, nano::generate_cache_flags const & = nano::generate_cache_flags{});
/**
@ -84,6 +87,11 @@ public:
static nano::epoch version (nano::block const & block);
nano::epoch version (store::transaction const & transaction, nano::block_hash const & hash) const;
uint64_t height (store::transaction const & transaction, nano::block_hash const & hash) const;
nano::receivable_iterator receivable_end () const;
// Returns the next receivable entry for an account greater than 'account'
nano::receivable_iterator receivable_upper_bound (store::transaction const & tx, nano::account const & account) const;
// Returns the next receivable entry for the account 'account' with hash greater than 'hash'
nano::receivable_iterator receivable_upper_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const;
static nano::uint128_t const unit;
nano::ledger_constants & constants;
nano::store::component & store;
@ -95,6 +103,8 @@ public:
bool pruning{ false };
private:
// Returns the next receivable entry equal or greater than 'key'
std::optional<std::pair<nano::pending_key, nano::pending_info>> receivable_lower_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const;
void initialize (nano::generate_cache_flags const &);
};

View file

@ -1,3 +1,4 @@
#include <nano/secure/ledger.hpp>
#include <nano/secure/pending_info.hpp>
nano::pending_info::pending_info (nano::account const & source_a, nano::amount const & amount_a, nano::epoch epoch_a) :
@ -70,3 +71,47 @@ bool nano::pending_key::operator< (nano::pending_key const & other_a) const
{
return account == other_a.account ? hash < other_a.hash : account < other_a.account;
}
nano::receivable_iterator::receivable_iterator (nano::ledger const & ledger, nano::store::transaction const & tx, std::optional<std::pair<nano::pending_key, nano::pending_info>> item) :
ledger{ &ledger },
tx{ &tx },
item{ item }
{
if (item.has_value ())
{
account = item.value ().first.account;
}
}
bool nano::receivable_iterator::operator== (receivable_iterator const & other) const
{
debug_assert (ledger == nullptr || other.ledger == nullptr || ledger == other.ledger);
debug_assert (tx == nullptr || other.tx == nullptr || tx == other.tx);
debug_assert (account.is_zero () || other.account.is_zero () || account == other.account);
return item == other.item;
}
bool nano::receivable_iterator::operator!= (receivable_iterator const & other) const
{
return !(*this == other);
}
nano::receivable_iterator & nano::receivable_iterator::operator++ ()
{
item = ledger->receivable_lower_bound (*tx, item.value ().first.account, item.value ().first.hash.number () + 1);
if (item && item.value ().first.account != account)
{
item = std::nullopt;
}
return *this;
}
std::pair<nano::pending_key, nano::pending_info> const & nano::receivable_iterator::operator* () const
{
return item.value ();
}
std::pair<nano::pending_key, nano::pending_info> const * nano::receivable_iterator::operator->() const
{
return &item.value ();
}

View file

@ -4,6 +4,16 @@
#include <nano/lib/numbers.hpp>
#include <nano/lib/stream.hpp>
namespace nano
{
class ledger;
}
namespace nano::store
{
class transaction;
}
namespace nano
{
/**
@ -33,6 +43,25 @@ public:
nano::account account{};
nano::block_hash hash{ 0 };
};
// This class iterates receivable enttries for an account
class receivable_iterator
{
public:
receivable_iterator () = default;
receivable_iterator (nano::ledger const & ledger, nano::store::transaction const & tx, std::optional<std::pair<nano::pending_key, nano::pending_info>> item);
bool operator== (receivable_iterator const & other) const;
bool operator!= (receivable_iterator const & other) const;
// Advances to the next receivable entry for the same account
receivable_iterator & operator++ ();
std::pair<nano::pending_key, nano::pending_info> const & operator* () const;
std::pair<nano::pending_key, nano::pending_info> const * operator->() const;
private:
nano::ledger const * ledger{ nullptr };
nano::store::transaction const * tx{ nullptr };
nano::account account{ 0 };
std::optional<std::pair<nano::pending_key, nano::pending_info>> item;
};
} // namespace nano
namespace std