diff --git a/spartan/bootstrap.sh b/spartan/bootstrap.sh index 9cea8ce82313..a7a0cd8eb7c1 100755 --- a/spartan/bootstrap.sh +++ b/spartan/bootstrap.sh @@ -62,6 +62,7 @@ function test_cmds { echo "$hash timeout -v 30m ./spartan/bootstrap.sh test-kind-4epochs" echo "$hash timeout -v 30m ./spartan/bootstrap.sh test-kind-upgrade-rollup-version" echo "$hash timeout -v 30m ./spartan/bootstrap.sh test-prod-deployment" + echo "$hash timeout -v 30m ./spartan/bootstrap.sh test-cli-upgrade-with-lock" fi } @@ -137,6 +138,11 @@ case "$cmd" in "test-prod-deployment") FRESH_INSTALL=false INSTALL_METRICS=false ./scripts/test_prod_deployment.sh ;; + "test-cli-upgrade-with-lock") + OVERRIDES="telemetry.enabled=false" \ + FRESH_INSTALL=${FRESH_INSTALL:-true} INSTALL_METRICS=false \ + ./scripts/test_kind.sh src/spartan/upgrade_via_cli.test.ts 1-validators.yaml upgrade-via-cli${NAME_POSTFIX:-} + ;; "test-local") # Isolate network stack in docker. docker_isolate ../scripts/run_native_testnet.sh -i -val 3 diff --git a/spartan/scripts/upgrade_rollup_with_lock.sh b/spartan/scripts/upgrade_rollup_with_lock.sh index 128604fe4f79..4ba6b7e9070e 100755 --- a/spartan/scripts/upgrade_rollup_with_lock.sh +++ b/spartan/scripts/upgrade_rollup_with_lock.sh @@ -16,7 +16,7 @@ set -exu # ETHEREUM_HOST=http://localhost:8545 \ # MNEMONIC="test test test test test test test test test test test junk" \ # ./upgrade_rollup_with_lock.sh \ -# --aztec-docker-tag c5e2b43044862882a68de47cac07b7116e74e51e \ +# --aztec-docker-image aztecprotocol/aztec:c5e2b43044862882a68de47cac07b7116e74e51e \ # --registry 0x29f815e32efdef19883cf2b92a766b7aebadd326 \ # --address 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 \ # --deposit-amount 200000000000000000000000 \ @@ -47,13 +47,13 @@ MINT="" SALT=$((RANDOM % 1000000)) # The default path to the aztec binary within the docker image AZTEC_BIN="/usr/src/yarn-project/aztec/dest/bin/index.js" -AZTEC_DOCKER_TAG="" +AZTEC_DOCKER_IMAGE="" # Parse command line arguments (these will override env vars if provided) while [[ $# -gt 0 ]]; do case $1 in - --aztec-docker-tag) - AZTEC_DOCKER_TAG="$2" + --aztec-docker-image) + AZTEC_DOCKER_IMAGE="$2" shift 2 ;; --aztec-bin) @@ -105,13 +105,12 @@ cleanup() { } # if aztec-docker-tag is set, use it -if [ -n "$AZTEC_DOCKER_TAG" ]; then - IMAGE_NAME="aztecprotocol/aztec:${AZTEC_DOCKER_TAG}" - EXE="docker run --rm --network=host --env-file .env.tmp $IMAGE_NAME $AZTEC_BIN" +if [ -n "$AZTEC_DOCKER_IMAGE" ]; then + EXE="docker run --rm --network=host --env-file .env.tmp $AZTEC_DOCKER_IMAGE $AZTEC_BIN" # Check if the image exists locally before pulling it - if ! docker images $IMAGE_NAME -q; then - echo "Pulling docker image $IMAGE_NAME" - docker pull $IMAGE_NAME + if ! docker images $AZTEC_DOCKER_IMAGE -q; then + echo "Pulling docker image $AZTEC_DOCKER_IMAGE" + docker pull $AZTEC_DOCKER_IMAGE fi trap cleanup EXIT INT TERM HUP QUIT # Create a temporary .env file diff --git a/yarn-project/cli/src/cmds/l1/governance_utils.ts b/yarn-project/cli/src/cmds/l1/governance_utils.ts index c10c0248b1f5..5ed2ff4875cd 100644 --- a/yarn-project/cli/src/cmds/l1/governance_utils.ts +++ b/yarn-project/cli/src/cmds/l1/governance_utils.ts @@ -95,7 +95,7 @@ export async function proposeWithLock({ withdrawAddress: clients.walletClient.account.address, }); if (json) { - log(JSON.stringify({ proposalId }, null, 2)); + log(JSON.stringify({ proposalId: Number(proposalId) }, null, 2)); } else { log(`Proposed with lock`); log(`Proposal ID: ${proposalId}`); diff --git a/yarn-project/end-to-end/src/spartan/upgrade_via_cli.test.ts b/yarn-project/end-to-end/src/spartan/upgrade_via_cli.test.ts new file mode 100644 index 000000000000..0d22cb53b2f8 --- /dev/null +++ b/yarn-project/end-to-end/src/spartan/upgrade_via_cli.test.ts @@ -0,0 +1,105 @@ +import { type PXE, createCompatibleClient } from '@aztec/aztec.js'; +import { GovernanceContract, RegistryContract, createEthereumChain, createL1Clients } from '@aztec/ethereum'; +import { createLogger } from '@aztec/foundation/log'; + +import type { ChildProcess } from 'child_process'; + +import { getAztecBin, isK8sConfig, runProjectScript, setupEnvironment, startPortForward } from './utils.js'; + +const config = setupEnvironment(process.env); + +// technically it doesn't require a k8s env, but it doesn't seem we're keeping the "local" versions of the spartan scripts up to date +// and I didn't want to plumb through the config and test this with the local scripts. +if (!isK8sConfig(config)) { + throw new Error('This test must be run in a k8s environment'); +} + +const debugLogger = createLogger('e2e:spartan-test:upgrade_via_cli'); + +describe('upgrade via cli', () => { + let pxe: PXE; + const forwardProcesses: ChildProcess[] = []; + let ETHEREUM_HOSTS: string; + let MNEMONIC: string; + beforeAll(async () => { + let PXE_URL: string; + MNEMONIC = config.L1_ACCOUNT_MNEMONIC; + { + const { process, port } = await startPortForward({ + resource: `svc/${config.INSTANCE_NAME}-aztec-network-pxe`, + namespace: config.NAMESPACE, + containerPort: config.CONTAINER_PXE_PORT, + }); + forwardProcesses.push(process); + PXE_URL = `http://127.0.0.1:${port}`; + } + { + const { process, port } = await startPortForward({ + resource: `svc/${config.INSTANCE_NAME}-aztec-network-eth-execution`, + namespace: config.NAMESPACE, + containerPort: config.CONTAINER_ETHEREUM_PORT, + }); + forwardProcesses.push(process); + ETHEREUM_HOSTS = `http://127.0.0.1:${port}`; + } + pxe = await createCompatibleClient(PXE_URL, debugLogger); + }); + + afterAll(() => { + forwardProcesses.forEach(p => p.kill()); + }); + + it( + 'should be able to get node enr', + async () => { + const info = await pxe.getNodeInfo(); + + const chain = createEthereumChain([ETHEREUM_HOSTS], info.l1ChainId); + const { walletClient: l1WalletClient, publicClient: l1PublicClient } = createL1Clients( + [ETHEREUM_HOSTS], + MNEMONIC, + chain.chainInfo, + ); + + const governance = new GovernanceContract( + info.l1ContractAddresses.governanceAddress.toString(), + l1PublicClient, + l1WalletClient, + ); + const { minimumVotes, proposeConfig } = await governance.getConfiguration(); + + const depositAmount = proposeConfig.lockAmount + minimumVotes; + + const registry = new RegistryContract(l1PublicClient, info.l1ContractAddresses.registryAddress.toString()); + const oldNumberOfVersions = await registry.getNumberOfVersions(); + + const exitCode = await runProjectScript( + 'spartan/scripts/upgrade_rollup_with_lock.sh', + [ + '--aztec-bin', + getAztecBin(), + '--registry', + info.l1ContractAddresses.registryAddress.toString(), + '--address', + l1WalletClient.account.address, + '--deposit-amount', + depositAmount.toString(), + '--mint', + ], + debugLogger, + { + MNEMONIC, + ETHEREUM_HOSTS, + L1_CHAIN_ID: info.l1ChainId.toString(), + LOG_JSON: 'false', + LOG_LEVEL: 'debug', + }, + ); + expect(exitCode).toBe(0); + + const newNumberOfVersions = await registry.getNumberOfVersions(); + expect(newNumberOfVersions).toBe(oldNumberOfVersions + 1); + }, + 6 * config.AZTEC_PROOF_SUBMISSION_WINDOW * config.AZTEC_SLOT_DURATION * 1000, + ); +}); diff --git a/yarn-project/end-to-end/src/spartan/utils.ts b/yarn-project/end-to-end/src/spartan/utils.ts index cebf8c7e932b..66c8b99ae9f2 100644 --- a/yarn-project/end-to-end/src/spartan/utils.ts +++ b/yarn-project/end-to-end/src/spartan/utils.ts @@ -86,6 +86,49 @@ export function setupEnvironment(env: unknown): EnvConfig { return config; } +/** + * @param path - The path to the script, relative to the project root + * @param args - The arguments to pass to the script + * @param logger - The logger to use + * @returns The exit code of the script + */ +function runScript(path: string, args: string[], logger: Logger, env?: Record) { + const childProcess = spawn(path, args, { + stdio: ['ignore', 'pipe', 'pipe'], + env: env ? { ...process.env, ...env } : process.env, + }); + return new Promise((resolve, reject) => { + childProcess.on('close', (code: number | null) => resolve(code ?? 0)); + childProcess.on('error', reject); + childProcess.stdout?.on('data', (data: Buffer) => { + logger.info(data.toString()); + }); + childProcess.stderr?.on('data', (data: Buffer) => { + logger.error(data.toString()); + }); + }); +} + +export function getAztecBin() { + return path.join(getGitProjectRoot(), 'yarn-project/aztec/dest/bin/index.js'); +} + +/** + * Runs the Aztec binary + * @param args - The arguments to pass to the Aztec binary + * @param logger - The logger to use + * @param env - Optional environment variables to set for the process + * @returns The exit code of the Aztec binary + */ +export function runAztecBin(args: string[], logger: Logger, env?: Record) { + return runScript('node', [getAztecBin(), ...args], logger, env); +} + +export function runProjectScript(script: string, args: string[], logger: Logger, env?: Record) { + const scriptPath = script.startsWith('/') ? script : path.join(getGitProjectRoot(), script); + return runScript(scriptPath, args, logger, env); +} + export async function startPortForward({ resource, namespace, @@ -128,7 +171,6 @@ export async function startPortForward({ } const portNumber = parseInt(str.slice(port + 1)); logger.info(`Port forward connected: ${portNumber}`); - logger.info(`Port forward connected: ${portNumber}`); resolve(portNumber); } else { logger.silent(str); @@ -580,3 +622,19 @@ export async function rollAztecPods(namespace: string) { await waitForResourceByLabel({ resource: 'pods', namespace: namespace, label: 'app=validator' }); await waitForResourceByLabel({ resource: 'pods', namespace: namespace, label: 'app=pxe' }); } + +/** + * Returns the absolute path to the git repository root + */ +export function getGitProjectRoot(): string { + try { + const rootDir = execSync('git rev-parse --show-toplevel', { + encoding: 'utf-8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + + return rootDir; + } catch (error) { + throw new Error(`Failed to determine git project root: ${error}`); + } +} diff --git a/yarn-project/ethereum/src/contracts/registry.ts b/yarn-project/ethereum/src/contracts/registry.ts index a42e756faf54..7c5ded5db51f 100644 --- a/yarn-project/ethereum/src/contracts/registry.ts +++ b/yarn-project/ethereum/src/contracts/registry.ts @@ -103,4 +103,9 @@ export class RegistryContract { coinIssuerAddress: EthAddress.fromString(coinIssuer), }; } + + public async getNumberOfVersions(): Promise { + const version = await this.registry.read.numberOfVersions(); + return Number(version); + } }