Skip to main content

magicblock_account/
patch.rs

1use core::fmt;
2
3use solana_clock::Slot;
4use solana_pubkey::Pubkey;
5
6use crate::{AccountMode, AccountSharedData, OwnedAccount, WritableAccount};
7
8const MAX_DATA_CHUNK_SIZE: usize = (u16::MAX - 256) as usize;
9
10/// Failure to apply an account lifecycle or ordering patch.
11#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
12pub enum AccountPatchError {
13    /// The requested account mode transition is not part of the lifecycle.
14    #[error("invalid account mode transition: {from:?} -> {to:?}")]
15    InvalidModeTransition {
16        /// Current account mode.
17        from: AccountMode,
18        /// Requested account mode.
19        to: AccountMode,
20    },
21    /// The requested slot regresses or fails the mode pair's advancement requirement.
22    #[error("invalid account slot transition: {from} -> {to}")]
23    InvalidSlotTransition {
24        /// Current account slot.
25        from: Slot,
26        /// Requested account slot.
27        to: Slot,
28    },
29}
30
31/// A bounded account-image patch.
32#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
33pub enum AccountFieldPatch {
34    /// Replaces the lamport balance.
35    Lamports(u64),
36    /// Replaces the owner.
37    Owner(Pubkey),
38    /// Writes bytes starting at `offset`, extending the account data if needed.
39    DataAt {
40        /// Byte offset into the current data buffer.
41        offset: usize,
42        /// Bytes to write.
43        data: Vec<u8>,
44    },
45    /// Replaces the account lifecycle.
46    Lifecycle {
47        /// Requested lifecycle mode.
48        mode: AccountMode,
49        /// Requested lifecycle slot.
50        slot: Slot,
51    },
52    /// Resizes the data buffer to an exact length, zero-filling when growing.
53    DataLen(usize),
54}
55
56impl AccountFieldPatch {
57    /// Applies this patch to `account`.
58    ///
59    /// The account methods mark dirtiness and preserve the writable invariants.
60    /// Invalid mode and slot transitions leave the account unchanged and return
61    /// their transition context.
62    pub fn apply(self, account: &mut AccountSharedData) -> Result<(), AccountPatchError> {
63        match self {
64            Self::Lamports(v) => account.set_lamports(v),
65            Self::Owner(v) => account.set_owner(v),
66            Self::Lifecycle { mode, slot } => return account.set_lifecycle(mode, slot),
67            Self::DataAt { offset, data } => account.set_data_at(offset, &data),
68            Self::DataLen(len) => account.resize(len, 0),
69        }
70        Ok(())
71    }
72
73    /// Decomposes an owned account into the ordered sequence of patches that
74    /// reconstruct its non-flag fields: lamports, lifecycle, owner, the exact
75    /// data length, then the data in `MAX_DATA_CHUNK_SIZE`-sized chunks.
76    pub fn sequence(account: OwnedAccount) -> Vec<Self> {
77        let mut sequence = Vec::with_capacity(5);
78        sequence.push(Self::Lamports(account.core.lamports));
79        sequence.push(Self::Lifecycle {
80            mode: account.core.mode,
81            slot: account.core.slot,
82        });
83        sequence.push(Self::Owner(account.core.owner));
84        sequence.push(Self::DataLen(account.data.len()));
85        let mut offset = 0;
86        for data in account.data.chunks(MAX_DATA_CHUNK_SIZE) {
87            sequence.push(Self::DataAt { offset, data: data.into() });
88            offset += data.len();
89        }
90        sequence
91    }
92}
93
94/// Concise, log-friendly rendering: scalar fields show their value, `DataAt`
95/// shows only `offset+len` (never the raw bytes).
96impl fmt::Debug for AccountFieldPatch {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        match self {
99            Self::Lamports(v) => write!(f, "lamports={v}"),
100            Self::Owner(v) => write!(f, "owner={v}"),
101            Self::Lifecycle { mode, slot } => write!(f, "lifecycle={mode:?}@{slot}"),
102            Self::DataAt { offset, data } => write!(f, "data@{offset}+{}", data.len()),
103            Self::DataLen(len) => write!(f, "data_len={len}"),
104        }
105    }
106}