Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
cdc7199
snapshot source using meta, rpc, archives
leighmcculloch Dec 19, 2025
7532b09
add local quickstart network configuration
leighmcculloch Jan 7, 2026
0ec1604
change .lock pattern to *.lock
leighmcculloch Jan 7, 2026
f4b20b9
update local network meta storage url to meta-archive
leighmcculloch Jan 7, 2026
2d8f45e
add stellar rpc client and transaction submission test
leighmcculloch Jan 7, 2026
94209c2
move balance prints into conditional branches
leighmcculloch Jan 7, 2026
0714455
compute and deploy native stellar asset contract
leighmcculloch Jan 7, 2026
3718a94
update snapshot fixtures to ledger 745 and restructure tests
leighmcculloch Jan 7, 2026
2866d30
update snapshot source files and test expectations
leighmcculloch Jan 7, 2026
5843a0b
remove snapshot-source-live crate and reformat tests
leighmcculloch Feb 16, 2026
c06c997
consolidate fetch crates into snapshot-source-tx
leighmcculloch Feb 16, 2026
0e6c2b7
add rpc_url param to mainnet and default urls for testnet/local
leighmcculloch Feb 20, 2026
feb9a1e
Merge branch 'main' into snapshot-source-tx
leighmcculloch Feb 22, 2026
b853722
update snapshots to protocol 25 and new entry format
leighmcculloch Feb 23, 2026
340d088
update test to use mainnet
leighmcculloch Feb 26, 2026
4ff511f
use hex::encode to format transaction hash
leighmcculloch Feb 26, 2026
1b4d0ab
add balance verification SQL query and CSV data
leighmcculloch Feb 26, 2026
8a7a316
cache cargo metadata workspace root in OnceLock
leighmcculloch Feb 27, 2026
1f9bfb7
add balance verification SQL query and CSV data
leighmcculloch Feb 27, 2026
75b823e
simplify balance query by removing CTEs
leighmcculloch Feb 27, 2026
d83cb6a
Merge branch 'main' into snapshot-source-tx
leighmcculloch Feb 27, 2026
de2d64d
add timing measurement to test_internal
leighmcculloch Feb 28, 2026
a88f6fb
narrow .lock gitignore to tests-snapshot-source
leighmcculloch Mar 3, 2026
3b69f4e
remove dead is_checkpoint_ledger function
leighmcculloch Mar 3, 2026
4e00659
change &PathBuf params to &Path in fetcher methods
leighmcculloch Mar 3, 2026
9c8f734
Merge branch 'main' into snapshot-source-tx
leighmcculloch Mar 16, 2026
b4a3fd5
Merge remote-tracking branch 'origin/main' into snapshot-source-tx
leighmcculloch Jun 1, 2026
d930039
Add unit tests for snapshot-source-tx deterministic logic
leighmcculloch Jun 1, 2026
f89e422
Update fork test snapshots for protocol 26
leighmcculloch Jun 1, 2026
6cc6d85
Address code-review findings in snapshot-source-tx
leighmcculloch Jun 1, 2026
895c404
Fix cargo-deny bans: minimize Cargo.lock churn and skip thiserror dup
leighmcculloch Jun 1, 2026
b778475
Add committed macro-expansion for the test_fork crate
leighmcculloch Jun 1, 2026
48107c5
Exclude unpublished soroban-ledger-snapshot-source-tx from semver-checks
leighmcculloch Jun 1, 2026
8c305c4
Allow licenses required by snapshot-source-tx dependencies
leighmcculloch Jun 2, 2026
aae78bf
Merge remote-tracking branch 'origin/main' into snapshot-source-tx
leighmcculloch Jun 2, 2026
f0d70a0
Update test_spec_shaking_v2 expansion for merged lockfile
leighmcculloch Jun 2, 2026
8e22ed3
Address PR review: safe cleanups for snapshot-source-tx
leighmcculloch Jun 2, 2026
09e412b
Address PR review #1 and #2: testnet meta URL + RPC usability
leighmcculloch Jun 2, 2026
2a4b06f
Keep soroban-ledger-snapshot-source-tx publishable
leighmcculloch Jun 2, 2026
ddf9e2c
Take testnet start date instead of full meta_url
leighmcculloch Jun 2, 2026
65ec7ed
Fix RustSec advisories via minimal dependency bumps
leighmcculloch Jun 2, 2026
3ef37d2
Revert "Fix RustSec advisories via minimal dependency bumps"
leighmcculloch Jun 2, 2026
53afdac
Raise MSRV to 1.93.0 and re-apply RustSec advisory fixes
leighmcculloch Jun 2, 2026
3f183c8
Always build external example/contract repos with latest stable Rust
leighmcculloch Jun 2, 2026
1e9407b
Revert MSRV 1.93 bump and external-repo toolchain override
leighmcculloch Jun 2, 2026
d677e5b
Merge origin/main into snapshot-source-tx
leighmcculloch Jun 3, 2026
9c60137
Minimize Cargo.lock churn to fix cargo-deny bans
leighmcculloch Jun 3, 2026
d1146e3
Regenerate spec-shaking-v2 expanded test for fixed lockfile
leighmcculloch Jun 3, 2026
c7198e4
Merge remote-tracking branch 'origin/main' into snapshot-source-tx
leighmcculloch Jun 16, 2026
4a2c5ca
Address dependency hygiene review comments
leighmcculloch Jun 16, 2026
908d667
Address claude review feedback on snapshot-source-tx
leighmcculloch Jun 16, 2026
8124d5d
Merge remote-tracking branch 'origin/main' into snapshot-source-tx
leighmcculloch Jun 18, 2026
cd1b05f
Regenerate fork example test snapshot for protocol 27
leighmcculloch Jun 18, 2026
b27b611
Safely unpack history-archive hash prefixes
leighmcculloch Jun 18, 2026
eab69bb
Surface V1 tx_changes as both before and after changes
leighmcculloch Jun 18, 2026
293fc5a
Bump soroban-ledger-snapshot-source-tx dep version to 27.0.0-rc.1
leighmcculloch Jun 18, 2026
c7a6b1f
Update README install example to v27
leighmcculloch Jun 18, 2026
5241f89
Drop Rust version mention from cache lock comment
leighmcculloch Jun 18, 2026
153ac50
Derive meta object paths from SEP-54 storage config
leighmcculloch Jun 18, 2026
a55e4d3
Only cache usable RPC responses
leighmcculloch Jun 18, 2026
6f62b7a
Merge remote-tracking branch 'origin/main' into snapshot-source-tx
claude Jul 15, 2026
a164afc
Pin serde to 1.0.210 (via camino 1.1.9) to match main's build
claude Jul 16, 2026
77fb9f6
Merge branch 'main' into snapshot-source-tx
leighmcculloch Jul 16, 2026
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
2,255 changes: 1,947 additions & 308 deletions Cargo.lock

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ resolver = "2"
members = [
"soroban-sdk",
"soroban-sdk-macros",
"cache-to-file",
"soroban-meta",
"soroban-spec",
"soroban-spec-rust",
"soroban-ledger-snapshot",
"soroban-token-sdk",
"soroban-token-spec",
"stellar-asset-spec",

"soroban-ledger-fetch-from/rpc",
"soroban-ledger-fetch-from/meta-storage",
"soroban-ledger-fetch-from/history-archive",
"soroban-ledger-fetch",
"soroban-ledger-snapshot-source-live",
"soroban-ledger-snapshot-source-tx",

"tests/*",
]

