Skip to content

Commit

Permalink
[Umi] Add merkle tree serializers and hash helpers (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva authored Jun 15, 2023
1 parent f02dda4 commit f445ce3
Show file tree
Hide file tree
Showing 21 changed files with 755 additions and 46 deletions.
3 changes: 2 additions & 1 deletion clients/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"author": "Metaplex Maintainers <[email protected]>",
"license": "Apache-2.0",
"dependencies": {
"@metaplex-foundation/mpl-toolbox": "^0.7.0"
"@metaplex-foundation/mpl-toolbox": "^0.7.0",
"@noble/hashes": "^1.3.1"
},
"peerDependencies": {
"@metaplex-foundation/umi": "^0.8.0"
Expand Down
10 changes: 4 additions & 6 deletions clients/js/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 3 additions & 16 deletions clients/js/src/createTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
createTreeConfig,
} from './generated';
import { getMerkleTreeSize } from './hooked';

export const createTree = async (
context: Parameters<typeof createAccount>[0] &
Expand All @@ -23,7 +24,8 @@ export const createTree = async (
): Promise<TransactionBuilder> => {
const space =
input.merkleTreeSize ??
getMerkleTreeAccountSize(
getMerkleTreeSize(
context,
input.maxDepth,
input.maxBufferSize,
input.canopyDepth
Expand Down Expand Up @@ -54,18 +56,3 @@ export const createTree = async (
)
);
};

export const getMerkleTreeAccountSize = (
maxDepth: number,
maxBufferSize: number,
canopyDepth?: number
): number =>
1 + // Account discriminant.
1 + // Header version.
54 + // Merkle tree header V1.
8 + // Merkle tree > sequenceNumber.
8 + // Merkle tree > activeIndex.
8 + // Merkle tree > bufferSize.
(40 + 32 * maxDepth) * maxBufferSize + // Merkle tree > changeLogs.
(32 * maxDepth + 40) + // Merkle tree > rightMostPath.
(canopyDepth ? Math.max(((1 << (canopyDepth + 1)) - 2) * 32, 0) : 0);
1 change: 1 addition & 0 deletions clients/js/src/generated/accounts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
* @see https://github.com/metaplex-foundation/kinobi
*/

export * from './merkleTree';
export * from './treeConfig';
export * from './voucher';
124 changes: 124 additions & 0 deletions clients/js/src/generated/accounts/merkleTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* This code was AUTOGENERATED using the kinobi library.
* Please DO NOT EDIT THIS FILE, instead use visitors
* to add features, then rerun kinobi to update it.
*
* @see https://github.com/metaplex-foundation/kinobi
*/

import {
Account,
Context,
Pda,
PublicKey,
RpcAccount,
RpcGetAccountOptions,
RpcGetAccountsOptions,
assertAccountExists,
deserializeAccount,
gpaBuilder,
publicKey as toPublicKey,
} from '@metaplex-foundation/umi';
import {
MerkleTreeAccountData,
getMerkleTreeAccountDataSerializer,
} from '../../hooked';
import {
CompressionAccountTypeArgs,
ConcurrentMerkleTreeHeaderDataArgs,
getCompressionAccountTypeSerializer,
getConcurrentMerkleTreeHeaderDataSerializer,
} from '../types';

export type MerkleTree = Account<MerkleTreeAccountData>;

export function deserializeMerkleTree(
context: Pick<Context, 'serializer'>,
rawAccount: RpcAccount
): MerkleTree {
return deserializeAccount(
rawAccount,
getMerkleTreeAccountDataSerializer(context)
);
}

export async function fetchMerkleTree(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKey: PublicKey | Pda,
options?: RpcGetAccountOptions
): Promise<MerkleTree> {
const maybeAccount = await context.rpc.getAccount(
toPublicKey(publicKey, false),
options
);
assertAccountExists(maybeAccount, 'MerkleTree');
return deserializeMerkleTree(context, maybeAccount);
}

export async function safeFetchMerkleTree(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKey: PublicKey | Pda,
options?: RpcGetAccountOptions
): Promise<MerkleTree | null> {
const maybeAccount = await context.rpc.getAccount(
toPublicKey(publicKey, false),
options
);
return maybeAccount.exists
? deserializeMerkleTree(context, maybeAccount)
: null;
}

export async function fetchAllMerkleTree(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKeys: Array<PublicKey | Pda>,
options?: RpcGetAccountsOptions
): Promise<MerkleTree[]> {
const maybeAccounts = await context.rpc.getAccounts(
publicKeys.map((key) => toPublicKey(key, false)),
options
);
return maybeAccounts.map((maybeAccount) => {
assertAccountExists(maybeAccount, 'MerkleTree');
return deserializeMerkleTree(context, maybeAccount);
});
}

export async function safeFetchAllMerkleTree(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKeys: Array<PublicKey | Pda>,
options?: RpcGetAccountsOptions
): Promise<MerkleTree[]> {
const maybeAccounts = await context.rpc.getAccounts(
publicKeys.map((key) => toPublicKey(key, false)),
options
);
return maybeAccounts
.filter((maybeAccount) => maybeAccount.exists)
.map((maybeAccount) =>
deserializeMerkleTree(context, maybeAccount as RpcAccount)
);
}

export function getMerkleTreeGpaBuilder(
context: Pick<Context, 'rpc' | 'serializer' | 'programs'>
) {
const s = context.serializer;
const programId = context.programs.getPublicKey(
'splAccountCompression',
'cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK'
);
return gpaBuilder(context, programId)
.registerFields<{
discriminator: CompressionAccountTypeArgs;
treeHeader: ConcurrentMerkleTreeHeaderDataArgs;
serializedTree: Uint8Array;
}>({
discriminator: [0, getCompressionAccountTypeSerializer(context)],
treeHeader: [1, getConcurrentMerkleTreeHeaderDataSerializer(context)],
serializedTree: [56, s.bytes()],
})
.deserializeUsing<MerkleTree>((account) =>
deserializeMerkleTree(context, account)
);
}
25 changes: 25 additions & 0 deletions clients/js/src/generated/types/compressionAccountType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* This code was AUTOGENERATED using the kinobi library.
* Please DO NOT EDIT THIS FILE, instead use visitors
* to add features, then rerun kinobi to update it.
*
* @see https://github.com/metaplex-foundation/kinobi
*/

import { Context, Serializer } from '@metaplex-foundation/umi';

export enum CompressionAccountType {
Uninitialized,
ConcurrentMerkleTree,
}

export type CompressionAccountTypeArgs = CompressionAccountType;

export function getCompressionAccountTypeSerializer(
context: Pick<Context, 'serializer'>
): Serializer<CompressionAccountTypeArgs, CompressionAccountType> {
const s = context.serializer;
return s.enum<CompressionAccountType>(CompressionAccountType, {
description: 'CompressionAccountType',
}) as Serializer<CompressionAccountTypeArgs, CompressionAccountType>;
}
59 changes: 59 additions & 0 deletions clients/js/src/generated/types/concurrentMerkleTreeHeader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* This code was AUTOGENERATED using the kinobi library.
* Please DO NOT EDIT THIS FILE, instead use visitors
* to add features, then rerun kinobi to update it.
*
* @see https://github.com/metaplex-foundation/kinobi
*/

import { Context, Serializer } from '@metaplex-foundation/umi';
import {
CompressionAccountType,
CompressionAccountTypeArgs,
ConcurrentMerkleTreeHeaderData,
ConcurrentMerkleTreeHeaderDataArgs,
getCompressionAccountTypeSerializer,
getConcurrentMerkleTreeHeaderDataSerializer,
} from '.';

/**
* Initialization parameters for an SPL ConcurrentMerkleTree.
*
* 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) |
*
*/

export type ConcurrentMerkleTreeHeader = {
/** Account type */
accountType: CompressionAccountType;
/** Versioned header */
header: ConcurrentMerkleTreeHeaderData;
};

export type ConcurrentMerkleTreeHeaderArgs = {
/** Account type */
accountType: CompressionAccountTypeArgs;
/** Versioned header */
header: ConcurrentMerkleTreeHeaderDataArgs;
};

export function getConcurrentMerkleTreeHeaderSerializer(
context: Pick<Context, 'serializer'>
): Serializer<ConcurrentMerkleTreeHeaderArgs, ConcurrentMerkleTreeHeader> {
const s = context.serializer;
return s.struct<ConcurrentMerkleTreeHeader>(
[
['accountType', getCompressionAccountTypeSerializer(context)],
['header', getConcurrentMerkleTreeHeaderDataSerializer(context)],
],
{ description: 'ConcurrentMerkleTreeHeader' }
) as Serializer<ConcurrentMerkleTreeHeaderArgs, ConcurrentMerkleTreeHeader>;
}
Loading

0 comments on commit f445ce3

Please sign in to comment.