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: Check sandbox version matches CLI's #1849

Merged
merged 2 commits into from
Aug 29, 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
2 changes: 1 addition & 1 deletion l1-contracts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aztec/l1-contracts",
"version": "0.0.1",
"version": "0.1.0",
"license": "Apache-2.0",
"description": "Aztec contracts for the Ethereum mainnet and testnets",
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/aztec-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@aztec/noir-contracts": "workspace:^",
"@aztec/types": "workspace:^",
"commander": "^9.0.0",
"semver": "^7.5.4",
"tslib": "^2.4.0",
"viem": "^1.2.5"
},
Expand All @@ -52,6 +53,7 @@
"@types/jest": "^29.5.0",
"@types/node": "^18.7.23",
"jest": "^29.5.0",
"jest-mock-extended": "^3.0.5",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
Expand Down
34 changes: 34 additions & 0 deletions yarn-project/aztec-cli/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AztecRPC, NodeInfo } from '@aztec/types';

import { MockProxy, mock } from 'jest-mock-extended';

import { checkServerVersion } from './client.js';

describe('client', () => {
describe('checkServerVersion', () => {
let rpc: MockProxy<AztecRPC>;

beforeEach(() => {
rpc = mock<AztecRPC>();
});

it('checks versions match', async () => {
rpc.getNodeInfo.mockResolvedValue({ client: 'rpc@0.1.0-alpha47' } as NodeInfo);
await checkServerVersion(rpc, '0.1.0-alpha47');
});

it('reports mismatch on older rpc version', async () => {
rpc.getNodeInfo.mockResolvedValue({ client: 'rpc@0.1.0-alpha47' } as NodeInfo);
await expect(checkServerVersion(rpc, '0.1.0-alpha48')).rejects.toThrowError(
/is older than the expected by this CLI/,
);
});

it('reports mismatch on newer rpc version', async () => {
rpc.getNodeInfo.mockResolvedValue({ client: 'rpc@0.1.0-alpha48' } as NodeInfo);
await expect(checkServerVersion(rpc, '0.1.0-alpha47')).rejects.toThrowError(
/is newer than the expected by this CLI/,
);
});
});
});
66 changes: 65 additions & 1 deletion yarn-project/aztec-cli/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { createAztecRpcClient } from '@aztec/aztec.js';
import { AztecRPC, createAztecRpcClient } from '@aztec/aztec.js';
import { makeFetch } from '@aztec/foundation/json-rpc/client';
import { DebugLogger } from '@aztec/foundation/log';

import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { gtr, ltr, satisfies, valid } from 'semver';
import { fileURLToPath } from 'url';

const retries = [1, 1, 2];

Expand All @@ -12,3 +18,61 @@ export function createClient(rpcUrl: string) {
const fetch = makeFetch(retries, true);
return createAztecRpcClient(rpcUrl, fetch);
}

/**
* Creates an Aztec RPC client with a given set of retries on non-server errors.
* Checks that the RPC server matches the expected version, and warns if not.
* @param rpcUrl - URL of the RPC server.
* @param logger - Debug logger to warn version incompatibilities.
* @returns An RPC client.
*/
export async function createCompatibleClient(rpcUrl: string, logger: DebugLogger) {
const client = createClient(rpcUrl);
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json');
const packageJsonContents = JSON.parse(readFileSync(packageJsonPath).toString());
const expectedVersionRange = packageJsonContents.version; // During sandbox, we'll expect exact matches

try {
await checkServerVersion(client, expectedVersionRange, logger);
} catch (err) {
if (err instanceof VersionMismatchError) {
logger.warn(err.message);
} else {
throw err;
}
}

return client;
}

/** Mismatch between server and client versions. */
class VersionMismatchError extends Error {}

/**
* Checks that the RPC server version matches the expected one by this CLI. Throws if not.
* @param rpc - RPC server connection.
* @param expectedVersionRange - Expected version by CLI.
*/
export async function checkServerVersion(rpc: AztecRPC, expectedVersionRange: string, logger?: DebugLogger) {
const serverName = 'Aztec Sandbox';
const { client } = await rpc.getNodeInfo();
const version = client.split('@')[1];
logger?.debug(`Comparing server version ${version} against CLI expected ${expectedVersionRange}`);
if (!version || !valid(version)) {
throw new VersionMismatchError(`Missing or invalid version identifier for ${serverName} (${version ?? 'empty'}).`);
} else if (!satisfies(version, expectedVersionRange)) {
if (gtr(version, expectedVersionRange)) {
throw new VersionMismatchError(
`${serverName} is running version ${version} which is newer than the expected by this CLI (${expectedVersionRange}). Consider upgrading your CLI to a newer version.`,
);
} else if (ltr(version, expectedVersionRange)) {
throw new VersionMismatchError(
`${serverName} is running version ${version} which is older than the expected by this CLI (${expectedVersionRange}). Consider upgrading your ${serverName} to a newer version.`,
);
} else {
throw new VersionMismatchError(
`${serverName} is running version ${version} which does not match the expected by this CLI (${expectedVersionRange}).`,
);
}
}
}
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 @@ -22,7 +22,7 @@ import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { mnemonicToAccount } from 'viem/accounts';

