From 47c1cd7b2fc733bf974b29124c205b2d7078a6ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Swen=20Sch=C3=A4ferjohann?= <42959314+SwenSchaeferjohann@users.noreply.github.com> Date: Wed, 11 Sep 2024 19:45:51 +0700 Subject: [PATCH] photon 0.45.0, add: getValidityProofV0 for custom tree support (#1202) Co-authored-by: swen --- cli/src/utils/constants.ts | 2 +- js/stateless.js/src/actions/create-account.ts | 5 +- js/stateless.js/src/rpc-interface.ts | 21 +++++- js/stateless.js/src/rpc.ts | 73 +++++++++++++++---- .../src/test-helpers/test-rpc/test-rpc.ts | 26 ++++++- js/stateless.js/tests/e2e/compress.test.ts | 3 +- js/stateless.js/tests/e2e/rpc-interop.test.ts | 3 +- scripts/install.sh | 2 +- 8 files changed, 108 insertions(+), 27 deletions(-) diff --git a/cli/src/utils/constants.ts b/cli/src/utils/constants.ts index 362e69e45f..25534c36fa 100644 --- a/cli/src/utils/constants.ts +++ b/cli/src/utils/constants.ts @@ -21,7 +21,7 @@ export const LIGHT_PROVER_PROCESS_NAME = "light-prover"; export const INDEXER_PROCESS_NAME = "photon"; export const FORESTER_PROCESS_NAME = "forester"; -export const PHOTON_VERSION = "0.44.0"; +export const PHOTON_VERSION = "0.45.0"; export const LIGHT_PROTOCOL_PROGRAMS_DIR_ENV = "LIGHT_PROTOCOL_PROGRAMS_DIR"; export const BASE_PATH = "../../bin/"; diff --git a/js/stateless.js/src/actions/create-account.ts b/js/stateless.js/src/actions/create-account.ts index a201ccd259..284809c5de 100644 --- a/js/stateless.js/src/actions/create-account.ts +++ b/js/stateless.js/src/actions/create-account.ts @@ -55,9 +55,8 @@ export async function createAccount( /// TODO: enforce program-derived const address = await deriveAddress(seed, addressTree); - /// TODO: pass trees - const proof = await rpc.getValidityProof(undefined, [ - bn(address.toBytes()), + const proof = await rpc.getValidityProofV0(undefined, [ + { address: bn(address.toBytes()), tree: addressTree, queue: addressQueue }, ]); const params: NewAddressParams = { diff --git a/js/stateless.js/src/rpc-interface.ts b/js/stateless.js/src/rpc-interface.ts index 6920569209..57dc87db66 100644 --- a/js/stateless.js/src/rpc-interface.ts +++ b/js/stateless.js/src/rpc-interface.ts @@ -62,6 +62,18 @@ export interface SignatureWithMetadata { slot: number; } +export interface HashWithTree { + hash: BN254; + tree: PublicKey; + queue: PublicKey; +} + +export interface AddressWithTree { + address: BN254; + tree: PublicKey; + queue: PublicKey; +} + export interface CompressedTransaction { compressionInfo: { closedAccounts: { @@ -497,9 +509,14 @@ export interface CompressionApiInterface { newAddresses: BN254[], ): Promise; + getValidityProofV0( + hashes: HashWithTree[], + newAddresses: AddressWithTree[], + ): Promise; + getValidityProofAndRpcContext( - hashes: BN254[], - newAddresses: BN254[], + hashes: HashWithTree[], + newAddresses: AddressWithTree[], ): Promise>; getCompressedAccountsByOwner( diff --git a/js/stateless.js/src/rpc.ts b/js/stateless.js/src/rpc.ts index 6175c82c7d..0963b2c2a9 100644 --- a/js/stateless.js/src/rpc.ts +++ b/js/stateless.js/src/rpc.ts @@ -37,6 +37,8 @@ import { WithContext, GetCompressedAccountsByOwnerConfig, WithCursor, + AddressWithTree, + HashWithTree, } from './rpc-interface'; import { MerkleContextWithMerkleProof, @@ -1367,15 +1369,52 @@ export class Rpc extends Connection implements CompressionApiInterface { */ async getValidityProof( hashes: BN254[] = [], - newAddresses: BN254[] = [], + newAddresses: BN254[] = [] ): Promise { - const { value } = await this.getValidityProofAndRpcContext( - hashes, - newAddresses, - ); + const defaultAddressTreePublicKey = defaultTestStateTreeAccounts().addressTree; + const defaultAddressQueuePublicKey = defaultTestStateTreeAccounts().addressQueue; + const defaultStateTreePublicKey = defaultTestStateTreeAccounts().merkleTree; + const defaultStateQueuePublicKey = defaultTestStateTreeAccounts().nullifierQueue; + const formattedHashes = hashes.map(item => { + if (item instanceof BN) { + return { hash: item, tree: defaultStateTreePublicKey, queue: defaultStateQueuePublicKey }; + } + return item; + }); + + const formattedNewAddresses = newAddresses.map(item => { + if (item instanceof BN) { + return { address: item, tree: defaultAddressTreePublicKey, queue: defaultAddressQueuePublicKey }; + } + return item; + }); + + return this.getValidityProofV0(formattedHashes, formattedNewAddresses); + } + + /** + * Fetch the latest validity proof for (1) compressed accounts specified by + * an array of account hashes. (2) new unique addresses specified by an + * array of addresses. + * + * Validity proofs prove the presence of compressed accounts in state trees + * and the non-existence of addresses in address trees, respectively. They + * enable verification without recomputing the merkle proof path, thus + * lowering verification and data costs. + * + * @param hashes Array of { hash: BN254, tree: PublicKey, queue: PublicKey }. + * @param newAddresses Array of { address: BN254, tree: PublicKey, queue: PublicKey }. + * @returns validity proof with context + */ + async getValidityProofV0( + hashes: HashWithTree[] = [], + newAddresses: AddressWithTree[] = [] + ): Promise { + const { value } = await this.getValidityProofAndRpcContext(hashes, newAddresses); return value; } - /** + + /** * Fetch the latest validity proof for (1) compressed accounts specified by * an array of account hashes. (2) new unique addresses specified by an * array of addresses. Returns with context slot. @@ -1386,21 +1425,25 @@ export class Rpc extends Connection implements CompressionApiInterface { * lowering verification and data costs. * * @param hashes Array of BN254 hashes. - * @param newAddresses Array of BN254 new addresses. + * @param newAddresses Array of BN254 new addresses. Optionally specify the + * tree and queue for each address. Default to public + * state tree/queue. * @returns validity proof with context */ async getValidityProofAndRpcContext( - hashes: BN254[] = [], - newAddresses: BN254[] = [], + hashes: HashWithTree[] = [], + newAddresses: AddressWithTree[] = [] ): Promise> { + const unsafeRes = await rpcRequest( this.compressionApiEndpoint, 'getValidityProof', { - hashes: hashes.map(hash => encodeBN254toBase58(hash)), - newAddresses: newAddresses.map(address => - encodeBN254toBase58(address), - ), + hashes: hashes.map(({ hash }) => encodeBN254toBase58(hash)), + newAddressesWithTrees: newAddresses.map(({ address, tree }) => ({ + address: encodeBN254toBase58(address), + tree: tree.toBase58(), + })), }, ); @@ -1428,8 +1471,8 @@ export class Rpc extends Connection implements CompressionApiInterface { merkleTrees: result.merkleTrees, leafIndices: result.leafIndices, nullifierQueues: [ - ...hashes.map(() => mockNullifierQueue), - ...newAddresses.map(() => mockAddressQueue), + ...hashes.map(({ queue }) => queue), + ...newAddresses.map(({ queue }) => queue), ], rootIndices: result.rootIndices, roots: result.roots, diff --git a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts index d5572f8290..3a1f38e84e 100644 --- a/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts +++ b/js/stateless.js/src/test-helpers/test-rpc/test-rpc.ts @@ -15,8 +15,10 @@ import { MerkleTree } from '../merkle-tree/merkle-tree'; import { getParsedEvents } from './get-parsed-events'; import { defaultTestStateTreeAccounts } from '../../constants'; import { + AddressWithTree, CompressedTransaction, GetCompressedAccountsByOwnerConfig, + HashWithTree, LatestNonVotingSignatures, LatestNonVotingSignaturesPaginated, SignatureWithMetadata, @@ -589,11 +591,14 @@ export class TestRpc extends Connection implements CompressionApiInterface { * {@link getValidityProof} instead. */ async getValidityProofAndRpcContext( - hashes: BN254[] = [], - newAddresses: BN254[] = [], + hashes: HashWithTree[] = [], + newAddresses: AddressWithTree[] = [], ): Promise> { + if (newAddresses.some(address => !(address instanceof BN))) { + throw new Error('AddressWithTree is not supported in test-rpc'); + } return { - value: await this.getValidityProof(hashes, newAddresses), + value: await this.getValidityProofV0(hashes, newAddresses), context: { slot: 1 }, }; } @@ -615,6 +620,10 @@ export class TestRpc extends Connection implements CompressionApiInterface { hashes: BN254[] = [], newAddresses: BN254[] = [], ): Promise { + + if (newAddresses.some(address => !(address instanceof BN))) { + throw new Error('AddressWithTree is not supported in test-rpc'); + } let validityProof: CompressedProofWithContext; if (hashes.length === 0 && newAddresses.length === 0) { @@ -737,4 +746,15 @@ export class TestRpc extends Connection implements CompressionApiInterface { return validityProof; } + + async getValidityProofV0( + hashes: HashWithTree[] = [], + newAddresses: AddressWithTree[] = [], + ): Promise { + /// TODO(swen): add support for custom trees + return this.getValidityProof( + hashes.map(hash => hash.hash), + newAddresses.map(address => address.address), + ); + } } diff --git a/js/stateless.js/tests/e2e/compress.test.ts b/js/stateless.js/tests/e2e/compress.test.ts index 41027f3886..77ef7ed1df 100644 --- a/js/stateless.js/tests/e2e/compress.test.ts +++ b/js/stateless.js/tests/e2e/compress.test.ts @@ -59,6 +59,7 @@ function txFees( return totalFee.toNumber(); } + /// TODO: add test case for payer != address describe('compress', () => { const { merkleTree } = defaultTestStateTreeAccounts(); @@ -73,7 +74,7 @@ describe('compress', () => { it('should create account with address', async () => { const preCreateAccountsBalance = await rpc.getBalance(payer.publicKey); - + await createAccount( rpc as TestRpc, payer, diff --git a/js/stateless.js/tests/e2e/rpc-interop.test.ts b/js/stateless.js/tests/e2e/rpc-interop.test.ts index 0618cc0f9a..4898af3f9a 100644 --- a/js/stateless.js/tests/e2e/rpc-interop.test.ts +++ b/js/stateless.js/tests/e2e/rpc-interop.test.ts @@ -641,9 +641,10 @@ describe('rpc-interop', () => { it('getCompressedAccount with address param should work ', async () => { const seed = new Uint8Array(randomBytes(32)); const addressTree = defaultTestStateTreeAccounts().addressTree; + const addressQueue = defaultTestStateTreeAccounts().addressQueue; const address = await deriveAddress(seed, addressTree); - await createAccount(rpc, payer, seed, LightSystemProgram.programId); + await createAccount(rpc, payer, seed, LightSystemProgram.programId, addressTree, addressQueue); // fetch the owners latest account const accounts = await rpc.getCompressedAccountsByOwner( diff --git a/scripts/install.sh b/scripts/install.sh index 32058c05f0..1a5ec71ac1 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -13,7 +13,7 @@ VERSIONS=( "solana:1.18.22" "anchor:anchor-v0.29.0" "jq:jq-1.7.1" - "photon:0.44.0" + "photon:0.45.0" ) # Architecture-specific suffixes