[workspace.package]
version = "23.4.0"
rust-version = "1.84.0"
rust-version = "1.85.0"

[workspace.dependencies]
soroban-sdk = { version = "23.4.0", path = "soroban-sdk" }
Expand All @@ -29,6 +38,14 @@ soroban-token-sdk = { version = "23.4.0", path = "soroban-token-sdk" }
soroban-token-spec = { version = "23.4.0", path = "soroban-token-spec" }
stellar-asset-spec = { version = "23.4.0", path = "stellar-asset-spec" }

cache-to-file = { version = "0.0.1", path = "cache-to-file" }
soroban-ledger-fetch-from-rpc = { version = "23.4.0", path = "soroban-ledger-fetch-from/rpc" }
soroban-ledger-fetch-from-meta-storage = { version = "23.4.0", path = "soroban-ledger-fetch-from/meta-storage" }
soroban-ledger-fetch-from-history-archive = { version = "23.4.0", path = "soroban-ledger-fetch-from/history-archive" }
soroban-ledger-fetch = { version = "23.4.0", path = "soroban-ledger-fetch" }
soroban-ledger-snapshot-source-live = { version = "23.4.0", path = "soroban-ledger-snapshot-source-live" }
soroban-ledger-snapshot-source-tx = { version = "23.4.0", path = "soroban-ledger-snapshot-source-tx" }

