Skip to content
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

WIP: Init With Root #6441

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
429 changes: 195 additions & 234 deletions account-compression/Cargo.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spl-account-compression"
version = "0.3.0"
version = "0.4.0"
description = "Solana Program Library Account Compression Program"
authors = ["Solana Labs Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub enum AccountCompressionError {
/// Tree information cannot be processed because the provided leaf_index
/// is out of bounds of tree's maximum leaf capacity
#[msg("Leaf index of concurrent merkle tree is out of bounds")]
LeafIndexOutOfBounds,
LeafIndexOutOfBounds
}

impl From<&ConcurrentMerkleTreeError> for AccountCompressionError {
Expand Down
139 changes: 73 additions & 66 deletions account-compression/programs/account-compression/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use anchor_lang::{
solana_program::sysvar::{clock::Clock, rent::Rent},
};
use borsh::{BorshDeserialize, BorshSerialize};

pub mod canopy;
pub mod error;
pub mod events;
Expand All @@ -47,6 +46,9 @@ use crate::state::{
merkle_tree_get_size, ConcurrentMerkleTreeHeader, CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1,
};
use crate::zero_copy::ZeroCopy;
use anchor_lang::solana_program::{
program::invoke, system_instruction::create_account, system_program,
};

/// Exported for Anchor / Solita
pub use spl_concurrent_merkle_tree::{
Expand All @@ -69,6 +71,20 @@ pub struct Initialize<'info> {
/// Program used to emit changelogs as cpi instruction data.
pub noop: Program<'info, Noop>,
}
/// Context for initializing a new SPL ConcurrentMerkleTree
#[derive(Accounts)]
pub struct InitializeWithRoot<'info> {
#[account(zero)]
/// CHECK: This account will be zeroed out, and the size will be validated
pub merkle_tree: UncheckedAccount<'info>,

/// Authority that controls write-access to the tree
/// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs.
pub authority: Signer<'info>,

/// Program used to emit changelogs as cpi instruction data.
pub noop: Program<'info, Noop>,
}
Comment on lines +74 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use Initialize as its a duplicate struct?


/// Context for inserting, appending, or replacing a leaf in the tree
///
Expand Down Expand Up @@ -173,69 +189,59 @@ pub mod spl_account_compression {
update_canopy(canopy_bytes, header.get_max_depth(), None)
}

/// Note:
/// Supporting this instruction open a security vulnerability for indexers.
/// This instruction has been deemed unusable for publicly indexed compressed NFTs.
/// Indexing batched data in this way requires indexers to read in the `uri`s onto physical storage
/// and then into their database. This opens up a DOS attack vector, whereby this instruction is
/// repeatedly invoked, causing indexers to fail.
///
/// Because this instruction was deemed insecure, this instruction has been removed
/// until secure usage is available on-chain.
// pub fn init_merkle_tree_with_root(
// ctx: Context<Initialize>,
// max_depth: u32,
// max_buffer_size: u32,
// root: [u8; 32],
// leaf: [u8; 32],
// index: u32,
// _changelog_db_uri: String,
// _metadata_db_uri: String,
// ) -> Result<()> {
// require_eq!(
// *ctx.accounts.merkle_tree.owner,
// crate::id(),
// AccountCompressionError::IncorrectAccountOwner
// );
// let mut merkle_tree_bytes = ctx.accounts.merkle_tree.try_borrow_mut_data()?;

// let (mut header_bytes, rest) =
// merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);

// let mut header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?;
// header.initialize(
// max_depth,
// max_buffer_size,
// &ctx.accounts.authority.key(),
// Clock::get()?.slot,
// );
// header.serialize(&mut header_bytes)?;
// let merkle_tree_size = merkle_tree_get_size(&header)?;
// let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);

