Skip to content

Commit

Permalink
refactor(solana): refactors snapshotter comments
Browse files Browse the repository at this point in the history
  • Loading branch information
allemanfredi committed Dec 18, 2024
1 parent 074a9b8 commit 3f611be
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 81 deletions.
46 changes: 35 additions & 11 deletions packages/solana/programs/snapshotter/src/contexts/calculate_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,47 @@ 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<CalculateRoot>, batch: u64) -> Result<()> {
/// let config = &mut ctx.accounts.config;
/// // ... perform operations that modify `config` ...
/// }
/// ```
///
#[account(
seeds = [Config::SEED_PREFIX],
bump,
mut
)]
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>,
}
61 changes: 38 additions & 23 deletions packages/solana/programs/snapshotter/src/contexts/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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>,
}
36 changes: 26 additions & 10 deletions packages/solana/programs/snapshotter/src/contexts/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
53 changes: 48 additions & 5 deletions packages/solana/programs/snapshotter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Initialize>) -> 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<Subscribe>, 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(())
Expand All @@ -79,37 +109,48 @@ pub mod snapshotter {
pub fn calculate_root(ctx: Context<CalculateRoot>, 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,
Expand All @@ -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(())
Expand Down
Loading

0 comments on commit 3f611be

Please sign in to comment.