From 42a379be78b018fef1491733006576c1c0496af5 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 13:39:28 +0000 Subject: [PATCH 01/15] test: account and recipient functions --- .../aztec_rpc_server/aztec_rpc_server.test.ts | 20 +++++++++++++++++-- .../src/aztec_rpc_server/aztec_rpc_server.ts | 10 ++-------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts index 0a672a468e5..f623e3d254f 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts @@ -1,7 +1,7 @@ import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ConstantKeyPair, TestKeyStore } from '@aztec/key-store'; -import { AztecNode } from '@aztec/types'; +import { AztecNode, AztecRPC } from '@aztec/types'; import { mock } from 'jest-mock-extended'; @@ -10,7 +10,7 @@ import { RpcServerConfig } from '../index.js'; import { AztecRPCServer } from './aztec_rpc_server.js'; describe('AztecRpcServer', function () { - let rpcServer: AztecRPCServer; + let rpcServer: AztecRPC; beforeEach(async () => { const keyStore = new TestKeyStore(await Grumpkin.new()); @@ -27,20 +27,36 @@ describe('AztecRpcServer', function () { const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); await rpcServer.registerAccount(await keyPair.getPrivateKey(), completeAddress); + + // Check that the account is correctly registered using the getAccounts and getRecipients methods const accounts = await rpcServer.getAccounts(); const recipients = await rpcServer.getRecipients(); expect(accounts).toEqual([completeAddress]); expect(recipients).toEqual([]); + + // Check that the account is correctly registered using the getAccount and getRecipient methods + const account = await rpcServer.getAccount(completeAddress.address); + const recipient = await rpcServer.getRecipient(completeAddress.address); + expect(account).toEqual(completeAddress); + expect(recipient).toBeUndefined(); }); it('registers a recipient and returns it as a recipient only and not as an account', async () => { const completeAddress = await CompleteAddress.random(); await rpcServer.registerRecipient(completeAddress); + + // Check that the recipient is correctly registered using the getAccounts and getRecipients methods const accounts = await rpcServer.getAccounts(); const recipients = await rpcServer.getRecipients(); expect(accounts).toEqual([]); expect(recipients).toEqual([completeAddress]); + + // Check that the recipient is correctly registered using the getAccount and getRecipient methods + const account = await rpcServer.getAccount(completeAddress.address); + const recipient = await rpcServer.getRecipient(completeAddress.address); + expect(account).toBeUndefined(); + expect(recipient).toEqual(completeAddress); }); it('cannot register the same account twice', async () => { diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts index bdf4488a2ff..a5e46c18c51 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts @@ -100,12 +100,9 @@ export class AztecRPCServer implements AztecRPC { return accounts; } - public async getAccount(address: AztecAddress): Promise { + public async getAccount(address: AztecAddress): Promise { const result = await this.getAccounts(); const account = result.find(r => r.address.equals(address)); - if (!account) { - throw new Error(`Unable to get complete address for address ${address.toString()}`); - } return Promise.resolve(account); } @@ -123,12 +120,9 @@ export class AztecRPCServer implements AztecRPC { return recipients; } - public async getRecipient(address: AztecAddress): Promise { + public async getRecipient(address: AztecAddress): Promise { const result = await this.getRecipients(); const recipient = result.find(r => r.address.equals(address)); - if (!recipient) { - throw new Error(`Unable to get complete address for address ${address.toString()}`); - } return Promise.resolve(recipient); } From dd19e331847c617896455ebd20de9352441c985a Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 14:06:53 +0000 Subject: [PATCH 02/15] WIP --- .../aztec_rpc_server/aztec_rpc_server.test.ts | 87 ------------------- .../test/aztec_rpc_server.test.ts | 22 +++++ .../test/aztec_rpc_test_suite.ts | 71 +++++++++++++++ 3 files changed, 93 insertions(+), 87 deletions(-) delete mode 100644 yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts create mode 100644 yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts create mode 100644 yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts deleted file mode 100644 index f623e3d254f..00000000000 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; -import { Grumpkin } from '@aztec/circuits.js/barretenberg'; -import { ConstantKeyPair, TestKeyStore } from '@aztec/key-store'; -import { AztecNode, AztecRPC } from '@aztec/types'; - -import { mock } from 'jest-mock-extended'; - -import { MemoryDB } from '../database/memory_db.js'; -import { RpcServerConfig } from '../index.js'; -import { AztecRPCServer } from './aztec_rpc_server.js'; - -describe('AztecRpcServer', function () { - let rpcServer: AztecRPC; - - beforeEach(async () => { - const keyStore = new TestKeyStore(await Grumpkin.new()); - const node = mock(); - const db = new MemoryDB(); - const config: RpcServerConfig = { - l2BlockPollingIntervalMS: 100, - }; - rpcServer = new AztecRPCServer(keyStore, node, db, config); - }); - - it('registers an account and returns it as an account only and not as a recipient', async () => { - const keyPair = ConstantKeyPair.random(await Grumpkin.new()); - const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); - - await rpcServer.registerAccount(await keyPair.getPrivateKey(), completeAddress); - - // Check that the account is correctly registered using the getAccounts and getRecipients methods - const accounts = await rpcServer.getAccounts(); - const recipients = await rpcServer.getRecipients(); - expect(accounts).toEqual([completeAddress]); - expect(recipients).toEqual([]); - - // Check that the account is correctly registered using the getAccount and getRecipient methods - const account = await rpcServer.getAccount(completeAddress.address); - const recipient = await rpcServer.getRecipient(completeAddress.address); - expect(account).toEqual(completeAddress); - expect(recipient).toBeUndefined(); - }); - - it('registers a recipient and returns it as a recipient only and not as an account', async () => { - const completeAddress = await CompleteAddress.random(); - - await rpcServer.registerRecipient(completeAddress); - - // Check that the recipient is correctly registered using the getAccounts and getRecipients methods - const accounts = await rpcServer.getAccounts(); - const recipients = await rpcServer.getRecipients(); - expect(accounts).toEqual([]); - expect(recipients).toEqual([completeAddress]); - - // Check that the recipient is correctly registered using the getAccount and getRecipient methods - const account = await rpcServer.getAccount(completeAddress.address); - const recipient = await rpcServer.getRecipient(completeAddress.address); - expect(account).toBeUndefined(); - expect(recipient).toEqual(completeAddress); - }); - - it('cannot register the same account twice', async () => { - const keyPair = ConstantKeyPair.random(await Grumpkin.new()); - const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); - - await rpcServer.registerAccount(await keyPair.getPrivateKey(), completeAddress); - await expect(async () => rpcServer.registerAccount(await keyPair.getPrivateKey(), completeAddress)).rejects.toThrow( - `Complete address corresponding to ${completeAddress.address} already exists`, - ); - }); - - it('cannot register the same recipient twice', async () => { - const completeAddress = await CompleteAddress.random(); - - await rpcServer.registerRecipient(completeAddress); - await expect(() => rpcServer.registerRecipient(completeAddress)).rejects.toThrow( - `Complete address corresponding to ${completeAddress.address} already exists`, - ); - }); - - it('throws when getting public storage for non-existent contract', async () => { - const contract = AztecAddress.random(); - await expect(async () => await rpcServer.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow( - `Contract ${contract.toString()} is not deployed`, - ); - }); -}); diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts new file mode 100644 index 00000000000..ee958e331ea --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts @@ -0,0 +1,22 @@ +import { Grumpkin } from '@aztec/circuits.js/barretenberg'; +import { TestKeyStore } from '@aztec/key-store'; +import { AztecNode, AztecRPC } from '@aztec/types'; + +import { mock } from 'jest-mock-extended'; + +import { MemoryDB } from '../../database/memory_db.js'; +import { RpcServerConfig } from '../../index.js'; +import { AztecRPCServer } from '../aztec_rpc_server.js'; +import { aztecRpcTestSuite } from './aztec_rpc_test_suite.js'; + +async function createAztecRpcServer(): Promise { + const keyStore = new TestKeyStore(await Grumpkin.new()); + const node = mock(); + const db = new MemoryDB(); + const config: RpcServerConfig = { + l2BlockPollingIntervalMS: 100, + }; + return new AztecRPCServer(keyStore, node, db, config); +} + +aztecRpcTestSuite('AztecRPCServer', await createAztecRpcServer()); diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts new file mode 100644 index 00000000000..5a8b4bc2870 --- /dev/null +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -0,0 +1,71 @@ +import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; +import { Grumpkin } from '@aztec/circuits.js/barretenberg'; +import { ConstantKeyPair } from '@aztec/key-store'; +import { AztecRPC } from '@aztec/types'; + +export const aztecRpcTestSuite = (testName: string, rpc: AztecRPC) => { + describe(testName, function () { + it('registers an account and returns it as an account only and not as a recipient', async () => { + const keyPair = ConstantKeyPair.random(await Grumpkin.new()); + const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); + + await rpc.registerAccount(await keyPair.getPrivateKey(), completeAddress); + + // Check that the account is correctly registered using the getAccounts and getRecipients methods + const accounts = await rpc.getAccounts(); + const recipients = await rpc.getRecipients(); + expect(accounts).toContainEqual(completeAddress); + expect(recipients).not.toContainEqual(completeAddress); + + // Check that the account is correctly registered using the getAccount and getRecipient methods + const account = await rpc.getAccount(completeAddress.address); + const recipient = await rpc.getRecipient(completeAddress.address); + expect(account).toEqual(completeAddress); + expect(recipient).toBeUndefined(); + }); + + it('registers a recipient and returns it as a recipient only and not as an account', async () => { + const completeAddress = await CompleteAddress.random(); + + await rpc.registerRecipient(completeAddress); + + // Check that the recipient is correctly registered using the getAccounts and getRecipients methods + const accounts = await rpc.getAccounts(); + const recipients = await rpc.getRecipients(); + expect(accounts).not.toContainEqual(completeAddress); + expect(recipients).toContainEqual(completeAddress); + + // Check that the recipient is correctly registered using the getAccount and getRecipient methods + const account = await rpc.getAccount(completeAddress.address); + const recipient = await rpc.getRecipient(completeAddress.address); + expect(account).toBeUndefined(); + expect(recipient).toEqual(completeAddress); + }); + + it('cannot register the same account twice', async () => { + const keyPair = ConstantKeyPair.random(await Grumpkin.new()); + const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); + + await rpc.registerAccount(await keyPair.getPrivateKey(), completeAddress); + await expect(async () => rpc.registerAccount(await keyPair.getPrivateKey(), completeAddress)).rejects.toThrow( + `Complete address corresponding to ${completeAddress.address} already exists`, + ); + }); + + it('cannot register the same recipient twice', async () => { + const completeAddress = await CompleteAddress.random(); + + await rpc.registerRecipient(completeAddress); + await expect(() => rpc.registerRecipient(completeAddress)).rejects.toThrow( + `Complete address corresponding to ${completeAddress.address} already exists`, + ); + }); + + it('throws when getting public storage for non-existent contract', async () => { + const contract = AztecAddress.random(); + await expect(async () => await rpc.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow( + `Contract ${contract.toString()} is not deployed`, + ); + }); + }); +}; From 5f09cc1d543fd566e207833b456f21e73ce6db44 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 14:36:56 +0000 Subject: [PATCH 03/15] test: aztec rpc sandbox test setup --- yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts | 1 + yarn-project/end-to-end/src/aztec_rpc.test.ts | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 yarn-project/end-to-end/src/aztec_rpc.test.ts diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts index 3af43b1a499..1cc9adbe3cb 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/index.ts @@ -1,2 +1,3 @@ export * from './aztec_rpc_server.js'; export * from './create_aztec_rpc_server.js'; +export { aztecRpcTestSuite } from './test/aztec_rpc_test_suite.js'; diff --git a/yarn-project/end-to-end/src/aztec_rpc.test.ts b/yarn-project/end-to-end/src/aztec_rpc.test.ts new file mode 100644 index 00000000000..f56cf34dc99 --- /dev/null +++ b/yarn-project/end-to-end/src/aztec_rpc.test.ts @@ -0,0 +1,6 @@ +import { aztecRpcTestSuite } from '@aztec/aztec-rpc'; +import { createAztecRpcClient } from '@aztec/aztec.js'; + +const { SANDBOX_URL = 'http://localhost:8080' } = process.env; + +aztecRpcTestSuite('AztecRPCServer', createAztecRpcClient(SANDBOX_URL)); From 52a1ff101af8ec45c1f173d442569c68ee25307d Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 14:37:40 +0000 Subject: [PATCH 04/15] chore: enabling aztec rpc sandbox test in CI --- .circleci/config.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 41b99c8e5fc..65ee5b68f8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -915,6 +915,17 @@ jobs: name: "Test" command: cond_spot_run_tests end-to-end e2e_aztec_js_browser.test.ts docker-compose-e2e-sandbox.yml + aztec-rpc: + docker: + - image: aztecprotocol/alpine-build-image + resource_class: small + steps: + - *checkout + - *setup_env + - run: + name: "Test" + command: cond_spot_run_tests end-to-end aztec_rpc.test.ts docker-compose-e2e-sandbox.yml + e2e-canary-test: docker: - image: aztecprotocol/alpine-build-image @@ -1301,6 +1312,7 @@ workflows: - e2e-p2p: *e2e_test - e2e-canary-test: *e2e_test - e2e-browser-sandbox: *e2e_test + - aztec-rpc: *e2e_test - e2e-end: requires: @@ -1326,6 +1338,7 @@ workflows: - e2e-p2p - e2e-browser-sandbox - e2e-canary-test + - aztec-rpc <<: *defaults - deploy-dockerhub: From 45f340d5a0d09bb09fad88eb271f00944f0502d1 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 14:43:01 +0000 Subject: [PATCH 05/15] naming fix --- .circleci/config.yml | 8 ++++---- .../src/{aztec_rpc.test.ts => aztec_rpc_sandbox.test.ts} | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename yarn-project/end-to-end/src/{aztec_rpc.test.ts => aztec_rpc_sandbox.test.ts} (70%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 65ee5b68f8c..1e94f79917e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -915,7 +915,7 @@ jobs: name: "Test" command: cond_spot_run_tests end-to-end e2e_aztec_js_browser.test.ts docker-compose-e2e-sandbox.yml - aztec-rpc: + aztec-rpc-sandbox: docker: - image: aztecprotocol/alpine-build-image resource_class: small @@ -924,7 +924,7 @@ jobs: - *setup_env - run: name: "Test" - command: cond_spot_run_tests end-to-end aztec_rpc.test.ts docker-compose-e2e-sandbox.yml + command: cond_spot_run_tests end-to-end aztec_rpc_sandbox.test.ts docker-compose-e2e-sandbox.yml e2e-canary-test: docker: @@ -1312,7 +1312,7 @@ workflows: - e2e-p2p: *e2e_test - e2e-canary-test: *e2e_test - e2e-browser-sandbox: *e2e_test - - aztec-rpc: *e2e_test + - aztec-rpc-sandbox: *e2e_test - e2e-end: requires: @@ -1338,7 +1338,7 @@ workflows: - e2e-p2p - e2e-browser-sandbox - e2e-canary-test - - aztec-rpc + - aztec-rpc-sandbox <<: *defaults - deploy-dockerhub: diff --git a/yarn-project/end-to-end/src/aztec_rpc.test.ts b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts similarity index 70% rename from yarn-project/end-to-end/src/aztec_rpc.test.ts rename to yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts index f56cf34dc99..ea02c64b154 100644 --- a/yarn-project/end-to-end/src/aztec_rpc.test.ts +++ b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts @@ -3,4 +3,4 @@ import { createAztecRpcClient } from '@aztec/aztec.js'; const { SANDBOX_URL = 'http://localhost:8080' } = process.env; -aztecRpcTestSuite('AztecRPCServer', createAztecRpcClient(SANDBOX_URL)); +aztecRpcTestSuite('aztec_rpc_sandbox', createAztecRpcClient(SANDBOX_URL)); From 1a574e8bb8dd1d149472386c4016d04491814198 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 15 Aug 2023 15:22:24 +0000 Subject: [PATCH 06/15] fix: propagating error messages to JsonRpcClient --- .../src/json-rpc/client/json_rpc_client.ts | 15 ++++--- .../src/json-rpc/server/json_rpc_server.ts | 45 ++++++++++++------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts index 3be85887024..760d2f9db7b 100644 --- a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts +++ b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts @@ -37,15 +37,20 @@ export async function defaultFetch(host: string, rpcMethod: string, body: any, u }); } - if (!resp.ok) { - throw new Error(resp.statusText); - } - + let responseJson; try { - return await resp.json(); + responseJson = await resp.json(); } catch (err) { + if (!resp.ok) { + throw new Error(resp.statusText); + } throw new Error(`Failed to parse body as JSON: ${resp.text()}`); } + if (!resp.ok) { + throw new Error(responseJson.error); + } + + return responseJson; } /** diff --git a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts index 8de26421e9f..dee461570e6 100644 --- a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts +++ b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts @@ -80,13 +80,20 @@ export class JsonRpcServer { } router.post(`/${method}`, async (ctx: Koa.Context) => { const { params = [], jsonrpc, id } = ctx.request.body as any; - const result = await this.proxy.call(method, params); - ctx.body = { - jsonrpc, - id, - result: convertBigintsInObj(result), - }; - ctx.status = 200; + try { + const result = await this.proxy.call(method, params); + ctx.body = { + jsonrpc, + id, + result: convertBigintsInObj(result), + }; + ctx.status = 200; + } catch (err: any) { + // Propagate the error message to the client. Plenty of the errors are expected to occur (e.g. adding + // a duplicate recipient) so this is necessary. + ctx.status = 409; + ctx.body = { error: err.message }; + } }); } } else { @@ -101,15 +108,23 @@ export class JsonRpcServer { ) { ctx.status = 400; ctx.body = { error: `Invalid method name: ${method}` }; - } - const result = await this.proxy.call(method, params); + } else { + try { + const result = await this.proxy.call(method, params); - ctx.body = { - jsonrpc, - id, - result: convertBigintsInObj(result), - }; - ctx.status = 200; + ctx.body = { + jsonrpc, + id, + result: convertBigintsInObj(result), + }; + ctx.status = 200; + } catch (err: any) { + // Propagate the error message to the client. Plenty of the errors are expected to occur (e.g. adding + // a duplicate recipient) so this is necessary. + ctx.status = 409; + ctx.body = { error: err.message }; + } + } }); } From 4a7b954bb57a6a1857569391a6df2a05146a4b8c Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 07:21:48 +0000 Subject: [PATCH 07/15] test: waiting for sandbox to be ready --- .../src/aztec_rpc_server/test/aztec_rpc_server.test.ts | 2 +- .../src/aztec_rpc_server/test/aztec_rpc_test_suite.ts | 8 +++++++- yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts | 10 ++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts index ee958e331ea..c4dab53a708 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts @@ -19,4 +19,4 @@ async function createAztecRpcServer(): Promise { return new AztecRPCServer(keyStore, node, db, config); } -aztecRpcTestSuite('AztecRPCServer', await createAztecRpcServer()); +aztecRpcTestSuite('AztecRPCServer', createAztecRpcServer); diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts index 5a8b4bc2870..42f65d70d5b 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -3,8 +3,14 @@ import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ConstantKeyPair } from '@aztec/key-store'; import { AztecRPC } from '@aztec/types'; -export const aztecRpcTestSuite = (testName: string, rpc: AztecRPC) => { +export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise) => { describe(testName, function () { + let rpc: AztecRPC; + + beforeAll(async () => { + rpc = await aztecRpcSetup(); + }); + it('registers an account and returns it as an account only and not as a recipient', async () => { const keyPair = ConstantKeyPair.random(await Grumpkin.new()); const completeAddress = await CompleteAddress.fromPrivateKey(await keyPair.getPrivateKey()); diff --git a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts index ea02c64b154..15444248db2 100644 --- a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts +++ b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts @@ -1,6 +1,12 @@ import { aztecRpcTestSuite } from '@aztec/aztec-rpc'; -import { createAztecRpcClient } from '@aztec/aztec.js'; +import { createAztecRpcClient, waitForSandbox } from '@aztec/aztec.js'; const { SANDBOX_URL = 'http://localhost:8080' } = process.env; -aztecRpcTestSuite('aztec_rpc_sandbox', createAztecRpcClient(SANDBOX_URL)); +const setup = async () => { + const aztecRpc = createAztecRpcClient(SANDBOX_URL); + await waitForSandbox(aztecRpc); + return aztecRpc; +}; + +aztecRpcTestSuite('aztec_rpc_sandbox', setup); From fc626e94876ef6b9a5d07b5669f34118e501e6ae Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 07:41:33 +0000 Subject: [PATCH 08/15] test: increased hook time --- .../aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts index 42f65d70d5b..76f07683e91 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -9,7 +9,7 @@ export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise beforeAll(async () => { rpc = await aztecRpcSetup(); - }); + }, 120_000); it('registers an account and returns it as an account only and not as a recipient', async () => { const keyPair = ConstantKeyPair.random(await Grumpkin.new()); From 8b4bc466487035b48efac7d235c48a561895ecc2 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 08:46:16 +0000 Subject: [PATCH 09/15] test: addContracts --- .../src/aztec_rpc_server/aztec_rpc_server.ts | 4 +++ .../test/aztec_rpc_test_suite.ts | 13 +++++++++- .../memory_contract_database.ts | 6 ++++- .../aztec.js/src/aztec_rpc_client/wallet.ts | 3 +++ .../aztec.js/src/contract/contract.test.ts | 26 ++++++++----------- yarn-project/types/src/contract_database.ts | 6 +++++ .../types/src/interfaces/aztec_rpc.ts | 8 ++++++ yarn-project/types/src/mocks.ts | 17 ++++++++++-- 8 files changed, 64 insertions(+), 19 deletions(-) diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts index a5e46c18c51..672ef0e9e08 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/aztec_rpc_server.ts @@ -136,6 +136,10 @@ export class AztecRPCServer implements AztecRPC { } } + public async getContracts(): Promise { + return (await this.db.getContracts()).map(c => c.address); + } + public async getPublicStorageAt(contract: AztecAddress, storageSlot: Fr) { if ((await this.getContractData(contract)) === undefined) { throw new Error(`Contract ${contract.toString()} is not deployed`); diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts index 76f07683e91..e64ae509b9e 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -1,7 +1,7 @@ import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ConstantKeyPair } from '@aztec/key-store'; -import { AztecRPC } from '@aztec/types'; +import { AztecRPC, DeployedContract, randomDeployedContract } from '@aztec/types'; export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise) => { describe(testName, function () { @@ -73,5 +73,16 @@ export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise `Contract ${contract.toString()} is not deployed`, ); }); + + it('successfully adds a contract', async () => { + const contracts: DeployedContract[] = [randomDeployedContract(), randomDeployedContract()]; + await rpc.addContracts(contracts); + + const expectedContractAddresses = contracts.map(contract => contract.address); + const contractAddresses = await rpc.getContracts(); + + // check if all the contracts were returned + expect(contractAddresses).toEqual(expect.arrayContaining(expectedContractAddresses)); + }); }); }; diff --git a/yarn-project/aztec-rpc/src/contract_database/memory_contract_database.ts b/yarn-project/aztec-rpc/src/contract_database/memory_contract_database.ts index 2940faa6fd2..1f7efbb2395 100644 --- a/yarn-project/aztec-rpc/src/contract_database/memory_contract_database.ts +++ b/yarn-project/aztec-rpc/src/contract_database/memory_contract_database.ts @@ -33,10 +33,14 @@ export class MemoryContractDatabase implements ContractDatabase { * @param address - The AztecAddress to search for in the stored contracts. * @returns A Promise resolving to the ContractDao instance matching the given address or undefined. */ - public getContract(address: AztecAddress) { + public getContract(address: AztecAddress): Promise { return Promise.resolve(this.contracts.find(c => c.address.equals(address))); } + public getContracts(): Promise { + return Promise.resolve(this.contracts); + } + /** * Retrieve the bytecode associated with a given contract address and function selector. * This function searches through the stored contracts to find a matching contract and function, diff --git a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts index bd01275174b..cc0d1ef6e8b 100644 --- a/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts +++ b/yarn-project/aztec.js/src/aztec_rpc_client/wallet.ts @@ -52,6 +52,9 @@ export abstract class BaseWallet implements Wallet { addContracts(contracts: DeployedContract[]): Promise { return this.rpc.addContracts(contracts); } + getContracts(): Promise { + return this.rpc.getContracts(); + } simulateTx(txRequest: TxExecutionRequest): Promise { return this.rpc.simulateTx(txRequest); } diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index 0a67a40b33d..c29107f6baa 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -1,7 +1,14 @@ import { AztecAddress, CompleteAddress, EthAddress } from '@aztec/circuits.js'; import { ABIParameterVisibility, ContractAbi, FunctionType } from '@aztec/foundation/abi'; -import { randomBytes } from '@aztec/foundation/crypto'; -import { ContractData, DeployedContract, NodeInfo, Tx, TxExecutionRequest, TxHash, TxReceipt } from '@aztec/types'; +import { + ContractData, + NodeInfo, + Tx, + TxExecutionRequest, + TxHash, + TxReceipt, + randomDeployedContract, +} from '@aztec/types'; import { MockProxy, mock } from 'jest-mock-extended'; @@ -80,17 +87,6 @@ describe('Contract Class', () => { ], }; - const randomContractAbi = (): ContractAbi => ({ - name: randomBytes(4).toString('hex'), - functions: [], - }); - - const randomDeployContract = (): DeployedContract => ({ - abi: randomContractAbi(), - address: AztecAddress.random(), - portalContract: EthAddress.random(), - }); - beforeEach(async () => { account = await CompleteAddress.random(); wallet = mock(); @@ -143,7 +139,7 @@ describe('Contract Class', () => { }); it('should add contract and dependencies to aztec rpc', async () => { - const entry = randomDeployContract(); + const entry = randomDeployedContract(); const contract = await Contract.at(entry.address, entry.abi, wallet); { @@ -154,7 +150,7 @@ describe('Contract Class', () => { } { - const dependencies = [randomDeployContract(), randomDeployContract()]; + const dependencies = [randomDeployedContract(), randomDeployedContract()]; await contract.attach(entry.portalContract, dependencies); expect(wallet.addContracts).toHaveBeenCalledTimes(1); expect(wallet.addContracts).toHaveBeenCalledWith([entry, ...dependencies]); diff --git a/yarn-project/types/src/contract_database.ts b/yarn-project/types/src/contract_database.ts index 3a8a81bb01a..7eb2333ed88 100644 --- a/yarn-project/types/src/contract_database.ts +++ b/yarn-project/types/src/contract_database.ts @@ -24,4 +24,10 @@ export interface ContractDatabase { * @returns A Promise resolving to the ContractDao instance matching the given address or undefined. */ getContract(address: AztecAddress): Promise; + + /** + * Retrieve all ContractDao instances stored in the database. + * @returns A Promise resolving to an array of all stored ContractDao instances. + */ + getContracts(): Promise; } diff --git a/yarn-project/types/src/interfaces/aztec_rpc.ts b/yarn-project/types/src/interfaces/aztec_rpc.ts index 6348b4d88b3..98dd2ac0af7 100644 --- a/yarn-project/types/src/interfaces/aztec_rpc.ts +++ b/yarn-project/types/src/interfaces/aztec_rpc.ts @@ -69,6 +69,7 @@ export interface AztecRPC { * @param privKey - Private key of the corresponding user master public key. * @param account - A complete address of the account. * @returns Empty promise. + * @throws If the account is already registered. */ registerAccount(privKey: PrivateKey, account: CompleteAddress): Promise; @@ -80,6 +81,7 @@ export interface AztecRPC { * This is because we don't have the associated private key and for this reason we can't decrypt * the recipient's notes. We can send notes to this account because we can encrypt them with the recipient's * public key. + * @throws If the recipient is already registered. */ registerRecipient(recipient: CompleteAddress): Promise; @@ -122,6 +124,12 @@ export interface AztecRPC { */ addContracts(contracts: DeployedContract[]): Promise; + /** + * Retrieves the list of addresses of contracts added to this rpc server. + * @returns A promise that resolves to an array of contracts addresses registered on this RPC server. + */ + getContracts(): Promise; + /** * Create a transaction for a contract function call with the provided arguments. * Throws an error if the contract or function is unknown. diff --git a/yarn-project/types/src/mocks.ts b/yarn-project/types/src/mocks.ts index 5ec58ec3e08..9136d6adb8f 100644 --- a/yarn-project/types/src/mocks.ts +++ b/yarn-project/types/src/mocks.ts @@ -1,9 +1,11 @@ -import { MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, Proof } from '@aztec/circuits.js'; +import { AztecAddress, EthAddress, MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, Proof } from '@aztec/circuits.js'; import { makeKernelPublicInputs, makePublicCallRequest } from '@aztec/circuits.js/factories'; +import { ContractAbi } from '@aztec/foundation/abi'; +import { randomBytes } from '@aztec/foundation/crypto'; import times from 'lodash.times'; -import { EncodedContractFunction, FunctionL2Logs, TxL2Logs } from './index.js'; +import { DeployedContract, EncodedContractFunction, FunctionL2Logs, TxL2Logs } from './index.js'; import { Tx } from './tx/index.js'; /** @@ -24,3 +26,14 @@ export const mockTx = (seed = 1) => { times(MAX_PUBLIC_CALL_STACK_LENGTH_PER_TX, makePublicCallRequest), ); }; + +export const randomContractAbi = (): ContractAbi => ({ + name: randomBytes(4).toString('hex'), + functions: [], +}); + +export const randomDeployedContract = (): DeployedContract => ({ + abi: randomContractAbi(), + address: AztecAddress.random(), + portalContract: EthAddress.random(), +}); From 7fb612a87f6400dffd0663b795359a81db6cddaa Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 09:14:11 +0000 Subject: [PATCH 10/15] test: simulateTx throws when expected --- .../test/aztec_rpc_test_suite.ts | 20 +++++++++++++++++-- .../entrypoint/entrypoint_collection.ts | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts index e64ae509b9e..be72f826de5 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -1,7 +1,7 @@ -import { AztecAddress, CompleteAddress, Fr } from '@aztec/circuits.js'; +import { AztecAddress, CompleteAddress, Fr, FunctionData, TxContext } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ConstantKeyPair } from '@aztec/key-store'; -import { AztecRPC, DeployedContract, randomDeployedContract } from '@aztec/types'; +import { AztecRPC, DeployedContract, TxExecutionRequest, randomDeployedContract } from '@aztec/types'; export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise) => { describe(testName, function () { @@ -84,5 +84,21 @@ export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise // check if all the contracts were returned expect(contractAddresses).toEqual(expect.arrayContaining(expectedContractAddresses)); }); + + it('throws when simulating a tx targeting public entrypoint', async () => { + const functionData = FunctionData.empty(); + functionData.isPrivate = false; + const txExecutionRequest = new TxExecutionRequest( + AztecAddress.random(), + functionData, + new Fr(0), + TxContext.empty(), + [], + ); + + await expect(async () => await rpc.simulateTx(txExecutionRequest)).rejects.toThrow( + 'Public entrypoints are not allowed', + ); + }); }); }; diff --git a/yarn-project/aztec.js/src/account/entrypoint/entrypoint_collection.ts b/yarn-project/aztec.js/src/account/entrypoint/entrypoint_collection.ts index 0be7bd65ad2..1da44c3c766 100644 --- a/yarn-project/aztec.js/src/account/entrypoint/entrypoint_collection.ts +++ b/yarn-project/aztec.js/src/account/entrypoint/entrypoint_collection.ts @@ -32,7 +32,7 @@ export class EntrypointCollection implements Entrypoint { /** * Registers an entrypoint against an aztec address - * @param addr - The aztec address agianst which to register the implementation. + * @param addr - The aztec address against which to register the implementation. * @param impl - The entrypoint to be registered. */ public registerAccount(addr: AztecAddress, impl: Entrypoint) { From ecdb06a909115fdfe9d7cd540e4ff182fc902f37 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 09:57:40 +0000 Subject: [PATCH 11/15] WIP --- .../aztec-node/src/aztec-node/server.ts | 2 +- .../test/aztec_rpc_server.test.ts | 9 +++- .../test/aztec_rpc_test_suite.ts | 43 +++++++++++++++---- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 17b34b7abe9..d56ee76e16d 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -71,7 +71,7 @@ export class AztecNodeService implements AztecNode { // first create and sync the archiver const archiver = await Archiver.createAndSync(config); - // we idenfity the P2P transaction protocol by using the rollup contract address. + // we identify the P2P transaction protocol by using the rollup contract address. // this may well change in future config.transactionProtocol = `/aztec/tx/${config.rollupContract.toString()}`; diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts index c4dab53a708..cc572ee3003 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_server.test.ts @@ -5,7 +5,7 @@ import { AztecNode, AztecRPC } from '@aztec/types'; import { mock } from 'jest-mock-extended'; import { MemoryDB } from '../../database/memory_db.js'; -import { RpcServerConfig } from '../../index.js'; +import { EthAddress, RpcServerConfig } from '../../index.js'; import { AztecRPCServer } from '../aztec_rpc_server.js'; import { aztecRpcTestSuite } from './aztec_rpc_test_suite.js'; @@ -16,6 +16,13 @@ async function createAztecRpcServer(): Promise { const config: RpcServerConfig = { l2BlockPollingIntervalMS: 100, }; + + // Setup the relevant mocks + node.getBlockHeight.mockResolvedValue(2); + node.getVersion.mockResolvedValue(1); + node.getChainId.mockResolvedValue(1); + node.getRollupAddress.mockResolvedValue(EthAddress.random()); + return new AztecRPCServer(keyStore, node, db, config); } diff --git a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts index be72f826de5..7b12753bf5c 100644 --- a/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts +++ b/yarn-project/aztec-rpc/src/aztec_rpc_server/test/aztec_rpc_test_suite.ts @@ -1,7 +1,13 @@ import { AztecAddress, CompleteAddress, Fr, FunctionData, TxContext } from '@aztec/circuits.js'; import { Grumpkin } from '@aztec/circuits.js/barretenberg'; import { ConstantKeyPair } from '@aztec/key-store'; -import { AztecRPC, DeployedContract, TxExecutionRequest, randomDeployedContract } from '@aztec/types'; +import { + AztecRPC, + DeployedContract, + INITIAL_L2_BLOCK_NUM, + TxExecutionRequest, + randomDeployedContract, +} from '@aztec/types'; export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise) => { describe(testName, function () { @@ -67,13 +73,6 @@ export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise ); }); - it('throws when getting public storage for non-existent contract', async () => { - const contract = AztecAddress.random(); - await expect(async () => await rpc.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow( - `Contract ${contract.toString()} is not deployed`, - ); - }); - it('successfully adds a contract', async () => { const contracts: DeployedContract[] = [randomDeployedContract(), randomDeployedContract()]; await rpc.addContracts(contracts); @@ -100,5 +99,33 @@ export const aztecRpcTestSuite = (testName: string, aztecRpcSetup: () => Promise 'Public entrypoints are not allowed', ); }); + + // Note: Not testing a successful run of `simulateTx`, `sendTx`, `getTxReceipt` and `viewTx` here as it requires + // a larger setup and it's sufficiently tested in the e2e tests. + + it('throws when getting public storage for non-existent contract', async () => { + const contract = AztecAddress.random(); + await expect(async () => await rpc.getPublicStorageAt(contract, new Fr(0n))).rejects.toThrow( + `Contract ${contract.toString()} is not deployed`, + ); + }); + + // Note: Not testing `getContractDataAndBytecode`, `getContractData` and `getUnencryptedLogs` here as these + // functions only call AztecNode and these methods are frequently used by the e2e tests. + + it('successfully gets a block number', async () => { + const blockNum = await rpc.getBlockNum(); + expect(blockNum).toBeGreaterThanOrEqual(INITIAL_L2_BLOCK_NUM); + }); + + it('successfully gets node info', async () => { + const nodeInfo = await rpc.getNodeInfo(); + expect(nodeInfo.version).toBeDefined(); + expect(nodeInfo.chainId).toBeDefined(); + expect(nodeInfo.rollupAddress).toBeDefined(); + }); + + // Note: Not testing `isGlobalStateSynchronised`, `isAccountStateSynchronised` and `getSyncStatus` as these methods + // only call synchroniser. }); }; From 618dbb5a608913bb9072be7dbdadd8497e654df4 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 10:41:41 +0000 Subject: [PATCH 12/15] fix: CI --- yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts index 15444248db2..326c7d6d584 100644 --- a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts +++ b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts @@ -1,10 +1,10 @@ import { aztecRpcTestSuite } from '@aztec/aztec-rpc'; -import { createAztecRpcClient, waitForSandbox } from '@aztec/aztec.js'; +import { createAztecRpcClient, mustSucceedFetch, waitForSandbox } from '@aztec/aztec.js'; const { SANDBOX_URL = 'http://localhost:8080' } = process.env; const setup = async () => { - const aztecRpc = createAztecRpcClient(SANDBOX_URL); + const aztecRpc = createAztecRpcClient(SANDBOX_URL, mustSucceedFetch); await waitForSandbox(aztecRpc); return aztecRpc; }; From db2a204ec182a6a1e6d3ee299b68f9fda690ece7 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 16 Aug 2023 11:42:16 +0000 Subject: [PATCH 13/15] CI fix --- yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts | 2 +- .../foundation/src/json-rpc/client/json_rpc_client.ts | 2 +- yarn-project/foundation/src/retry/index.ts | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts b/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts index c052734c543..6d7584dd5f1 100644 --- a/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts +++ b/yarn-project/end-to-end/src/e2e_aztec_js_browser.test.ts @@ -126,7 +126,7 @@ conditionalDescribe()('e2e_aztec.js_browser', () => { const accounts = await testClient.getAccounts(); const stringAccounts = accounts.map(acc => acc.address.toString()); expect(stringAccounts.includes(result)).toBeTruthy(); - }); + }, 15_000); it('Deploys Private Token contract', async () => { const txHash = await page.evaluate( diff --git a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts index 760d2f9db7b..2c0f340e888 100644 --- a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts +++ b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts @@ -47,7 +47,7 @@ export async function defaultFetch(host: string, rpcMethod: string, body: any, u throw new Error(`Failed to parse body as JSON: ${resp.text()}`); } if (!resp.ok) { - throw new Error(responseJson.error); + throw new Error(responseJson.error, { cause: 'thrownByServer' }); } return responseJson; diff --git a/yarn-project/foundation/src/retry/index.ts b/yarn-project/foundation/src/retry/index.ts index 0a098f45ef2..d03debae6ef 100644 --- a/yarn-project/foundation/src/retry/index.ts +++ b/yarn-project/foundation/src/retry/index.ts @@ -38,6 +38,12 @@ export async function retry( try { return await fn(); } catch (err: any) { + if (err.cause == 'thrownByServer') { + // If the error is specifically marked as thrown by the server, we don't retry because the error should be + // propagated. This is because it's an "intentional error" (e.g. "Contract is not deployed") and not + // a connection error or an unexpected software bug. + throw err; + } const s = backoff.next().value; if (s === undefined) { throw err; From 0b4a3d1c23b40a7406b95414b6d58e0446a19da1 Mon Sep 17 00:00:00 2001 From: benesjan Date: Thu, 17 Aug 2023 06:37:23 +0000 Subject: [PATCH 14/15] feat: more generic error --- .../foundation/src/json-rpc/server/json_rpc_server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts index dee461570e6..f217fee70dc 100644 --- a/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts +++ b/yarn-project/foundation/src/json-rpc/server/json_rpc_server.ts @@ -91,7 +91,7 @@ export class JsonRpcServer { } catch (err: any) { // Propagate the error message to the client. Plenty of the errors are expected to occur (e.g. adding // a duplicate recipient) so this is necessary. - ctx.status = 409; + ctx.status = 400; ctx.body = { error: err.message }; } }); @@ -121,7 +121,7 @@ export class JsonRpcServer { } catch (err: any) { // Propagate the error message to the client. Plenty of the errors are expected to occur (e.g. adding // a duplicate recipient) so this is necessary. - ctx.status = 409; + ctx.status = 400; ctx.body = { error: err.message }; } } From e1d40d90ae92b65d1aa5b397b2bc878fb758afb5 Mon Sep 17 00:00:00 2001 From: benesjan Date: Thu, 17 Aug 2023 07:51:08 +0000 Subject: [PATCH 15/15] refactor: retry cleanup --- .../src/aztec_rpc_client/aztec_rpc_client.ts | 1 + .../end-to-end/src/aztec_rpc_sandbox.test.ts | 4 +-- .../foundation/src/json-rpc/client/index.ts | 7 ++++- .../src/json-rpc/client/json_rpc_client.ts | 31 +++++++++++++++++-- yarn-project/foundation/src/retry/index.ts | 10 +++--- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts b/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts index 5dcd4d4610b..432d33b80df 100644 --- a/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts +++ b/yarn-project/aztec.js/src/aztec_rpc_client/aztec_rpc_client.ts @@ -13,6 +13,7 @@ import { } from '@aztec/types'; export { mustSucceedFetch } from '@aztec/foundation/json-rpc/client'; +export { mustSucceedFetchUnlessNoRetry } from '@aztec/foundation/json-rpc/client'; export const createAztecRpcClient = (url: string, fetch = defaultFetch): AztecRPC => createJsonRpcClient( diff --git a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts index 326c7d6d584..84082670d0e 100644 --- a/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts +++ b/yarn-project/end-to-end/src/aztec_rpc_sandbox.test.ts @@ -1,10 +1,10 @@ import { aztecRpcTestSuite } from '@aztec/aztec-rpc'; -import { createAztecRpcClient, mustSucceedFetch, waitForSandbox } from '@aztec/aztec.js'; +import { createAztecRpcClient, mustSucceedFetchUnlessNoRetry, waitForSandbox } from '@aztec/aztec.js'; const { SANDBOX_URL = 'http://localhost:8080' } = process.env; const setup = async () => { - const aztecRpc = createAztecRpcClient(SANDBOX_URL, mustSucceedFetch); + const aztecRpc = createAztecRpcClient(SANDBOX_URL, mustSucceedFetchUnlessNoRetry); await waitForSandbox(aztecRpc); return aztecRpc; }; diff --git a/yarn-project/foundation/src/json-rpc/client/index.ts b/yarn-project/foundation/src/json-rpc/client/index.ts index 90a8bcc736d..786e07ee69e 100644 --- a/yarn-project/foundation/src/json-rpc/client/index.ts +++ b/yarn-project/foundation/src/json-rpc/client/index.ts @@ -1 +1,6 @@ -export { createJsonRpcClient, mustSucceedFetch, defaultFetch } from './json_rpc_client.js'; +export { + createJsonRpcClient, + mustSucceedFetch, + mustSucceedFetchUnlessNoRetry, + defaultFetch, +} from './json_rpc_client.js'; diff --git a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts index 2c0f340e888..9163396a482 100644 --- a/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts +++ b/yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts @@ -5,7 +5,7 @@ import { RemoteObject } from 'comlink'; import { createDebugLogger } from '../../log/index.js'; -import { retry } from '../../retry/index.js'; +import { NoRetryError, retry } from '../../retry/index.js'; import { ClassConverter, JsonClassConverterInput, StringClassConverterInput } from '../class_converter.js'; import { JsonStringify, convertFromJsonObj, convertToJsonObj } from '../convert.js'; @@ -18,9 +18,17 @@ const debug = createDebugLogger('json-rpc:json_rpc_client'); * @param host - The host URL. * @param method - The RPC method name. * @param body - The RPC payload. + * @param noRetry - Whether to throw a `NoRetryError` in case the response is not ok and the body contains an error + * message (see `retry` function for more details). * @returns The parsed JSON response, or throws an error. */ -export async function defaultFetch(host: string, rpcMethod: string, body: any, useApiEndpoints: boolean) { +export async function defaultFetch( + host: string, + rpcMethod: string, + body: any, + useApiEndpoints: boolean, + noRetry = false, +) { debug(`JsonRpcClient.fetch`, host, rpcMethod, '->', body); let resp: Response; if (useApiEndpoints) { @@ -47,7 +55,11 @@ export async function defaultFetch(host: string, rpcMethod: string, body: any, u throw new Error(`Failed to parse body as JSON: ${resp.text()}`); } if (!resp.ok) { - throw new Error(responseJson.error, { cause: 'thrownByServer' }); + if (noRetry) { + throw new NoRetryError(responseJson.error); + } else { + throw new Error(responseJson.error); + } } return responseJson; @@ -60,6 +72,19 @@ export async function mustSucceedFetch(host: string, rpcMethod: string, body: an return await retry(() => defaultFetch(host, rpcMethod, body, useApiEndpoints), 'JsonRpcClient request'); } +/** + * A fetch function with retries unless the error is a NoRetryError. + */ +export async function mustSucceedFetchUnlessNoRetry( + host: string, + rpcMethod: string, + body: any, + useApiEndpoints: boolean, +) { + const noRetry = true; + return await retry(() => defaultFetch(host, rpcMethod, body, useApiEndpoints, noRetry), 'JsonRpcClient request'); +} + /** * Creates a Proxy object that delegates over RPC and satisfies RemoteObject. * The server should have ran new JsonRpcServer(). diff --git a/yarn-project/foundation/src/retry/index.ts b/yarn-project/foundation/src/retry/index.ts index d03debae6ef..4287678bffa 100644 --- a/yarn-project/foundation/src/retry/index.ts +++ b/yarn-project/foundation/src/retry/index.ts @@ -2,6 +2,9 @@ import { createDebugLogger } from '../log/index.js'; import { sleep } from '../sleep/index.js'; import { Timer } from '../timer/index.js'; +/** An error that indicates that the operation should not be retried. */ +export class NoRetryError extends Error {} + /** * Generates a backoff sequence for retrying operations with an increasing delay. * The backoff sequence follows this pattern: 1, 1, 1, 2, 4, 8, 16, 32, 64, ... @@ -27,6 +30,7 @@ export function* backoffGenerator() { * @param backoff - The optional backoff generator providing the intervals in seconds between retries. Defaults to a predefined series. * @param log - Logger to use for logging. * @returns A Promise that resolves with the successful result of the provided function, or rejects if backoff generator ends. + * @throws If `NoRetryError` is thrown by the `fn`, it is rethrown. */ export async function retry( fn: () => Promise, @@ -38,10 +42,8 @@ export async function retry( try { return await fn(); } catch (err: any) { - if (err.cause == 'thrownByServer') { - // If the error is specifically marked as thrown by the server, we don't retry because the error should be - // propagated. This is because it's an "intentional error" (e.g. "Contract is not deployed") and not - // a connection error or an unexpected software bug. + if (err instanceof NoRetryError) { + // A special error that indicates that the operation should not be retried. Rethrow it. throw err; } const s = backoff.next().value;