-
Notifications
You must be signed in to change notification settings - Fork 0
feat: adds top level engine crate for global orchestration #24
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
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
b0fb4be
feat: adds top level engine crate for global orchestration
bmuddha 3ac638e
fix: update type name after typo fix
bmuddha 2be699e
fix: verify transaction count after replay
bmuddha bef65b7
test: cover recovery from accountsdb count lag
bmuddha a47acdb
fix: address engine review feedback
bmuddha 33a0d2a
fix: register compute budget builtin
bmuddha baf565c
refactor: adapt engine subscription receivers
bmuddha 1e063a0
refactor: require complete account replacements
bmuddha File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| [package] | ||
| name = "magicblock-engine" | ||
|
|
||
| authors.workspace = true | ||
| edition.workspace = true | ||
| homepage.workspace = true | ||
| license.workspace = true | ||
| repository.workspace = true | ||
| rust-version.workspace = true | ||
| version.workspace = true | ||
|
|
||
| [lib] | ||
| name = "engine" | ||
|
|
||
| [features] | ||
| testkit = ["keeper/testkit", "nucleus/testkit", "tokio/time"] | ||
|
|
||
| [dependencies] | ||
| keeper = { workspace = true } | ||
| ledger = { workspace = true } | ||
| magic-root-interface = { workspace = true } | ||
| magic-root-program = { workspace = true } | ||
| nucleus = { workspace = true, features = ["config", "shutdown"] } | ||
| processor = { workspace = true } | ||
|
|
||
| derive_more = { workspace = true } | ||
| num_cpus = { workspace = true } | ||
| oneshot = { workspace = true } | ||
| thiserror = { workspace = true } | ||
| tokio = { workspace = true, features = ["sync"] } | ||
| tracing = { workspace = true } | ||
| wincode = { workspace = true } | ||
|
|
||
| agave-transaction-view = { workspace = true } | ||
| solana-account = { workspace = true } | ||
| solana-compute-budget-program = { workspace = true, features = ["agave-unstable-api"] } | ||
| solana-instruction = { workspace = true } | ||
| solana-keypair = { workspace = true } | ||
| solana-message = { workspace = true } | ||
| solana-program-runtime = { workspace = true } | ||
| solana-pubkey = { workspace = true } | ||
| solana-sdk-ids = { workspace = true } | ||
| solana-signer = { workspace = true } | ||
| solana-system-program = { workspace = true, features = ["agave-unstable-api"] } | ||
| solana-transaction = { workspace = true, features = ["wincode"] } | ||
|
|
||
| [dev-dependencies] | ||
| keeper = { workspace = true, features = ["testkit"] } | ||
| magicblock-engine = { path = ".", features = ["testkit"] } | ||
| nucleus = { workspace = true, features = ["testkit"] } | ||
| v42-calculator-interface = { workspace = true, features = ["builder"] } | ||
|
|
||
| solana-instruction-error = { workspace = true } | ||
| solana-packet = { workspace = true } | ||
| solana-signer = { workspace = true } | ||
| solana-system-interface = { workspace = true, features = ["bincode"] } | ||
| solana-sysvar = { workspace = true } | ||
| tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "sync", "time"] } | ||
|
|
||
| [lints] | ||
| workspace = true |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # `magicblock-engine` | ||
|
|
||
| This crate exposes `Engine`, the consumer-facing handle over keeper state, | ||
| transaction sequencing, simulation, block pacing, recovery, and MagicRoot | ||
| account operations. It registers MagicRoot and the System Program as native | ||
| builtins before keeper opens startup state. | ||
|
|
||
| `Engine::signer` is always the local keypair. `Engine::authority` returns the | ||
| configured remote authority for a replica, or the local identity when no | ||
| override is configured. Replication uses that distinction to sign locally while | ||
| authenticating its immediate upstream. | ||
|
|
||
| ## Account replacement | ||
|
|
||
| `AccountAccessor::{create, update}` composes complete-account MagicRoot patch | ||
| transactions. Replacement slots are monotonic: a newer slot is accepted, an | ||
| equal slot requires a genuine account-mode transition, and an older slot is | ||
| rejected even when the mode changes. Failed replacements are transactionally | ||
| rolled back. Complete-account patch sequences cover non-flag fields, while | ||
| finalization atomically installs the caller-supplied complete flag value without | ||
| changing lamports. Callers are responsible for supplying current state; later | ||
| replacements remain subject to the account's slot and lifecycle rules. `create` | ||
| appends any `PostFinalize` actions immediately after finalization in the same | ||
| transaction. Magicblock construction rejects instruction, address, account-meta, | ||
| and instruction-data lengths that cannot be represented by the V1 wire fields. | ||
|
|
||
| ## Startup and recovery | ||
|
|
||
| Keeper restores an accountsdb snapshot when the active store is corrupt, its | ||
| sealed superblock trails the retained ledger, or its committed transaction count | ||
| trails the ledger's durable count. Accountsdb's count is a checkpoint high-water | ||
| mark, so a count ahead of the locally retained ledger is current, including for | ||
| snapshots staged by a replication follower. Superblock lag remains recoverable | ||
| independently of the counters. | ||
|
|
||
| If accountsdb then trails the ledger tip, `Engine::new` replays retained entries | ||
| from the successor of its sealed snapshot through a temporary sequencer. Replay | ||
| quiesces at superblock seals and compares the reconstructed checksum with the | ||
| recorded seal. A mismatch returns `ReplayError::StateMismatch`. Current state | ||
| opens without replay when its slot and transaction count are each at least the | ||
| ledger values. After replay actually runs, the final transaction counts must be | ||
| equal or startup returns `ReplayError::StateMismatch`. | ||
|
|
||
| Internal pacing appends one reset marker at the current slot and clears | ||
| chain-mirrored volatile accounts before the pacemaker task starts. Internal | ||
| system accounts remain available. Replicas use external pacing and retain | ||
| restored volatile state. External block producers supply the slot and timestamp; | ||
| the sequencer overwrites hash-chain metadata with its locally computed hash and | ||
| parent. | ||
|
|
||
| ## Shutdown | ||
|
|
||
| Shutdown behavior follows the pacing source. Internal pacing publishes a final | ||
| block and flushes durable state. External pacing flushes the durable cursor | ||
| before writing `CURRENT/volatile.db`, allowing the next open and replication | ||
| handshake to resume from matching state. The pacemaker holds the sequencer | ||
| barrier while issuing a terminal ledger sync, which closes the appender and | ||
| reader workers without waiting for every engine handle to be dropped. | ||
|
|
||
| The embedding service retains the `ShutdownManager` passed to `Engine::new` and | ||
| calls `terminate` after stopping external ingress. The manager stops the | ||
| replication client, pacemaker, sequencer, and backing services in order. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| //! Account- and transaction-scoped operation facades. | ||
|
|
||
| use std::{sync::atomic::Ordering, time::Duration}; | ||
|
|
||
| use keeper::{ExecutionRecord, TransactionView}; | ||
| use magic_root_interface::MagicRootInstruction; | ||
| use processor::{SequencerMessage, Simulation, SimulatorMessage}; | ||
| use solana_account::OwnedAccount; | ||
| use solana_instruction::Instruction; | ||
| use solana_pubkey::Pubkey; | ||
| use solana_transaction::TransactionResult; | ||
| use tokio::time; | ||
|
|
||
| use crate::{Engine, error::EngineError, error::Result, transaction}; | ||
|
|
||
| /// Upper bound on awaiting a submitted transaction's committed result. | ||
| const EXECUTION_TIMEOUT: Duration = Duration::from_secs(8); | ||
|
|
||
| /// Account-scoped operations bound to a single `pubkey`. | ||
| pub struct AccountAccessor<'a> { | ||
| pub(crate) pubkey: Pubkey, | ||
| pub(crate) engine: &'a Engine, | ||
| } | ||
|
|
||
| /// Transaction-submission operations bound to an engine instance. | ||
| pub struct TransactionAccessor<'a> { | ||
| pub(crate) engine: &'a Engine, | ||
| pub(crate) transaction: TransactionView, | ||
| } | ||
|
|
||
| impl AccountAccessor<'_> { | ||
| /// Creates the account by patching in every field and finalizing it, | ||
| /// optionally running follow-up `actions` once it is finalized. | ||
| pub async fn create( | ||
| &self, | ||
| acc: impl Into<OwnedAccount>, | ||
| actions: Option<Vec<Instruction>>, | ||
| ) -> Result<()> { | ||
| let mut instructions = MagicRootInstruction::compose_account(self.pubkey, acc.into())?; | ||
| if let Some(actions) = actions { | ||
| instructions.push(MagicRootInstruction::PostFinalize(actions).compose(self.pubkey)?); | ||
| } | ||
| self.execute(instructions).await | ||
| } | ||
|
|
||
| /// Updates the account by patching in every field of `account` | ||
| pub async fn update(&self, acc: impl Into<OwnedAccount>) -> Result<()> { | ||
| let instructions = MagicRootInstruction::compose_account(self.pubkey, acc.into())?; | ||
| self.execute(instructions).await | ||
| } | ||
|
|
||
| /// Closes the account. | ||
| pub async fn delete(&self) -> Result<()> { | ||
| let instructions = vec![MagicRootInstruction::Delete.compose(self.pubkey)?]; | ||
| self.execute(instructions).await | ||
| } | ||
|
|
||
| /// Composes the instructions into a signed engine transaction, executes it, | ||
| /// and flattens the committed transaction result into the engine error type. | ||
| async fn execute(&self, instructions: Vec<Instruction>) -> Result<()> { | ||
| let txn = transaction::magicblock(&instructions, self.engine)?; | ||
| self.engine.transaction(txn)?.execute().await?.map_err(Into::into) | ||
| } | ||
| } | ||
|
|
||
| impl TransactionAccessor<'_> { | ||
| /// Submits `transaction` for execution and awaits its committed result. | ||
| /// A timeout does not cancel the submitted transaction. | ||
| pub async fn execute(self) -> Result<TransactionResult<()>> { | ||
| if self.engine.terminating.load(Ordering::Acquire) { | ||
| return Err(EngineError::ShuttingDown); | ||
| } | ||
| let signature = self.transaction.signatures()[0]; | ||
| let msg = SequencerMessage::Transaction(self.transaction); | ||
| let rx = self.engine.transactions().subscribe_signature(signature).await; | ||
| self.engine.sequencer.send(msg).await?; | ||
| let status = time::timeout(EXECUTION_TIMEOUT, rx) | ||
| .await | ||
| .map_err(|_| EngineError::TransactionTimeout)? | ||
| .map_err(|e| e.to_string())?; | ||
| Ok(status.result) | ||
| } | ||
|
|
||
| /// Submits `transaction` for execution without awaiting its result. | ||
| pub async fn schedule(self) -> Result<()> { | ||
| if self.engine.terminating.load(Ordering::Acquire) { | ||
| return Err(EngineError::ShuttingDown); | ||
| } | ||
| let msg = SequencerMessage::Transaction(self.transaction); | ||
| self.engine.sequencer.send(msg).await.map_err(Into::into) | ||
| } | ||
|
|
||
| /// Simulates `transaction` against current state without committing it. | ||
| pub async fn simulate(self) -> Result<TransactionResult<ExecutionRecord>> { | ||
| if self.engine.terminating.load(Ordering::Acquire) { | ||
| return Err(EngineError::ShuttingDown); | ||
| } | ||
| let (response, rx) = oneshot::channel(); | ||
| let msg = SimulatorMessage::Transaction(Simulation { | ||
| transaction: self.transaction, | ||
| response, | ||
| }); | ||
| self.engine.sequencer.simulation.send(msg).await?; | ||
| rx.await.map_err(Into::into) | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.