diff --git a/.github/workflows/sepolia-test.yml b/.github/workflows/sepolia-test.yml new file mode 100644 index 00000000000..b93a3b615b2 --- /dev/null +++ b/.github/workflows/sepolia-test.yml @@ -0,0 +1,80 @@ +name: Run public testnet test +on: + schedule: + - cron: '00 08 * * 1-5' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} + GIT_COMMIT: ${{ github.sha }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + # Uncomment the following to run against Sepolia + # SEQ_PUBLISHER_PRIVATE_KEY: ${{ secrets.SEPOLIA_SEQ_PRIVATE_KEY }} + # PROVER_PUBLISHER_PRIVATE_KEY: ${{ secrets.SEPOLIA_PROVER_PRIVATE_KEY }} + # ETHEREUM_HOST: 'https://sepolia.infura.io/v3/${{ secrets.SEPOLIA_API_KEY }}'; + # L1_CHAIN_ID: '11155111' # Sepolia Chain ID + +jobs: + setup: + uses: ./.github/workflows/setup-runner.yml + with: + username: ${{ github.event.pull_request.user.login || github.actor }} + runner_type: builder-x86 + secrets: inherit + + build: + needs: setup + runs-on: ${{ github.event.pull_request.user.login || github.actor }}-x86 + outputs: + e2e_list: ${{ steps.e2e_list.outputs.list }} + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ env.GIT_COMMIT }}" + + - uses: ./.github/ci-setup-action + with: + concurrency_key: build-test-artifacts-${{ github.actor }} + + - name: "Build E2E Image" + timeout-minutes: 40 + run: | + earthly-ci ./yarn-project+export-e2e-test-images + + - name: Create list of devnet end-to-end jobs + id: e2e_list + run: echo "list=$(earthly ls ./yarn-project/end-to-end | grep 'public_testnet' | sed 's/+//' | jq -R . | jq -cs .)" >> $GITHUB_OUTPUT + + e2e: + needs: build + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + test: ${{ fromJson( needs.build.outputs.e2e_list )}} + steps: + - uses: actions/checkout@v4 + with: { ref: "${{ env.GIT_COMMIT }}" } + - uses: ./.github/ci-setup-action + - name: Setup and Test + timeout-minutes: 40 + uses: ./.github/ensure-tester-with-images + with: + # big machine since we're doing proving + runner_type: "64core-tester-x86" + builder_type: builder-x86 + # these are copied to the tester and expected by the earthly command below + # if they fail to copy, it will try to build them on the tester and fail + builder_images_to_copy: aztecprotocol/end-to-end:${{ env.GIT_COMMIT }} + # command to produce the images in case they don't exist + builder_command: scripts/earthly-ci ./yarn-project+export-e2e-test-images + run: | + set -eux + cd ./yarn-project/end-to-end/ + export FORCE_COLOR=1 + ../../scripts/earthly-ci -P --no-output +${{ matrix.test }} diff --git a/yarn-project/accounts/src/testing/create_account.ts b/yarn-project/accounts/src/testing/create_account.ts index 5f9903165da..02baa1f108b 100644 --- a/yarn-project/accounts/src/testing/create_account.ts +++ b/yarn-project/accounts/src/testing/create_account.ts @@ -1,3 +1,4 @@ +import { type WaitOpts } from '@aztec/aztec.js'; import { type AccountWalletWithSecretKey } from '@aztec/aztec.js/wallet'; import { type PXE } from '@aztec/circuit-types'; import { Fr, deriveSigningKey } from '@aztec/circuits.js'; @@ -27,6 +28,7 @@ export async function createAccounts( pxe: PXE, numberOfAccounts = 1, secrets: Fr[] = [], + waitOpts: WaitOpts = { interval: 0.1 }, ): Promise { const accounts = []; @@ -56,6 +58,6 @@ export async function createAccounts( // Send them and await them to be mined const txs = await Promise.all(accounts.map(account => account.deploy())); - await Promise.all(txs.map(tx => tx.wait({ interval: 0.1 }))); + await Promise.all(txs.map(tx => tx.wait(waitOpts))); return Promise.all(accounts.map(account => account.getWallet())); } diff --git a/yarn-project/end-to-end/Earthfile b/yarn-project/end-to-end/Earthfile index 3f094fccbbb..38d58b341c8 100644 --- a/yarn-project/end-to-end/Earthfile +++ b/yarn-project/end-to-end/Earthfile @@ -184,6 +184,9 @@ e2e-static-calls: e2e-token-contract: DO +E2E_TEST --test=./src/e2e_token_contract +e2e-public-testnet: + DO +E2E_TEST --test=./src/public-testnet + flakey-e2e-tests: DO +E2E_TEST --test=./src/flakey --allow_fail=true diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index c57f8fe542c..e35d3157f77 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -232,7 +232,7 @@ async function teardown(context: SubsystemsContext | undefined) { export async function createAndSyncProverNode( rollupAddress: EthAddress, - proverNodePrivateKey: Buffer, + proverNodePrivateKey: `0x${string}`, aztecNodeConfig: AztecNodeConfig, aztecNode: AztecNode, ) { @@ -255,7 +255,7 @@ export async function createAndSyncProverNode( proverId: new Fr(42), realProofs: false, proverAgentConcurrency: 2, - publisherPrivateKey: `0x${proverNodePrivateKey.toString('hex')}`, + publisherPrivateKey: proverNodePrivateKey, proverNodeMaxPendingJobs: 100, }; const proverNode = await createProverNode(proverConfig, { @@ -332,7 +332,7 @@ async function setupFromFresh( logger.verbose('Creating and syncing a simulated prover node...'); const proverNode = await createAndSyncProverNode( deployL1ContractsValues.l1ContractAddresses.rollupAddress, - proverNodePrivateKey!, + `0x${proverNodePrivateKey!.toString('hex')}`, aztecNodeConfig, aztecNode, ); @@ -416,7 +416,7 @@ async function setupFromState(statePath: string, logger: Logger): Promise { const l1Artifacts: L1ContractArtifactsForDeployment = { registry: { @@ -145,7 +147,7 @@ export const setupL1Contracts = async ( }, }; - const l1Data = await deployL1Contracts(l1RpcUrl, account, foundry, logger, l1Artifacts, { + const l1Data = await deployL1Contracts(l1RpcUrl, account, chain, logger, l1Artifacts, { l2FeeJuiceAddress: FeeJuiceAddress, vkTreeRoot: getVKTreeRoot(), salt: args.salt, @@ -291,6 +293,8 @@ type SetupOptions = { deployL1ContractsValues?: DeployL1Contracts; /** Whether to skip deployment of protocol contracts (auth registry, etc) */ skipProtocolContracts?: boolean; + /** Salt to use in L1 contract deployment */ + salt?: number; } & Partial; /** Context for an end-to-end test as returned by the `setup` function */ @@ -329,6 +333,7 @@ export async function setup( pxeOpts: Partial = {}, enableGas = false, enableValidators = false, + chain: Chain = foundry, ): Promise { const config = { ...getConfigEnvVars(), ...opts }; const logger = getLogger(); @@ -336,6 +341,9 @@ export async function setup( let anvil: Anvil | undefined; if (!config.l1RpcUrl) { + if (chain.id != foundry.id) { + throw new Error(`No ETHEREUM_HOST set but non anvil chain requested`); + } if (PXE_URL) { throw new Error( `PXE_URL provided but no ETHEREUM_HOST set. Refusing to run, please set both variables so tests can deploy L1 contracts to the same Anvil instance`, @@ -359,19 +367,29 @@ export async function setup( await ethCheatCodes.loadChainState(opts.stateLoad); } - const publisherHdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 0 }); - const publisherPrivKeyRaw = publisherHdAccount.getHdKey().privateKey; - const publisherPrivKey = publisherPrivKeyRaw === null ? null : Buffer.from(publisherPrivKeyRaw); + let publisherPrivKey = undefined; + let publisherHdAccount = undefined; + + if (config.publisherPrivateKey && config.publisherPrivateKey != NULL_KEY) { + publisherHdAccount = privateKeyToAccount(config.publisherPrivateKey); + } else if (!MNEMONIC) { + throw new Error(`Mnemonic not provided and no publisher private key`); + } else { + publisherHdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 0 }); + const publisherPrivKeyRaw = publisherHdAccount.getHdKey().privateKey; + publisherPrivKey = publisherPrivKeyRaw === null ? null : Buffer.from(publisherPrivKeyRaw); + config.publisherPrivateKey = `0x${publisherPrivKey!.toString('hex')}`; + } if (PXE_URL) { // we are setting up against a remote environment, l1 contracts are assumed to already be deployed - return await setupWithRemoteEnvironment(publisherHdAccount, config, logger, numberOfAccounts, enableGas); + return await setupWithRemoteEnvironment(publisherHdAccount!, config, logger, numberOfAccounts, enableGas); } const deployL1ContractsValues = - opts.deployL1ContractsValues ?? (await setupL1Contracts(config.l1RpcUrl, publisherHdAccount, logger)); + opts.deployL1ContractsValues ?? + (await setupL1Contracts(config.l1RpcUrl, publisherHdAccount!, logger, { salt: opts.salt }, chain)); - config.publisherPrivateKey = `0x${publisherPrivKey!.toString('hex')}`; config.l1Contracts = deployL1ContractsValues.l1ContractAddresses; // Run the test with validators enabled diff --git a/yarn-project/end-to-end/src/public-testnet/e2e_public_testnet_transfer.test.ts b/yarn-project/end-to-end/src/public-testnet/e2e_public_testnet_transfer.test.ts new file mode 100644 index 00000000000..fb9affae54d --- /dev/null +++ b/yarn-project/end-to-end/src/public-testnet/e2e_public_testnet_transfer.test.ts @@ -0,0 +1,114 @@ +import { createAccounts } from '@aztec/accounts/testing'; +import { type AztecNodeConfig } from '@aztec/aztec-node'; +import { type AztecNode, type DebugLogger, Fr, type PXE } from '@aztec/aztec.js'; +import { NULL_KEY } from '@aztec/ethereum'; +import { EasyPrivateTokenContract } from '@aztec/noir-contracts.js'; +import { type ProverNode, type ProverNodeConfig, getProverNodeConfigFromEnv } from '@aztec/prover-node'; + +import { foundry, sepolia } from 'viem/chains'; + +import { createAndSyncProverNode } from '../fixtures/snapshot_manager.js'; +import { getPrivateKeyFromIndex, setup } from '../fixtures/utils.js'; + +// process.env.SEQ_PUBLISHER_PRIVATE_KEY = ''; +// process.env.PROVER_PUBLISHER_PRIVATE_KEY = ''; +// process.env.ETHEREUM_HOST= 'https://sepolia.infura.io/v3/'; +// process.env.L1_CHAIN_ID = '11155111'; + +describe(`deploys and transfers a private only token`, () => { + let secretKey1: Fr; + let secretKey2: Fr; + let proverConfig: ProverNodeConfig; + let config: AztecNodeConfig; + let aztecNode: AztecNode; + let proverNode: ProverNode; + + let pxe: PXE; + let logger: DebugLogger; + let teardown: () => Promise; + + beforeEach(async () => { + const chainId = !process.env.L1_CHAIN_ID ? foundry.id : +process.env.L1_CHAIN_ID; + const chain = chainId == sepolia.id ? sepolia : foundry; // Not the best way of doing this. + ({ logger, pxe, teardown, config, aztecNode } = await setup( + 0, + { skipProtocolContracts: true, stateLoad: undefined }, + {}, + false, + false, + chain, + )); + proverConfig = getProverNodeConfigFromEnv(); + const proverNodePrivateKey = getPrivateKeyFromIndex(2); + proverConfig.publisherPrivateKey = + proverConfig.publisherPrivateKey === NULL_KEY + ? `0x${proverNodePrivateKey?.toString('hex')}` + : proverConfig.publisherPrivateKey; + + proverNode = await createAndSyncProverNode( + config.l1Contracts.rollupAddress, + proverConfig.publisherPrivateKey, + config, + aztecNode, + ); + }, 600_000); + + afterEach(async () => { + await proverNode.stop(); + await teardown(); + }); + + it('calls a private function', async () => { + const initialBalance = 100000000000n; + const transferValue = 5n; + secretKey1 = Fr.random(); + secretKey2 = Fr.random(); + + logger.info(`Deploying accounts.`); + + const accounts = await createAccounts(pxe, 2, [secretKey1, secretKey2], { + interval: 0.1, + proven: true, + provenTimeout: 600, + }); + + logger.info(`Accounts deployed, deploying token.`); + + const [deployerWallet, recipientWallet] = accounts; + + const token = await EasyPrivateTokenContract.deploy( + deployerWallet, + initialBalance, + deployerWallet.getAddress(), + deployerWallet.getAddress(), + ) + .send({ + universalDeploy: true, + skipPublicDeployment: true, + skipClassRegistration: true, + skipInitialization: false, + skipPublicSimulation: true, + }) + .deployed({ + proven: true, + provenTimeout: 600, + }); + + logger.info(`Performing transfer.`); + + await token.methods + .transfer(transferValue, deployerWallet.getAddress(), recipientWallet.getAddress(), deployerWallet.getAddress()) + .send() + .wait({ proven: true, provenTimeout: 600 }); + + logger.info(`Transfer completed`); + + const balanceDeployer = await token.methods.get_balance(deployerWallet.getAddress()).simulate(); + const balanceRecipient = await token.methods.get_balance(recipientWallet.getAddress()).simulate(); + + logger.info(`Deployer balance: ${balanceDeployer}, Recipient balance: ${balanceRecipient}`); + + expect(balanceDeployer).toBe(initialBalance - transferValue); + expect(balanceRecipient).toBe(transferValue); + }, 600_000); +}); diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 8897a4d1811..95951a6fe7a 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -161,12 +161,14 @@ export const deployL1Contracts = async ( }; return await (await fetch(rpcUrl, content)).json(); }; - const interval = 12; - const res = await rpcCall(rpcUrl, 'anvil_setBlockTimestampInterval', [interval]); - if (res.error) { - throw new Error(`Error setting block interval: ${res.error.message}`); + if (chain.id == foundry.id) { + const interval = 12; + const res = await rpcCall(rpcUrl, 'anvil_setBlockTimestampInterval', [interval]); + if (res.error) { + throw new Error(`Error setting block interval: ${res.error.message}`); + } + logger.info(`Set block interval to ${interval}`); } - logger.info(`Set block interval to ${interval}`); logger.info(`Deploying contracts from ${account.address.toString()}...`);