Add tx-granular ledger snapshot source using meta, RPC, and archives#1657
Open
leighmcculloch wants to merge 63 commits into
Open
Add tx-granular ledger snapshot source using meta, RPC, and archives#1657leighmcculloch wants to merge 63 commits into
leighmcculloch wants to merge 63 commits into
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
leighmcculloch
force-pushed
the
snapshot-source-tx
branch
2 times, most recently
from
December 19, 2025 14:52
42f37c9 to
598ad4c
Compare
leighmcculloch
force-pushed
the
snapshot-source-tx
branch
3 times, most recently
from
December 19, 2025 15:34
0a8ae20 to
19904da
Compare
leighmcculloch
force-pushed
the
snapshot-source-tx
branch
from
December 19, 2025 15:34
19904da to
cdc7199
Compare
|
is it possible to set it so the timestamp is also set after the snapshot has been loaded? Currently we need to manually set it after the |
Contributor
|
This seems like an interesting way of handling a simulation locally. Or at least doing a read call locally. |
|
Looks awesome. Thanks, Leigh! ❤️ |
willemneal
reviewed
Dec 22, 2025
Comment on lines
+77
to
+87
| let ledger_cache_dir = self.cache_path.join( | ||
| self.tx_hash | ||
| .map(|h| { | ||
| let tx_hash_str: String = h.iter().map(|b| format!("{b:02x}")).collect(); | ||
| format!("{}-{}-before", self.fetcher.ledger(), tx_hash_str) | ||
| }) | ||
| .unwrap_or_else(|| format!("{}-after", self.fetcher.ledger())), | ||
| ); | ||
|
|
||
| // Ensure cache directory exists | ||
| std::fs::create_dir_all(&ledger_cache_dir).expect("failed to create cache directory"); |
Contributor
There was a problem hiding this comment.
This probably should be the cache crate's responsibility. If it is abstracted more than this crate will never need to use a fs. This way we could eventually use Wasm to run this in the browser.
This was referenced Jan 6, 2026
The embedded WASM in test_spec_shaking_v2_tests.rs was generated against the pre-fix lockfile; regenerate it with the minimized lockfile so the expand-test-wasms CI check (git diff HEAD --exit-code) passes.
- Drop the unmaintained fs2 dependency; use std::fs::File::lock (stable since Rust 1.89, MSRV is 1.91) for the cache's exclusive advisory lock. - Remove the unused stellar-rpc-client (and its tokio companion) dev-deps from test_fork. These were leftover scaffolding and the sole source of the vulnerable rustls-webpki 0.101.7 (GHSA-82j2-j2ch-gfr8); the lockfile now resolves only the patched rustls-webpki 0.103.13. zstd-sys's GPL note needs no action: its declared SPDX is MIT/Apache-2.0, so cargo deny check licenses passes.
- Embed the stellar-xdr curr schema version in committed snapshot fixtures (CachedEntry envelope) and reject mismatches on read, so fixtures produced against a different XDR JSON representation fail fast with an actionable message instead of silently deserializing wrong data. Migrate the 170 existing fixtures to the envelope (stamped with the resolved 26.0.1 schema). - Don't let logging serialization affect fetch results: use unwrap_or_default for the tracing values instead of '?', and log 'not found' for absent entries rather than 'found'. - Don't permanently cache a non-usable (lagging-node) RPC response: remove the cache file so a later run re-queries once the node has caught up. - Fix README example dependency version (23 -> 26). - Remove erroneously committed, gitignored .lock files under tests-snapshot-source. Verified: crate + test_fork compile clean on MSRV; offline mainnet fork test passes reading the migrated fixtures; cargo fmt clean.
# Conflicts: # Cargo.lock # deny.toml
The example fork test's Env snapshot was committed at protocol_version 26; after the p27 merge the regenerated snapshot is protocol_version 27. The mainnet snapshots were already updated in the merge; this updates the example one (run offline via the committed 61340000 fixtures), fixing the test job's git-diff check.
Replace panicking `[0..=1]` slice indexing of the ledger-hex and bucket hash strings with a `hash_prefixes` helper that uses `str::get`, returning a typed `InvalidLedgerHex`/`InvalidBucketHash` error for malformed or truncated values (the bucket hash comes from the archive response).
A V1 TransactionMeta carries both the pre-tx `State` snapshots and the post-tx mutations in its single `tx_changes` list. Return it from both tx_changes_before and tx_changes_after so the iterator's should_yield can split State (before) from non-State (after), instead of dropping V1 after-changes. Removes the open TODO.
Read ledgersPerBatch / batchesPerPartition from the storage's .config.json (<meta_url>/.config.json) instead of hardcoding the partition (64000) and batch (1) sizes. path_for_ledger is now parameterized (partition_size = ledgers_per_batch * batches_per_partition, batch_size = ledgers_per_batch), and the fetcher reads the config once (cached) and threads it through prefetch_meta / fetch_from_meta / get_ledger.
Move the lagging-node usability check (last_modified < ledger && latest_ledger >= ledger) into the cache collector closure, so a non-usable response is never persisted: cache() discards the temp file on collector error rather than caching a stale answer that would be replayed forever and permanently disable the RPC fast path for that (ledger, key).
cargo_metadata 0.19.2 -> camino 1.2.2 depends on serde_core, forcing the serde family to 1.0.228 workspace-wide, whereas main resolves serde 1.0.210. That bumped a guest-compiled dep and shifted the test_spec_shaking_v2 expanded WASM away from the committed fixture (expand-test-wasms). Downgrade camino to 1.1.9 (still satisfies cargo_metadata's ^1.0.7, predates the serde_core split) so serde resolves to 1.0.210 like main, keeping the committed expanded fixture valid and reducing lockfile churn.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Add new crates for fetching ledger entries from multiple data sources to enable more seamless and built-in fork testing in the sdk. Add
soroban-ledger-snapshot-source-txfor transaction-level snapshot sources.sequenceDiagram autonumber actor App as participant SelTxMeta as Query<br/>Ledger<br/>Tx Meta participant LedgerMeta as Query<br/>Ledger – 1..N<br/>Tx Meta participant Archive as History<br/>Archive participant RPC as RPC App->>SelTxMeta: Lookup with key Note over App,SelTxMeta: If found → use it.<br/>If not → continue. App->>SelTxMeta: Look in prior txs in same ledger Note over App,SelTxMeta: If found → use it.<br/>If not → continue. opt Optionally use RPC App->>RPC: getLedgerEntries([key]) Note over App,RPC: Use if (lastModified < queryLedger)<br/>AND (rpcLatestSeen >= queryLedger).<br/>Otherwise continue. end App->>LedgerMeta: Look in prior ledger meta Note over App,LedgerMeta: If found → use it.<br/>If checkpoint ledger not reached → continue to next ledger meta<br/>If checkpoint ledger is reached → continue to archive. App->>Archive: Download checkpoint from history archive and search Note over App,Archive: If found → use it.<br/>If not → does not exist.Why
The current fork testing experience utilising the stellar-cli has low granularity only at the boundaries of ledgers, and requires downloading full history archives and manually identifying footprints ahead of time, which is difficult to do well and a poor developer experience. This change enables the SDK to lazily fetch ledger entries on-demand from the most efficient source available, caching results locally for subsequent runs. Developers will be able to fork test against any ledger and transaction without pre-identifying the footprint.
The change uses a Ledger Meta Storage (SEP-54), an RPC, and a History Archive to collect ledger entries.
The change caches results in three layers. All raw files downloaded are cached in the system cache directory and reused across tests, across workspaces. All ledger entries found are cached in the system cache directory. All ledger entries found for the current workspace are cached in the
tests-snapshot-sourcedirectory intended to be committed so that CI runs reproducibly without needing to collect entries. Note that the format of that cache is not a ledger snapshot json file because for many tests running concurrently one file per ledger entry is easier to manage.Close #1448
Try it out
Add the following dependencies to your
Cargo.toml:Note: Requires
soroban-sdkv23.4.0or later.Example
Observing State Changes
The
TxSnapshotSourcelooks up state with transaction-level granularity when a transaction hash is provided allowing developers to debug a transaction by starting at the point just before the transaction. For example:Before a specific tx: Pass the tx hash to get state just before that tx executed:
End of ledger (after all txs): Use
Nonefor the tx_hash to get state at the end of the ledger:Different ledgers/txs: Update the test to investigate the state of the balance at each of the following ledgers and transactions to see how the transactions affected the balance. Check out the links to see the operations the transactions performed and how they align with the changes in balances observed.
Debugging with RUST_LOG
Enable logging to see which data sources are being queried and what entries are found:
Example output:
TODO
Thanks
Thanks @orbitlens for sharing the idea of using transaction meta as a way to collect recent state. Thanks to all the people who provided feedback to me about how they use the existing
stellar snapshot createfunctionality.