// // Get rightmost proof from accounts
// let mut proof = vec![];
// for node in ctx.remaining_accounts.iter() {
// proof.push(node.key().to_bytes());
// }
// fill_in_proof_from_canopy(canopy_bytes, header.max_depth, index, &mut proof)?;
// assert_eq!(proof.len(), max_depth as usize);

// let id = ctx.accounts.merkle_tree.key();
// // A call is made to ConcurrentMerkleTree::initialize_with_root(root, leaf, proof, index)
// let change_log = merkle_tree_apply_fn!(
// header,
// id,
// tree_bytes,
// initialize_with_root,
// root,
// leaf,
// &proof,
// index
// )?;
// wrap_event(change_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;
// update_canopy(canopy_bytes, header.max_depth, Some(change_log))
// }
// Creates a new merkle tree with an existing root in the buffer.
// The indexers for the specific tree use case are in charge of handling the indexing or shadow indexing of the tree.
// Shadow indexing is a technique that allows for the indexing of a tree incrementally with update operations.
pub fn init_merkle_tree_with_root(
ctx: Context<InitializeWithRoot>,
max_depth: u32,
max_buffer_size: u32,
root: [u8; 32],
leaf: [u8; 32], // the first leaf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: comment or param name should be rightmost_leaf imo as "first leaf" would only be rightmost if it was empty?

leaf_index: u32,
) -> Result<()> {
require_eq!(
*ctx.accounts.merkle_tree.owner,
crate::id(),
AccountCompressionError::IncorrectAccountOwner
);
let mut merkle_tree_bytes: std::cell::RefMut<'_, &mut [u8]> =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the type added for merkle_tree_bytes? Couldn't it just be let mut merkle_tree_bytes = ctx.accounts.merkle_tree..."

ctx.accounts.merkle_tree.try_borrow_mut_data()?;

let proof: Vec<[u8; 32]> = ctx.remaining_accounts
.into_iter()
.map(|a| a.key().to_bytes())
.collect();
assert_eq!(proof.len(), max_depth as usize);
let (mut header_bytes, rest) =
merkle_tree_bytes.split_at_mut(CONCURRENT_MERKLE_TREE_HEADER_SIZE_V1);
let mut header = ConcurrentMerkleTreeHeader::try_from_slice(&header_bytes)?;
header.initialize(
max_depth,
max_buffer_size,
&ctx.accounts.authority.key(),
Clock::get()?.slot,
);
header.serialize(&mut header_bytes)?;
let merkle_tree_size = merkle_tree_get_size(&header)?;
let (tree_bytes, canopy_bytes) = rest.split_at_mut(merkle_tree_size);
let id = ctx.accounts.merkle_tree.key();
let change_log_event = merkle_tree_apply_fn_mut!(
header,
id,
tree_bytes,
initialize_with_root,
root,
leaf,
&proof,
leaf_index,
)?;
wrap_event(
&AccountCompressionEvent::ChangeLog(*change_log_event),
&ctx.accounts.noop,
)?;
update_canopy(canopy_bytes, header.get_max_depth(), None)
}

/// Executes an instruction that overwrites a leaf node.
/// Composing programs should check that the data hashed into previous_leaf
Expand Down Expand Up @@ -461,10 +467,11 @@ pub mod spl_account_compression {
// Close merkle tree account
// 1. Move lamports
let dest_starting_lamports = ctx.accounts.recipient.lamports();
**ctx.accounts.recipient.lamports.borrow_mut() = dest_starting_lamports
let tree_lamports = dest_starting_lamports
.checked_add(ctx.accounts.merkle_tree.lamports())
.unwrap();
**ctx.accounts.merkle_tree.lamports.borrow_mut() = 0;
**ctx.accounts.recipient.try_borrow_mut_lamports()? = tree_lamports;
**ctx.accounts.merkle_tree.try_borrow_mut_lamports()? = 0;
austbot marked this conversation as resolved.
Show resolved Hide resolved

// 2. Set all CMT account bytes to 0
header_bytes.fill(0);
Expand Down
Loading
Loading