Skip to content

CAP-0065 - Reusable module cache #4621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,4 +256,8 @@ soroban.config.ledger-max-read-entry | counter | soroban config settin
soroban.config.ledger-max-read-ledger-byte | counter | soroban config setting `ledger_max_read_bytes`
soroban.config.ledger-max-write-entry | counter | soroban config setting `ledger_max_write_ledger_entries`
soroban.config.ledger-max-write-ledger-byte | counter | soroban config setting `ledger_max_write_bytes`
soroban.config.bucket-list-target-size-byte | counter | soroban config setting `bucket_list_target_size_bytes`
soroban.config.bucket-list-target-size-byte | counter | soroban config setting `bucket_list_target_size_bytes`
soroban.module-cache.num-entries | counter | current number of entries in module cache
soroban.module-cache.compilation-time | timer | times each contract compilation when adding to module cache
soroban.module-cache.rebuild-time | timer | times each rebuild of module cache (including all compilations)
soroban.module-cache.rebuild-bytes | counter | bytes of WASM bytecode compiled in last rebuild of module cache
6 changes: 6 additions & 0 deletions docs/stellar-core_example.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ CATCHUP_RECENT=0
# merging and vertification.
WORKER_THREADS=11

# COMPILATION_THREADS (integer) default 6
# Number of threads launched temporarily when compiling contracts at
# startup. These are short lived, CPU-bound threads that are not
# in competition with the worker threads.
COMPILATION_THREADS=6

# QUORUM_INTERSECTION_CHECKER (boolean) default true
# Enable/disable computation of quorum intersection monitoring
QUORUM_INTERSECTION_CHECKER=true
Expand Down
43 changes: 20 additions & 23 deletions scripts/ParseDump.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@
import re
import subprocess


# input: ./src/stellar-core(+0xd9f6bd) [0x55ab4c2456bd]
def extract_relative_offset(line):
def extract_addr(line):
if "stellar-core" not in line:
return ""

# extracts (+0xd9f6bd) by matching on (+ ... )
# ./src/stellar-core(+0xd9f6bd) [0x55ab4c2456bd] -> (+0xd9f6bd)
formated_offset = re.findall(r"\(\+.*?\)", line)
return None

# Remove first two characters and final character to extract raw offset in 0x.. form
# (+0xd9f6bd) -> 0xd9f6bd
return formated_offset[0][2:-1]
# extracts 0x55ab4c2456bd by matching on [...]$
# ./src/stellar-core(+0xd9f6bd) [0x55ab4c2456bd] -> 0x55ab4c2456bd
match = re.search(r"\[(0x[0-9a-f]+)\]\s*$", line)
if match is not None:
return match.group(1)
return None


def main():
Expand All @@ -30,13 +28,12 @@ def main():

parser.add_argument(
"stack_trace",
type=argparse.FileType('r'),
help="Stack trace reported by stellar-core (should start with something like: ./src/stellar-core(+0xd9f6bd) [0x55a1fcb7d6bd])",
)

args = parser.parse_args()
stack_traces = args.stack_trace.split("\n")

