Skip to content

Commit

Permalink
Add soroban_env_host::host::LegacyEpoch (#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon authored Apr 11, 2023
1 parent 1a130cc commit 8846543
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
83 changes: 83 additions & 0 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,82 @@ impl Default for DiagnosticLevel {
}
}

/// LegacyEpoch defines a set of _past_ (legacy) behaviour that we have logic in
/// the host to handle, that can be activated by the embedder to replay past
/// contracts "the same way they ran before".
///
/// Legacy epochs are added when some change in behaviour is detected by the
/// embedder, during the second phase of the embedder upgrading the host, when
/// the embedder performs a replay of some past transactions in order to expire
/// old versions of the host, and notices some behaviour that diverges: that
/// does not replay identically. At that point the _current_ behaviour is always
/// considered part of `LegacyEpoch:Current` and a new _earlier_ epoch is added
/// before it, that is named for the _previous_ behaviour.
///
/// To illustrate this with an example, first suppose that host versions are
/// being _sequentially numbered_ by the embedder as v74, v75, v76, v77, etc. We
/// do not assign such versions in the host itself, since the host does not know
/// precisely when the embedder takes updates (and in any case the embedder
/// needs to pin the entire dependency tree of versions the host's dependencies
/// are resolved to in the embedding).
///
/// Now suppose that host v75 accidentally shipped a bug in the binary search
/// function in `MeteredVector`, and suppose further that the embedder
/// accidentally recorded a transaction using that buggy binary search. So there
/// is a transaction labeled with host logic v75 that needs to run on bad binary
/// search to execute correctly.
///
/// Now suppose it's time for the embedder to upgrade. Upgrading only works when
/// the embedder embeds a _pair_ of host versions. Without loss of generality we
/// can assume they have v75 and v76, the network is running on v76, and so
/// their "upgrade" consists of expiring v75 (which is no longer recording new
/// transactions) and activating v77.
///
/// To do this, the embedder replays all transactions labeled with v75 on v76.
/// They will observe divergence on the transaction that relies on the bad
/// binary search. Once the embedder has to debugged that fact and isolated it,
/// they will want to add an enum entry `LegacyEpoch::BadBinSearch` to the host,
/// designating the _old_ behaviour. Then when replaying a v75 transaction, the
/// embedder will pass `LegacyEpoch::BadBinSearch`, and the host would be
/// modified to keep the old bad binary search logic under a guard like `if
/// self.is_in_legacy_epoch(LegacyEpoch::BadBinSearch) ... `.
///
/// Unfortunately such a change cannot be made to v76 since it's already frozen,
/// in the field and recording transactions: it's the version in the live
/// network. However, the change _can_ be made to the as-yet-unnumbered host
/// that will soon be designated v77, since that version is still not yet
/// finalized or deployed. So v77 will contain the legacy epoch-guarded copy of
/// the old, bad code path.
///
/// Schematically, the upgrade looks like this:
///
/// ```text
///
/// [v75: has only bad logic] (expiring)
/// ||
/// \/
/// [v76: has only good logic] (active)
/// ||
/// \/
/// [v77: has both, with epoch guard] (to be deployed)
///
///```
///
/// Once the embedder confirms that replay of all "v75 transactions" succeed on
/// host v77 emulating "the parts of v75 relevant to any actually-recorded
/// transactions" (as indicated with `LegacyEpoch::BadBinSearch`) the embedder
/// can do a release containing only hosts v76 and v77, and forget the rest of
/// v75.

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum LegacyEpoch {
#[default]
Current,
}

#[derive(Clone, Default)]
pub(crate) struct HostImpl {
legacy_epoch: RefCell<LegacyEpoch>,
source_account: RefCell<Option<AccountId>>,
ledger: RefCell<Option<LedgerInfo>>,
objects: RefCell<Vec<HostObject>>,
Expand Down Expand Up @@ -241,6 +315,7 @@ impl Host {
/// [`Env::get_contract_data`].
pub fn with_storage_and_budget(storage: Storage, budget: Budget) -> Self {
Self(Rc::new(HostImpl {
legacy_epoch: Default::default(),
source_account: RefCell::new(None),
ledger: RefCell::new(None),
objects: Default::default(),
Expand All @@ -260,6 +335,14 @@ impl Host {
}))
}

pub fn set_legacy_epoch(&self, legacy_epoch: LegacyEpoch) {
*self.0.legacy_epoch.borrow_mut() = legacy_epoch
}

pub fn is_in_legacy_epoch(&self, legacy_epoch: LegacyEpoch) -> bool {
*self.0.legacy_epoch.borrow() <= legacy_epoch
}

pub fn set_source_account(&self, source_account: AccountId) {
*self.0.source_account.borrow_mut() = Some(source_account);
}
Expand Down
2 changes: 1 addition & 1 deletion soroban-env-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ pub use host::testutils::call_with_suppressed_panic_hook;
pub use host::ContractFunctionSet;
pub use host::{
metered_map::MeteredOrdMap, metered_vector::MeteredVector, DiagnosticLevel, Host, HostError,
LedgerInfo,
LedgerInfo, LegacyEpoch,
};
pub use soroban_env_common::*;

0 comments on commit 8846543

Please sign in to comment.