From b407dddd99fd444942c188016a306f074020161f Mon Sep 17 00:00:00 2001 From: jon wong Date: Mon, 11 Jul 2022 14:58:56 -0400 Subject: [PATCH] Adding various docs to clarify structs / traits (#140) * Adding various docs to clarify structs / traits Intended to be viewed via `docs.rs`. Locally, run `cargo doc --open` to preview * Addressing comments --- contracts/programs/gummyroll/src/error.rs | 11 ++++ contracts/programs/gummyroll/src/lib.rs | 62 +++++++++++++++++-- contracts/programs/gummyroll/src/state/mod.rs | 36 ++++++++++- contracts/programs/gummyroll/src/utils.rs | 4 +- 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/contracts/programs/gummyroll/src/error.rs b/contracts/programs/gummyroll/src/error.rs index 8e4ff41a6ff..e552881d36a 100644 --- a/contracts/programs/gummyroll/src/error.rs +++ b/contracts/programs/gummyroll/src/error.rs @@ -1,16 +1,27 @@ use anchor_lang::prelude::*; use concurrent_merkle_tree::error::CMTError; +/// Errors related to misconfiguration or misuse of the Merkle tree #[error_code] pub enum GummyrollError { + /// This error is currently not used. #[msg("Incorrect leaf length. Expected vec of 32 bytes")] IncorrectLeafLength, + + /// A modification to the tree was invalid and a changelog was not emitted. + /// The proof may be invalid or out-of-date, or the provided leaf hash was invalid. #[msg("Concurrent merkle tree error")] ConcurrentMerkleTreeError, + + /// An issue was detected with loading the provided account data for this Gummyroll tree. #[msg("Issue zero copying concurrent merkle tree data")] ZeroCopyError, + + /// See [MerkleRollHeader](/gummyroll/state/struct.MerkleRollHeader.html) for valid configuration options. #[msg("An unsupported max depth or max buffer size constant was provided")] MerkleRollConstantsError, + + /// When using Canopy, the stored byte length should a multiple of the node's byte length (32 bytes) #[msg("Expected a different byte length for the merkle roll canopy")] CanopyLengthMismatch, } diff --git a/contracts/programs/gummyroll/src/lib.rs b/contracts/programs/gummyroll/src/lib.rs index 7c6f69b2800..3d7b665c087 100644 --- a/contracts/programs/gummyroll/src/lib.rs +++ b/contracts/programs/gummyroll/src/lib.rs @@ -1,3 +1,21 @@ +//! Gummyroll is an on-chain Merkle tree that supports concurrent writes. +//! +//! A buffer of proof-like changelogs is stored on-chain that allow multiple proof-based writes to succeed within the same slot. +//! This is accomplished by fast-forwarding out-of-date (or possibly invalid) proofs based on information stored in the changelogs. +//! See a copy of the whitepaper [here](https://google.com) +//! +//! While Gummyroll trees can generically store arbitrary information, +//! one exemplified use-case is the [Bubblegum](https://google.com) contract, +//! which uses Gummyroll trees to store encoded information about NFTs. +//! The use of Gummyroll within Bubblegum allows for: +//! - up to 1 billion NFTs to be stored in a single account on-chain (10,000x decrease in on-chain cost) +//! - (by default) up to 1024 concurrent updates per slot (this number is not correct) +//! +//! Operationally, Gummyroll trees **must** be supplemented by off-chain indexers to cache information +//! about leafs and to power an API that can supply up-to-date proofs to allow updates to the tree. +//! All modifications to Gummyroll trees are settled on the Solana ledger via instructions against the Gummyroll contract. +//! A production-ready indexer (Plerkle) can be found in the [Metaplex program library](https://google.com) + use anchor_lang::{ emit, prelude::*, @@ -19,47 +37,78 @@ pub use concurrent_merkle_tree::{error::CMTError, merkle_roll::MerkleRoll, state declare_id!("GRoLLMza82AiYN7W9S9KCCtCyyPRAQP2ifBy4v4D5RMD"); +/// Context for initializing a new Merkle tere #[derive(Accounts)] pub struct Initialize<'info> { #[account(zero)] /// CHECK: This account will be zeroed out, and the size will be validated pub merkle_roll: UncheckedAccount<'info>, + + /// Authority that validates the content of the trees. + /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs. pub authority: Signer<'info>, + + /// Authority that is responsible for signing for new additions to the tree. /// CHECK: unsafe pub append_authority: UncheckedAccount<'info>, + + /// Program used to emit changelogs as instruction data. + /// See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh` pub candy_wrapper: Program<'info, CandyWrapper>, } +/// Context for inserting, appending, or replacing a leaf in the tree #[derive(Accounts)] pub struct Modify<'info> { #[account(mut)] /// CHECK: This account is validated in the instruction pub merkle_roll: UncheckedAccount<'info>, + + /// Authority that validates the content of the trees. + /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs. pub authority: Signer<'info>, + + /// Program used to emit changelogs as instruction data. + /// See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh` pub candy_wrapper: Program<'info, CandyWrapper>, } +/// Context for appending a new leaf to the tree #[derive(Accounts)] pub struct Append<'info> { #[account(mut)] /// CHECK: This account is validated in the instruction pub merkle_roll: UncheckedAccount<'info>, + + /// Authority that validates the content of the trees. + /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs. pub authority: Signer<'info>, + + /// Authority that is responsible for signing for new additions to the tree. pub append_authority: Signer<'info>, + + /// Program used to emit changelogs as instruction data. + /// See `WRAPYChf58WFCnyjXKJHtrPgzKXgHp6MD9aVDqJBbGh` pub candy_wrapper: Program<'info, CandyWrapper>, } +/// Context for validating a provided proof against the Merkle tree. +/// Throws an error if provided proof is invalid. #[derive(Accounts)] pub struct VerifyLeaf<'info> { /// CHECK: This account is validated in the instruction pub merkle_roll: UncheckedAccount<'info>, } +/// Context for transferring `authority` or `append_authority` #[derive(Accounts)] pub struct TransferAuthority<'info> { #[account(mut)] /// CHECK: This account is validated in the instruction pub merkle_roll: UncheckedAccount<'info>, + + /// Authority that validates the content of the trees. + /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs. pub authority: Signer<'info>, } @@ -265,8 +314,8 @@ macro_rules! merkle_roll_apply_fn { pub mod gummyroll { use super::*; - /// Creates a new merkle tree with maximum leaf capacity of power(2, max_depth) - /// and a minimum concurrency limit of max_buffer_size. + /// Creates a new merkle tree with maximum leaf capacity of `power(2, max_depth)` + /// and a minimum concurrency limit of `max_buffer_size`. /// /// Concurrency limit represents the # of replace instructions that can be successfully /// executed with proofs dated for the same root. For example, a maximum buffer size of 1024 @@ -402,8 +451,8 @@ pub mod gummyroll { update_canopy(canopy_bytes, header.max_depth, Some(change_log)) } - /// Transfers authority or append authority - /// requires `authority` to sign + /// Transfers `authority` or `append_authority`. + /// Requires `authority` to sign pub fn transfer_authority( ctx: Context, new_authority: Option, @@ -437,7 +486,8 @@ pub mod gummyroll { Ok(()) } - /// If proof is invalid, error is thrown + /// Verifies a provided proof and leaf. + /// If invalid, throws an error. pub fn verify_leaf( ctx: Context, root: [u8; 32], @@ -461,7 +511,7 @@ pub mod gummyroll { Ok(()) } - /// This instruction allows the tree's mint_authority to append a new leaf to the tree + /// This instruction allows the tree's `append_authority` to append a new leaf to the tree /// without having to supply a valid proof. /// /// This is accomplished by using the rightmost_proof of the merkle roll to construct a diff --git a/contracts/programs/gummyroll/src/state/mod.rs b/contracts/programs/gummyroll/src/state/mod.rs index 13edcc3cf22..bf658a1d090 100644 --- a/contracts/programs/gummyroll/src/state/mod.rs +++ b/contracts/programs/gummyroll/src/state/mod.rs @@ -1,3 +1,5 @@ +//! State related to storing a buffer of Merkle tree roots on-chain. +//! use anchor_lang::prelude::*; use borsh::{BorshDeserialize, BorshSerialize}; use concurrent_merkle_tree::state::{ChangeLog, Node}; @@ -28,9 +30,14 @@ pub struct NewLeafEvent { pub struct ChangeLogEvent { /// Public key of the Merkle Roll pub id: Pubkey, + /// Nodes of off-chain merkle tree pub path: Vec, + + /// Index corresponding to the number of successful operations on this tree. + /// Used by the off-chain indexer to figure out when there are gaps to be backfilled. pub seq: u64, + /// Bitmap of node parity (used when hashing) pub index: u32, } @@ -62,13 +69,40 @@ impl From<(Box>, Pubkey, u64)> } } +/// Initialization parameters for a Gummyroll Merkle tree. +/// +/// Only the following permutations are valid: +/// +/// | max_depth | max_buffer_size | +/// | --------- | --------------------- | +/// | 14 | (64, 256, 1024, 2048) | +/// | 20 | (64, 256, 1024, 2048) | +/// | 24 | (64, 256, 512, 1024, 2048) | +/// | 26 | (64, 256, 512, 1024, 2048) | +/// | 30 | (512, 1024, 2048) | +/// #[derive(BorshDeserialize, BorshSerialize)] #[repr(C)] pub struct MerkleRollHeader { + /// Buffer of changelogs stored on-chain. + /// Must be a power of 2; see above table for valid combinations. pub max_buffer_size: u32, + + /// Depth of the Merkle tree to store. + /// Tree capacity can be calculated as power(2, max_depth). + /// See above table for valid options. pub max_depth: u32, + + /// Authority that validates the content of the trees. + /// Typically a program, e.g., the Bubblegum contract validates that leaves are valid NFTs. pub authority: Pubkey, + + /// Authority that is responsible for signing for new additions to the tree. + /// DEPRECATED: Likely to be removed! pub append_authority: Pubkey, + + /// Slot corresponding to when the Merkle tree was created. + /// Provides a lower-bound on what slot to start (re-)building a tree from. pub creation_slot: u64, } @@ -99,4 +133,4 @@ impl anchor_lang::Id for CandyWrapper { fn id() -> Pubkey { candy_wrapper::id() } -} \ No newline at end of file +} diff --git a/contracts/programs/gummyroll/src/utils.rs b/contracts/programs/gummyroll/src/utils.rs index 594d1d72ba2..f6f114cbd3c 100644 --- a/contracts/programs/gummyroll/src/utils.rs +++ b/contracts/programs/gummyroll/src/utils.rs @@ -1,8 +1,10 @@ +//! Various utilities for Gummyroll trees +//! +use crate::state::CandyWrapper; use anchor_lang::{ prelude::*, solana_program::{msg, program::invoke, program_error::ProgramError}, }; -use crate::state::CandyWrapper; use bytemuck::{Pod, PodCastError}; use concurrent_merkle_tree::merkle_roll::MerkleRoll; use std::any::type_name;