addr2line_base_args = [
command = [
"addr2line",
"-f", # Display function names
"-C", # Demangle function names
Expand All @@ -45,16 +42,16 @@ def main():
"-e",
args.exe.name,
]

for line in stack_traces:
relative_offset = extract_relative_offset(line)
if not relative_offset:
print("??")
else:
command = addr2line_base_args.copy()
command.append(relative_offset)
subprocess.run(command)

addrs = False
for line in args.stack_trace.readlines():
addr = extract_addr(line)
if addr is not None:
addrs = True
command.append(addr)
if not addrs:
print("No addresses found in stack trace")
return
subprocess.run(command)

if __name__ == "__main__":
main()
9 changes: 7 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ $(RUST_DEP_TREE_STAMP): $(wildcard rust/soroban/*/Cargo.*) Makefile $(RUST_TOOLC
done
touch $@

# Of course, RUST_PROFILE can't be used as an argument directly because cargo
# doesn't let you pass --debug, that's the default! you can only pass --release.
# So we have to derive a new variable here.
RUST_PROFILE_ARG := $(if $(findstring release,$(RUST_PROFILE)),--release,)

# This next build command looks a little weird but it's necessary. We have to
# provide an auxiliary metadata string (using RUSTFLAGS=-Cmetadata=$*)
# essentially manually marking-as-different the separate dependency trees
Expand Down Expand Up @@ -252,7 +257,7 @@ $(SOROBAN_LIBS_STAMP): $(wildcard rust/soroban/p*/Cargo.lock) Makefile $(RUST_DE
CARGO_NET_GIT_FETCH_WITH_CLI=true \
$(CARGO) build \
--package soroban-env-host \
--$(RUST_PROFILE) \
$(RUST_PROFILE_ARG) \
--locked \
$$FEATURE_FLAGS \
|| exit 1; \
Expand All @@ -279,7 +284,7 @@ $(LIBRUST_STELLAR_CORE): $(RUST_HOST_DEPFILES) $(SRC_RUST_FILES) Makefile $(SORO
CARGO_NET_GIT_FETCH_WITH_CLI=true \
$(CARGO) rustc \
--package stellar-core \
--$(RUST_PROFILE) \
$(RUST_PROFILE_ARG) \
--locked \
--target-dir $(abspath $(RUST_TARGET_DIR)) \
$(CARGO_FEATURE_TRACY) $(CARGO_FEATURE_NEXT) $(CARGO_FEATURE_TESTUTILS) \
Expand Down
37 changes: 37 additions & 0 deletions src/bucket/BucketSnapshot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,43 @@ LiveBucketSnapshot::scanForEviction(
return Loop::INCOMPLETE;
}

// Scans contract code entries in the bucket.
Loop
LiveBucketSnapshot::scanForContractCode(
std::function<Loop(BucketEntry const&)> callback) const
{
ZoneScoped;
if (isEmpty())
{
return Loop::INCOMPLETE;
}

auto range = mBucket->getContractCodeRange();
if (!range)
{
return Loop::INCOMPLETE;
}

auto& stream = getStream();
stream.seek(range->first);

BucketEntry be;
while (stream.pos() < range->second && stream.readOne(be))
{

if (((be.type() == LIVEENTRY || be.type() == INITENTRY) &&
be.liveEntry().data.type() == CONTRACT_CODE) ||
(be.type() == DEADENTRY && be.deadEntry().type() == CONTRACT_CODE))
{
if (callback(be) == Loop::COMPLETE)
{
return Loop::COMPLETE;
}
}
}
return Loop::INCOMPLETE;
}

template <class BucketT>
XDRInputFileStream&
BucketSnapshotBase<BucketT>::getStream() const
Expand Down
4 changes: 4 additions & 0 deletions src/bucket/BucketSnapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ class LiveBucketSnapshot : public BucketSnapshotBase<LiveBucket>
std::list<EvictionResultEntry>& evictableKeys,
SearchableLiveBucketListSnapshot const& bl,
uint32_t ledgerVers) const;

// Scans contract code entries in the bucket.
Loop
scanForContractCode(std::function<Loop(BucketEntry const&)> callback) const;
};

class HotArchiveBucketSnapshot : public BucketSnapshotBase<HotArchiveBucket>
Expand Down
31 changes: 31 additions & 0 deletions src/bucket/InMemoryIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ InMemoryIndex::InMemoryIndex(BucketManager const& bm,
std::streamoff lastOffset = 0;
std::optional<std::streamoff> firstOffer;
std::optional<std::streamoff> lastOffer;
std::optional<std::streamoff> firstContractCode;
std::optional<std::streamoff> lastContractCode;

while (in && in.readOne(be, hasher))
{
Expand Down Expand Up @@ -98,6 +100,16 @@ InMemoryIndex::InMemoryIndex(BucketManager const& bm,
lastOffer = lastOffset;
}

// Populate contractCodeRange
if (!firstContractCode && lk.type() == CONTRACT_CODE)
{
firstContractCode = lastOffset;
}
if (!lastContractCode && lk.type() > CONTRACT_CODE)
{
lastContractCode = lastOffset;
}

lastOffset = in.pos();
}

Expand All @@ -119,5 +131,24 @@ InMemoryIndex::InMemoryIndex(BucketManager const& bm,
{
mOfferRange = std::nullopt;
}

if (firstContractCode)
{
if (lastContractCode)
{
mContractCodeRange = {*firstContractCode, *lastContractCode};
}
// If we didn't see any entries after contract code, then the upper
// bound is EOF
else
{
mContractCodeRange = {*firstContractCode,
std::numeric_limits<std::streamoff>::max()};
}
}
else
{
mContractCodeRange = std::nullopt;
}
}
}
7 changes: 7 additions & 0 deletions src/bucket/InMemoryIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class InMemoryIndex
AssetPoolIDMap mAssetPoolIDMap;
BucketEntryCounters mCounters{};
std::optional<std::pair<std::streamoff, std::streamoff>> mOfferRange;
std::optional<std::pair<std::streamoff, std::streamoff>> mContractCodeRange;

public:
using IterT = InMemoryBucketState::IterT;
Expand Down Expand Up @@ -232,6 +233,12 @@ class InMemoryIndex
return mOfferRange;
}

std::optional<std::pair<std::streamoff, std::streamoff>>
getContractCodeRange() const
{
return mContractCodeRange;
}

#ifdef BUILD_TESTS
bool
operator==(InMemoryIndex const& in) const
Expand Down
6 changes: 6 additions & 0 deletions src/bucket/LiveBucket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ LiveBucket::getOfferRange() const
return getIndex().getOfferRange();
}

std::optional<std::pair<std::streamoff, std::streamoff>>
LiveBucket::getContractCodeRange() const
{
return getIndex().getContractCodeRange();
}