[workspace.dependencies.soroban-env-common]
version = "=23.0.1"
#git = "https://github.com/stellar/rs-soroban-env"
Expand Down
15 changes: 15 additions & 0 deletions cache-to-file/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "cache-to-file"
description = "File caching utilities with locking"
homepage = "https://github.com/stellar/rs-soroban-sdk"
repository = "https://github.com/stellar/rs-soroban-sdk"
authors = ["Stellar Development Foundation <info@stellar.org>"]
readme = "../README.md"
license = "Apache-2.0"
version = "0.0.1"
edition = "2021"
rust-version.workspace = true

[dependencies]
thiserror = "2.0"
fs2 = "0.4"
49 changes: 49 additions & 0 deletions cache-to-file/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::Path;

use fs2::FileExt;

// TODO: Move this crate to its own repo.

/// Cache a file, collecting bytes if not present, and return a reader
pub fn cache<P, C, CE>(path: P, collect: C) -> Result<impl Read, CacheError<CE>>
where
P: AsRef<Path>,
C: FnOnce(&mut dyn Write) -> Result<(), CE>,
{
let path = path.as_ref();

// Fast path: if file already exists, just read it (no lock needed).
// Slow path: acquire lock, check again, collect and write if needed.
if !path.exists() {
let lock_path = path.with_extension("lock");
let lock_file = File::create(&lock_path).map_err(CacheError::Io)?;
lock_file.lock_exclusive().map_err(CacheError::Io)?;

if !path.exists() {
// Write atomically via temp file and rename.
let temp_path = path.with_extension("dl");
{
let mut temp = File::create(&temp_path).map_err(CacheError::Io)?;
collect(&mut temp).map_err(CacheError::Collector)?;
temp.sync_all().map_err(CacheError::Io)?;
}
fs::rename(&temp_path, path).map_err(CacheError::Io)?;
}
}

// Open data file for reading.
let file = File::open(path).map_err(CacheError::Io)?;
Ok(file)
}

/// Error type for cache operations
#[derive(Debug, thiserror::Error)]
pub enum CacheError<CE> {
#[error("collector error: {0}")]
Collector(CE),
#[error("io error: {0}")]
#[allow(dead_code)]
Io(#[from] std::io::Error),
}
19 changes: 19 additions & 0 deletions soroban-ledger-fetch-from/history-archive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "soroban-ledger-fetch-from-history-archive"
description = "Client for accessing Stellar History Archives to retrieve ledger snapshots."
homepage = "https://github.com/stellar/rs-soroban-sdk"
repository = "https://github.com/stellar/rs-soroban-sdk"
authors = ["Stellar Development Foundation <info@stellar.org>"]
readme = "../README.md"
license = "Apache-2.0"
version.workspace = true
edition = "2021"
rust-version.workspace = true

[dependencies]
stellar-xdr = { workspace = true, features = ["curr", "std"] }
thiserror = "1.0"
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls"] }
flate2 = { version = "1.0", features = ["rust_backend"] }
serde = { version = "1.0.0", features = ["derive"] }
serde_json = "1.0.0"
117 changes: 117 additions & 0 deletions soroban-ledger-fetch-from/history-archive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use flate2::read::GzDecoder;
use std::io::{self, copy, Cursor, Write};
use stellar_xdr::curr::{self as xdr, Frame, Limited, ReadXdr};

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("downloading history: {0}")]
DownloadingHistory(reqwest::Error),

