diff --git a/.github/workflows/deploy-staging-networks.yml b/.github/workflows/deploy-staging-networks.yml index d8d858607829..c002f9b4ad77 100644 --- a/.github/workflows/deploy-staging-networks.yml +++ b/.github/workflows/deploy-staging-networks.yml @@ -125,6 +125,12 @@ jobs: BOT_SWAPS_FOLLOW_CHAIN=PENDING BOT_SWAPS_TX_INTERVAL_SECONDS=350 + RPC_INGRESS_ENABLED=true + RPC_INGRESS_HOST=staging.alpha-testnet.aztec-labs.com + RPC_INGRESS_STATIC_IP_NAME=staging-rc-1-ingress + RPC_INGRESS_SSL_CERT_NAME=staging-public-rpc-cert + + FLUSH_ENTRY_QUEUE=false EOF echo "NAMESPACE=$NAMESPACE" >> $GITHUB_ENV @@ -234,6 +240,13 @@ jobs: DEPLOY_INTERNAL_BOOTNODE=false BOT_TRANSFERS_REPLICAS=0 BOT_SWAPS_REPLICAS=0 + FLUSH_ENTRY_QUEUE=false + + # RPC_INGRESS_ENABLED=true + # RPC_INGRESS_HOST=rpc.testnet.aztec-labs.com + # RPC_INGRESS_STATIC_IP_NAME=testnet-rpc-ingress + # RPC_INGRESS_SSL_CERT_NAME=testnet-rpc-cert + EOF echo "NAMESPACE=$NAMESPACE" >> $GITHUB_ENV diff --git a/spartan/scripts/deploy_network.sh b/spartan/scripts/deploy_network.sh index 45b32dbf6617..1d9b8c3b129d 100755 --- a/spartan/scripts/deploy_network.sh +++ b/spartan/scripts/deploy_network.sh @@ -78,6 +78,13 @@ BOT_SWAPS_TX_INTERVAL_SECONDS=${BOT_SWAPS_TX_INTERVAL_SECONDS:-60} BOT_TRANSFERS_FOLLOW_CHAIN=${BOT_TRANSFERS_FOLLOW_CHAIN:-PENDING} BOT_SWAPS_FOLLOW_CHAIN=${BOT_SWAPS_FOLLOW_CHAIN:-PENDING} +RPC_INGRESS_ENABLED=${RPC_INGRESS_ENABLED:-false} +RPC_INGRESS_HOST=${RPC_INGRESS_HOST:-} +RPC_INGRESS_STATIC_IP_NAME=${RPC_INGRESS_STATIC_IP_NAME:-} +RPC_INGRESS_SSL_CERT_NAME=${RPC_INGRESS_SSL_CERT_NAME:-} + +FLUSH_ENTRY_QUEUE=${FLUSH_ENTRY_QUEUE:-true} + ######################## # CHAOS MESH VARIABLES ######################## @@ -301,6 +308,11 @@ BOT_SWAPS_FOLLOW_CHAIN = "${BOT_SWAPS_FOLLOW_CHAIN}" BOT_TRANSFERS_L2_PRIVATE_KEY = "${BOT_TRANSFERS_L2_PRIVATE_KEY:-0xcafe01}" BOT_SWAPS_L2_PRIVATE_KEY = "${BOT_SWAPS_L2_PRIVATE_KEY:-0xcafe02}" PROVER_FAILED_PROOF_STORE = "${PROVER_FAILED_PROOF_STORE}" + +RPC_INGRESS_ENABLED = ${RPC_INGRESS_ENABLED} +RPC_INGRESS_HOST = "${RPC_INGRESS_HOST}" +RPC_INGRESS_STATIC_IP_NAME = "${RPC_INGRESS_STATIC_IP_NAME}" +RPC_INGRESS_SSL_CERT_NAME = "${RPC_INGRESS_SSL_CERT_NAME}" EOF tf_run "${DEPLOY_AZTEC_INFRA_DIR}" "${DESTROY_AZTEC_INFRA}" "${CREATE_AZTEC_INFRA}" diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index d839fb3e8919..68a20fc51a27 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -160,10 +160,21 @@ locals { "rpc.yaml", "rpc-resources-${var.RPC_RESOURCE_PROFILE}.yaml" ] - custom_settings = { - "nodeType" = "rpc" - "node.env.NETWORK" = var.NETWORK - } + custom_settings = merge( + { + "nodeType" = "rpc" + "node.env.NETWORK" = var.NETWORK + "ingress.rpc.enabled" = var.RPC_INGRESS_ENABLED + "ingress.rpc.host" = var.RPC_INGRESS_HOST + }, + var.RPC_INGRESS_ENABLED ? { + "service.rpc.annotations.cloud\\.google\\.com/neg" = "{\"ingress\": true}" + "ingress.rpc.annotations.kubernetes\\.io/ingress\\.class" = "gce" + "ingress.rpc.annotations.kubernetes\\.io/ingress\\.global-static-ip-name" = var.RPC_INGRESS_STATIC_IP_NAME + "ingress.rpc.annotations.ingress\\.gcp\\.kubernetes\\.io/pre-shared-cert" = var.RPC_INGRESS_SSL_CERT_NAME + "ingress.rpc.annotations.kubernetes\\.io/ingress\\.allow-http" = "false" + } : {} + ) boot_node_host_path = "node.env.BOOT_NODE_HOST" bootstrap_nodes_path = "node.env.BOOTSTRAP_NODES" } diff --git a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml index 26cae84f0c36..8cefb7be858f 100644 --- a/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/prover-resources-prod.yaml @@ -3,12 +3,13 @@ node: node: resources: requests: - cpu: "3" - memory: "12Gi" + cpu: "1.6" + memory: "5Gi" nodeSelector: local-ssd: "false" node-type: "network" + cores: "2" affinity: podAntiAffinity: @@ -40,6 +41,7 @@ broker: nodeSelector: local-ssd: "false" node-type: "network" + cores: "2" persistence: enabled: true diff --git a/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml index b8148af1ebfd..78fbe238d282 100644 --- a/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml @@ -1,6 +1,7 @@ nodeSelector: local-ssd: "false" node-type: "network" + cores: "2" affinity: podAntiAffinity: @@ -20,8 +21,8 @@ hostNetwork: true node: resources: requests: - cpu: "3" - memory: "12Gi" + cpu: "1.6" + memory: "5Gi" persistence: enabled: true diff --git a/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml index 3a06eeb215ea..7a48fb39783e 100644 --- a/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/validator-resources-prod.yaml @@ -3,6 +3,7 @@ validator: nodeSelector: local-ssd: "false" node-type: "network" + cores: "2" affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: @@ -17,8 +18,8 @@ validator: node: resources: requests: - cpu: "3" - memory: "12Gi" + cpu: "1.6" + memory: "5Gi" statefulSet: volumeClaimTemplates: - metadata: diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index e29c8bfd2e30..d6c6ba397d41 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -381,3 +381,28 @@ variable "PROVER_FAILED_PROOF_STORE" { nullable = false default = "" } + +# RPC ingress configuration (GKE-specific) +variable "RPC_INGRESS_ENABLED" { + description = "Enable GKE ingress for RPC nodes" + type = bool + default = false +} + +variable "RPC_INGRESS_HOST" { + description = "Hostname for RPC ingress" + type = string + default = "" +} + +variable "RPC_INGRESS_STATIC_IP_NAME" { + description = "Name of the GCP static IP resource for the ingress" + type = string + default = "" +} + +variable "RPC_INGRESS_SSL_CERT_NAME" { + description = "Name of the GCP managed SSL certificate for the ingress" + type = string + default = "" +} diff --git a/spartan/terraform/gke-cluster/auto-updater-config.tf b/spartan/terraform/gke-cluster/auto-updater-config.tf index 9faafeb0ccdf..dad5c929034e 100644 --- a/spartan/terraform/gke-cluster/auto-updater-config.tf +++ b/spartan/terraform/gke-cluster/auto-updater-config.tf @@ -47,7 +47,7 @@ resource "google_storage_bucket_object" "staging_public" { content = jsonencode({ version = "" config = { - governanceProposerPayload = "0x3fe8bFFd590d57E39281596433F1d95249f5d469" + governanceProposerPayload = "0x0972CE94b1AC39Ecf737e8221cD290A84bA63921" } }) } diff --git a/spartan/terraform/gke-cluster/cluster/main.tf b/spartan/terraform/gke-cluster/cluster/main.tf index 5a8beab96c11..459e9c44296d 100644 --- a/spartan/terraform/gke-cluster/cluster/main.tf +++ b/spartan/terraform/gke-cluster/cluster/main.tf @@ -41,44 +41,44 @@ resource "google_container_cluster" "primary" { } # Create 2 core node pool with local ssd -resource "google_container_node_pool" "aztec_nodes_2core_ssd" { - name = "${var.cluster_name}-2core-ssd" - location = var.zone - cluster = var.cluster_name - version = var.node_version +# resource "google_container_node_pool" "aztec_nodes_2core_ssd" { +# name = "${var.cluster_name}-2core-ssd" +# location = var.zone +# cluster = var.cluster_name +# version = var.node_version - # Enable autoscaling - autoscaling { - min_node_count = 0 - max_node_count = 512 - } +# # Enable autoscaling +# autoscaling { +# min_node_count = 0 +# max_node_count = 512 +# } - # Node configuration - node_config { - machine_type = "n2d-standard-2" - ephemeral_storage_local_ssd_config { - local_ssd_count = 1 - } +# # Node configuration +# node_config { +# machine_type = "n2d-standard-2" +# ephemeral_storage_local_ssd_config { +# local_ssd_count = 1 +# } - service_account = var.service_account - oauth_scopes = [ - "https://www.googleapis.com/auth/cloud-platform" - ] +# service_account = var.service_account +# oauth_scopes = [ +# "https://www.googleapis.com/auth/cloud-platform" +# ] - labels = { - env = "production" - local-ssd = "true" - node-type = "network" +# labels = { +# env = "production" +# local-ssd = "true" +# node-type = "network" - } - tags = ["aztec-gke-node", "aztec"] - } +# } +# tags = ["aztec-gke-node", "aztec"] +# } - management { - auto_repair = true - auto_upgrade = false - } -} +# management { +# auto_repair = true +# auto_upgrade = false +# } +# } # Create 2 core node pool no ssd resource "google_container_node_pool" "aztec_nodes-2core" { @@ -105,6 +105,7 @@ resource "google_container_node_pool" "aztec_nodes-2core" { env = "production" local-ssd = "false" node-type = "network" + cores = "2" } tags = ["aztec-gke-node", "aztec"] } @@ -141,6 +142,7 @@ resource "google_container_node_pool" "aztec_nodes-4core" { env = "production" local-ssd = "false" node-type = "network" + cores = "4" } tags = ["aztec-gke-node", "aztec"] } @@ -245,49 +247,49 @@ resource "google_container_node_pool" "spot_nodes_8core" { } # Create 2 core spot instance node pool with autoscaling -resource "google_container_node_pool" "spot_nodes_2core" { - name = "${var.cluster_name}-2core-spot" - location = var.zone - cluster = var.cluster_name - version = var.node_version - # Enable autoscaling - autoscaling { - min_node_count = 0 - max_node_count = 1500 - } +# resource "google_container_node_pool" "spot_nodes_2core" { +# name = "${var.cluster_name}-2core-spot" +# location = var.zone +# cluster = var.cluster_name +# version = var.node_version +# # Enable autoscaling +# autoscaling { +# min_node_count = 0 +# max_node_count = 1500 +# } - # Node configuration - node_config { - machine_type = "t2d-standard-2" - spot = true +# # Node configuration +# node_config { +# machine_type = "t2d-standard-2" +# spot = true - service_account = var.service_account - oauth_scopes = [ - "https://www.googleapis.com/auth/cloud-platform" - ] +# service_account = var.service_account +# oauth_scopes = [ +# "https://www.googleapis.com/auth/cloud-platform" +# ] - labels = { - env = "production" - pool = "spot" - local-ssd = "false" - node-type = "network" - } - tags = ["aztec-gke-node", "spot"] +# labels = { +# env = "production" +# pool = "spot" +# local-ssd = "false" +# node-type = "network" +# } +# tags = ["aztec-gke-node", "spot"] - # Spot instance termination handler - taint { - key = "cloud.google.com/gke-spot" - value = "true" - effect = "NO_SCHEDULE" - } - } +# # Spot instance termination handler +# taint { +# key = "cloud.google.com/gke-spot" +# value = "true" +# effect = "NO_SCHEDULE" +# } +# } - # Management configuration - management { - auto_repair = true - auto_upgrade = false - } -} +# # Management configuration +# management { +# auto_repair = true +# auto_upgrade = false +# } +# } # Create 8 core high memory (64 GB) node pool with autoscaling, used for metrics resource "google_container_node_pool" "infra_nodes_8core_highmem" { diff --git a/spartan/terraform/gke-cluster/network-ingress.tf b/spartan/terraform/gke-cluster/network-ingress.tf new file mode 100644 index 000000000000..2f56168f85bf --- /dev/null +++ b/spartan/terraform/gke-cluster/network-ingress.tf @@ -0,0 +1,45 @@ +resource "google_compute_global_address" "staging_public_rpc_ip" { + name = "staging-public-rpc-ip" + description = "Static IP for staging-public network RPC ingress" + + lifecycle { + prevent_destroy = true + } +} + +resource "google_compute_managed_ssl_certificate" "staging_public_rpc_cert" { + name = "staging-public-rpc-cert" + description = "Managed SSL certificate for staging-public RPC ingress" + + managed { + domains = ["staging.alpha-testnet.aztec-labs.com"] + } + + lifecycle { + prevent_destroy = true + } +} + +# TODO: enable these resources once testnet is migrated to use deploy_network.sh + +#resource "google_compute_global_address" "testnet_rpc_ip" { +# name = "testnet-rpc-ingress" +# description = "Static IP for testnet RPC ingress" +# +# lifecycle { +# prevent_destroy = true +# } +#} +# +#resource "google_compute_managed_ssl_certificate" "testnet_rpc_cert" { +# name = "testnet-rpc-cert" +# description = "Managed SSL certificate for testnet RPC ingress" +# +# managed { +# domains = ["rpc.testnet.aztec-labs.com"] +# } +# +# lifecycle { +# prevent_destroy = true +# } +#} diff --git a/spartan/terraform/gke-cluster/outputs.tf b/spartan/terraform/gke-cluster/outputs.tf index 5a027b234440..59de5f460e05 100644 --- a/spartan/terraform/gke-cluster/outputs.tf +++ b/spartan/terraform/gke-cluster/outputs.tf @@ -6,3 +6,13 @@ output "region" { description = "Google cloud region" value = var.region } + +output "staging_public_rpc_ip" { + value = google_compute_global_address.staging_public_rpc_ip.address + description = "The static IP address for staging-public RPC ingress" +} + +output "staging_public_rpc_cert_name" { + value = google_compute_managed_ssl_certificate.staging_public_rpc_cert.name + description = "The name of the managed SSL certificate for staging-public RPC" +} diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 6873588a1892..31f4eff0c5c1 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -82,7 +82,7 @@ import { tryStop, } from '@aztec/stdlib/interfaces/server'; import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs'; -import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging'; +import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging'; import { P2PClientType } from '@aztec/stdlib/p2p'; import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing'; import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees'; @@ -858,13 +858,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { return [witness.index, witness.path]; } + public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise { + const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message); + return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined; + } + /** * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block. * @param l1ToL2Message - The L1 to L2 message to check. * @returns Whether the message is synced and ready to be included in a block. */ public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise { - return (await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message)) !== undefined; + const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message); + return messageIndex !== undefined; } /** diff --git a/yarn-project/aztec.js/src/api/utils.ts b/yarn-project/aztec.js/src/api/utils.ts index 6fd68b85741a..3104484b9dfb 100644 --- a/yarn-project/aztec.js/src/api/utils.ts +++ b/yarn-project/aztec.js/src/api/utils.ts @@ -19,3 +19,4 @@ export { waitForPXE } from '../utils/pxe.js'; export { waitForNode, createAztecNodeClient, type AztecNode } from '../utils/node.js'; export { getFeeJuiceBalance } from '../utils/fee_juice.js'; export { readFieldCompressedString } from '../utils/field_compressed_string.js'; +export { isL1ToL2MessageReady, waitForL1ToL2MessageReady } from '../utils/cross_chain.js'; diff --git a/yarn-project/aztec.js/src/contract/batch_call.ts b/yarn-project/aztec.js/src/contract/batch_call.ts index 863f1becbcbe..091f53dfe9e2 100644 --- a/yarn-project/aztec.js/src/contract/batch_call.ts +++ b/yarn-project/aztec.js/src/contract/batch_call.ts @@ -30,6 +30,14 @@ export class BatchCall extends BaseContractInteraction { return await this.wallet.createTxExecutionRequest(requestWithoutFee, fee, { txNonce, cancellable }); } + /** + * Creates a new instance with no actual calls. Useful for triggering a no-op. + * @param wallet - The wallet to use for sending the batch call. + */ + public static empty(wallet: Wallet) { + return new BatchCall(wallet, []); + } + /** * Returns an execution request that represents this operation. * @param options - An optional object containing additional configuration for the request generation. diff --git a/yarn-project/aztec.js/src/utils/cross_chain.ts b/yarn-project/aztec.js/src/utils/cross_chain.ts new file mode 100644 index 000000000000..0f56fbf4b0e0 --- /dev/null +++ b/yarn-project/aztec.js/src/utils/cross_chain.ts @@ -0,0 +1,54 @@ +import type { Fr } from '@aztec/foundation/fields'; +import { retryUntil } from '@aztec/foundation/retry'; + +import type { PXE } from '../api/interfaces.js'; + +/** + * Waits for the L1 to L2 message to be ready to be consumed. + * @param pxe - PXE instance + * @param l1ToL2MessageHash - Hash of the L1 to L2 message + * @param opts - Options + */ +export async function waitForL1ToL2MessageReady( + pxe: Pick, + l1ToL2MessageHash: Fr, + opts: { + /** Timeout for the operation in seconds */ timeoutSeconds: number; + /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; + }, +) { + const messageBlockNumber = await pxe.getL1ToL2MessageBlock(l1ToL2MessageHash); + return retryUntil( + () => isL1ToL2MessageReady(pxe, l1ToL2MessageHash, { ...opts, messageBlockNumber }), + `L1 to L2 message ${l1ToL2MessageHash.toString()} ready`, + opts.timeoutSeconds, + 1, + ); +} + +/** + * Returns whether the L1 to L2 message is ready to be consumed. + * @param pxe - PXE instance + * @param l1ToL2MessageHash - Hash of the L1 to L2 message + * @param opts - Options + * @returns True if the message is ready to be consumed, false otherwise + */ +export async function isL1ToL2MessageReady( + pxe: Pick, + l1ToL2MessageHash: Fr, + opts: { + /** True if the message is meant to be consumed from a public function */ forPublicConsumption: boolean; + /** Cached synced block number for the message (will be fetched from PXE otherwise) */ messageBlockNumber?: number; + }, +): Promise { + const blockNumber = await pxe.getBlockNumber(); + const messageBlockNumber = opts.messageBlockNumber ?? (await pxe.getL1ToL2MessageBlock(l1ToL2MessageHash)); + if (messageBlockNumber === undefined) { + return false; + } + + // Note that public messages can be consumed 1 block earlier, since the sequencer will include the messages + // in the L1 to L2 message tree before executing the txs for the block. In private, however, we need to wait + // until the message is included so we can make use of the membership witness. + return opts.forPublicConsumption ? blockNumber + 1 >= messageBlockNumber : blockNumber >= messageBlockNumber; +} diff --git a/yarn-project/bot/src/config.ts b/yarn-project/bot/src/config.ts index ad8377ce6a34..e2744a361269 100644 --- a/yarn-project/bot/src/config.ts +++ b/yarn-project/bot/src/config.ts @@ -154,7 +154,7 @@ export const botConfigMappings: ConfigMappingsType = { l1ToL2MessageTimeoutSeconds: { env: 'BOT_L1_TO_L2_TIMEOUT_SECONDS', description: 'How long to wait for L1 to L2 messages to become available on L2', - ...numberConfigHelper(60), + ...numberConfigHelper(3600), }, senderPrivateKey: { env: 'BOT_PRIVATE_KEY', diff --git a/yarn-project/bot/src/factory.ts b/yarn-project/bot/src/factory.ts index d88dce305f05..04ec4d67dcda 100644 --- a/yarn-project/bot/src/factory.ts +++ b/yarn-project/bot/src/factory.ts @@ -13,7 +13,7 @@ import { type PXE, createLogger, createPXEClient, - retryUntil, + waitForL1ToL2MessageReady, } from '@aztec/aztec.js'; import { createEthereumChain, createExtendedL1Client } from '@aztec/ethereum'; import { Fr } from '@aztec/foundation/fields'; @@ -410,15 +410,15 @@ export class BotFactory { const mintAmount = await portal.getTokenManager().getMintAmount(); const claim = await portal.bridgeTokensPublic(recipient, mintAmount, true /* mint */); - const isSynced = async () => await this.pxe.isL1ToL2MessageSynced(Fr.fromHexString(claim.messageHash)); - await retryUntil(isSynced, `message ${claim.messageHash} sync`, this.config.l1ToL2MessageTimeoutSeconds, 1); + await this.withNoMinTxsPerBlock(() => + waitForL1ToL2MessageReady(this.pxe, Fr.fromHexString(claim.messageHash), { + timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds, + forPublicConsumption: false, + }), + ); this.log.info(`Created a claim for ${mintAmount} L1 fee juice to ${recipient}.`, claim); - // Progress by 2 L2 blocks so that the l1ToL2Message added above will be available to use on L2. - await this.advanceL2Block(); - await this.advanceL2Block(); - return claim; } @@ -434,11 +434,4 @@ export class BotFactory { await this.nodeAdmin.setConfig({ minTxsPerBlock }); } } - - private async advanceL2Block() { - await this.withNoMinTxsPerBlock(async () => { - const initialBlockNumber = await this.node!.getBlockNumber(); - await retryUntil(async () => (await this.node!.getBlockNumber()) >= initialBlockNumber + 1); - }); - } } diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index c91e8dfe9307..ae3bc217e84a 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -200,6 +200,10 @@ export class PXEService implements PXE { return pxeService; } + public getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise { + return this.node.getL1ToL2MessageBlock(l1ToL2Message); + } + // Aztec node proxy methods public isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise { diff --git a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts index e8f6c3207c37..87cb535abcb1 100644 --- a/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts +++ b/yarn-project/slasher/src/watchers/epoch_prune_watcher.ts @@ -93,7 +93,9 @@ export class EpochPruneWatcher extends (EventEmitter as new () => WatcherEmitter }) .catch(async error => { if (error instanceof TransactionsNotAvailableError) { - this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`, error); + this.log.info(`Data for pruned epoch ${epochNumber} was not available. Will want to slash.`, { + message: error.message, + }); const validators = await this.getValidatorsForEpoch(epochNumber); return { validators, diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index 4344beef3bbf..17ed537238e8 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -119,6 +119,11 @@ describe('AztecNodeApiSchema', () => { expect(response).toEqual([1n, expect.any(SiblingPath)]); }); + it('getL1ToL2MessageBlock', async () => { + const response = await context.client.getL1ToL2MessageBlock(Fr.random()); + expect(response).toEqual(5); + }); + it('isL1ToL2MessageSynced', async () => { const response = await context.client.isL1ToL2MessageSynced(Fr.random()); expect(response).toBe(true); @@ -534,6 +539,10 @@ class MockAztecNode implements AztecNode { expect(noteHash).toBeInstanceOf(Fr); return Promise.resolve(MembershipWitness.random(NOTE_HASH_TREE_HEIGHT)); } + getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise { + expect(l1ToL2Message).toBeInstanceOf(Fr); + return Promise.resolve(5); + } isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise { expect(l1ToL2Message).toBeInstanceOf(Fr); return Promise.resolve(true); diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index e63e5bcdf313..11b5b2bee02f 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -199,10 +199,14 @@ export interface AztecNode l1ToL2Message: Fr, ): Promise<[bigint, SiblingPath] | undefined>; + /** Returns the L2 block number in which this L1 to L2 message becomes available, or undefined if not found. */ + getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise; + /** - * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block. + * Returns whether an L1 to L2 message is synced by archiver. * @param l1ToL2Message - The L1 to L2 message to check. - * @returns Whether the message is synced and ready to be included in a block. + * @returns Whether the message is synced. + * @deprecated Use `getL1ToL2MessageBlock` instead. This method may return true even if the message is not ready to use. */ isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise; @@ -501,6 +505,8 @@ export const AztecNodeApiSchema: ApiSchemaFor = { .args(L2BlockNumberSchema, schemas.Fr) .returns(z.tuple([schemas.BigInt, SiblingPath.schemaFor(L1_TO_L2_MSG_TREE_HEIGHT)]).optional()), + getL1ToL2MessageBlock: z.function().args(schemas.Fr).returns(z.number().optional()), + isL1ToL2MessageSynced: z.function().args(schemas.Fr).returns(z.boolean()), getL2ToL1Messages: z diff --git a/yarn-project/stdlib/src/interfaces/pxe.test.ts b/yarn-project/stdlib/src/interfaces/pxe.test.ts index 16d5f830440e..839e457bd444 100644 --- a/yarn-project/stdlib/src/interfaces/pxe.test.ts +++ b/yarn-project/stdlib/src/interfaces/pxe.test.ts @@ -197,6 +197,11 @@ describe('PXESchema', () => { expect(result).toBeInstanceOf(TxReceipt); }); + it('getL1ToL2MessageBlock', async () => { + const result = await context.client.getL1ToL2MessageBlock(Fr.random()); + expect(result).toEqual(5); + }); + it('getTxEffect', async () => { const { l2BlockHash, l2BlockNumber, data } = (await context.client.getTxEffect(TxHash.random()))!; expect(data).toBeInstanceOf(TxEffect); @@ -324,6 +329,9 @@ class MockPXE implements PXE { isL1ToL2MessageSynced(_l1ToL2Message: Fr): Promise { return Promise.resolve(false); } + getL1ToL2MessageBlock(_l1ToL2Message: Fr): Promise { + return Promise.resolve(5); + } registerAccount(secretKey: Fr, partialAddress: Fr): Promise { expect(secretKey).toBeInstanceOf(Fr); expect(partialAddress).toBeInstanceOf(Fr); diff --git a/yarn-project/stdlib/src/interfaces/pxe.ts b/yarn-project/stdlib/src/interfaces/pxe.ts index 770d3677d870..d525423cb954 100644 --- a/yarn-project/stdlib/src/interfaces/pxe.ts +++ b/yarn-project/stdlib/src/interfaces/pxe.ts @@ -56,12 +56,20 @@ import { */ export interface PXE { /** - * Returns whether an L1 to L2 message is synced by archiver and if it's ready to be included in a block. + * Returns whether an L1 to L2 message is synced by archiver. * @param l1ToL2Message - The L1 to L2 message to check. * @returns Whether the message is synced and ready to be included in a block. + * @deprecated Use `waitForL1ToL2MessageReady` and `isL1ToL2MessageReady` instead. */ isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise; + /** + * Returns the L2 block number in which this L1 to L2 message becomes available, or undefined if not found. + * @param l1ToL2Message - The L1 to L2 message to check. + * @returns The L2 block number or undefined if not synced yet. + */ + getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise; + /** * Registers a user account in PXE given its master encryption private key. * Once a new account is registered, the PXE Service will trial-decrypt all published notes on @@ -447,6 +455,7 @@ const PXEInfoSchema = z.object({ export const PXESchema: ApiSchemaFor = { isL1ToL2MessageSynced: z.function().args(schemas.Fr).returns(z.boolean()), + getL1ToL2MessageBlock: z.function().args(schemas.Fr).returns(z.number().optional()), registerAccount: z.function().args(schemas.Fr, schemas.Fr).returns(CompleteAddress.schema), getRegisteredAccounts: z.function().returns(z.array(CompleteAddress.schema)), registerSender: z.function().args(schemas.AztecAddress).returns(schemas.AztecAddress), diff --git a/yarn-project/stdlib/src/messaging/inbox_leaf.ts b/yarn-project/stdlib/src/messaging/inbox_leaf.ts index 335ea77a562c..d0f43e3d9052 100644 --- a/yarn-project/stdlib/src/messaging/inbox_leaf.ts +++ b/yarn-project/stdlib/src/messaging/inbox_leaf.ts @@ -35,4 +35,9 @@ export class InboxLeaf { const end = start + BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP); return [start, end]; } + + /** Returns the L2 block number for a given leaf index */ + static l2BlockFromIndex(index: bigint): number { + return Number(index / BigInt(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP)) + INITIAL_L2_BLOCK_NUM; + } }