From b7888ac2652ed719b75839f605e542cf493d6950 Mon Sep 17 00:00:00 2001 From: Colin LeMahieu Date: Sat, 16 Mar 2024 16:08:09 +0000 Subject: [PATCH] Add iterators for receivable entries to nano::ledger. --- nano/core_test/ledger.cpp | 95 ++++++++++++++++++++++++++++++++++++ nano/secure/ledger.cpp | 30 ++++++++++++ nano/secure/ledger.hpp | 10 ++++ nano/secure/pending_info.cpp | 45 +++++++++++++++++ nano/secure/pending_info.hpp | 29 +++++++++++ 5 files changed, 209 insertions(+) diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 4c021abdd..e8ef9b67a 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -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); +} diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 8b412717b..cc256bdf9 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -1545,6 +1545,36 @@ uint64_t nano::ledger::height (store::transaction const & transaction, nano::blo return block_l->sideband ().height; } +std::optional> 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) { diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index f5009b664..8ceaedf36 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -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> receivable_lower_bound (store::transaction const & tx, nano::account const & account, nano::block_hash const & hash) const; void initialize (nano::generate_cache_flags const &); }; diff --git a/nano/secure/pending_info.cpp b/nano/secure/pending_info.cpp index 637f4adf7..61241f216 100644 --- a/nano/secure/pending_info.cpp +++ b/nano/secure/pending_info.cpp @@ -1,3 +1,4 @@ +#include #include 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> 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 const & nano::receivable_iterator::operator* () const +{ + return item.value (); +} + +std::pair const * nano::receivable_iterator::operator->() const +{ + return &item.value (); +} diff --git a/nano/secure/pending_info.hpp b/nano/secure/pending_info.hpp index 584337e95..0136da020 100644 --- a/nano/secure/pending_info.hpp +++ b/nano/secure/pending_info.hpp @@ -4,6 +4,16 @@ #include #include +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> 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 const & operator* () const; + std::pair const * operator->() const; + +private: + nano::ledger const * ledger{ nullptr }; + nano::store::transaction const * tx{ nullptr }; + nano::account account{ 0 }; + std::optional> item; +}; } // namespace nano namespace std