#[error("downloading history: got status code {0}")]
DownloadingHistoryGotStatusCode(reqwest::StatusCode),

#[error("json decoding history: {0}")]
JsonDecodingHistory(serde_json::Error),

#[error("getting bucket: {0}")]
GettingBucket(reqwest::Error),

#[error("getting bucket: got status code {0}")]
GettingBucketGotStatusCode(reqwest::StatusCode),

#[error("streaming bucket: {0}")]
StreamingBucket(io::Error),

#[error("streaming history: {0}")]
StreamingHistory(io::Error),

#[error("xdr parsing error: {0}")]
Xdr(#[from] xdr::Error),
}

pub fn history(archive_url: &str, ledger: u32) -> Result<History, Error> {
let mut bytes = Vec::new();
get_history(archive_url, ledger, &mut bytes)?;
parse_history(Cursor::new(bytes))
}

pub fn get_history<W: Write + ?Sized>(
archive_url: &str,
ledger: u32,
writer: &mut W,
) -> Result<(), Error> {
let history_url = {
let ledger_hex = format!("{ledger:08x}");
let ledger_hex_0 = ledger_hex[0..=1].to_string();
let ledger_hex_1 = ledger_hex[2..=3].to_string();
let ledger_hex_2 = ledger_hex[4..=5].to_string();
format!("{archive_url}/history/{ledger_hex_0}/{ledger_hex_1}/{ledger_hex_2}/history-{ledger_hex}.json")
};
//eprintln!("url: {history_url}");

let mut response = reqwest::blocking::Client::new()
.get(&history_url)
.send()
.map_err(Error::DownloadingHistory)?;

if !response.status().is_success() {
return Err(Error::DownloadingHistoryGotStatusCode(response.status()));
}

copy(&mut response, writer).map_err(Error::StreamingHistory)?;
Ok(())
}

pub fn parse_history<R: std::io::Read>(reader: R) -> Result<History, Error> {
let history: History = serde_json::from_reader(reader).map_err(Error::JsonDecodingHistory)?;
Ok(history)
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct History {
pub current_ledger: u32,
pub current_buckets: Vec<HistoryBucket>,
pub hot_archive_buckets: Option<Vec<HistoryBucket>>,
pub network_passphrase: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryBucket {
pub curr: String,
pub snap: String,
}

pub fn get_bucket<W: Write + ?Sized>(
archive_url: &str,
bucket: &str,
writer: &mut W,
) -> Result<Option<u64>, Error> {
let bucket_0 = &bucket[0..=1];
let bucket_1 = &bucket[2..=3];
let bucket_2 = &bucket[4..=5];
let bucket_url =
format!("{archive_url}/bucket/{bucket_0}/{bucket_1}/{bucket_2}/bucket-{bucket}.xdr.gz");

let response = reqwest::blocking::Client::new()
.get(&bucket_url)
.send()
.map_err(Error::GettingBucket)?;

if !response.status().is_success() {
return Err(Error::GettingBucketGotStatusCode(response.status()));
}

let content_length = response.content_length();

let mut decoder = GzDecoder::new(response);
copy(&mut decoder, writer).map_err(Error::StreamingBucket)?;
Ok(content_length)
}

pub fn parse_bucket<'a, R: std::io::Read + 'a>(
reader: &'a mut Limited<R>,
) -> impl Iterator<Item = Result<Frame<xdr::BucketEntry>, xdr::Error>> + 'a {
Frame::<xdr::BucketEntry>::read_xdr_iter(reader)
}
29 changes: 29 additions & 0 deletions soroban-ledger-fetch-from/meta-storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "soroban-ledger-fetch-from-meta-storage"
description = "Download LedgerCloseMeta from SEP-54 compatible ledger storage."
homepage = "https://github.com/stellar/rs-soroban-sdk"
repository = "https://github.com/stellar/rs-soroban-sdk"
authors = ["Stellar Development Foundation <info@stellar.org>"]
readme = "../README.md"
license = "Apache-2.0"
version.workspace = true
edition = "2021"
rust-version.workspace = true

[features]
default = []
cli = ["dep:clap"]

[[bin]]
name = "soroban-ledger-meta-storage"
path = "src/bin/soroban-ledger-meta-storage/main.rs"
required-features = ["cli"]

[dependencies]
stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] }
thiserror = "1.0"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
zstd = "0.13"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "4.0", features = ["derive"], optional = true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use soroban_ledger_fetch_from_meta_storage::cli;
use std::env;

