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

feat(cli): retry on http errors #1606

Merged
merged 1 commit into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions yarn-project/aztec-cli/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createAztecRpcClient } from '@aztec/aztec.js';
import { makeFetch } from '@aztec/foundation/json-rpc/client';

const retries = [1, 1, 2];

/**
* Creates an Aztec RPC client with a given set of retries on non-server errors.
* @param rpcUrl - URL of the RPC server.
* @returns An RPC client.
*/
export function createClient(rpcUrl: string) {
const fetch = makeFetch(retries, true);
return createAztecRpcClient(rpcUrl, fetch);
}
30 changes: 15 additions & 15 deletions yarn-project/aztec-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
ContractDeployer,
Fr,
Point,
createAztecRpcClient,
generatePublicKey,
getAccountWallets,
getSchnorrAccount,
Expand All @@ -23,6 +22,7 @@ import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { mnemonicToAccount } from 'viem/accounts';

import { createClient } from './client.js';
import { encodeArgs, parseStructString } from './encoding.js';
import {
deployAztecContracts,
Expand Down Expand Up @@ -127,7 +127,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
)
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async options => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const privateKey = options.privateKey
? new PrivateKey(Buffer.from(stripLeadingHex(options.privateKey), 'hex'))
: PrivateKey.random();
Expand Down Expand Up @@ -161,7 +161,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
const contractAbi = await getContractAbi(options.contractAbi, log);
const constructorAbi = contractAbi.functions.find(({ name }) => name === 'constructor');

