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

update generalized-state-compression.md #442

105 changes: 77 additions & 28 deletions content/courses/state-compression/generalized-state-compression.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ description:
- State Compression on Solana is most commonly used for compressed NFTs, but
it's possible to use it for arbitrary data
- State Compression lowers the amount of data you have to store onchain by
leveraging Merkle trees.
leveraging [Merkle trees](https://en.wikipedia.org/wiki/Merkle_tree).
- Merkle trees store a single hash that represents an entire binary tree of
hashes. Each leaf on a Merkle tree is a hash of that leaf's data.
- Concurrent Merkle trees are a specialized version of Merkle trees that allow
Expand Down Expand Up @@ -263,18 +263,48 @@ a simple messaging program, your `Message` struct might look as follows:
```rust
#[derive(AnchorSerialize)]
pub struct MessageLog {
leaf_node: [u8; 32], // The leaf node hash
from: Pubkey, // Pubkey of the message sender
to: Pubkey, // Pubkey of the message recipient
message: String, // The message to send
pub leaf_node: [u8; 32], // leaf node hash
pub from: Pubkey,
pub to: Pubkey,
pub message: String, // message to send
}

impl MessageLog {
// Constructs a new message log from given leaf node and message
pub fn new(leaf_node: [u8; 32], from: Pubkey, to: Pubkey, message: String) -> Self {
Self { leaf_node, from, to, message }
Self {
leaf_node,
from,
to,
message,
}
}
}

#[derive(Accounts)]
pub struct MessageAccounts<'info> {
// The payer for the transaction
#[account(mut)]
pub owner: Signer<'info>,

// The pda authority for the merkle tree, only used for signing
#[account(
seeds = [merkle_tree.key().as_ref()],
bump,
)]
pub tree_authority: SystemAccount<'info>,

// The merkle tree account
/// CHECK: This account is validated by the spl account compression program
#[account(mut)]
pub merkle_tree: UncheckedAccount<'info>,

// The noop program to log data
pub log_wrapper: Program<'info, Noop>,

// The spl account compression program
pub compression_program: Program<'info, SplAccountCompression>,
}
```

To be abundantly clear, **this is not an account that you will be able to read
Expand Down Expand Up @@ -311,18 +341,19 @@ pub fn create_messages_tree(
) -> Result<()> {
// Get the address for the Merkle tree account
let merkle_tree = ctx.accounts.merkle_tree.key();

// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[
&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
],
];

// Create cpi context for init_empty_merkle_tree instruction.
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.compression_program.to_account_info(), // The spl account compression program
Initialize {
spl_account_compression::cpi::accounts::Initialize {
authority: ctx.accounts.tree_authority.to_account_info(), // The authority for the Merkle tree, using a PDA
merkle_tree: ctx.accounts.merkle_tree.to_account_info(), // The Merkle tree account to be initialized
noop: ctx.accounts.log_wrapper.to_account_info(), // The noop program to log data
Expand All @@ -331,7 +362,7 @@ pub fn create_messages_tree(
);

// CPI to initialize an empty Merkle tree with given max depth and buffer size
init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size)?;
spl_account_compression::cpi::init_empty_merkle_tree(cpi_ctx, max_depth, max_buffer_size)?;

Ok(())
}
Expand Down Expand Up @@ -367,19 +398,29 @@ like this:
pub fn append_message(ctx: Context<MessageAccounts>, message: String) -> Result<()> {
// Hash the message + whatever key should have update authority
let leaf_node = keccak::hashv(&[message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes();
// Create a new "message log" using the leaf node hash, sender, receipient, and message
let message_log = MessageLog::new(leaf_node.clone(), ctx.accounts.sender.key().clone(), ctx.accounts.receipient.key().clone(), message);

// Create a new "message log" using the leaf node hash, sender, recipient, and message
let message_log = MessageLog::new(
leaf_node.clone(),
ctx.accounts.sender.key().clone(),
ctx.accounts.recipient.key().clone(),
message
);

// Log the "message log" data using noop program
wrap_application_data_v1(message_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;

// Get the address for the Merkle tree account
let merkle_tree = ctx.accounts.merkle_tree.key();

// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[
&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
],
];

// Create a new cpi context and append the leaf node to the Merkle tree.
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.compression_program.to_account_info(), // The spl account compression program
Expand All @@ -390,8 +431,10 @@ pub fn append_message(ctx: Context<MessageAccounts>, message: String) -> Result<
},
signer_seeds // The seeds for pda signing
);