fn main() {
if let Err(e) = cli::run(env::args_os()) {
match e {
cli::Error::Clap(e) => e.exit(),
_ => {
eprintln!("Error: {e}");
std::process::exit(1);
}
}
}
}
96 changes: 96 additions & 0 deletions soroban-ledger-fetch-from/meta-storage/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! CLI module for downloading LedgerCloseMeta from Stellar public blockchain data.
//!
//! This module is only available when the `cli` feature is enabled.

use crate::ledger;
use clap::{Parser, ValueEnum};
use std::{ffi::OsString, fmt::Debug, io::Write};
use stellar_xdr::curr::{Limited, Limits, WriteXdr};

/// CLI tool to download LedgerCloseMeta from Stellar public blockchain data
#[derive(Parser, Debug, Clone)]
#[command(
author,
version,
about,
long_about = None,
disable_help_subcommand = true,
)]
pub struct Root {
/// Ledger sequence number to download
#[arg(short, long)]
sequence: u32,

/// Output format
#[arg(short, long, value_enum, default_value_t)]
format: Format,

/// Meta storage URL (SEP-54 compatible)
#[arg(
long,
default_value = "https://aws-public-blockchain.s3.us-east-2.amazonaws.com/v1.1/stellar/ledgers/pubnet"
)]
meta_url: String,
}

#[derive(Default, Clone, ValueEnum, Debug)]
pub enum Format {
#[default]
Json,
Xdr,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Clap(#[from] clap::Error),
#[error("failed to download ledger meta: {0}")]
Download(#[from] crate::Error),
#[error("failed to serialize to JSON: {0}")]
Json(#[from] serde_json::Error),
#[error("failed to write XDR: {0}")]
Xdr(#[from] stellar_xdr::curr::Error),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}

impl Root {
/// Run the CLI command.
///
/// ## Errors
///
/// If the command fails to execute.
pub fn run(&self) -> Result<(), Error> {
let meta = ledger(&self.meta_url, self.sequence)?;

match self.format {
Format::Json => {
let json = serde_json::to_string_pretty(&meta)?;
println!("{}", json);
}
Format::Xdr => {
let limits = Limits::none();
let mut stdout = std::io::stdout();
let mut limited_writer = Limited::new(&mut stdout, limits);
meta.write_xdr(&mut limited_writer)?;
stdout.flush()?;
}
}

Ok(())
}
}

/// Run the CLI with the given args.
///
/// ## Errors
///
/// If the input cannot be parsed or the command fails.
pub fn run<I, T>(args: I) -> Result<(), Error>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let root = Root::try_parse_from(args)?;
root.run()
}
Loading
Loading