From 3f611be779ed8181996c9cb4a50cdecda65054a4 Mon Sep 17 00:00:00 2001 From: Alessandro Manfredi Date: Wed, 18 Dec 2024 07:00:42 +0100 Subject: [PATCH] refactor(solana): refactors snapshotter comments --- .../src/contexts/calculate_root.rs | 46 ++++++++--- .../snapshotter/src/contexts/initialize.rs | 61 +++++++++------ .../snapshotter/src/contexts/subscribe.rs | 36 ++++++--- .../solana/programs/snapshotter/src/lib.rs | 53 +++++++++++-- .../programs/snapshotter/src/state/config.rs | 77 +++++++++++-------- 5 files changed, 192 insertions(+), 81 deletions(-) diff --git a/packages/solana/programs/snapshotter/src/contexts/calculate_root.rs b/packages/solana/programs/snapshotter/src/contexts/calculate_root.rs index 0d083ef9..73f3b8fe 100644 --- a/packages/solana/programs/snapshotter/src/contexts/calculate_root.rs +++ b/packages/solana/programs/snapshotter/src/contexts/calculate_root.rs @@ -2,14 +2,27 @@ use crate::*; #[derive(Accounts)] pub struct CalculateRoot<'info> { - // The `config` account will be accessed and potentially modified by this instruction. - // The `#[account(...)]` attribute macro applies constraints to this account: - // - `seeds = [Config::SEED_PREFIX]`: The account is derived from a program-derived address (PDA) - // using `Config::SEED_PREFIX` as the seed. This ensures that the correct PDA is referenced. - // - `bump`: Used in combination with the seeds to find the PDA. The `bump` ensures a valid PDA - // that is not already taken by another account. - // - `mut`: The `mut` keyword indicates that this account will be mutated (written to) - // during the instruction execution. + /// The `config` account holds the snapshotter's configuration and state. + /// + /// - **PDA Derivation**: The account is a PDA derived using the `Config::SEED_PREFIX` as the seed. This ensures + /// that the account is uniquely identified and securely accessed by the program. + /// + /// - **Bump Seed**: The `bump` attribute is used to derive the PDA alongside the seeds. It guarantees that the PDA + /// is valid and helps prevent collisions with other accounts. + /// + /// - **Mutability**: The `mut` keyword indicates that this account will be modified during the instruction execution. + /// Specifically, the `calculate_root` function updates fields within the `Config` account, such as the Merkle root, + /// finalization status, expected batch number, and nonce. + /// + /// ## Example Usage in `calculate_root` Function: + /// + /// ```rust + /// pub fn calculate_root(ctx: Context, batch: u64) -> Result<()> { + /// let config = &mut ctx.accounts.config; + /// // ... perform operations that modify `config` ... + /// } + /// ``` + /// #[account( seeds = [Config::SEED_PREFIX], bump, @@ -17,8 +30,19 @@ pub struct CalculateRoot<'info> { )] pub config: Account<'info, Config>, - // The `clock` field references the Sysvar `Clock` account, which provides the current - // cluster time, slot, epoch, etc. It is read-only and used for time-based logic within - // the instruction if necessary. + /// The `clock` Sysvar account provides access to the current cluster time, slot, epoch, etc. + /// + /// - **Purpose**: Useful for implementing time-based logic within the instruction, such as enforcing timeouts, + /// scheduling, or validating the timing of certain operations. + /// + /// - **Read-Only**: The `clock` account is read-only and cannot be modified by the program. It simply provides + /// information about the current state of the Solana cluster's timing parameters. + /// + /// ## Example Usage in `calculate_root` Function: + /// + /// ```rust + /// let current_epoch = ctx.accounts.clock.epoch; + /// ``` + /// pub clock: Sysvar<'info, Clock>, } diff --git a/packages/solana/programs/snapshotter/src/contexts/initialize.rs b/packages/solana/programs/snapshotter/src/contexts/initialize.rs index 93083f84..1237c763 100644 --- a/packages/solana/programs/snapshotter/src/contexts/initialize.rs +++ b/packages/solana/programs/snapshotter/src/contexts/initialize.rs @@ -2,28 +2,39 @@ use crate::*; #[derive(Accounts)] pub struct Initialize<'info> { - // The `owner` is a Signer account, indicating that whoever calls this instruction - // must sign the transaction with their private key. This ensures that the initializer - // has authority. - // `#[account(mut)]` is used here because the `payer` field below uses this account's - // lamports to fund the creation of the `config` account. + /// The `owner` is a Signer account, indicating that whoever calls this instruction + /// must sign the transaction with their private key. This ensures that the initializer + /// has authority. + /// + /// `#[account(mut)]` is used here because the `owner` account's lamports are used + /// to fund the creation of the `config` account. + /// + /// ## Attributes + /// + /// - **`mut`**: Marks the `owner` account as mutable, allowing its lamports to be debited. #[account(mut)] - /// The owner of the program. Must sign the transaction, and pays for the account rent. + /// The owner of the program. Must sign the transaction and pays for the account rent. pub owner: Signer<'info>, - // The `config` account will be created (initialized) by this instruction. - // The `init` attribute indicates that this account should be created (allocated and assigned) - // and initialized with the given seeds, bump, and space. - // - // The `payer = owner` parameter means the `owner` account pays for the rent and creation cost - // of this account. - // - // `seeds = [Config::SEED_PREFIX]` sets the PDA (program-derived address) seed that - // uniquely identifies the `config` account. The `bump` is used to find a valid PDA - // that isn't already taken. - // - // `space = Config::MAXIMUM_SIZE` sets the account size so that the Solana runtime - // allocates the right amount of space in the ledger for this account's data. + /// The `config` account will be created (initialized) by this instruction. + /// + /// The `init` attribute indicates that this account should be created (allocated and assigned) + /// and initialized with the given seeds, bump, and space. + /// + /// - **`payer = owner`**: The `owner` account pays for the rent and creation cost of this account. + /// - **`seeds = [Config::SEED_PREFIX]`**: Sets the PDA (program-derived address) seed that + /// uniquely identifies the `config` account. This ensures that the correct PDA is referenced. + /// - **`bump`**: Used in combination with the seeds to find the PDA. The `bump` ensures a valid PDA + /// that is not already taken by another account. + /// - **`space = Config::MAXIMUM_SIZE`**: Allocates the appropriate amount of space for the `config` account's data. + /// + /// ## Attributes + /// + /// - **`init`**: Indicates that this account should be initialized by the instruction. + /// - **`payer = owner`**: Specifies that the `owner` account will pay for the account's rent and creation. + /// - **`seeds = [Config::SEED_PREFIX]`**: Defines the seed used to derive the PDA for the `config` account. + /// - **`bump`**: Provides the bump seed necessary for PDA derivation. + /// - **`space = Config::MAXIMUM_SIZE`**: Allocates sufficient space for the `config` account's data. #[account( init, payer = owner, @@ -32,13 +43,17 @@ pub struct Initialize<'info> { space = Config::MAXIMUM_SIZE, )] /// The `config` account that stores program data needed for other instructions. + /// /// It is created on initialization, ensuring that all subsequent operations /// have a consistent place to store relevant configuration information. pub config: Account<'info, Config>, - // The `system_program` is the program that is responsible for creating and - // allocating accounts. It is necessary whenever new accounts need to be - // created and funded. - /// The standard system program, required here to create and fund the `config` account. + /// The `system_program` is the program responsible for creating and allocating accounts. + /// + /// It is necessary whenever new accounts need to be created and funded. + /// + /// ## Attributes + /// + /// - **Standard System Program**: Required here to create and fund the `config` account. pub system_program: Program<'info, System>, } diff --git a/packages/solana/programs/snapshotter/src/contexts/subscribe.rs b/packages/solana/programs/snapshotter/src/contexts/subscribe.rs index 50efbcdb..aee7fbce 100644 --- a/packages/solana/programs/snapshotter/src/contexts/subscribe.rs +++ b/packages/solana/programs/snapshotter/src/contexts/subscribe.rs @@ -2,20 +2,36 @@ use crate::*; #[derive(Accounts)] pub struct Subscribe<'info> { - // The `config` account represents a piece of the program's state, previously initialized and stored on-chain. - // By including `seeds = [Config::SEED_PREFIX]` and `bump`, this indicates that the `config` account's - // public key is derived using a Program-Derived Address (PDA) approach. The `bump` is an auto-calculated - // value that ensures the generated PDA does not collide with an existing account. - // - // The `mut` keyword means that this account will be modified during the execution of the instruction. - // Common modifications may include incrementing counters, logging subscription events, or updating other - // relevant fields that track user subscriptions. + /// The `config` account represents a piece of the program's state, previously initialized and stored on-chain. + /// By including `seeds = [Config::SEED_PREFIX]` and `bump`, this indicates that the `config` account's + /// public key is derived using a Program-Derived Address (PDA) approach. The `bump` is an auto-calculated + /// value that ensures the generated PDA does not collide with an existing account. + /// + /// The `mut` keyword means that this account will be modified during the execution of the instruction. + /// Common modifications may include incrementing counters, logging subscription events, or updating other + /// relevant fields that track user subscriptions. #[account( seeds = [Config::SEED_PREFIX], bump, mut )] - /// The `config` account that holds data relevant to the subscription logic. This - /// account is expected to be pre-initialized and possibly managed by earlier steps in the program. + /// The `config` account that holds data relevant to the subscription logic. + /// + /// This account is expected to be pre-initialized and possibly managed by earlier steps in the program. + /// + /// ## Attributes + /// + /// - **`seeds = [Config::SEED_PREFIX]`**: + /// - **Purpose**: Defines the seed used to derive the PDA for the `config` account. + /// - **Function**: Ensures that the account address is uniquely and deterministically derived based on the provided seed. + /// - **Security**: Prevents unauthorized accounts from masquerading as the `config` account by enforcing a specific derivation path. + /// + /// - **`bump`**: + /// - **Purpose**: A nonce used alongside the seeds to find a valid PDA that does not collide with existing accounts. + /// - **Function**: Anchor automatically calculates and provides the bump value required for PDA derivation. + /// + /// - **`mut`**: + /// - **Purpose**: Marks the `config` account as mutable, allowing its data to be modified during instruction execution. + /// - **Function**: Enables the program to update fields within the `Config` account, such as adding a new subscription. pub config: Account<'info, Config>, } diff --git a/packages/solana/programs/snapshotter/src/lib.rs b/packages/solana/programs/snapshotter/src/lib.rs index e16b5b6f..8035d439 100644 --- a/packages/solana/programs/snapshotter/src/lib.rs +++ b/packages/solana/programs/snapshotter/src/lib.rs @@ -34,29 +34,59 @@ pub mod snapshotter { // Determines how many accounts are processed per batch during root calculation. pub const BATCH_SIZE: usize = 10; - // Initializes the `Config` account when the program is first set up. - // Sets the root to a default value, marks it as not finalized, and - // sets the expected batch to zero, preparing the system for future operations. + /// Initializes the `Config` account when the program is first set up. + /// + /// This function sets the initial state of the `Config` account by: + /// - Setting the `root` to a default hash value. + /// - Marking the `root_finalized` flag as `false`. + /// - Setting the `expected_batch` to `0`. + /// - Initializing the `nonce` to `0`. + /// + /// These initializations prepare the system for future operations related to snapshotting. + /// + /// # Arguments + /// + /// * `ctx` - The context containing all the accounts required for initialization. + /// + /// # Returns + /// + /// * `Result<()>` - Returns `Ok(())` if successful, or an error otherwise. pub fn initialize(ctx: Context) -> Result<()> { let config = &mut ctx.accounts.config; + // Set the root to a default hash value (all zeros) config.root = Hash::default().to_bytes(); + // Mark the root as not finalized config.root_finalized = false; + // Set the expected batch to zero, indicating that the first batch to process is batch 0 config.expected_batch = 0; + // Initialize the nonce to zero, which can be used for versioning or replay protection config.nonce = 0; Ok(()) } - // Subscribes a new account to the `subscribed_accounts` list within the `Config`. - // This function ensures that the account is not already subscribed before adding it. + /// Subscribes a new account to the `subscribed_accounts` list within the `Config`. + /// + /// This function ensures that the account is not already subscribed before adding it to prevent duplicates. + /// + /// # Arguments + /// + /// * `ctx` - The context containing all the accounts required for subscription. + /// * `account_to_subscribe` - The public key of the account to be subscribed. + /// + /// # Returns + /// + /// * `Result<()>` - Returns `Ok(())` if successful, or an error otherwise. pub fn subscribe(ctx: Context, account_to_subscribe: Pubkey) -> Result<()> { let config = &mut ctx.accounts.config; + // Check if the account is already subscribed to prevent duplicates if config.subscribed_accounts.contains(&account_to_subscribe) { return Err(error!(ErrorCode::AccountAlreadySubscribed)); } + // Add the new account to the list of subscribed accounts config.subscribed_accounts.push(account_to_subscribe); Ok(()) @@ -79,37 +109,48 @@ pub mod snapshotter { pub fn calculate_root(ctx: Context, batch: u64) -> Result<()> { let config = &mut ctx.accounts.config; + // Step 1: Verify that the provided batch number matches the expected batch if batch != config.expected_batch { return Err(error!(ErrorCode::InvalidBatch)); } + // Determine if the current batch is the last batch let is_last_batch = ((config.subscribed_accounts.len() / BATCH_SIZE) as u64) == batch; + // Step 2: Verify the number of remaining accounts if (is_last_batch && ctx.remaining_accounts.len() > BATCH_SIZE) || (!is_last_batch && ctx.remaining_accounts.len() != BATCH_SIZE) { return Err(error!(ErrorCode::InvalidRemainingAccountsLength)); } + // Step 3: Finalize or prepare for the next batch if is_last_batch { + // If it's the last batch, finalize the root and reset for future operations config.root_finalized = true; config.expected_batch = 0; config.nonce += 1; } else { + // If not the last batch, prepare for the next batch config.root_finalized = false; config.expected_batch += 1; } let mut account_hashes = Vec::new(); + // Calculate the starting index for the current batch let start_index: usize = (batch * BATCH_SIZE as u64).try_into().unwrap(); + // Step 4: Iterate over the accounts in the current batch for (index, account) in ctx.remaining_accounts.iter().enumerate() { + // Verify that the account is indeed subscribed and in the correct order if *account.key != config.subscribed_accounts[start_index + index] { return Err(error!(ErrorCode::InvalidSubscribedAccount)); } + // Borrow the lamports (SOL balance) and data of the account for hashing let lamport_ref = account.lamports.borrow(); let data_ref = account.data.borrow(); + // Compute the hash of the account's state account_hashes.push(account_hasher( &account.key, **lamport_ref, @@ -119,8 +160,10 @@ pub mod snapshotter { )); } + // Step 5: Update the Merkle Mountain Range (MMR) with the new account hashes let mut mmr = MerkleMountainRange::from(Hash::new_from_array(config.root)); mmr.update_root(account_hashes)?; + // Store the updated root back into the `Config` config.root = mmr.root.to_bytes(); Ok(()) diff --git a/packages/solana/programs/snapshotter/src/state/config.rs b/packages/solana/programs/snapshotter/src/state/config.rs index 732f2bfb..def261a3 100644 --- a/packages/solana/programs/snapshotter/src/state/config.rs +++ b/packages/solana/programs/snapshotter/src/state/config.rs @@ -3,55 +3,68 @@ use crate::*; #[account] #[derive(Default)] pub struct Config { - // A list of Pubkeys (addresses) that are considered to be subscribed - // to something within the context of this program (e.g., updates, notifications, etc.). + /// A list of `Pubkey`s (public keys) representing accounts that are subscribed to updates, notifications, or other relevant events + /// within the context of this program. + /// + /// - **Purpose**: To keep track of all accounts that need to be monitored or included in snapshot calculations. + /// - **Usage**: Other instructions can reference this list to perform actions like notifying subscribers or including their data in computations. pub subscribed_accounts: Vec, - // A 32-byte value representing some form of root data. This could be, for example, a Merkle root - // used to verify the integrity of a dataset or something similar that needs to be referenced - // persistently across multiple instructions. + /// A 32-byte array representing a cryptographic root, such as a Merkle root. + /// + /// - **Purpose**: To verify the integrity of a dataset or to provide a reference point for subsequent operations. + /// - **Usage**: This root is used across multiple instructions to ensure consistency and validity of the data being processed. pub root: [u8; 32], - // A boolean flag indicating whether the `root` is finalized. If `true`, it might mean - // no further changes to the `root` are allowed, or that some condition has been met. + /// A boolean flag indicating whether the current `root` has been finalized. + /// + /// - **Purpose**: To determine if the `root` is in a stable state and no further modifications are expected. + /// - **Usage**: Finalized roots might trigger specific actions or prevent further changes to ensure data integrity. pub root_finalized: bool, - // An unsigned 64-bit integer that might represent an expected batch number or - // counter for a process that runs periodically or sequentially. + /// An unsigned 64-bit integer representing the expected batch number for root calculations. + /// + /// - **Purpose**: To manage and track the progression of batch processing, ensuring that roots are calculated in the correct sequence. + /// - **Usage**: Helps in synchronizing batch operations and preventing out-of-order processing. pub expected_batch: u64, - // An unsigned 64-bit integer that represent the number of calculated roots + /// An unsigned 64-bit integer serving as a nonce, representing the number of calculated roots. + /// + /// - **Purpose**: To provide a unique identifier for each root calculation cycle, aiding in versioning and replay protection. + /// - **Usage**: Incremented each time a new root is calculated, ensuring that each root is distinct and traceable. pub nonce: u64, } -// `impl Config` block provides associated functions for `Config`. impl Config { - // MAXIMUM_SIZE provides a static size allocation for this account’s storage. - // Anchor requires specifying account size, and this constant ensures that the - // account is allocated enough space for the data it holds. - // - // Explanation of each term: - // - 8 bytes for the account discriminator (Anchor uses an 8-byte prefix to identify account types) - // - 4 bytes for the `subscribed_accounts` vector's length (since the vector is variable-length, - // its length is stored as a 32-bit integer) - // - (32 * 256) bytes for the maximum number of `Pubkey`s we anticipate storing. Each `Pubkey` is 32 bytes, - // and we’re currently limiting ourselves to 256 of them. - // - 32 bytes for the `root` array - // - 1 byte for the boolean `root_finalized` - // - 8 bytes for the `expected_batch` (u64) - // - // In the future, if we need to store more than 256 `Pubkey`s, we might consider - // reallocation (using `realloc` in Anchor) or a PDA-based approach to store additional data. + /// `MAXIMUM_SIZE` defines the total byte size allocated for the `Config` account. + /// + /// Anchor requires specifying the exact account size to allocate storage on the blockchain. + /// This constant ensures that the account is allocated enough space for all its fields. + /// + /// **Breakdown of `MAXIMUM_SIZE`:** + /// - `8` bytes: Account discriminator (used by Anchor to identify account types). + /// - `4` bytes: Length prefix for the `subscribed_accounts` vector (`Vec`). + /// - `32 * 256` bytes: Maximum storage for `subscribed_accounts`. Each `Pubkey` is `32` bytes, and the vector is limited to `256` entries. + /// - `32` bytes: The `root` array. + /// - `1` byte: The `root_finalized` boolean flag. + /// - `8` bytes: The `expected_batch` (`u64`). + /// - `8` bytes: The `nonce` (`u64`). + /// + /// **Total**: `8 + 4 + (32 * 256) + 32 + 1 + 8 + 8 = 8 + 4 + 8192 + 32 + 1 + 8 + 8 = 82653` bytes. + /// + /// **Note**: The maximum number of `Pubkey`s (`256`) can be adjusted based on program requirements. If more are needed, + /// consider using dynamic allocation techniques or multiple accounts to store additional data. pub const MAXIMUM_SIZE: usize = 8 // discriminator + 4 // subscribed_accounts vec length + (32 * 256) // max subscribed_accounts data + 32 // root + 1 // root_finalized - + 8 // expected_batch - + 8; // nonce + + 8 // expected_batch (u64) + + 8; // nonce (u64) - // The seed prefix is a static byte array used as part of the seeds to derive - // the program's `Config` account's PDA (Program-Derived Address). - // Using a well-known, stable prefix ensures that we can reliably find the `Config` PDA. + /// `SEED_PREFIX` is a static byte array used as a seed for deriving the Program Derived Address (PDA) of the `Config` account. + /// + /// - **Purpose**: To ensure that the PDA is uniquely and deterministically derived, preventing address collisions. + /// - **Usage**: Combined with other seeds and a bump value to generate a secure PDA that the program can control. pub const SEED_PREFIX: &'static [u8; 6] = b"config"; }