std::vector<BucketEntry>
LiveBucket::convertToBucketEntry(bool useInit,
std::vector<LedgerEntry> const& initEntries,
Expand Down
5 changes: 5 additions & 0 deletions src/bucket/LiveBucket.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ class LiveBucket : public BucketBase<LiveBucket, LiveBucketIndex>,
std::optional<std::pair<std::streamoff, std::streamoff>>
getOfferRange() const;

// Returns [lowerBound, upperBound) of file offsets for all contract code
// entries in the bucket, or std::nullopt if no contract code exists
std::optional<std::pair<std::streamoff, std::streamoff>>
getContractCodeRange() 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.
Expand Down
21 changes: 21 additions & 0 deletions src/bucket/LiveBucketIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,27 @@ LiveBucketIndex::getOfferRange() const
return mInMemoryIndex->getOfferRange();
}

std::optional<std::pair<std::streamoff, std::streamoff>>
LiveBucketIndex::getContractCodeRange() const
{
if (mDiskIndex)
{
// Get the smallest and largest possible contract code keys
LedgerKey lowerBound(CONTRACT_CODE);
lowerBound.contractCode().hash.fill(
std::numeric_limits<uint8_t>::min());

LedgerKey upperBound(CONTRACT_CODE);
upperBound.contractCode().hash.fill(
std::numeric_limits<uint8_t>::max());

return mDiskIndex->getOffsetBounds(lowerBound, upperBound);
}

releaseAssertOrThrow(mInMemoryIndex);
return mInMemoryIndex->getContractCodeRange();
}

uint32_t
LiveBucketIndex::getPageSize() const
{
Expand Down
3 changes: 3 additions & 0 deletions src/bucket/LiveBucketIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ class LiveBucketIndex : public NonMovableOrCopyable

void maybeAddToCache(std::shared_ptr<BucketEntry const> const& entry) const;

std::optional<std::pair<std::streamoff, std::streamoff>>
getContractCodeRange() const;

BucketEntryCounters const& getBucketEntryCounters() const;
uint32_t getPageSize() const;

Expand Down
12 changes: 12 additions & 0 deletions src/bucket/SearchableBucketList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ SearchableLiveBucketListSnapshot::scanForEviction(
return result;
}

void
SearchableLiveBucketListSnapshot::scanForContractCode(
std::function<Loop(BucketEntry const&)> callback) const
{
ZoneScoped;
releaseAssert(mSnapshot);
auto f = [&callback](auto const& b) {
return b.scanForContractCode(callback);
};
loopAllBuckets(f, *mSnapshot);
}

// This query has two steps:
// 1. For each bucket, determine what PoolIDs contain the target asset via the
// assetToPoolID index
Expand Down
3 changes: 3 additions & 0 deletions src/bucket/SearchableBucketList.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class SearchableLiveBucketListSnapshot
std::shared_ptr<EvictionStatistics> stats,
StateArchivalSettings const& sas, uint32_t ledgerVers) const;

void
scanForContractCode(std::function<Loop(BucketEntry const&)> callback) const;

friend SearchableSnapshotConstPtr
BucketSnapshotManager::copySearchableLiveBucketListSnapshot() const;
};
Expand Down
4 changes: 2 additions & 2 deletions src/bucket/test/BucketListTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,9 +591,9 @@ TEST_CASE_VERSIONS("bucket tombstones mutually-annihilate init entries",
FIRST_PROTOCOL_SUPPORTING_INITENTRY_AND_METAENTRY))
{
// init/dead pairs should mutually-annihilate pretty readily as
// they go, empirically this test peaks at buckets around 400
// they go, empirically this test peaks at buckets around 500
// entries.
REQUIRE((currSz + snapSz) < 500);
REQUIRE((currSz + snapSz) < 600);
}
CLOG_INFO(Bucket, "Level {} size: {}", k, (currSz + snapSz));
}
Expand Down
9 changes: 9 additions & 0 deletions src/crypto/ByteSlice.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0

#include "rust/RustBridge.h"
#include <cstdint>
#include <string>
#include <vector>
#include <xdrpp/message.h>
Expand Down Expand Up @@ -71,6 +73,13 @@ class ByteSlice
: mData(bytes.data()), mSize(bytes.size())
{
}
ByteSlice(::rust::Vec<uint8_t> const& bytes)
: mData(bytes.data()), mSize(bytes.size())
{
}
ByteSlice(RustBuf const& bytes) : ByteSlice(bytes.data)
{
}
ByteSlice(char const* str) : ByteSlice((void const*)str, strlen(str))
{
}
Expand Down
2 changes: 2 additions & 0 deletions src/ledger/LedgerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "catchup/LedgerApplyManager.h"
#include "history/HistoryManager.h"
#include "ledger/NetworkConfig.h"
#include "rust/RustBridge.h"
#include <memory>

namespace stellar
Expand Down Expand Up @@ -310,6 +311,7 @@ class LedgerManager
virtual void manuallyAdvanceLedgerHeader(LedgerHeader const& header) = 0;

virtual SorobanMetrics& getSorobanMetrics() = 0;
virtual ::rust::Box<rust_bridge::SorobanModuleCache> getModuleCache() = 0;

virtual ~LedgerManager()
{
Expand Down
Loading