Skip to content

Commit f677ce8

Browse files
Accept custom snapshot sources in Env::from_ledger_snapshot (#1620)
### What Accept custom snapshot source input types as ledger snapshots through a new SnapshotSourceInput struct that wraps source, ledger_info, and snapshot parameters. Implement From<LedgerSnapshot> for SnapshotSourceInput to maintain backward compatibility. Cache anything loaded from the snapshot source and write it out for reuse. ### Why Enables Env to be initialized from different snapshot source types beyond just the LedgerSnapshot file, providing more flexibility for testing scenarios while keeping the existing API intact. The snapshot source is only used to load the data once, then it is loaded from the cache file for test reproducibility and speed. This will support hooking in more snapshot sources such as RPCs, history archives, and the meta lake which I am working on. For #1448 ### Merging Targeting `main`, but dependent on: - #1623
1 parent 500fa07 commit f677ce8

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

soroban-sdk/src/env.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ use crate::{
479479
testutils::{
480480
budget::Budget, default_ledger_info, Address as _, AuthSnapshot, AuthorizedInvocation,
481481
ContractFunctionSet, EventsSnapshot, Generators, Ledger as _, MockAuth, MockAuthContract,
482-
Register, Snapshot, StellarAssetContract, StellarAssetIssuer,
482+
Register, Snapshot, SnapshotSourceInput, StellarAssetContract, StellarAssetIssuer,
483483
},
484484
Bytes, BytesN, ConstructorArgs,
485485
};
@@ -1597,16 +1597,22 @@ impl Env {
15971597
self.to_snapshot().write_file(p).unwrap();
15981598
}
15991599

1600-
/// Creates a new Env loaded with the [`LedgerSnapshot`].
1600+
/// Creates a new Env loaded with the snapshot source.
16011601
///
1602-
/// The ledger info and state in the snapshot are loaded into the Env.
1603-
pub fn from_ledger_snapshot(s: LedgerSnapshot) -> Env {
1602+
/// The ledger info and state from the snapshot source are loaded into the Env.
1603+
pub fn from_ledger_snapshot(input: impl Into<SnapshotSourceInput>) -> Env {
1604+
let SnapshotSourceInput {
1605+
source,
1606+
ledger_info,
1607+
snapshot,
1608+
} = input.into();
1609+
16041610
Env::new_for_testutils(
16051611
EnvTestConfig::default(), // TODO: Allow setting the config.
1606-
Rc::new(s.clone()),
1612+
source,
16071613
None,
1608-
Some(s.ledger_info()),
1609-
Some(Rc::new(s.clone())),
1614+
ledger_info,
1615+
snapshot,
16101616
)
16111617
}
16121618

soroban-sdk/src/testutils.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ use soroban_ledger_snapshot::LedgerSnapshot;
2525

2626
pub use crate::env::EnvTestConfig;
2727

28+
/// Trait for providing ledger data to the test environment.
29+
///
30+
/// Implement this trait to create custom snapshot sources that load ledger state
31+
/// from sources other than [`LedgerSnapshot`] files, such as RPC endpoints,
32+
/// history archives, or in-memory data structures.
33+
///
34+
/// Use with [`SnapshotSourceInput`] and [`Env::from_ledger_snapshot`] to initialize
35+
/// a test environment from a custom source.
36+
pub use crate::env::internal::storage::SnapshotSource;
37+
38+
/// Error type returned by [`SnapshotSource::get`].
39+
///
40+
/// Required for implementing custom snapshot sources.
41+
pub use crate::env::internal::HostError;
42+
2843
pub trait Register {
2944
fn register<'i, I, A>(self, env: &Env, id: I, args: A) -> crate::Address
3045
where
@@ -605,3 +620,75 @@ impl StellarAssetContract {
605620
self.asset.clone()
606621
}
607622
}
623+
624+
/// Input for creating an [`Env`] from a custom snapshot source.
625+
///
626+
/// This struct enables [`Env::from_ledger_snapshot`] to accept custom snapshot
627+
/// source types beyond [`LedgerSnapshot`], providing flexibility for testing
628+
/// scenarios that load ledger state from different sources such as RPC endpoints,
629+
/// history archives, or in-memory data structures.
630+
///
631+
/// # Fields
632+
///
633+
/// * `source` - A snapshot source implementing the [`SnapshotSource`] trait.
634+
/// This is used to load ledger entries on demand during test execution.
635+
///
636+
/// * `ledger_info` - Optional ledger info to initialize the environment with.
637+
/// If `None`, default test ledger info is used.
638+
///
639+
/// * `snapshot` - Optional [`LedgerSnapshot`] used as the base for capturing
640+
/// state changes. When the test completes, modified entries are written to
641+
/// this snapshot. If `None`, a new empty snapshot is created.
642+
///
643+
/// # Example
644+
///
645+
/// ```
646+
/// use soroban_sdk::testutils::{SnapshotSource, SnapshotSourceInput, HostError};
647+
/// use soroban_sdk::xdr::{LedgerEntry, LedgerKey};
648+
/// use soroban_sdk::Env;
649+
/// use std::rc::Rc;
650+
///
651+
/// struct MyCustomSource;
652+
///
653+
/// impl SnapshotSource for MyCustomSource {
654+
/// fn get(
655+
/// &self,
656+
/// key: &Rc<LedgerKey>,
657+
/// ) -> Result<Option<(Rc<LedgerEntry>, Option<u32>)>, HostError> {
658+
/// // Return None for keys not found, or Some((entry, live_until_ledger))
659+
/// Ok(None)
660+
/// }
661+
/// }
662+
///
663+
/// let input = SnapshotSourceInput {
664+
/// source: Rc::new(MyCustomSource),
665+
/// ledger_info: None,
666+
/// snapshot: None,
667+
/// };
668+
/// let env = Env::from_ledger_snapshot(input);
669+
/// ```
670+
pub struct SnapshotSourceInput {
671+
pub source: Rc<dyn SnapshotSource>,
672+
pub ledger_info: Option<LedgerInfo>,
673+
pub snapshot: Option<Rc<LedgerSnapshot>>,
674+
}
675+
676+
/// Converts a [`LedgerSnapshot`] into a [`SnapshotSourceInput`].
677+
///
678+
/// This conversion maintains backward compatibility with the existing API,
679+
/// allowing [`LedgerSnapshot`] to be used directly with [`Env::from_ledger_snapshot`].
680+
///
681+
/// The [`LedgerSnapshot`] is wrapped in an [`Rc`] and used for all three fields:
682+
/// - As the snapshot source for loading ledger entries
683+
/// - To provide the ledger info for the environment
684+
/// - As the base snapshot for capturing state changes
685+
impl From<LedgerSnapshot> for SnapshotSourceInput {
686+
fn from(s: LedgerSnapshot) -> Self {
687+
let s = Rc::new(s);
688+
Self {
689+
source: s.clone(),
690+
ledger_info: Some(s.ledger_info()),
691+
snapshot: Some(s),
692+
}
693+
}
694+
}

0 commit comments

Comments
 (0)