import { createClient } from './client.js';
import { createCompatibleClient } 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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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(abiPath, log);
const constructorAbi = contractAbi.functions.find(({ name }) => name === 'constructor');

const client = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
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 = createClient(options.rpcUrl);
const client = await createCompatibleClient(options.rpcUrl, debugLogger);
const num = await client.getBlockNumber();
log(`${num}\n`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
toContractDao,
} from '@aztec/types';

import { RpcServerConfig } from '../config/index.js';
import { RpcServerConfig, getPackageInfo } from '../config/index.js';
import { ContractDataOracle } from '../contract_data_oracle/index.js';
import { Database } from '../database/index.js';
import { KernelOracle } from '../kernel_oracle/index.js';
Expand All @@ -56,6 +56,7 @@ import { Synchroniser } from '../synchroniser/index.js';
export class AztecRPCServer implements AztecRPC {
private synchroniser: Synchroniser;
private log: DebugLogger;
private clientInfo: string;

constructor(
private keyStore: KeyStore,
Expand All @@ -66,6 +67,9 @@ export class AztecRPCServer implements AztecRPC {
) {
this.log = createDebugLogger(logSuffix ? `aztec:rpc_server_${logSuffix}` : `aztec:rpc_server`);
this.synchroniser = new Synchroniser(node, db, logSuffix);

const { version, name } = getPackageInfo();
this.clientInfo = `${name.split('/')[name.split('/').length - 1]}@${version}`;
}

/**
Expand Down Expand Up @@ -276,6 +280,7 @@ export class AztecRPCServer implements AztecRPC {
version,
chainId,
rollupAddress,
client: this.clientInfo,
};
}

Expand Down
13 changes: 13 additions & 0 deletions yarn-project/aztec-rpc/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

/**
* Configuration settings for the RPC Server.
*/
Expand All @@ -18,3 +22,12 @@ export function getConfigEnvVars(): RpcServerConfig {
l2BlockPollingIntervalMS: RPC_SERVER_BLOCK_POLLING_INTERVAL_MS ? +RPC_SERVER_BLOCK_POLLING_INTERVAL_MS : 1000,
};
}

/**
* Returns package name and version.
*/
export function getPackageInfo() {
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json');
const { version, name } = JSON.parse(readFileSync(packageJsonPath).toString());
return { version, name };
}
2 changes: 1 addition & 1 deletion yarn-project/aztec-sandbox/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aztec/aztec-sandbox",
"version": "0.0.0",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./dest/index.js",
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/aztec-sandbox/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import { deployL1Contracts } from '@aztec/ethereum';
import { createDebugLogger } from '@aztec/foundation/log';
import { retryUntil } from '@aztec/foundation/retry';

import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { HDAccount, createPublicClient, http as httpViemTransport } from 'viem';
import { mnemonicToAccount } from 'viem/accounts';
import { foundry } from 'viem/chains';
Expand Down Expand Up @@ -61,8 +64,10 @@ async function main() {
const rpcConfig = getRpcConfigEnvVars();
const hdAccount = mnemonicToAccount(MNEMONIC);
const privKey = hdAccount.getHdKey().privateKey;
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../package.json');
const version: string = JSON.parse(readFileSync(packageJsonPath).toString()).version;

logger.info('Setting up Aztec Sandbox, please stand by...');
logger.info(`Setting up Aztec Sandbox v${version}, please stand by...`);
logger.info('Deploying rollup contracts to L1...');
const deployedL1Contracts = await waitThenDeploy(aztecNodeConfig.rpcUrl, hdAccount);
aztecNodeConfig.publisherPrivateKey = new PrivateKey(Buffer.from(privKey!));
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec.js/src/contract/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Contract Class', () => {
const mockTxHash = { type: 'TxHash' } as any as TxHash;
const mockTxReceipt = { type: 'TxReceipt' } as any as TxReceipt;
const mockViewResultValue = 1;
const mockNodeInfo: NodeInfo = { version: 1, chainId: 2, rollupAddress: EthAddress.random() };
const mockNodeInfo: NodeInfo = { version: 1, chainId: 2, rollupAddress: EthAddress.random(), client: '' };

const defaultAbi: ContractAbi = {
name: 'FooContract',
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/noir-compiler/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aztec/noir-compiler",
"version": "0.0.0",
"version": "0.1.0",
"type": "module",
"exports": {
".": "./dest/index.js",
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/prover-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aztec/prover-client",
"version": "0.0.0",
"version": "0.1.0",
"type": "module",
"exports": "./dest/index.js",
"typedocOptions": {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/rollup-provider/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aztec/rollup-provider",
"version": "0.0.0",
"version": "0.1.0",
"main": "dest/index.js",
"type": "module",
"exports": "./dest/index.js",
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/types/src/interfaces/aztec_rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type NodeInfo = {
* The rollup contract address
*/
rollupAddress: EthAddress;
/**
* Identifier of the client software.
*/
client: string;
};

/** Provides up to which block has been synced by different components. */
Expand Down
Loading