diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index b4650218a63..4276621579e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -58,13 +58,7 @@ import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice'; import { getCanonicalInstanceDeployer } from '@aztec/protocol-contracts/instance-deployer'; import { getCanonicalKeyRegistryAddress } from '@aztec/protocol-contracts/key-registry'; import { getCanonicalMultiCallEntrypointAddress } from '@aztec/protocol-contracts/multi-call-entrypoint'; -import { - AggregateTxValidator, - DataTxValidator, - type GlobalVariableBuilder, - SequencerClient, - getGlobalVariableBuilder, -} from '@aztec/sequencer-client'; +import { AggregateTxValidator, DataTxValidator, GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client'; import { PublicProcessorFactory, WASMSimulator, createSimulationProvider } from '@aztec/simulator'; import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -202,7 +196,7 @@ export class AztecNodeService implements AztecNode { sequencer, ethereumChain.chainInfo.id, config.version, - getGlobalVariableBuilder(config), + new GlobalVariableBuilder(config), store, txValidator, telemetry, diff --git a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts index 4497c9e2341..5516a198777 100644 --- a/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts +++ b/yarn-project/end-to-end/src/composed/integration_l1_publisher.test.ts @@ -41,7 +41,7 @@ import { AvailabilityOracleAbi, OutboxAbi, RollupAbi } from '@aztec/l1-artifacts import { SHA256Trunc, StandardTree } from '@aztec/merkle-tree'; import { getVKTreeRoot } from '@aztec/noir-protocol-circuits-types'; import { TxProver } from '@aztec/prover-client'; -import { type L1Publisher, getL1Publisher } from '@aztec/sequencer-client'; +import { L1Publisher } from '@aztec/sequencer-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { MerkleTrees, ServerWorldStateSynchronizer, type WorldStateConfig } from '@aztec/world-state'; @@ -161,7 +161,7 @@ describe('L1Publisher integration', () => { builder = await TxProver.new(config, new NoopTelemetryClient()); prover = builder.createBlockProver(builderDb.asLatest()); - publisher = getL1Publisher( + publisher = new L1Publisher( { l1RpcUrl: config.l1RpcUrl, requiredConfirmations: 1, diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index ae6235af9d1..2dbb9260d31 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -3,7 +3,7 @@ import { type AztecNode } from '@aztec/circuit-types'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { createStore } from '@aztec/kv-store/utils'; import { createProverClient } from '@aztec/prover-client'; -import { getL1Publisher } from '@aztec/sequencer-client'; +import { L1Publisher } from '@aztec/sequencer-client'; import { createSimulationProvider } from '@aztec/simulator'; import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; @@ -43,7 +43,7 @@ export async function createProverNode( const prover = await createProverClient(config, telemetry); // REFACTOR: Move publisher out of sequencer package and into an L1-related package - const publisher = getL1Publisher(config, telemetry); + const publisher = new L1Publisher(config, telemetry); const txProvider = deps.aztecNodeTxProvider ? new AztecNodeTxProvider(deps.aztecNodeTxProvider) diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index f293f4ed979..02e17329981 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -8,8 +8,8 @@ import { type WorldStateSynchronizer } from '@aztec/world-state'; import { BlockBuilderFactory } from '../block_builder/index.js'; import { type SequencerClientConfig } from '../config.js'; -import { getGlobalVariableBuilder } from '../global_variable_builder/index.js'; -import { getL1Publisher } from '../publisher/index.js'; +import { GlobalVariableBuilder } from '../global_variable_builder/index.js'; +import { L1Publisher } from '../publisher/index.js'; import { Sequencer, type SequencerConfig } from '../sequencer/index.js'; import { TxValidatorFactory } from '../tx_validator/tx_validator_factory.js'; @@ -43,8 +43,8 @@ export class SequencerClient { simulationProvider: SimulationProvider, telemetryClient: TelemetryClient, ) { - const publisher = getL1Publisher(config, telemetryClient); - const globalsBuilder = getGlobalVariableBuilder(config); + const publisher = new L1Publisher(config, telemetryClient); + const globalsBuilder = new GlobalVariableBuilder(config); const merkleTreeDb = worldStateSynchronizer.getLatest(); const publicProcessorFactory = new PublicProcessorFactory( diff --git a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts index 6ba6e3a1f79..f37db0af1cd 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/global_builder.ts @@ -5,71 +5,47 @@ import { GasFees, GlobalVariables, } from '@aztec/circuits.js'; +import { type L1ReaderConfig, createEthereumChain } from '@aztec/ethereum'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; +import { RollupAbi } from '@aztec/l1-artifacts'; + +import { + type GetContractReturnType, + type HttpTransport, + type PublicClient, + createPublicClient, + getAddress, + getContract, + http, +} from 'viem'; +import type * as chains from 'viem/chains'; /** - * Reads values from L1 state that is used for the global values. + * Simple global variables builder. */ -export interface L1GlobalReader { - /** - * Fetches the version of the rollup contract. - * @returns The version of the rollup contract. - */ - getVersion(): Promise; - /** - * Gets the chain id. - * @returns The chain id. - */ - getChainId(): Promise; - - /** - * Gets the current L1 time. - * @returns The current L1 time. - */ - getL1CurrentTime(): Promise; +export class GlobalVariableBuilder { + private log = createDebugLogger('aztec:sequencer:global_variable_builder'); - /** - * Gets the current slot. - * @returns The current slot. - */ - getCurrentSlot(): Promise; + private rollupContract: GetContractReturnType>; + private publicClient: PublicClient; - /** - * Get the slot for a specific timestamp. - * @param timestamp - The timestamp to get the slot for. - */ - getSlotAt(timestamp: readonly [bigint]): Promise; + constructor(config: L1ReaderConfig) { + const { l1RpcUrl, l1ChainId: chainId, l1Contracts } = config; - /** - * Gets the timestamp for a slot - * @param slot - The slot to get the timestamp for. - * @returns The timestamp for the slot. - */ - getTimestampForSlot(slot: readonly [bigint]): Promise; -} + const chain = createEthereumChain(l1RpcUrl, chainId); -/** - * Builds global variables from L1 state. - */ -export interface GlobalVariableBuilder { - /** - * Builds global variables. - * @param blockNumber - The block number to build global variables for. - * @param coinbase - The address to receive block reward. - * @param feeRecipient - The address to receive fees. - * @returns The global variables for the given block number. - */ - buildGlobalVariables(blockNumber: Fr, coinbase: EthAddress, feeRecipient: AztecAddress): Promise; -} + this.publicClient = createPublicClient({ + chain: chain.chainInfo, + transport: http(chain.rpcUrl), + }); -/** - * Simple test implementation of a builder that uses the minimum time possible for the global variables. - * Also uses a "hack" to make use of the warp cheatcode that manipulates time on Aztec. - */ -export class SimpleTestGlobalVariableBuilder implements GlobalVariableBuilder { - private log = createDebugLogger('aztec:sequencer:simple_test_global_variable_builder'); - constructor(private readonly reader: L1GlobalReader) {} + this.rollupContract = getContract({ + address: getAddress(l1Contracts.rollupAddress.toString()), + abi: RollupAbi, + client: this.publicClient, + }); + } /** * Simple builder of global variables that use the minimum time possible. @@ -83,18 +59,18 @@ export class SimpleTestGlobalVariableBuilder implements GlobalVariableBuilder { coinbase: EthAddress, feeRecipient: AztecAddress, ): Promise { - // Not just the current slot, the slot of the next block. - const ts = (await this.reader.getL1CurrentTime()) + BigInt(ETHEREUM_SLOT_DURATION); + const version = new Fr(await this.rollupContract.read.VERSION()); + const chainId = new Fr(this.publicClient.chain.id); + + const ts = (await this.publicClient.getBlock()).timestamp; - const slot = await this.reader.getSlotAt([ts]); - const timestamp = await this.reader.getTimestampForSlot([slot]); + // Not just the current slot, the slot of the next block. + const slot = await this.rollupContract.read.getSlotAt([ts + BigInt(ETHEREUM_SLOT_DURATION)]); + const timestamp = await this.rollupContract.read.getTimestampForSlot([slot]); const slotFr = new Fr(slot); const timestampFr = new Fr(timestamp); - const version = new Fr(await this.reader.getVersion()); - const chainId = new Fr(await this.reader.getChainId()); - const gasFees = GasFees.default(); const globalVariables = new GlobalVariables( chainId, diff --git a/yarn-project/sequencer-client/src/global_variable_builder/index.ts b/yarn-project/sequencer-client/src/global_variable_builder/index.ts index 7125a7e7584..5669a0412ae 100644 --- a/yarn-project/sequencer-client/src/global_variable_builder/index.ts +++ b/yarn-project/sequencer-client/src/global_variable_builder/index.ts @@ -1,16 +1 @@ -import { type L1ReaderConfig } from '@aztec/ethereum'; - -import { type GlobalVariableBuilder, SimpleTestGlobalVariableBuilder } from './global_builder.js'; -import { ViemReader } from './viem-reader.js'; - -export { SimpleTestGlobalVariableBuilder as SimpleGlobalVariableBuilder } from './global_builder.js'; export { GlobalVariableBuilder } from './global_builder.js'; - -/** - * Returns a new instance of the global variable builder. - * @param config - Configuration to initialize the builder. - * @returns A new instance of the global variable builder. - */ -export function getGlobalVariableBuilder(config: L1ReaderConfig): GlobalVariableBuilder { - return new SimpleTestGlobalVariableBuilder(new ViemReader(config)); -} diff --git a/yarn-project/sequencer-client/src/global_variable_builder/viem-reader.ts b/yarn-project/sequencer-client/src/global_variable_builder/viem-reader.ts deleted file mode 100644 index dd6f59956ff..00000000000 --- a/yarn-project/sequencer-client/src/global_variable_builder/viem-reader.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type L1ReaderConfig, createEthereumChain } from '@aztec/ethereum'; -import { RollupAbi } from '@aztec/l1-artifacts'; - -import { - type GetContractReturnType, - type HttpTransport, - type PublicClient, - createPublicClient, - getAddress, - getContract, - http, -} from 'viem'; -import type * as chains from 'viem/chains'; - -import { type L1GlobalReader } from './global_builder.js'; - -/** - * Reads values from L1 state using viem. - */ -export class ViemReader implements L1GlobalReader { - private rollupContract: GetContractReturnType>; - private publicClient: PublicClient; - - constructor(config: L1ReaderConfig) { - const { l1RpcUrl, l1ChainId: chainId, l1Contracts } = config; - - const chain = createEthereumChain(l1RpcUrl, chainId); - - this.publicClient = createPublicClient({ - chain: chain.chainInfo, - transport: http(chain.rpcUrl), - }); - - this.rollupContract = getContract({ - address: getAddress(l1Contracts.rollupAddress.toString()), - abi: RollupAbi, - client: this.publicClient, - }); - } - - public async getVersion(): Promise { - return BigInt(await this.rollupContract.read.VERSION()); - } - - public async getChainId(): Promise { - return await Promise.resolve(BigInt(this.publicClient.chain.id)); - } - - public async getL1CurrentTime(): Promise { - return await Promise.resolve((await this.publicClient.getBlock()).timestamp); - } - - public async getCurrentSlot(): Promise { - return BigInt(await this.rollupContract.read.getCurrentSlot()); - } - - public async getSlotAt(timestamp: readonly [bigint]): Promise { - return BigInt(await this.rollupContract.read.getSlotAt(timestamp)); - } - - public async getTimestampForSlot(slot: readonly [bigint]): Promise { - return BigInt(await this.rollupContract.read.getTimestampForSlot(slot)); - } -} diff --git a/yarn-project/sequencer-client/src/publisher/index.ts b/yarn-project/sequencer-client/src/publisher/index.ts index 95f8d7c55fe..e51b4d3cdea 100644 --- a/yarn-project/sequencer-client/src/publisher/index.ts +++ b/yarn-project/sequencer-client/src/publisher/index.ts @@ -1,16 +1,2 @@ -import { type TelemetryClient } from '@aztec/telemetry-client'; - -import { type PublisherConfig, type TxSenderConfig } from './config.js'; -import { L1Publisher } from './l1-publisher.js'; -import { ViemTxSender } from './viem-tx-sender.js'; - export { L1Publisher } from './l1-publisher.js'; export * from './config.js'; - -/** - * Returns a new instance of the L1Publisher. - * @param config - Configuration to initialize the new instance. - */ -export function getL1Publisher(config: PublisherConfig & TxSenderConfig, client: TelemetryClient): L1Publisher { - return new L1Publisher(new ViemTxSender(config), client, config); -} diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts index c7fe318845f..41b25de86a7 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts @@ -1,26 +1,75 @@ import { L2Block } from '@aztec/circuit-types'; +import { EthAddress, Fr } from '@aztec/circuits.js'; import { sleep } from '@aztec/foundation/sleep'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type MockProxy, mock } from 'jest-mock-extended'; +import { type GetTransactionReceiptReturnType, type PrivateKeyAccount } from 'viem'; -import { L1Publisher, type L1PublisherTxSender, type MinimalTransactionReceipt } from './l1-publisher.js'; +import { type PublisherConfig, type TxSenderConfig } from './config.js'; +import { L1Publisher } from './l1-publisher.js'; + +interface MockAvailabilityOracleWrite { + publish: (args: readonly [`0x${string}`], options: { account: PrivateKeyAccount }) => Promise<`0x${string}`>; +} + +interface MockAvailabilityOracleRead { + isAvailable: (args: readonly [`0x${string}`]) => Promise; +} + +class MockAvailabilityOracle { + constructor(public write: MockAvailabilityOracleWrite, public read: MockAvailabilityOracleRead) {} +} + +interface MockPublicClient { + getTransactionReceipt: ({ hash }: { hash: '0x${string}' }) => Promise; + getBlock(): Promise<{ timestamp: number }>; + getTransaction: ({ hash }: { hash: '0x${string}' }) => Promise<{ input: `0x${string}`; hash: `0x${string}` }>; +} + +interface MockRollupContractWrite { + publishAndProcess: ( + args: readonly [`0x${string}`, `0x${string}`, `0x${string}`], + options: { account: PrivateKeyAccount }, + ) => Promise<`0x${string}`>; + + process: ( + args: readonly [`0x${string}`, `0x${string}`], + options: { account: PrivateKeyAccount }, + ) => Promise<`0x${string}`>; +} + +interface MockRollupContractRead { + archive: () => Promise<`0x${string}`>; +} + +class MockRollupContract { + constructor(public write: MockRollupContractWrite, public read: MockRollupContractRead) {} +} describe('L1Publisher', () => { - let txSender: MockProxy; - let publishTxHash: string; - let processTxHash: string; - let publishAndProcessTxHash: string; - let processTxReceipt: MinimalTransactionReceipt; - let publishTxReceipt: MinimalTransactionReceipt; - let publishAndProcessTxReceipt: MinimalTransactionReceipt; + let rollupContractRead: MockProxy; + let rollupContractWrite: MockProxy; + let rollupContract: MockRollupContract; + + let availabilityOracleRead: MockProxy; + let availabilityOracleWrite: MockProxy; + let availabilityOracle: MockAvailabilityOracle; + + let publicClient: MockProxy; + + let processTxHash: `0x${string}`; + let publishAndProcessTxHash: `0x${string}`; + let processTxReceipt: GetTransactionReceiptReturnType; + let publishAndProcessTxReceipt: GetTransactionReceiptReturnType; let l2Block: L2Block; let header: Buffer; let archive: Buffer; - let txsEffectsHash: Buffer; let body: Buffer; + let account: PrivateKeyAccount; + let publisher: L1Publisher; beforeEach(() => { @@ -28,112 +77,142 @@ describe('L1Publisher', () => { header = l2Block.header.toBuffer(); archive = l2Block.archive.root.toBuffer(); - txsEffectsHash = l2Block.body.getTxsEffectsHash(); body = l2Block.body.toBuffer(); - txSender = mock(); - - publishTxHash = `0x${Buffer.from('txHashPublish').toString('hex')}`; // random tx hash processTxHash = `0x${Buffer.from('txHashProcess').toString('hex')}`; // random tx hash publishAndProcessTxHash = `0x${Buffer.from('txHashPublishAndProcess').toString('hex')}`; // random tx hash - publishTxReceipt = { - transactionHash: publishTxHash, - status: true, - logs: [{ data: txsEffectsHash.toString('hex') }], - } as MinimalTransactionReceipt; + processTxReceipt = { transactionHash: processTxHash, - status: true, - logs: [{ data: '' }], - } as MinimalTransactionReceipt; + status: 'success', + logs: [], + } as unknown as GetTransactionReceiptReturnType; publishAndProcessTxReceipt = { transactionHash: publishAndProcessTxHash, - status: true, - logs: [{ data: txsEffectsHash.toString('hex') }], - } as MinimalTransactionReceipt; - txSender.sendPublishTx.mockResolvedValueOnce(publishTxHash); - txSender.sendProcessTx.mockResolvedValueOnce(processTxHash); - txSender.sendPublishAndProcessTx.mockResolvedValueOnce(publishAndProcessTxHash); - txSender.getTransactionReceipt.mockResolvedValueOnce(publishTxReceipt).mockResolvedValueOnce(processTxReceipt); - txSender.getCurrentArchive.mockResolvedValue(l2Block.header.lastArchive.root.toBuffer()); - - publisher = new L1Publisher(txSender, new NoopTelemetryClient(), { l1PublishRetryIntervalMS: 1 }); + status: 'success', + logs: [], + } as unknown as GetTransactionReceiptReturnType; + + rollupContractWrite = mock(); + rollupContractRead = mock(); + rollupContract = new MockRollupContract(rollupContractWrite, rollupContractRead); + + availabilityOracleWrite = mock(); + availabilityOracleRead = mock(); + availabilityOracle = new MockAvailabilityOracle(availabilityOracleWrite, availabilityOracleRead); + + publicClient = mock(); + + const config = { + l1RpcUrl: `http://127.0.0.1:8545`, + l1ChainId: 1, + publisherPrivateKey: `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80`, + l1Contracts: { + availabilityOracleAddress: EthAddress.ZERO.toString(), + rollupAddress: EthAddress.ZERO.toString(), + }, + l1PublishRetryIntervalMS: 1, + } as unknown as TxSenderConfig & PublisherConfig; + + publisher = new L1Publisher(config, new NoopTelemetryClient()); + + (publisher as any)['availabilityOracleContract'] = availabilityOracle; + (publisher as any)['rollupContract'] = rollupContract; + (publisher as any)['publicClient'] = publicClient; + + account = (publisher as any)['account']; }); - it('publishes l2 block to l1', async () => { + it('publishes and process l2 block to l1', async () => { + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.publishAndProcess.mockResolvedValueOnce(publishAndProcessTxHash); + publicClient.getTransactionReceipt.mockResolvedValueOnce(publishAndProcessTxReceipt); + const result = await publisher.processL2Block(l2Block); expect(result).toEqual(true); - expect(txSender.sendPublishAndProcessTx).toHaveBeenCalledWith({ header, archive, body }); - expect(txSender.getTransactionReceipt).toHaveBeenCalledWith(publishAndProcessTxHash); + + const args = [`0x${header.toString('hex')}`, `0x${archive.toString('hex')}`, `0x${body.toString('hex')}`] as const; + expect(rollupContractWrite.publishAndProcess).toHaveBeenCalledWith(args, { account: account }); + expect(publicClient.getTransactionReceipt).toHaveBeenCalledWith({ hash: publishAndProcessTxHash }); }); it('publishes l2 block to l1 (already published body)', async () => { - txSender.checkIfTxsAreAvailable.mockResolvedValueOnce(true); + availabilityOracleRead.isAvailable.mockResolvedValueOnce(true); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.process.mockResolvedValueOnce(processTxHash); + publicClient.getTransactionReceipt.mockResolvedValueOnce(processTxReceipt); const result = await publisher.processL2Block(l2Block); expect(result).toEqual(true); - expect(txSender.sendProcessTx).toHaveBeenCalledWith({ header, archive, body }); - expect(txSender.getTransactionReceipt).toHaveBeenCalledWith(processTxHash); + const args = [`0x${header.toString('hex')}`, `0x${archive.toString('hex')}`] as const; + expect(rollupContractWrite.process).toHaveBeenCalledWith(args, { account }); + expect(publicClient.getTransactionReceipt).toHaveBeenCalledWith({ hash: processTxHash }); }); it('does not publish if last archive root is different to expected', async () => { - txSender.getCurrentArchive.mockResolvedValueOnce(L2Block.random(43).archive.root.toBuffer()); + rollupContractRead.archive.mockResolvedValue(Fr.random().toString()); + const result = await publisher.processL2Block(l2Block); expect(result).toBe(false); - expect(txSender.sendPublishTx).not.toHaveBeenCalled(); - expect(txSender.sendProcessTx).not.toHaveBeenCalled(); - expect(txSender.sendPublishAndProcessTx).not.toHaveBeenCalled(); + expect(availabilityOracleWrite.publish).not.toHaveBeenCalled(); + expect(rollupContractWrite.process).not.toHaveBeenCalled(); + expect(rollupContractWrite.publishAndProcess).not.toHaveBeenCalled(); }); it('does not retry if sending a process tx fails', async () => { - txSender.checkIfTxsAreAvailable.mockResolvedValueOnce(true); - txSender.sendProcessTx.mockReset().mockRejectedValueOnce(new Error()).mockResolvedValueOnce(processTxHash); + availabilityOracleRead.isAvailable.mockResolvedValueOnce(true); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.process + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(processTxHash as `0x${string}`); const result = await publisher.processL2Block(l2Block); expect(result).toEqual(false); - expect(txSender.sendProcessTx).toHaveBeenCalledTimes(1); + expect(rollupContractWrite.process).toHaveBeenCalledTimes(1); }); it('does not retry if sending a publish and process tx fails', async () => { - txSender.sendPublishAndProcessTx.mockReset().mockRejectedValueOnce(new Error()); - // .mockResolvedValueOnce(publishAndProcessTxHash); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.publishAndProcess.mockRejectedValueOnce(new Error()); const result = await publisher.processL2Block(l2Block); expect(result).toEqual(false); - expect(txSender.sendPublishAndProcessTx).toHaveBeenCalledTimes(1); + expect(rollupContractWrite.publishAndProcess).toHaveBeenCalledTimes(1); }); it('retries if fetching the receipt fails (process)', async () => { - txSender.checkIfTxsAreAvailable.mockResolvedValueOnce(true); - txSender.getTransactionReceipt - .mockReset() - .mockRejectedValueOnce(new Error()) - .mockResolvedValueOnce(processTxReceipt); + availabilityOracleRead.isAvailable.mockResolvedValueOnce(true); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.process.mockResolvedValueOnce(processTxHash); + publicClient.getTransactionReceipt.mockRejectedValueOnce(new Error()).mockResolvedValueOnce(processTxReceipt); const result = await publisher.processL2Block(l2Block); expect(result).toEqual(true); - expect(txSender.getTransactionReceipt).toHaveBeenCalledTimes(2); + expect(publicClient.getTransactionReceipt).toHaveBeenCalledTimes(2); }); it('retries if fetching the receipt fails (publish process)', async () => { - txSender.getTransactionReceipt - .mockReset() + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.publishAndProcess.mockResolvedValueOnce(publishAndProcessTxHash as `0x${string}`); + publicClient.getTransactionReceipt .mockRejectedValueOnce(new Error()) .mockResolvedValueOnce(publishAndProcessTxReceipt); const result = await publisher.processL2Block(l2Block); expect(result).toEqual(true); - expect(txSender.getTransactionReceipt).toHaveBeenCalledTimes(2); + expect(publicClient.getTransactionReceipt).toHaveBeenCalledTimes(2); }); it('returns false if publish and process tx reverts', async () => { - txSender.getTransactionReceipt.mockReset().mockResolvedValueOnce({ ...publishAndProcessTxReceipt, status: false }); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.publishAndProcess.mockResolvedValueOnce(publishAndProcessTxHash); + publicClient.getTransactionReceipt.mockResolvedValueOnce({ ...publishAndProcessTxReceipt, status: 'reverted' }); const result = await publisher.processL2Block(l2Block); @@ -141,8 +220,10 @@ describe('L1Publisher', () => { }); it('returns false if process tx reverts', async () => { - txSender.checkIfTxsAreAvailable.mockResolvedValueOnce(true); - txSender.getTransactionReceipt.mockReset().mockResolvedValueOnce({ ...processTxReceipt, status: false }); + availabilityOracleRead.isAvailable.mockResolvedValueOnce(true); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + + publicClient.getTransactionReceipt.mockResolvedValueOnce({ ...processTxReceipt, status: 'reverted' }); const result = await publisher.processL2Block(l2Block); @@ -150,25 +231,29 @@ describe('L1Publisher', () => { }); it('returns false if sending publish and progress tx is interrupted', async () => { - txSender.sendPublishAndProcessTx.mockReset().mockImplementationOnce(() => sleep(10, publishAndProcessTxHash)); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.publishAndProcess.mockImplementationOnce( + () => sleep(10, publishAndProcessTxHash) as Promise<`0x${string}`>, + ); const resultPromise = publisher.processL2Block(l2Block); publisher.interrupt(); const result = await resultPromise; expect(result).toEqual(false); - expect(txSender.getTransactionReceipt).not.toHaveBeenCalled(); + expect(publicClient.getTransactionReceipt).not.toHaveBeenCalled(); }); it('returns false if sending process tx is interrupted', async () => { - txSender.checkIfTxsAreAvailable.mockResolvedValueOnce(true); - txSender.sendProcessTx.mockReset().mockImplementationOnce(() => sleep(10, processTxHash)); + availabilityOracleRead.isAvailable.mockResolvedValueOnce(true); + rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); + rollupContractWrite.process.mockImplementationOnce(() => sleep(10, processTxHash) as Promise<`0x${string}`>); const resultPromise = publisher.processL2Block(l2Block); publisher.interrupt(); const result = await resultPromise; expect(result).toEqual(false); - expect(txSender.getTransactionReceipt).not.toHaveBeenCalled(); + expect(publicClient.getTransactionReceipt).not.toHaveBeenCalled(); }); }); diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 58b71d9ea7c..e1980dcb4a4 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -1,17 +1,34 @@ import { type L2Block, type Signature } from '@aztec/circuit-types'; import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats'; -import { type EthAddress, type Header, type Proof } from '@aztec/circuits.js'; +import { ETHEREUM_SLOT_DURATION, EthAddress, type Header, type Proof } from '@aztec/circuits.js'; +import { createEthereumChain } from '@aztec/ethereum'; import { type Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { serializeToBuffer } from '@aztec/foundation/serialize'; import { InterruptibleSleep } from '@aztec/foundation/sleep'; import { Timer } from '@aztec/foundation/timer'; +import { AvailabilityOracleAbi, RollupAbi } from '@aztec/l1-artifacts'; import { type TelemetryClient } from '@aztec/telemetry-client'; import pick from 'lodash.pick'; - -import { type L2BlockReceiver } from '../receiver.js'; -import { type PublisherConfig } from './config.js'; +import { + type GetContractReturnType, + type Hex, + type HttpTransport, + type PrivateKeyAccount, + type PublicClient, + type WalletClient, + createPublicClient, + createWalletClient, + getAddress, + getContract, + hexToBytes, + http, +} from 'viem'; +import { privateKeyToAccount } from 'viem/accounts'; +import type * as chains from 'viem/chains'; + +import { type PublisherConfig, type TxSenderConfig } from './config.js'; import { L1PublisherMetrics } from './l1-publisher-metrics.js'; /** @@ -27,7 +44,7 @@ export type TransactionStats = { }; /** - * Minimal information from a tx receipt returned by an L1PublisherTxSender. + * Minimal information from a tx receipt. */ export type MinimalTransactionReceipt = { /** True if the tx was successful, false if reverted. */ @@ -43,72 +60,11 @@ export type MinimalTransactionReceipt = { }; /** - * Pushes txs to the L1 chain and waits for their completion. + * @notice An attestation for the sequencing model. + * @todo This is not where it belongs. But I think we should do a bigger rewrite of some of + * this spaghetti. */ -export interface L1PublisherTxSender { - /** Returns the EOA used for sending txs to L1. */ - getSenderAddress(): Promise; - - /** Returns the address of the L2 proposer at the NEXT Ethereum block zero if anyone can submit. */ - getProposerAtNextEthBlock(): Promise; - - /** Returns the current epoch committee */ - getCurrentEpochCommittee(): Promise; - - /** - * Publishes tx effects to Availability Oracle. - * @param encodedBody - Encoded block body. - * @returns The hash of the mined tx. - */ - sendPublishTx(encodedBody: Buffer): Promise; - - /** - * Sends a tx to the L1 rollup contract with a new L2 block. Returns once the tx has been mined. - * @param encodedData - Serialized data for processing the new L2 block. - * @returns The hash of the mined tx. - */ - sendProcessTx(encodedData: L1ProcessArgs): Promise; - - /** - * Publishes tx effects to availability oracle and send L2 block to rollup contract - * @param encodedData - Data for processing the new L2 block. - * @returns The hash of the tx. - */ - sendPublishAndProcessTx(encodedData: L1ProcessArgs): Promise; - - /** - * Sends a tx to the L1 rollup contract with a proof. Returns once the tx has been mined. - * @param encodedData - Serialized data for processing the new L2 block. - * @returns The hash of the mined tx. - */ - sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise; - - /** - * Returns a tx receipt if the tx has been mined. - * @param txHash - Hash of the tx to look for. - * @returns Undefined if the tx hasn't been mined yet, the receipt otherwise. - */ - getTransactionReceipt(txHash: string): Promise; - - /** - * Returns info on a tx by calling eth_getTransaction. - * @param txHash - Hash of the tx to look for. - */ - getTransactionStats(txHash: string): Promise; - - /** - * Returns the current archive root. - * @returns The current archive root of the rollup contract. - */ - getCurrentArchive(): Promise; - - /** - * Checks if the transaction effects of the given block are available. - * @param block - The block of which to check whether txs are available. - * @returns True if the txs are available, false otherwise. - */ - checkIfTxsAreAvailable(block: L2Block): Promise; -} +export type Attestation = { isEmpty: boolean; v: number; r: `0x${string}`; s: `0x${string}` }; /** Arguments to the process method of the rollup contract */ export type L1ProcessArgs = { @@ -144,30 +100,99 @@ export type L1SubmitProofArgs = { * * Adapted from https://github.com/AztecProtocol/aztec2-internal/blob/master/falafel/src/rollup_publisher.ts. */ -export class L1Publisher implements L2BlockReceiver { +export class L1Publisher { private interruptibleSleep = new InterruptibleSleep(); private sleepTimeMs: number; private interrupted = false; private metrics: L1PublisherMetrics; private log = createDebugLogger('aztec:sequencer:publisher'); - constructor(private txSender: L1PublisherTxSender, client: TelemetryClient, config?: PublisherConfig) { + private availabilityOracleContract: GetContractReturnType< + typeof AvailabilityOracleAbi, + WalletClient + >; + private rollupContract: GetContractReturnType< + typeof RollupAbi, + WalletClient + >; + private publicClient: PublicClient; + private account: PrivateKeyAccount; + + constructor(config: TxSenderConfig & PublisherConfig, client: TelemetryClient) { this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000; this.metrics = new L1PublisherMetrics(client, 'L1Publisher'); + + const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config; + const chain = createEthereumChain(rpcUrl, chainId); + this.account = privateKeyToAccount(publisherPrivateKey); + const walletClient = createWalletClient({ + account: this.account, + chain: chain.chainInfo, + transport: http(chain.rpcUrl), + }); + + this.publicClient = createPublicClient({ + chain: chain.chainInfo, + transport: http(chain.rpcUrl), + }); + + this.availabilityOracleContract = getContract({ + address: getAddress(l1Contracts.availabilityOracleAddress.toString()), + abi: AvailabilityOracleAbi, + client: walletClient, + }); + this.rollupContract = getContract({ + address: getAddress(l1Contracts.rollupAddress.toString()), + abi: RollupAbi, + client: walletClient, + }); + } + + public getSenderAddress(): Promise { + return Promise.resolve(EthAddress.fromString(this.account.address)); } - public async senderAddress(): Promise { - return await this.txSender.getSenderAddress(); + // Computes who will be the L2 proposer at the next Ethereum block + // Using next Ethereum block so we do NOT need to wait for it being mined before seeing the effect + // @note Assumes that all ethereum slots have blocks + async getProposerAtNextEthBlock(): Promise { + try { + const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION)); + const submitter = await this.rollupContract.read.getProposerAt([ts]); + return EthAddress.fromString(submitter); + } catch (err) { + this.log.warn(`Failed to get submitter: ${err}`); + return EthAddress.ZERO; + } } public async isItMyTurnToSubmit(): Promise { - const submitter = await this.txSender.getProposerAtNextEthBlock(); - const sender = await this.txSender.getSenderAddress(); + const submitter = await this.getProposerAtNextEthBlock(); + const sender = await this.getSenderAddress(); return submitter.isZero() || submitter.equals(sender); } - public getCurrentEpochCommittee(): Promise { - return this.txSender.getCurrentEpochCommittee(); + public async getCurrentEpochCommittee(): Promise { + const committee = await this.rollupContract.read.getCurrentEpochCommittee(); + return committee.map(EthAddress.fromString); + } + + checkIfTxsAreAvailable(block: L2Block): Promise { + const args = [`0x${block.body.getTxsEffectsHash().toString('hex').padStart(64, '0')}`] as const; + return this.availabilityOracleContract.read.isAvailable(args); + } + + async getTransactionStats(txHash: string): Promise { + const tx = await this.publicClient.getTransaction({ hash: txHash as Hex }); + if (!tx) { + return undefined; + } + const calldata = hexToBytes(tx.input); + return { + transactionHash: tx.hash, + calldataSize: calldata.length, + calldataGas: getCalldataGasUsage(calldata), + }; } /** @@ -197,7 +222,7 @@ export class L1Publisher implements L2BlockReceiver { let txHash; const timer = new Timer(); - if (await this.txSender.checkIfTxsAreAvailable(block)) { + if (await this.checkIfTxsAreAvailable(block)) { this.log.verbose(`Transaction effects of block ${block.number} already published.`, ctx); txHash = await this.sendProcessTx(processTxArgs); } else { @@ -217,7 +242,7 @@ export class L1Publisher implements L2BlockReceiver { // Tx was mined successfully if (receipt.status) { - const tx = await this.txSender.getTransactionStats(txHash); + const tx = await this.getTransactionStats(txHash); const stats: L1PublishBlockStats = { ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), ...pick(tx!, 'calldataGas', 'calldataSize'), @@ -277,7 +302,7 @@ export class L1Publisher implements L2BlockReceiver { // Tx was mined successfully if (receipt.status) { - const tx = await this.txSender.getTransactionStats(txHash); + const tx = await this.getTransactionStats(txHash); const stats: L1PublishProofStats = { ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), ...pick(tx!, 'calldataGas', 'calldataSize'), @@ -313,13 +338,18 @@ export class L1Publisher implements L2BlockReceiver { this.interrupted = false; } + async getCurrentArchive(): Promise { + const archive = await this.rollupContract.read.archive(); + return Buffer.from(archive.replace('0x', ''), 'hex'); + } + /** * Verifies that the given value of last archive in a block header equals current archive of the rollup contract * @param lastArchive - The last archive of the block we wish to publish. * @returns Boolean indicating if the hashes are equal. */ private async checkLastArchiveHash(lastArchive: Buffer): Promise { - const fromChain = await this.txSender.getCurrentArchive(); + const fromChain = await this.getCurrentArchive(); const areSame = lastArchive.equals(fromChain); if (!areSame) { this.log.debug(`Contract archive: ${fromChain.toString('hex')}`); @@ -332,7 +362,19 @@ export class L1Publisher implements L2BlockReceiver { try { const size = Object.values(submitProofArgs).reduce((acc, arg) => acc + arg.length, 0); this.log.info(`SubmitProof size=${size} bytes`); - return await this.txSender.sendSubmitProofTx(submitProofArgs); + + const { header, archive, proverId, aggregationObject, proof } = submitProofArgs; + const args = [ + `0x${header.toString('hex')}`, + `0x${archive.toString('hex')}`, + `0x${proverId.toString('hex')}`, + `0x${aggregationObject.toString('hex')}`, + `0x${proof.toString('hex')}`, + ] as const; + + return await this.rollupContract.write.submitProof(args, { + account: this.account, + }); } catch (err) { this.log.error(`Rollup submit proof failed`, err); return undefined; @@ -343,7 +385,11 @@ export class L1Publisher implements L2BlockReceiver { while (!this.interrupted) { try { this.log.info(`TxEffects size=${encodedBody.length} bytes`); - return await this.txSender.sendPublishTx(encodedBody); + const args = [`0x${encodedBody.toString('hex')}`] as const; + + return await this.availabilityOracleContract.write.publish(args, { + account: this.account, + }); } catch (err) { this.log.error(`TxEffects publish failed`, err); return undefined; @@ -354,7 +400,24 @@ export class L1Publisher implements L2BlockReceiver { private async sendProcessTx(encodedData: L1ProcessArgs): Promise { while (!this.interrupted) { try { - return await this.txSender.sendProcessTx(encodedData); + if (encodedData.attestations) { + const attestations = encodedData.attestations.map(attest => attest.toViemSignature()); + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + attestations, + ] as const; + + return await this.rollupContract.write.process(args, { + account: this.account, + }); + } else { + const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const; + + return await this.rollupContract.write.process(args, { + account: this.account, + }); + } } catch (err) { this.log.error(`Rollup publish failed`, err); return undefined; @@ -365,7 +428,30 @@ export class L1Publisher implements L2BlockReceiver { private async sendPublishAndProcessTx(encodedData: L1ProcessArgs): Promise { while (!this.interrupted) { try { - return await this.txSender.sendPublishAndProcessTx(encodedData); + // @note This is quite a sin, but I'm committing war crimes in this code already. + if (encodedData.attestations) { + const attestations = encodedData.attestations.map(attest => attest.toViemSignature()); + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + attestations, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return await this.rollupContract.write.publishAndProcess(args, { + account: this.account, + }); + } else { + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return await this.rollupContract.write.publishAndProcess(args, { + account: this.account, + }); + } } catch (err) { this.log.error(`Rollup publish failed`, err); return undefined; @@ -373,10 +459,34 @@ export class L1Publisher implements L2BlockReceiver { } } - private async getTransactionReceipt(txHash: string): Promise { + /** + * Returns a tx receipt if the tx has been mined. + * @param txHash - Hash of the tx to look for. + * @returns Undefined if the tx hasn't been mined yet, the receipt otherwise. + */ + async getTransactionReceipt(txHash: string): Promise { while (!this.interrupted) { try { - return await this.txSender.getTransactionReceipt(txHash); + const receipt = await this.publicClient.getTransactionReceipt({ + hash: txHash as Hex, + }); + + if (receipt) { + if (receipt.transactionHash !== txHash) { + throw new Error(`Tx hash mismatch: ${receipt.transactionHash} !== ${txHash}`); + } + + return { + status: receipt.status === 'success', + transactionHash: txHash, + gasUsed: receipt.gasUsed, + gasPrice: receipt.effectiveGasPrice, + logs: receipt.logs, + }; + } + + this.log.debug(`Receipt not found for tx hash ${txHash}`); + return undefined; } catch (err) { //this.log.error(`Error getting tx receipt`, err); await this.sleepOrInterrupted(); @@ -388,3 +498,12 @@ export class L1Publisher implements L2BlockReceiver { await this.interruptibleSleep.sleep(this.sleepTimeMs); } } + +/** + * Returns cost of calldata usage in Ethereum. + * @param data - Calldata. + * @returns 4 for each zero byte, 16 for each nonzero. + */ +function getCalldataGasUsage(data: Uint8Array) { + return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16; +} diff --git a/yarn-project/sequencer-client/src/publisher/viem-tx-sender.ts b/yarn-project/sequencer-client/src/publisher/viem-tx-sender.ts deleted file mode 100644 index e33aafbe79f..00000000000 --- a/yarn-project/sequencer-client/src/publisher/viem-tx-sender.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { type L2Block } from '@aztec/circuit-types'; -import { ETHEREUM_SLOT_DURATION, EthAddress } from '@aztec/circuits.js'; -import { createEthereumChain } from '@aztec/ethereum'; -import { createDebugLogger } from '@aztec/foundation/log'; -import { AvailabilityOracleAbi, RollupAbi } from '@aztec/l1-artifacts'; - -import { - type GetContractReturnType, - type Hex, - type HttpTransport, - type PublicClient, - type WalletClient, - createPublicClient, - createWalletClient, - getAddress, - getContract, - hexToBytes, - http, -} from 'viem'; -import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts'; -import * as chains from 'viem/chains'; - -import { type TxSenderConfig } from './config.js'; -import { - type L1PublisherTxSender, - type L1SubmitProofArgs, - type MinimalTransactionReceipt, - type L1ProcessArgs as ProcessTxArgs, - type TransactionStats, -} from './l1-publisher.js'; - -/** - * Pushes transactions to the L1 rollup contract using viem. - */ -export class ViemTxSender implements L1PublisherTxSender { - private availabilityOracleContract: GetContractReturnType< - typeof AvailabilityOracleAbi, - WalletClient - >; - private rollupContract: GetContractReturnType< - typeof RollupAbi, - WalletClient - >; - - private log = createDebugLogger('aztec:sequencer:viem-tx-sender'); - private publicClient: PublicClient; - private account: PrivateKeyAccount; - - constructor(config: TxSenderConfig) { - const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config; - const chain = createEthereumChain(rpcUrl, chainId); - this.account = privateKeyToAccount(publisherPrivateKey); - const walletClient = createWalletClient({ - account: this.account, - chain: chain.chainInfo, - transport: http(chain.rpcUrl), - }); - - this.publicClient = createPublicClient({ - chain: chain.chainInfo, - transport: http(chain.rpcUrl), - }); - - this.availabilityOracleContract = getContract({ - address: getAddress(l1Contracts.availabilityOracleAddress.toString()), - abi: AvailabilityOracleAbi, - client: walletClient, - }); - this.rollupContract = getContract({ - address: getAddress(l1Contracts.rollupAddress.toString()), - abi: RollupAbi, - client: walletClient, - }); - } - - getSenderAddress(): Promise { - return Promise.resolve(EthAddress.fromString(this.account.address)); - } - - // Computes who will be the L2 proposer at the next Ethereum block - // Using next Ethereum block so we do NOT need to wait for it being mined before seeing the effect - // @note Assumes that all ethereum slots have blocks - async getProposerAtNextEthBlock(): Promise { - try { - const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION)); - const submitter = await this.rollupContract.read.getProposerAt([ts]); - return EthAddress.fromString(submitter); - } catch (err) { - this.log.warn(`Failed to get submitter: ${err}`); - return EthAddress.ZERO; - } - } - - async getCurrentEpochCommittee(): Promise { - const committee = await this.rollupContract.read.getCurrentEpochCommittee(); - return committee.map(address => EthAddress.fromString(address)); - } - - async getCurrentArchive(): Promise { - const archive = await this.rollupContract.read.archive(); - return Buffer.from(archive.replace('0x', ''), 'hex'); - } - - checkIfTxsAreAvailable(block: L2Block): Promise { - const args = [`0x${block.body.getTxsEffectsHash().toString('hex').padStart(64, '0')}`] as const; - return this.availabilityOracleContract.read.isAvailable(args); - } - - async getTransactionStats(txHash: string): Promise { - const tx = await this.publicClient.getTransaction({ hash: txHash as Hex }); - if (!tx) { - return undefined; - } - const calldata = hexToBytes(tx.input); - return { - transactionHash: tx.hash, - calldataSize: calldata.length, - calldataGas: getCalldataGasUsage(calldata), - }; - } - - /** - * Returns a tx receipt if the tx has been mined. - * @param txHash - Hash of the tx to look for. - * @returns Undefined if the tx hasn't been mined yet, the receipt otherwise. - */ - async getTransactionReceipt(txHash: string): Promise { - const receipt = await this.publicClient.getTransactionReceipt({ - hash: txHash as Hex, - }); - - if (receipt) { - return { - status: receipt.status === 'success', - transactionHash: txHash, - gasUsed: receipt.gasUsed, - gasPrice: receipt.effectiveGasPrice, - logs: receipt.logs, - }; - } - - this.log.debug(`Receipt not found for tx hash ${txHash}`); - return undefined; - } - - /** - * Publishes tx effects to Availability Oracle. - * @param encodedBody - Encoded block body. - * @returns The hash of the mined tx. - */ - async sendPublishTx(encodedBody: Buffer): Promise { - const args = [`0x${encodedBody.toString('hex')}`] as const; - - const gas = await this.availabilityOracleContract.estimateGas.publish(args, { - account: this.account, - }); - const hash = await this.availabilityOracleContract.write.publish(args, { - gas, - account: this.account, - }); - return hash; - } - - /** - * Sends a tx to the L1 rollup contract with a new L2 block. Returns once the tx has been mined. - * @param encodedData - Serialized data for processing the new L2 block. - * @returns The hash of the mined tx. - */ - async sendProcessTx(encodedData: ProcessTxArgs): Promise { - if (encodedData.attestations) { - // Get `0x${string}` encodings - const attestations = encodedData.attestations.map(attest => attest.toViemSignature()); - - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - attestations, - ] as const; - - const gas = await this.rollupContract.estimateGas.process(args, { - account: this.account, - }); - return await this.rollupContract.write.process(args, { - gas, - account: this.account, - }); - } else { - const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const; - - const gas = await this.rollupContract.estimateGas.process(args, { - account: this.account, - }); - return await this.rollupContract.write.process(args, { - gas, - account: this.account, - }); - } - } - - /** - * @notice Publishes the body AND process the block in one transaction - * @param encodedData - Serialized data for processing the new L2 block. - * @returns The hash of the transaction - */ - async sendPublishAndProcessTx(encodedData: ProcessTxArgs): Promise { - // @note This is quite a sin, but I'm committing war crimes in this code already. - if (encodedData.attestations) { - const attestations = encodedData.attestations.map(attest => attest.toViemSignature()); - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - attestations, - `0x${encodedData.body.toString('hex')}`, - ] as const; - - const gas = await this.rollupContract.estimateGas.publishAndProcess(args, { - account: this.account, - }); - return await this.rollupContract.write.publishAndProcess(args, { - gas, - account: this.account, - }); - } else { - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.body.toString('hex')}`, - ] as const; - - const gas = await this.rollupContract.estimateGas.publishAndProcess(args, { - account: this.account, - }); - return await this.rollupContract.write.publishAndProcess(args, { - gas, - account: this.account, - }); - } - } - - /** - * Sends a tx to the L1 rollup contract with a proof. Returns once the tx has been mined. - * @param encodedData - Serialized data for the proof. - * @returns The hash of the mined tx. - */ - async sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise { - const { header, archive, proverId, aggregationObject, proof } = submitProofArgs; - const args = [ - `0x${header.toString('hex')}`, - `0x${archive.toString('hex')}`, - `0x${proverId.toString('hex')}`, - `0x${aggregationObject.toString('hex')}`, - `0x${proof.toString('hex')}`, - ] as const; - - const gas = await this.rollupContract.estimateGas.submitProof(args, { - account: this.account, - }); - const hash = await this.rollupContract.write.submitProof(args, { - gas, - account: this.account, - }); - - return hash; - } - - /** - * Gets the chain object for the given chain id. - * @param chainId - Chain id of the target EVM chain. - * @returns Viem's chain object. - */ - private getChain(chainId: number) { - for (const chain of Object.values(chains)) { - if ('id' in chain && chain.id === chainId) { - return chain; - } - } - - throw new Error(`Chain with id ${chainId} not found`); - } -} - -/** - * Returns cost of calldata usage in Ethereum. - * @param data - Calldata. - * @returns 4 for each zero byte, 16 for each nonzero. - */ -function getCalldataGasUsage(data: Uint8Array) { - return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16; -} diff --git a/yarn-project/sequencer-client/src/receiver.ts b/yarn-project/sequencer-client/src/receiver.ts deleted file mode 100644 index 77ae6ee05a4..00000000000 --- a/yarn-project/sequencer-client/src/receiver.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type L2Block, type Signature } from '@aztec/circuit-types'; - -/** - * Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system. - * See https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#RollupReceiver. - */ -export interface L2BlockReceiver { - processL2Block(block: L2Block, attestations?: Signature[]): Promise; -} diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index c32e512cf66..1fe8fa5f52c 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -404,7 +404,7 @@ export class Sequencer { this.log.info( `Submitted rollup block ${block.number} with ${ processedTxs.length - } transactions duration=${workDuration}ms (Submitter: ${await this.publisher.senderAddress()})`, + } transactions duration=${workDuration}ms (Submitter: ${await this.publisher.getSenderAddress()})`, ); } catch (err) { this.metrics.recordFailedBlock();