const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const publicKey = options.publicKey ? Point.fromString(options.publicKey) : undefined;
const salt = options.salt ? Fr.fromBuffer(Buffer.from(stripLeadingHex(options.salt), 'hex')) : undefined;
const deployer = new ContractDeployer(contractAbi, client, publicKey);
Expand Down Expand Up @@ -189,7 +189,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.requiredOption('-ca, --contract-address <address>', 'An Aztec address to check if contract has been deployed to.')
.option('-u, --rpc-url <url>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async options => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const address = AztecAddress.fromString(options.contractAddress);
const isDeployed = await isContractDeployed(client, address);
if (isDeployed) log(`\nContract found at ${address.toString()}\n`);
Expand All @@ -202,7 +202,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets the receipt for the specified transaction hash.')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (_txHash, options) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const txHash = TxHash.fromString(_txHash);
const receipt = await client.getTxReceipt(txHash);
if (!receipt) {
Expand All @@ -219,7 +219,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.option('-b, --include-bytecode <boolean>', "Include the contract's public function bytecode, if any.", false)
.action(async (contractAddress, options) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const address = AztecAddress.fromString(contractAddress);
const contractDataWithOrWithoutBytecode = options.includeBytecode
? await client.getContractDataAndBytecode(address)
Expand Down Expand Up @@ -255,7 +255,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
const fromBlock = from ? parseInt(from) : 1;
const limitCount = limit ? parseInt(limit) : 100;

const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const logs = await client.getUnencryptedLogs(fromBlock, limitCount);
if (!logs.length) {
log(`No logs found in blocks ${fromBlock} to ${fromBlock + limitCount}`);
Expand All @@ -273,7 +273,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.requiredOption('-pa, --partial-address <partialAddress', 'The partially computed address of the account contract.')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async options => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const address = AztecAddress.fromString(options.address);
const publicKey = Point.fromString(options.publicKey);
const partialAddress = Fr.fromString(options.partialAddress);
Expand All @@ -287,7 +287,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets all the Aztec accounts stored in the Aztec RPC.')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (options: any) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const accounts = await client.getAccounts();
if (!accounts.length) {
log('No accounts found.');
Expand All @@ -305,7 +305,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.argument('<address>', 'The Aztec address to get account for')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (_address, options) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const address = AztecAddress.fromString(_address);
const account = await client.getAccount(address);

Expand All @@ -321,7 +321,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets all the recipients stored in the Aztec RPC.')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (options: any) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const recipients = await client.getRecipients();
if (!recipients.length) {
log('No recipients found.');
Expand All @@ -339,7 +339,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.argument('<address>', 'The Aztec address to get recipient for')
.option('-u, --rpc-url <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (_address, options) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const address = AztecAddress.fromString(_address);
const recipient = await client.getRecipient(address);

Expand Down Expand Up @@ -381,7 +381,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {

const privateKey = new PrivateKey(Buffer.from(stripLeadingHex(options.privateKey), 'hex'));

const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const wallet = await getAccountWallets(
client,
SchnorrAccountContractAbi,
Expand Down Expand Up @@ -428,7 +428,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
`Invalid number of args passed. Expected ${fnAbi.parameters.length}; Received: ${options.args.length}`,
);
}
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const from = await getTxSender(client, options.from);
const result = await client.viewTx(functionName, functionArgs, contractAddress, from);
log('\nView result: ', JsonStringify(result, true), '\n');
Expand Down Expand Up @@ -463,7 +463,7 @@ export function getProgram(log: LogFn, debugLogger: DebugLogger): Command {
.description('Gets the current Aztec L2 block number.')
.option('-u, --rpcUrl <string>', 'URL of the Aztec RPC', AZTEC_RPC_HOST || 'http://localhost:8080')
.action(async (options: any) => {
const client = createAztecRpcClient(options.rpcUrl);
const client = createClient(options.rpcUrl);
const num = await client.getBlockNum();
log(`${num}\n`);
});
Expand Down
1 change: 1 addition & 0 deletions yarn-project/foundation/src/json-rpc/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export {
mustSucceedFetch,
mustSucceedFetchUnlessNoRetry,
defaultFetch,
makeFetch,
} from './json_rpc_client.js';
22 changes: 20 additions & 2 deletions yarn-project/foundation/src/json-rpc/client/json_rpc_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
// while avoiding Promise of Promise.
import { RemoteObject } from 'comlink';

import { createDebugLogger } from '../../log/index.js';
import { NoRetryError, retry } from '../../retry/index.js';
import { DebugLogger, createDebugLogger } from '../../log/index.js';
import { NoRetryError, makeBackoff, retry } from '../../retry/index.js';
import { ClassConverter, JsonClassConverterInput, StringClassConverterInput } from '../class_converter.js';
import { JsonStringify, convertFromJsonObj, convertToJsonObj } from '../convert.js';

Expand Down Expand Up @@ -72,6 +72,24 @@ export async function mustSucceedFetch(host: string, rpcMethod: string, body: an
return await retry(() => defaultFetch(host, rpcMethod, body, useApiEndpoints), 'JsonRpcClient request');
}

/**
* Makes a fetch function that retries based on the given attempts.
* @param retries - Sequence of intervals (in seconds) to retry.
* @param noRetry - Whether to stop retries on server errors.
* @param log - Optional logger for logging attempts.
* @returns A fetch function.
*/
export function makeFetch(retries: number[], noRetry: boolean, log?: DebugLogger) {
return async (host: string, rpcMethod: string, body: any, useApiEndpoints: boolean) => {
return await retry(
() => defaultFetch(host, rpcMethod, body, useApiEndpoints, noRetry),
'JsonRpcClient request',
makeBackoff(retries),
log,
);
};
}

/**
* A fetch function with retries unless the error is a NoRetryError.
*/
Expand Down
11 changes: 11 additions & 0 deletions yarn-project/foundation/src/retry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ export function* backoffGenerator() {
}
}

/**
* Generates a backoff sequence based on the array of retry intervals to use with the `retry` function.
* @param retries - Intervals to retry (in seconds).
* @returns A generator sequence.
*/
export function* makeBackoff(retries: number[]) {
for (const retry of retries) {
yield retry;
}
}

/**
* Retry a given asynchronous function with a specific backoff strategy, until it succeeds or backoff generator ends.
* It logs the error and retry interval in case an error is caught. The function can be named for better log output.
Expand Down