// CPI to append the leaf node to the Merkle tree
append(cpi_ctx, leaf_node)?;

Ok(())
}
```
Expand Down Expand Up @@ -442,17 +485,14 @@ pub fn update_message(
old_message: String,
new_message: String
) -> Result<()> {
let old_leaf = keccak
::hashv(&[old_message.as_bytes(), ctx.accounts.sender.key().as_ref()])
.to_bytes();

let old_leaf = keccak::hashv(&[old_message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes();
let merkle_tree = ctx.accounts.merkle_tree.key();

// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[
&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
],
];

Expand All @@ -462,7 +502,6 @@ pub fn update_message(
msg!("Messages are the same!");
return Ok(());
}

let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.compression_program.to_account_info(), // The spl account compression program
VerifyLeaf {
Expand All @@ -474,12 +513,16 @@ pub fn update_message(
verify_leaf(cpi_ctx, root, old_leaf, index)?;
}

let new_leaf = keccak
::hashv(&[new_message.as_bytes(), ctx.accounts.sender.key().as_ref()])
.to_bytes();
let new_leaf = keccak::hashv(&[new_message.as_bytes(), ctx.accounts.sender.key().as_ref()]).to_bytes();

// Log out for indexers
let message_log = MessageLog::new(new_leaf.clone(), ctx.accounts.sender.key().clone(), ctx.accounts.recipient.key().clone(), new_message);
let message_log = MessageLog::new(
new_leaf.clone(),
ctx.accounts.sender.key().clone(),
ctx.accounts.recipient.key().clone(),
new_message
);

// Log the "message log" data using noop program
wrap_application_data_v1(message_log.try_to_vec()?, &ctx.accounts.log_wrapper)?;

Expand All @@ -494,7 +537,7 @@ pub fn update_message(
},
signer_seeds // The seeds for pda signing
);
// CPI to append the leaf node to the Merkle tree
// CPI to replace the leaf node in the Merkle tree
replace_leaf(cpi_ctx, root, old_leaf, new_leaf, index)?;
}

Expand Down Expand Up @@ -628,7 +671,7 @@ app.
Start by initializing an Anchor program:

```bash
anchor init compressed-notes
anchor init compressed-notes --template=multiple
```

We’ll be using the `spl-account-compression` crate with the `cpi` feature
Expand All @@ -638,7 +681,7 @@ enabled. Let’s add it as a dependency in `programs/compressed-notes/Cargo.toml
[dependencies]
anchor-lang = "0.28.0"
spl-account-compression = { version="0.2.0", features = ["cpi"] }
solana-program = "1.16.0"
solana-program = ">=1.18.11,<=2"
```

We’ll be testing locally but we need both the Compression program and the Noop
Expand Down Expand Up @@ -806,7 +849,7 @@ pub mod compressed_notes {
// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
]];

// Create cpi context for init_empty_merkle_tree instruction.
Expand Down Expand Up @@ -878,7 +921,7 @@ pub mod compressed_notes {
// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
]];
// Create a new cpi context and append the leaf node to the Merkle tree.
let cpi_ctx = CpiContext::new_with_signer(
Expand Down Expand Up @@ -958,7 +1001,7 @@ pub mod compressed_notes {
// Define the seeds for pda signing
let signer_seeds: &[&[&[u8]]] = &[&[
merkle_tree.as_ref(), // The address of the Merkle tree account as a seed
&[*ctx.bumps.get("tree_authority").unwrap()], // The bump seed for the pda
&[ctx.bumps.tree_authority], // The bump seed for the pda
]];

// Verify Leaf
Expand Down Expand Up @@ -1019,6 +1062,12 @@ install it:
yarn add @solana/spl-account-compression
```

or

```bash
npm install @solana/spl-account-compression
```

Next, we’re going to give you the contents of a utility file we’ve created to
make testing easier. Create a `utils.ts` file in the `tests` directory, add in
the below, then we’ll explain it.
Expand Down
Loading