-
Notifications
You must be signed in to change notification settings - Fork 0
feat: added internal built-in magic root program #23
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
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a8d6d8f
feat: added internal built-in magic root program
bmuddha 9f0cf9c
fix: move the recursive call check to authorize step
bmuddha fc749da
fix: identify immutable post-finalize accounts
bmuddha 08f757a
fix: check post-finalize actions on what they modified (#54)
Dodecahedr0x 6d13ac8
chore: update workspace lockfile
bmuddha f58e511
refactor: reuse account mutability in post-finalize
bmuddha 6cc17f1
fix: reject immutable writable post-finalize accounts
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
Large diffs are not rendered by default.
Oops, something went wrong.
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 |
|---|---|---|
| @@ -1,4 +1,20 @@ | ||
| # `magic-root-interface` | ||
|
|
||
| This crate defines the MagicRoot program id shared by the runtime and the | ||
| native program. | ||
| This crate defines the MagicRoot program id and `MagicRootInstruction` wire | ||
| schema shared by `magic-root-program` and `engine`. Instruction builders can | ||
| depend on the interface without linking the native program runtime. | ||
|
|
||
| `MagicRootInstruction::compose` prepends the writable target account and | ||
| serializes the instruction with wincode. `PostFinalize` also appends each | ||
| follow-up program id and its account metas. Signer bits are cleared in the outer | ||
| instruction; the native program supplies the declared follow-up signers when it | ||
| invokes each action. | ||
|
|
||
| `MagicRootInstruction::compose_account` builds the ordered patch sequence for a | ||
| complete account's non-flag fields and appends a finalization instruction that | ||
| installs its complete flag value without changing lamports. The underlying | ||
| patch sequence applies mode before slot so the program can validate | ||
| lifecycle-aware slot progression. Callers are responsible for composing current | ||
| account state. Internal create composition appends `PostFinalize` immediately | ||
| after finalization; MagicRoot relies on this ordering after authenticating the | ||
| engine authority and caller. |
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 |
|---|---|---|
| @@ -1,5 +1,73 @@ | ||
| #![doc = include_str!("../README.md")] | ||
|
|
||
| use solana_pubkey::declare_id; | ||
| use solana_account::{AccountFieldPatch, OwnedAccount, StateFlags}; | ||
| use solana_instruction::{AccountMeta, Instruction, error::InstructionError}; | ||
| use solana_pubkey::{Pubkey, declare_id}; | ||
| use wincode::{SchemaRead, SchemaWrite}; | ||
|
|
||
| declare_id!("MagicRootDRJ5atQjSJUxFjXzjeZXMADHUDznbk22gy"); | ||
|
|
||
| /// Instructions accepted by the MagicRoot built-in program. | ||
| #[derive(SchemaRead, SchemaWrite)] | ||
| pub enum MagicRootInstruction { | ||
| /// Apply a single-field patch to the target account. | ||
| Patch(AccountFieldPatch), | ||
| /// Replace the target's complete flag value and, when executable, load it | ||
| /// into the transaction's program cache. Does not change lamports. | ||
| Finalize(StateFlags), | ||
| /// Close the target account. | ||
| Delete, | ||
| /// Run follow-up instructions immediately after finalizing the same target | ||
| /// (e.g. initializing a freshly created account); each is invoked via CPI | ||
| /// against the accounts it declares. | ||
| PostFinalize(Vec<Instruction>), | ||
| } | ||
|
|
||
| impl MagicRootInstruction { | ||
| /// Composes this variant into an [`Instruction`] targeting the MagicRoot | ||
| /// program: prepends the target `account` meta, appends any metas the | ||
| /// variant requires, and serializes the instruction data. | ||
| pub fn compose(&self, account: Pubkey) -> Result<Instruction, InstructionError> { | ||
| let mut accounts = vec![AccountMeta::new(account, false)]; | ||
| self.extend_metas(&mut accounts); | ||
| // NOTE this code can never error, wincode serialization for instruction is infallible | ||
| let data = wincode::serialize(self).map_err(|_| InstructionError::BorshIoError)?; | ||
| Ok(Instruction { program_id: ID, accounts, data }) | ||
| } | ||
|
|
||
| /// Composes `account` into the ordered instruction sequence that patches | ||
| /// every non-flag field on `target`, then finalizes it with `account`'s | ||
| /// complete flag value. | ||
| pub fn compose_account( | ||
| target: Pubkey, | ||
| account: OwnedAccount, | ||
| ) -> Result<Vec<Instruction>, InstructionError> { | ||
| let flags = account.flags(); | ||
| let patches = AccountFieldPatch::sequence(account); | ||
| let mut instructions = Vec::with_capacity(patches.len() + 1); | ||
| for patch in patches { | ||
| instructions.push(Self::Patch(patch).compose(target)?); | ||
| } | ||
| instructions.push(Self::Finalize(flags).compose(target)?); | ||
| Ok(instructions) | ||
| } | ||
|
|
||
| /// Appends the extra account metas a variant requires. Only [`PostFinalize`] | ||
| /// contributes any: the program id and de-signed accounts of each follow-up | ||
| /// instruction. | ||
| /// | ||
| /// [`PostFinalize`]: MagicRootInstruction::PostFinalize | ||
| fn extend_metas(&self, accounts: &mut Vec<AccountMeta>) { | ||
| let Self::PostFinalize(ixs) = self else { | ||
| return; | ||
| }; | ||
| for ix in ixs { | ||
| accounts.push(AccountMeta::new_readonly(ix.program_id, false)); | ||
| let metas = ix.accounts.clone().into_iter().map(|mut meta| { | ||
| meta.is_signer = false; | ||
| meta | ||
| }); | ||
| accounts.extend(metas) | ||
| } | ||
| } | ||
| } |
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,32 @@ | ||
| [package] | ||
| name = "magic-root-program" | ||
|
|
||
| authors.workspace = true | ||
| edition.workspace = true | ||
| homepage.workspace = true | ||
| license.workspace = true | ||
| repository.workspace = true | ||
| rust-version.workspace = true | ||
| version.workspace = true | ||
|
|
||
| [dependencies] | ||
| magic-root-interface = { workspace = true } | ||
|
|
||
| nucleus = { workspace = true, features = ["tls"] } | ||
| wincode = { workspace = true } | ||
|
|
||
| solana-account = { workspace = true } | ||
| solana-instruction = { workspace = true } | ||
| solana-instruction-error = { workspace = true } | ||
| solana-program-runtime = { workspace = true } | ||
| solana-svm-log-collector = { workspace = true } | ||
| solana-transaction-context = { workspace = true } | ||
|
|
||
| [dev-dependencies] | ||
| solana-pubkey = { workspace = true } | ||
| solana-sdk-ids = { workspace = true } | ||
| solana-svm-callback = { workspace = true, features = ["agave-unstable-api"] } | ||
| solana-svm-feature-set = { workspace = true, features = ["agave-unstable-api"] } | ||
|
|
||
| [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,44 @@ | ||
| # `magic-root-program` | ||
|
|
||
| MagicRoot is the engine's privileged native program for patching, finalizing, | ||
| and closing accounts. Its wire schema and program id are defined by | ||
| `magic-root-interface`. | ||
|
|
||
| Every invocation must use the engine `AUTHORITY` as transaction payer/signer. | ||
| Direct transaction instructions are accepted. CPI is accepted only when the | ||
| immediate caller is registered in the transaction program cache as a builtin; | ||
| MagicRoot itself cannot be the caller. Native-loader account metadata alone does | ||
| not grant access. Caller authorization and decoding complete before target-state | ||
| authorization. The SVM's separate top-level-only privilege rule is unchanged. | ||
|
|
||
| ## Instructions | ||
|
|
||
| - `Patch` applies one `AccountFieldPatch`. Lamport changes are balanced against | ||
| the authority account, including no-op balance patches. Slot patches must | ||
| advance the stored slot; an equal slot is accepted only when an earlier patch | ||
| in the same transaction genuinely changed the account mode. Older slots are | ||
| always rejected. Data patches may produce at most 10 MiB of account data; | ||
| larger lengths return `InvalidRealloc`. Account mode transitions use the | ||
| policy owned by `AccountSharedData`: read-only and placeholder accounts may | ||
| transition to any mode except transient, transient accounts may resolve only | ||
| to read-only, and delegated accounts may transition only to transient. | ||
| Ephemeral accounts may close. Other mode changes are rejected. | ||
| - `Finalize` atomically installs the caller-supplied complete flag value and | ||
| loads an executable target into the transaction program cache. It does not | ||
| change lamports; failed executable loading rolls back the installed flags. | ||
| - `Delete` transitions read-only, placeholder, or ephemeral targets to | ||
| `AccountMode::Closed`, and accountsdb removes them during writeback. Modes that | ||
| cannot transition to closed are rejected. | ||
| - `PostFinalize` invokes follow-up instructions through native CPI and is | ||
| placed immediately after the target's `Finalize` by internal composers. It | ||
| rejects any immutable instruction account marked writable and any action that | ||
| targets MagicRoot itself. | ||
|
|
||
| After authority and caller checks pass, MagicRoot does not determine whether a | ||
| complete account image is stale. Callers must supply current state; slot and | ||
| lifecycle validation still apply. | ||
|
|
||
| Complete-account patch sequences apply mode before slot. `AccountSharedData` | ||
| marks mode dirty only when its value changes, so a no-op mode patch cannot make | ||
| an equal-slot replacement appear fresh. Rejection aborts the transaction, and | ||
| therefore rolls back every earlier field patch in that sequence. |
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,86 @@ | ||
| use solana_account::{ | ||
| AccountFieldPatch, AccountMode, ReadableAccount, StateFlags, WritableAccount, | ||
| }; | ||
| use solana_instruction_error::InstructionError; | ||
| use solana_program_runtime::{invoke_context::InvokeContext, loaded_programs::ProgramCacheEntry}; | ||
| use solana_svm_log_collector::ic_msg; | ||
| use solana_transaction_context::{IndexOfAccount, MAX_ACCOUNT_DATA_LEN}; | ||
|
|
||
| /// Applies a single-field patch to the target account. | ||
| pub(crate) fn patch( | ||
| ctx: &InvokeContext<'_, '_>, | ||
| target: IndexOfAccount, | ||
| patch: AccountFieldPatch, | ||
| ) -> Result<(), InstructionError> { | ||
| let mut account = ctx.transaction_context.accounts().try_borrow_mut(target)?; | ||
| ic_msg!(ctx, "MagicRoot: patch {:?}", patch); | ||
| let data_len = match &patch { | ||
| AccountFieldPatch::DataAt { offset, data } => { | ||
| let end = offset.checked_add(data.len()).ok_or(InstructionError::InvalidRealloc)?; | ||
| Some(account.data().len().max(end)) | ||
| } | ||
| AccountFieldPatch::DataLen(len) => Some(*len), | ||
| _ => None, | ||
| }; | ||
| if data_len.is_some_and(|len| len > MAX_ACCOUNT_DATA_LEN as usize) { | ||
| return Err(InstructionError::InvalidRealloc); | ||
| } | ||
| let old = account.lamports(); | ||
| if let Err(error) = patch.apply(&mut account) { | ||
| ic_msg!(ctx, "MagicRoot: {}", error); | ||
| return Err(InstructionError::InvalidArgument); | ||
| } | ||
| let new = account.lamports(); | ||
| let mut authority = ctx.transaction_context.accounts().try_borrow_mut(0)?; | ||
| // Balance target changes against the authority to preserve total lamports. | ||
| if new > old { | ||
| authority.checked_sub_lamports(new - old)?; | ||
| } else if new < old { | ||
| authority.checked_add_lamports(old - new)?; | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Loads an executable target into the transaction's program cache. | ||
| pub(crate) fn finalize( | ||
| ctx: &mut InvokeContext<'_, '_>, | ||
| target: IndexOfAccount, | ||
| flags: StateFlags, | ||
| ) -> Result<(), InstructionError> { | ||
| let mut account = ctx.transaction_context.accounts().try_borrow_mut(target)?; | ||
| account.set_flags(flags); | ||
| if !account.executable() { | ||
| return Ok(()); | ||
| } | ||
| let pubkey = ctx.transaction_context.get_key_of_account_at_index(target)?; | ||
| let entry = ProgramCacheEntry::new( | ||
| ctx.environment_config | ||
| .program_runtime_environments_for_execution | ||
| .get_env_for_execution() | ||
| .clone(), | ||
| account.data(), | ||
| ) | ||
| .map_err(|_| { | ||
| ic_msg!(ctx, "MagicRoot: program load failed {}", pubkey); | ||
| InstructionError::ProgramEnvironmentSetupFailure | ||
| })? | ||
| .into(); | ||
| ctx.program_cache_for_tx_batch.store_modified_entry(*pubkey, entry); | ||
| ic_msg!(ctx, "MagicRoot: finalized program load"); | ||
| Ok(()) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /// Marks the target account closed for removal from storage. | ||
| pub(crate) fn delete( | ||
| ctx: &InvokeContext<'_, '_>, | ||
| target: IndexOfAccount, | ||
| ) -> Result<(), InstructionError> { | ||
| let mut acc = ctx.transaction_context.accounts().try_borrow_mut(target)?; | ||
|
|
||
| if let Err(error) = acc.set_mode(AccountMode::Closed) { | ||
| ic_msg!(ctx, "MagicRoot: {}", error); | ||
| return Err(InstructionError::InvalidArgument); | ||
| } | ||
| ic_msg!(ctx, "MagicRoot: removed"); | ||
| Ok(()) | ||
| } | ||
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,25 @@ | ||
| #![doc = include_str!("../README.md")] | ||
|
|
||
| mod account; | ||
| mod post_finalize; | ||
| mod processor; | ||
| #[cfg(test)] | ||
| mod tests; | ||
|
|
||
| use solana_instruction_error::InstructionError; | ||
| use solana_program_runtime::invoke_context::InvokeContext; | ||
|
|
||
| /// Instruction-account index of the account a MagicRoot instruction operates on. | ||
| pub const TARGET_ACCOUNT_IDX: u16 = 0; | ||
|
|
||
| /// Executes a MagicRoot instruction: authorizes the caller, decodes the | ||
| /// instruction, then dispatches it to the matching handler. | ||
| pub fn process(ctx: &mut InvokeContext<'_, '_>) -> Result<(), InstructionError> { | ||
| processor::process(ctx) | ||
| } | ||
|
|
||
| #[allow(missing_docs)] | ||
| pub mod entrypoint { | ||
| use solana_program_runtime::declare_process_instruction; | ||
| declare_process_instruction!(MagicRootEntrypoint, 150, |ctx| { super::process(ctx) }); | ||
| } |
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,39 @@ | ||
| use solana_instruction::Instruction; | ||
| use solana_instruction_error::InstructionError; | ||
| use solana_program_runtime::invoke_context::InvokeContext; | ||
| use solana_svm_log_collector::ic_msg; | ||
|
|
||
| /// Runs the post-finalize follow-up instructions, invoking each action via CPI | ||
| /// while vouching for exactly the signers that action itself declares, then | ||
| /// rejects the whole instruction if an account exposed as writable did not end | ||
| /// in a mode this engine may mutate. | ||
| pub(crate) fn process( | ||
| ctx: &mut InvokeContext<'_, '_>, | ||
| actions: Vec<Instruction>, | ||
| ) -> Result<(), InstructionError> { | ||
| ic_msg!(ctx, "MagicRoot: post-finalize {} action(s)", actions.len()); | ||
| for action in actions { | ||
| let signers: Vec<_> = action | ||
| .accounts | ||
| .iter() | ||
| .filter_map(|meta| meta.is_signer.then_some(meta.pubkey)) | ||
| .collect(); | ||
| ctx.native_invoke(action, &signers)?; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| let instruction = ctx.transaction_context.get_current_instruction_context()?; | ||
| let count = instruction.get_number_of_instruction_accounts(); | ||
| for i in 0..count { | ||
| let index = instruction.get_index_of_instruction_account_in_transaction(i)?; | ||
| let account = ctx.transaction_context.accounts().try_borrow(index)?; | ||
| if instruction.is_instruction_account_writable(i)? && !account.mutable() { | ||
| ic_msg!( | ||
| ctx, | ||
| "MagicRoot: post-finalize rejected immutable writable account: {}", | ||
| ctx.transaction_context.get_key_of_account_at_index(index)? | ||
| ); | ||
| return Err(InstructionError::Immutable); | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
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.