diff --git a/src/bucket/BucketBase.cpp b/src/bucket/BucketBase.cpp index 4d80327a72..e02a582f45 100644 --- a/src/bucket/BucketBase.cpp +++ b/src/bucket/BucketBase.cpp @@ -426,6 +426,13 @@ BucketBase::merge( return out.getBucket(bucketManager, &mk); } +template +std::optional> +BucketBase::getRangeForType(LedgerEntryType type) const +{ + return getIndex().getRangeForType(type); +} + template void BucketBase::mergeInternal< MemoryMergeInput, std::function, std::vector>&, bool&>( diff --git a/src/bucket/BucketBase.h b/src/bucket/BucketBase.h index 84f55d0096..f87e8ecbe1 100644 --- a/src/bucket/BucketBase.h +++ b/src/bucket/BucketBase.h @@ -187,6 +187,12 @@ class BucketBase : public NonMovableOrCopyable static std::string randomBucketName(std::string const& tmpDir); static std::string randomBucketIndexName(std::string const& tmpDir); + // Returns [lowerBound, upperBound) of file offsets for all entries of the + // given type in the bucket, or std::nullopt if no entries of this type + // exist. + std::optional> + getRangeForType(LedgerEntryType type) const; + #ifdef BUILD_TESTS IndexT const& getIndexForTesting() const diff --git a/src/bucket/BucketListSnapshot.cpp b/src/bucket/BucketListSnapshot.cpp index 195ca53e60..6723530ded 100644 --- a/src/bucket/BucketListSnapshot.cpp +++ b/src/bucket/BucketListSnapshot.cpp @@ -709,9 +709,10 @@ namespace // to be positioned at the start of the type range. This is basically the same // as SearchableLiveBucketListSnapshot::scanForEntriesOfType's scanBucket except // with more control over when iteration happens. -class BucketEntryIterator +template class BucketEntryIterator { - BucketEntry mEntry; + BUCKET_TYPE_ASSERT(BucketT); + BucketT::EntryT mEntry; LedgerKey mKey; XDRInputFileStream mStream; LedgerEntryType const mType; @@ -722,7 +723,7 @@ class BucketEntryIterator { } - BucketEntry const& + BucketT::EntryT const& getEntry() const { return mEntry; @@ -738,7 +739,7 @@ class BucketEntryIterator { while (mStream.readOne(mEntry)) { - if (isBucketMetaEntry(mEntry)) + if (isBucketMetaEntry(mEntry)) { continue; } @@ -758,10 +759,11 @@ class BucketEntryIterator }; } // namespace +template void -SearchableLiveBucketListSnapshot::scanForLiveEntriesOfType( +SearchableBucketListSnapshot::scanForCurrentEntriesOfType( LedgerEntryType type, - std::function callback) const + std::function callback) const { ZoneScoped; // We implement this as a k-way merge over all buckets. We use a loser tree @@ -777,9 +779,9 @@ SearchableLiveBucketListSnapshot::scanForLiveEntriesOfType( // intermediate nodes, we just store an index, since copying the XDR types // is probably more expensive than the extra indirection. - std::vector iterators; + std::vector> iterators; loopAllBuckets( - [&iterators, type](std::shared_ptr const& bucket) { + [&iterators, type](std::shared_ptr const& bucket) { if (bucket->isEmpty()) { return Loop::INCOMPLETE; @@ -878,9 +880,27 @@ SearchableLiveBucketListSnapshot::scanForLiveEntriesOfType( { last = key; auto& entry = iter.getEntry(); - if (entry.type() == LIVEENTRY || entry.type() == INITENTRY) + if constexpr (std::is_same_v) { - callback(entry.liveEntry(), key); + if (entry.type() == LIVEENTRY || entry.type() == INITENTRY) + { + if (callback(entry.liveEntry(), key) == Loop::COMPLETE) + { + return; + } + } + } + else + { + static_assert(std::is_same_v, + "unexpected bucket type"); + if (entry.type() == HOT_ARCHIVE_ARCHIVED) + { + if (callback(entry.archivedEntry(), key) == Loop::COMPLETE) + { + return; + } + } } } first = false; diff --git a/src/bucket/BucketListSnapshot.h b/src/bucket/BucketListSnapshot.h index d2422dd494..905ccd4451 100644 --- a/src/bucket/BucketListSnapshot.h +++ b/src/bucket/BucketListSnapshot.h @@ -201,6 +201,19 @@ template class SearchableBucketListSnapshot // Access to underlying data (for copying/refreshing) std::shared_ptr const> const& getSnapshotData() const; + + // Iterate over the visible, non-shadowed entries of a given type, i.e., the + // newest entry for each key if it isn't tombstoned. Calls callback for + // these entries, stopping early if callback returns Loop::COMPLETE. For the + // live bucket list, this will be the current values (LIVEENTRY or + // INITENTRY) for all keys of the given type that aren't dead (DEADENTRY). + // For the hot archive bucket list, this will be the latest version of all + // keys of the given type that are still archived (HOT_ARCHIVE_ARCHIVED) and + // haven't been restored (HOT_ARCHIVE_LIVE). + void scanForCurrentEntriesOfType( + LedgerEntryType type, + std::function callback) + const; }; // Live bucket list snapshot with additional query methods @@ -241,13 +254,6 @@ class SearchableLiveBucketListSnapshot LedgerEntryType type, std::function callback) const; - // Iterate over all live entries of a given type. Note that this handles - // shadowing and only returns the latest live entry for each key. - void scanForLiveEntriesOfType( - LedgerEntryType type, - std::function callback) - const; - friend class ImmutableLedgerData; friend class ImmutableLedgerView; }; diff --git a/src/bucket/HotArchiveBucketIndex.cpp b/src/bucket/HotArchiveBucketIndex.cpp index 80f885b2d1..83915a75b9 100644 --- a/src/bucket/HotArchiveBucketIndex.cpp +++ b/src/bucket/HotArchiveBucketIndex.cpp @@ -28,6 +28,12 @@ HotArchiveBucketIndex::HotArchiveBucketIndex( mDiskIndex.getPageSize(), filename); } +std::optional> +HotArchiveBucketIndex::getRangeForType(LedgerEntryType type) const +{ + return mDiskIndex.getRangeForType(type); +} + std::streamoff HotArchiveBucketIndex::getPageSize(Config const& cfg, size_t bucketSize) { diff --git a/src/bucket/HotArchiveBucketIndex.h b/src/bucket/HotArchiveBucketIndex.h index 2b0574bde7..2759e86566 100644 --- a/src/bucket/HotArchiveBucketIndex.h +++ b/src/bucket/HotArchiveBucketIndex.h @@ -83,6 +83,9 @@ class HotArchiveBucketIndex : public NonMovableOrCopyable { } + std::optional> + getRangeForType(LedgerEntryType type) const; + std::pair scan(IterT start, LedgerKey const& k) const; BucketEntryCounters const& diff --git a/src/bucket/LiveBucket.cpp b/src/bucket/LiveBucket.cpp index 0669c8b61b..33416b9013 100644 --- a/src/bucket/LiveBucket.cpp +++ b/src/bucket/LiveBucket.cpp @@ -371,12 +371,6 @@ LiveBucket::getMaxCacheSize() const } #endif // BUILD_TESTS -std::optional> -LiveBucket::getRangeForType(LedgerEntryType type) const -{ - return getIndex().getRangeForType(type); -} - std::vector LiveBucket::convertToBucketEntry(bool useInit, std::vector const& initEntries, diff --git a/src/bucket/LiveBucket.h b/src/bucket/LiveBucket.h index fc68867b21..6cf732ec68 100644 --- a/src/bucket/LiveBucket.h +++ b/src/bucket/LiveBucket.h @@ -105,14 +105,6 @@ class LiveBucket : public BucketBase, size_t getMaxCacheSize() const; #endif - // Returns [lowerBound, upperBound) of file offsets for all entries of the - // given type in the bucket, or std::nullopt if no entries of this type - // exist. Note that if the underlying index is a page based index, this is a - // rough bound such that entries of another type may also be present in the - // range. - std::optional> - getRangeForType(LedgerEntryType type) const; - // Create a fresh bucket from given vectors of init (created) and live // (updated) LedgerEntries, and dead LedgerEntryKeys. The bucket will // be sorted, hashed, and adopted in the provided BucketManager. diff --git a/src/bucket/test/BucketIndexTests.cpp b/src/bucket/test/BucketIndexTests.cpp index 4b14f0f881..42188cb41e 100644 --- a/src/bucket/test/BucketIndexTests.cpp +++ b/src/bucket/test/BucketIndexTests.cpp @@ -1607,9 +1607,9 @@ TEST_CASE("getRangeForType bounds verification", "[bucket][bucketindex]") testAllIndexTypes(f); } -// Fixture for the scanForLiveEntriesOfType randomized test. Records the -// live-entry set created by buildMultiVersionTest. Includes the genesis -// root ACCOUNT but not genesis CONFIG_SETTINGs. +// Fixture for the scanForCurrentEntriesOfType randomized test. Records the +// live-entry set created by buildMultiVersionTest. Includes the genesis root +// ACCOUNT but not genesis CONFIG_SETTINGs. class BucketIndexScanTest : public BucketIndexTest { UnorderedMap mAllEntries; @@ -1654,7 +1654,7 @@ class BucketIndexScanTest : public BucketIndexTest } }; -TEST_CASE("scanForLiveEntriesOfType randomized testing", +TEST_CASE("scanForCurrentEntriesOfType randomized testing", "[bucket][bucketindex]") { // Scan for each of the given types and check the result against the @@ -1682,6 +1682,7 @@ TEST_CASE("scanForLiveEntriesOfType randomized testing", // Each key should only be emitted once REQUIRE(found.emplace(key, entry).second); + return Loop::INCOMPLETE; }); REQUIRE(found == expected); } @@ -1704,8 +1705,10 @@ TEST_CASE("scanForLiveEntriesOfType randomized testing", INFO( "type = " << xdr::xdr_traits::enum_name(type)); ledgerView.scanCurrentLiveEntriesOfType( - type, - [](LedgerEntry const&, LedgerKey const&) { REQUIRE(false); }); + type, [](LedgerEntry const&, LedgerKey const&) { + REQUIRE(false); + return Loop::COMPLETE; + }); } }); @@ -1721,7 +1724,7 @@ TEST_CASE("scanForLiveEntriesOfType randomized testing", }); } -TEST_CASE("scanForLiveEntriesOfType loser tree unit tests", +TEST_CASE("scanForCurrentEntriesOfType loser tree unit tests", "[bucket][bucketindex]") { auto f = [&](Config& cfg) { @@ -1794,6 +1797,7 @@ TEST_CASE("scanForLiveEntriesOfType loser tree unit tests", // Each key must be emitted exactly once REQUIRE(found.emplace(key, entry).second); + return Loop::INCOMPLETE; }); return found; }; @@ -1801,8 +1805,10 @@ TEST_CASE("scanForLiveEntriesOfType loser tree unit tests", auto requireNoCallback = [](ImmutableLedgerView const& view, LedgerEntryType type) { view.scanCurrentLiveEntriesOfType( - type, - [](LedgerEntry const&, LedgerKey const&) { REQUIRE(false); }); + type, [](LedgerEntry const&, LedgerKey const&) { + REQUIRE(false); + return Loop::COMPLETE; + }); }; SECTION("k disjoint buckets") diff --git a/src/invariant/ConservationOfLumens.cpp b/src/invariant/ConservationOfLumens.cpp index 35feba1d13..f835faa662 100644 --- a/src/invariant/ConservationOfLumens.cpp +++ b/src/invariant/ConservationOfLumens.cpp @@ -177,20 +177,13 @@ ConservationOfLumens::checkOnOperationApply( return {}; } -// Helper function that processes an entry if it hasn't been seen before. +// Helper function that processes an entry. // Returns true on success, false on error (with error set in errorMsg). static bool -processEntryIfNew(LedgerEntry const& entry, LedgerKey const& key, - std::unordered_set& countedKeys, - Asset const& asset, - AssetContractInfo const& assetContractInfo, - int64_t& sumBalance, std::string& errorMsg) +processEntry(LedgerEntry const& entry, LedgerKey const& key, Asset const& asset, + AssetContractInfo const& assetContractInfo, int64_t& sumBalance, + std::string& errorMsg) { - if (countedKeys.count(key) != 0) - { - return true; - } - auto result = getAssetBalance(entry, asset, assetContractInfo); if (result.overflowed) @@ -216,8 +209,6 @@ processEntryIfNew(LedgerEntry const& entry, LedgerKey const& key, return false; } - countedKeys.emplace(key); - return true; } @@ -236,28 +227,17 @@ scanLiveBuckets(ApplyLedgerView const& applyView, Asset const& asset, continue; } - std::unordered_set countedKeys; - - applyView.scanLiveEntriesOfType( - type, [&](BucketEntry const& be) -> Loop { + applyView.scanCurrentLiveEntriesOfType( + type, [&](LedgerEntry const& le, LedgerKey const& key) -> Loop { if (isStopping()) { return Loop::COMPLETE; } - if (be.type() == LIVEENTRY || be.type() == INITENTRY) - { - if (!processEntryIfNew( - be.liveEntry(), LedgerEntryKey(be.liveEntry()), - countedKeys, asset, assetContractInfo, sumBalance, - errorMsg)) - { - return Loop::COMPLETE; - } - } - else if (be.type() == DEADENTRY) + if (!processEntry(le, key, asset, assetContractInfo, sumBalance, + errorMsg)) { - countedKeys.emplace(be.deadEntry()); + return Loop::COMPLETE; } return Loop::INCOMPLETE; }); @@ -275,36 +255,35 @@ scanHotArchiveBuckets(ApplyLedgerView const& applyView, Asset const& asset, int64_t& sumBalance, std::string& errorMsg, std::function const& isStopping) { - std::unordered_set countedKeys; - applyView.scanAllArchiveEntries([&](HotArchiveBucketEntry const& be) { - if (isStopping()) + // Scan all entry types that can hold the native asset + for (auto let : xdr::xdr_traits::enum_values()) + { + LedgerEntryType type = static_cast(let); + if (!canHoldAsset(type, asset)) { - return Loop::COMPLETE; + continue; } - if (be.type() == HOT_ARCHIVE_ARCHIVED) - { - if (!canHoldAsset(be.archivedEntry().data.type(), asset)) - { + applyView.scanCurrentHotArchiveEntriesOfType( + type, [&](LedgerEntry const& le, LedgerKey const& key) -> Loop { + if (isStopping()) + { + return Loop::COMPLETE; + } + + if (!processEntry(le, key, asset, assetContractInfo, sumBalance, + errorMsg)) + { + return Loop::COMPLETE; + } return Loop::INCOMPLETE; - } - if (!processEntryIfNew(be.archivedEntry(), - LedgerEntryKey(be.archivedEntry()), - countedKeys, asset, assetContractInfo, - sumBalance, errorMsg)) - { - return Loop::COMPLETE; - } - } - else if (be.type() == HOT_ARCHIVE_LIVE && - canHoldAsset(be.key().type(), asset)) + }); + + if (!errorMsg.empty()) { - // HOT_ARCHIVE_LIVE means entry was restored from archive, - // so mark it as seen (shadowing any archived versions) - countedKeys.emplace(be.key()); + return; } - return Loop::INCOMPLETE; - }); + } } std::string @@ -340,8 +319,7 @@ ConservationOfLumens::checkSnapshot( sumBalance, header.feePool); } - // Scan the Live BucketList for native balances using loopAllBuckets - + // Scan the Live BucketList for native balances scanLiveBuckets(applyView, nativeAsset, mLumenContractInfo, sumBalance, errorMsg, isStopping); diff --git a/src/ledger/ImmutableLedgerView.cpp b/src/ledger/ImmutableLedgerView.cpp index 796e51c5d0..b4194365bb 100644 --- a/src/ledger/ImmutableLedgerView.cpp +++ b/src/ledger/ImmutableLedgerView.cpp @@ -410,9 +410,17 @@ ImmutableLedgerView::scanLiveEntriesOfType( void ImmutableLedgerView::scanCurrentLiveEntriesOfType( LedgerEntryType type, - std::function callback) const + std::function callback) const { - mLiveSnapshot.scanForLiveEntriesOfType(type, std::move(callback)); + mLiveSnapshot.scanForCurrentEntriesOfType(type, std::move(callback)); +} + +void +ImmutableLedgerView::scanCurrentHotArchiveEntriesOfType( + LedgerEntryType type, + std::function callback) const +{ + mHotArchiveSnapshot.scanForCurrentEntriesOfType(type, std::move(callback)); } // === Hot Archive BucketList wrapper methods === diff --git a/src/ledger/ImmutableLedgerView.h b/src/ledger/ImmutableLedgerView.h index 3f6a523ea4..feb9cf0c83 100644 --- a/src/ledger/ImmutableLedgerView.h +++ b/src/ledger/ImmutableLedgerView.h @@ -174,10 +174,17 @@ class ImmutableLedgerView : public virtual AbstractLedgerView std::function callback) const; // Scan the live bucket list for entries of a given type. Calls callback - // with the latest live version for each entry. + // with the latest version for each non-dead entry. void scanCurrentLiveEntriesOfType( LedgerEntryType type, - std::function callback) + std::function callback) + const; + + // Scan the hot archive bucket list for entries of a given type. Calls + // callback with the latest version for each non-restored entry. + void scanCurrentHotArchiveEntriesOfType( + LedgerEntryType type, + std::function callback) const; // === Hot Archive BucketList methods === @@ -214,6 +221,7 @@ class ApplyLedgerView : private ImmutableLedgerView, using ImmutableLedgerView::loadLiveKeys; using ImmutableLedgerView::loadPoolShareTrustLinesByAccountAndAsset; using ImmutableLedgerView::scanAllArchiveEntries; + using ImmutableLedgerView::scanCurrentHotArchiveEntriesOfType; using ImmutableLedgerView::scanCurrentLiveEntriesOfType; using ImmutableLedgerView::scanForEviction; using ImmutableLedgerView::scanLiveEntriesOfType; diff --git a/src/ledger/InMemorySorobanState.cpp b/src/ledger/InMemorySorobanState.cpp index cf0e1e690e..be119f3d32 100644 --- a/src/ledger/InMemorySorobanState.cpp +++ b/src/ledger/InMemorySorobanState.cpp @@ -459,16 +459,19 @@ InMemorySorobanState::initializeStateFromSnapshot( auto contractDataHandler = [this](LedgerEntry const& le, LedgerKey const&) { createContractDataEntry(le); + return Loop::INCOMPLETE; }; auto ttlHandler = [this](LedgerEntry const& le, LedgerKey const&) { createTTL(le); + return Loop::INCOMPLETE; }; auto contractCodeHandler = [this, &sorobanConfig, ledgerVersion](LedgerEntry const& le, LedgerKey const&) { createContractCodeEntry(le, sorobanConfig, ledgerVersion); + return Loop::INCOMPLETE; }; applyView.scanCurrentLiveEntriesOfType(CONTRACT_DATA,