From e918aaa7b62d694bd1b242b0088e3ff9a5584da3 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Fri, 28 Nov 2025 17:42:00 +0000 Subject: [PATCH] feat: update n_tps benchmark --- spartan/aztec-chaos-scenarios/values.yaml | 7 + spartan/environments/tps-scenario.env | 2 +- .../grafana/dashboards/network-tps.json | 273 ++++++++++++------ spartan/terraform/deploy-aztec-infra/main.tf | 12 +- .../full-node-resources-2-core-spot.yaml | 12 - .../values/full-node-resources-prod.yaml | 1 - .../deploy-aztec-infra/values/full-node.yaml | 1 + .../values/rpc-resources-prod.yaml | 4 - .../aztec-node/src/aztec-node/server.ts | 10 +- yarn-project/aztec.js/src/contract/sent_tx.ts | 2 +- .../end-to-end/src/spartan/n_tps.test.ts | 129 ++++++--- yarn-project/end-to-end/src/spartan/utils.ts | 84 ++++-- .../stdlib/src/interfaces/aztec-node.test.ts | 3 + .../stdlib/src/interfaces/aztec-node.ts | 8 + yarn-project/stdlib/src/tx/tx.ts | 3 +- 15 files changed, 377 insertions(+), 174 deletions(-) diff --git a/spartan/aztec-chaos-scenarios/values.yaml b/spartan/aztec-chaos-scenarios/values.yaml index bae1c1edce87..f61c31d93684 100644 --- a/spartan/aztec-chaos-scenarios/values.yaml +++ b/spartan/aztec-chaos-scenarios/values.yaml @@ -22,6 +22,8 @@ networkShaping: components: - sequencer-node - prover-node + - full-node + - rpc-node - node delay: # Base latency of all network traffic @@ -44,6 +46,8 @@ networkShaping: components: - sequencer-node - prover-node + - full-node + - rpc-node - node packetLoss: @@ -62,6 +66,8 @@ networkShaping: components: - sequencer-node - prover-node + - full-node + - rpc-node - node isolatePeers: @@ -69,6 +75,7 @@ networkShaping: components: - sequencer-node - prover-node + - rpc-node proverFailure: enabled: false diff --git a/spartan/environments/tps-scenario.env b/spartan/environments/tps-scenario.env index 284283b263f2..9fffdf53dabc 100644 --- a/spartan/environments/tps-scenario.env +++ b/spartan/environments/tps-scenario.env @@ -5,7 +5,7 @@ DESTROY_NAMESPACE=true DESTROY_ETH_DEVNET=true CREATE_ETH_DEVNET=true AZTEC_EPOCH_DURATION=8 -AZTEC_SLOT_DURATION=36 +AZTEC_SLOT_DURATION=72 AZTEC_PROOF_SUBMISSION_WINDOW=16 ETHEREUM_CHAIN_ID=1337 LABS_INFRA_MNEMONIC="test test test test test test test test test test test junk" diff --git a/spartan/metrics/grafana/dashboards/network-tps.json b/spartan/metrics/grafana/dashboards/network-tps.json index a5e11353e97a..fb2276e7944a 100644 --- a/spartan/metrics/grafana/dashboards/network-tps.json +++ b/spartan/metrics/grafana/dashboards/network-tps.json @@ -20,6 +20,7 @@ }, "enable": false, "expr": "max(increase(aztec_archiver_prune_count{k8s_namespace_name=\"$namespace\"}[5m])) > 1", + "hide": false, "iconColor": "yellow", "name": "Reorg", "step": "1m", @@ -33,6 +34,115 @@ "id": 16, "links": [], "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "spartan-metrics-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "binBps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 63, + "interval": "1m", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.3.0", + "targets": [ + { + "editorMode": "builder", + "expr": "avg by(service_name) (rate(gossipsub_rpc_recv_bytes_total{k8s_namespace_name=\"$namespace\"}[$__rate_interval]))", + "hide": false, + "legendFormat": "recv-{{service_name}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "spartan-metrics-prometheus" + }, + "editorMode": "code", + "expr": "-avg by(service_name) (rate(gossipsub_rpc_sent_bytes_total{k8s_namespace_name=\"$namespace\"}[$__rate_interval]))", + "hide": false, + "legendFormat": "sent-{{service_name}}", + "range": true, + "refId": "B" + } + ], + "title": "GossipSub bandwidth", + "type": "timeseries" + }, { "collapsed": false, "gridPos": { @@ -63,7 +173,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -98,7 +208,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -136,7 +246,7 @@ "steps": [ { "color": "red", - "value": null + "value": 0 }, { "color": "green", @@ -172,7 +282,7 @@ "textMode": "auto", "wideLayout": true }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -209,7 +319,7 @@ "steps": [ { "color": "blue", - "value": null + "value": 0 } ] }, @@ -242,7 +352,7 @@ "textMode": "value_and_name", "wideLayout": true }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -351,6 +461,7 @@ "type": "linear" }, "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -366,7 +477,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -395,10 +506,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -456,6 +569,7 @@ "type": "linear" }, "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -471,7 +585,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -500,10 +614,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -560,6 +676,7 @@ "type": "linear" }, "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -577,7 +694,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 } ] }, @@ -602,10 +719,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -658,6 +777,7 @@ "type": "linear" }, "showPoints": "never", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -674,7 +794,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -700,10 +820,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -774,6 +896,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -790,7 +913,7 @@ "steps": [ { "color": "super-light-blue", - "value": null + "value": 0 } ] }, @@ -816,11 +939,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -879,6 +1003,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -895,7 +1020,7 @@ "steps": [ { "color": "super-light-blue", - "value": null + "value": 0 } ] }, @@ -921,11 +1046,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -984,6 +1110,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1000,7 +1127,7 @@ "steps": [ { "color": "super-light-blue", - "value": null + "value": 0 } ] }, @@ -1026,11 +1153,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -1107,6 +1235,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1123,7 +1252,7 @@ "steps": [ { "color": "super-light-blue", - "value": null + "value": 0 } ] }, @@ -1149,11 +1278,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -1275,7 +1405,7 @@ "reverse": false } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -1332,6 +1462,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1347,7 +1478,7 @@ "steps": [ { "color": "green", - "value": null + "value": 0 }, { "color": "red", @@ -1375,11 +1506,12 @@ "showLegend": true }, "tooltip": { + "hideZeros": false, "mode": "single", "sort": "none" } }, - "pluginVersion": "11.2.1", + "pluginVersion": "12.3.0", "targets": [ { "datasource": { @@ -1450,8 +1582,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1605,8 +1736,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1760,8 +1890,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1915,8 +2044,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2037,8 +2165,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2159,8 +2286,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2280,8 +2406,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2402,8 +2527,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2538,8 +2662,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2661,8 +2784,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2766,8 +2888,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2871,8 +2992,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2976,8 +3096,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3166,8 +3285,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3288,8 +3406,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3393,8 +3510,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3498,8 +3614,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3583,8 +3698,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3658,8 +3772,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -3729,8 +3842,7 @@ "mode": "absolute", "steps": [ { - "color": "yellow", - "value": null + "color": "yellow" } ] } @@ -3838,8 +3950,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3956,8 +4067,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4078,8 +4188,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4184,8 +4293,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4307,8 +4415,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4411,8 +4518,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -4464,32 +4570,27 @@ "type": "timeseries" } ], + "preload": false, "refresh": "30s", - "schemaVersion": 39, + "schemaVersion": 42, "tags": [], "templating": { "list": [ { "current": { - "selected": false, "text": "default", "value": "default" }, - "hide": 0, "includeAll": false, - "multi": false, "name": "data_source", "options": [], "query": "prometheus", - "queryValue": "", "refresh": 1, "regex": "", - "skipUrlSync": false, "type": "datasource" }, { "current": { - "selected": true, "text": "devnet", "value": "devnet" }, @@ -4499,10 +4600,8 @@ }, "definition": "label_values(target_info,k8s_namespace_name)", "description": "", - "hide": 0, "includeAll": false, "label": "Service namespace", - "multi": false, "name": "namespace", "options": [], "query": { @@ -4512,13 +4611,10 @@ }, "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" }, { "current": { - "selected": false, "text": "validator", "value": "validator" }, @@ -4527,9 +4623,7 @@ "uid": "spartan-metrics-prometheus" }, "definition": "label_values(aztec_archiver_l1_block_height,service_name)", - "hide": 0, "includeAll": false, - "multi": false, "name": "service_name", "options": [], "query": { @@ -4539,8 +4633,6 @@ }, "refresh": 1, "regex": "", - "skipUrlSync": false, - "sort": 0, "type": "query" } ] @@ -4553,6 +4645,5 @@ "timezone": "browser", "title": "Network TPS", "uid": "aztec-network-tps-2", - "version": 26, - "weekStart": "" + "version": 36 } diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index 23e7b18c3091..5503a4129bdd 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -270,7 +270,14 @@ locals { } } } - })] : [] + })] : [yamlencode({ + service = { + rpc = { + enabled = true + type = "LoadBalancer" + } + } + })] custom_settings = merge({ "nodeType" = "rpc" @@ -337,7 +344,8 @@ locals { } boot_node_host_path = "node.env.BOOT_NODE_HOST" bootstrap_nodes_path = "node.env.BOOTSTRAP_NODES" - wait = true + // this Helm app will have lots of replicas, if we wait for all to come online we'll surely time out. + wait = false } : null archive = var.DEPLOY_ARCHIVAL_NODE ? { diff --git a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-2-core-spot.yaml b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-2-core-spot.yaml index 6e213717be2e..6aae5fc709b7 100644 --- a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-2-core-spot.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-2-core-spot.yaml @@ -5,17 +5,6 @@ nodeSelector: pool: "spot" affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - node - topologyKey: "kubernetes.io/hostname" - namespaceSelector: {} - nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: @@ -30,7 +19,6 @@ tolerations: effect: "NoSchedule" replicaCount: 1 -hostNetwork: true node: resources: diff --git a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml index dcd0c2c73065..add22664e3a4 100644 --- a/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/full-node-resources-prod.yaml @@ -16,7 +16,6 @@ affinity: namespaceSelector: {} replicaCount: 1 -hostNetwork: true node: resources: diff --git a/spartan/terraform/deploy-aztec-infra/values/full-node.yaml b/spartan/terraform/deploy-aztec-infra/values/full-node.yaml index a414d1ecf0e6..ac81a957591f 100644 --- a/spartan/terraform/deploy-aztec-infra/values/full-node.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/full-node.yaml @@ -1,4 +1,5 @@ node: + nodeType: "full-node" env: OTEL_SERVICE_NAME: "full-node" 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 cd4874f15d70..5eb5f1273039 100644 --- a/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml +++ b/spartan/terraform/deploy-aztec-infra/values/rpc-resources-prod.yaml @@ -25,10 +25,6 @@ statefulSet: storage: 16Gi service: - # rpc: - # type: LoadBalancer - # annotations: - # networking.gke.io/load-balancer-type: "External" p2p: enabled: true nodePortEnabled: false diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 9f98ecf52706..913675d55382 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -63,7 +63,7 @@ import type { NodeInfo, ProtocolContractAddresses, } from '@aztec/stdlib/contract'; -import type { GasFees } from '@aztec/stdlib/gas'; +import { GasFees } from '@aztec/stdlib/gas'; import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash'; import { type AztecNode, @@ -596,6 +596,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { return await this.globalVariableBuilder.getCurrentBaseFees(); } + public async getMaxPriorityFees(): Promise { + for await (const tx of this.p2pClient.iteratePendingTxs()) { + return tx.getGasSettings().maxPriorityFeesPerGas; + } + + return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n }); + } + /** * Method to fetch the latest block number synchronized by the node. * @returns The block number. diff --git a/yarn-project/aztec.js/src/contract/sent_tx.ts b/yarn-project/aztec.js/src/contract/sent_tx.ts index 796118e63786..6c0e2279c65b 100644 --- a/yarn-project/aztec.js/src/contract/sent_tx.ts +++ b/yarn-project/aztec.js/src/contract/sent_tx.ts @@ -20,7 +20,7 @@ export type WaitOpts = { export const DefaultWaitOpts: WaitOpts = { ignoreDroppedReceiptsFor: 5, - timeout: 60, + timeout: 300, interval: 1, }; diff --git a/yarn-project/end-to-end/src/spartan/n_tps.test.ts b/yarn-project/end-to-end/src/spartan/n_tps.test.ts index ac76898d2720..ad8eaba2e392 100644 --- a/yarn-project/end-to-end/src/spartan/n_tps.test.ts +++ b/yarn-project/end-to-end/src/spartan/n_tps.test.ts @@ -1,16 +1,17 @@ import type { SentTx } from '@aztec/aztec.js/contracts'; import { SponsoredFeePaymentMethod } from '@aztec/aztec.js/fee'; -import type { AztecNode } from '@aztec/aztec.js/node'; +import { type AztecNode, createAztecNodeClient } from '@aztec/aztec.js/node'; import { Fr } from '@aztec/foundation/fields'; import { createLogger } from '@aztec/foundation/log'; import { SerialQueue } from '@aztec/foundation/queue'; import { sleep } from '@aztec/foundation/sleep'; import { BenchmarkingContract } from '@aztec/noir-test-contracts.js/Benchmarking'; -import { Tx } from '@aztec/stdlib/tx'; +import { BlockHeader, Tx, TxHash } from '@aztec/stdlib/tx'; import { ProvenTx, TestWallet, proveInteraction } from '@aztec/test-wallet/server'; import { jest } from '@jest/globals'; import type { ChildProcess } from 'child_process'; +import { createHistogram } from 'perf_hooks'; import { getSponsoredFPCAddress } from '../fixtures/utils.js'; import { @@ -18,7 +19,14 @@ import { createWalletAndAztecNodeClient, deploySponsoredTestAccounts, } from './setup_test_wallets.js'; -import { setupEnvironment, startPortForwardForRPC } from './utils.js'; +import { + cleanHelm, + getChartDir, + getExternalIP, + getGitProjectRoot, + installChaosMeshChart, + setupEnvironment, +} from './utils.js'; const config = { ...setupEnvironment(process.env) }; @@ -38,7 +46,7 @@ describe('sustained N TPS test', () => { const NUM_AVAILABLE_RPCS = parseInt(process.env.RPC_REPLICAS || '1', 10); const testAccounts: BenchmarkWallet[] = []; - const aztecNodes: AztecNode[] = []; + let aztecNode: AztecNode; let benchmarkContracts: BenchmarkingContract[] = []; const workers: SerialQueue = new SerialQueue(); @@ -57,30 +65,36 @@ describe('sustained N TPS test', () => { beforeAll(async () => { logger.info(`Starting test setup for sustained ${TARGET_TPS} TPS over ${TEST_DURATION_SECONDS} seconds...`); + + const spartanDir = `${getGitProjectRoot()}/spartan`; + const chaosMeshInstallation = installChaosMeshChart({ + logger, + targetNamespace: config.NAMESPACE, + instanceName: 'network-shaping', + valuesFile: 'network-requirements.yaml', + helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'), + }); + workers.start(NUM_WALLETS); const localWallets: TestWallet[] = []; const cleanupFunctions = []; + const rpcIP = await getExternalIP(config.NAMESPACE, 'rpc-aztec-node'); + const rpcUrl = `http://${rpcIP}:8080`; + aztecNode = createAztecNodeClient(rpcUrl); + for (let i = 0; i < NUM_WALLETS; i++) { logger.info( `Starting port forward for PXE for wallet ${i + 1}/${NUM_WALLETS} to RPC index ${i % NUM_AVAILABLE_RPCS}`, ); - const { process: aztecRpcProcess, port: aztecRpcPort } = await startPortForwardForRPC( - config.NAMESPACE, - 'pod', - i % NUM_AVAILABLE_RPCS, - ); - forwardProcesses.push(aztecRpcProcess); - const rpcUrl = `http://127.0.0.1:${aztecRpcPort}`; logger.info(`Creating wallet and pxe for wallet ${i + 1}/${NUM_WALLETS}`); - const { wallet, aztecNode, cleanup } = await createWalletAndAztecNodeClient(rpcUrl, config.REAL_VERIFIER, logger); + const { wallet, cleanup } = await createWalletAndAztecNodeClient(rpcUrl, config.REAL_VERIFIER, logger); localWallets.push(wallet); cleanupFunctions.push(cleanup); - aztecNodes.push(aztecNode); } const localTestAccounts = await Promise.all( - localWallets.map(lw => deploySponsoredTestAccounts(lw, aztecNodes[0], logger)), + localWallets.map(lw => deploySponsoredTestAccounts(lw, aztecNode, logger)), ); for (let i = 0; i < NUM_WALLETS; i++) { @@ -93,21 +107,36 @@ describe('sustained N TPS test', () => { logger.info('Deploying Benchmarking contract/s...'); const sponsor = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()); - const benchmarkContractPromises = Array(NUM_WALLETS) + + // Deploy first contract to register the contract class + logger.info('Deploying first contract to register contract class...'); + const firstContract = await BenchmarkingContract.deploy(testAccounts[0].testAccounts.wallet) + .send({ from: testAccounts[0].testAccounts.accounts[0], fee: { paymentMethod: sponsor } }) + .deployed(); + + logger.info('Contract class registered. Deploying remaining contracts in parallel...'); + + // Deploy remaining contracts in parallel (contract class already registered) + const remainingContractPromises = Array(NUM_WALLETS - 1) .fill(0) .map((_, index) => - BenchmarkingContract.deploy(testAccounts[index].testAccounts.wallet) - .send({ from: testAccounts[index].testAccounts.accounts[0], fee: { paymentMethod: sponsor } }) + BenchmarkingContract.deploy(testAccounts[index + 1].testAccounts.wallet) + .send({ from: testAccounts[index + 1].testAccounts.accounts[0], fee: { paymentMethod: sponsor } }) .deployed(), ); - benchmarkContracts = await Promise.all(benchmarkContractPromises); + const remainingContracts = await Promise.all(remainingContractPromises); + + benchmarkContracts = [firstContract, ...remainingContracts]; logger.info( `Test setup complete. Planning ${TOTAL_TXS} transactions over ${TEST_DURATION_SECONDS} seconds at ${TARGET_TPS} TPS`, ); + + await chaosMeshInstallation; + cleanupFunctions.push(() => cleanHelm('network-shaping', config.NAMESPACE, logger)); }); - const submitProven = async () => { + const submitProven = async (): Promise<[txs: SentTx[], txSendTimes: Map]> => { const sponsor = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()); const txs: ProvenTx[] = []; const txPromises = []; @@ -131,11 +160,13 @@ describe('sustained N TPS test', () => { const allSentTxs: SentTx[] = []; let sentSoFar = 0; + const txSendTimes = new Map(); for (let sec = 0; sec < TEST_DURATION_SECONDS; sec++) { const secondStart = Date.now(); const chunk = txs.splice(0, TARGET_TPS); chunk.forEach((tx, idx) => { const sentTx = tx.send(); + txSendTimes.set(tx.txHash.toString(), Date.now()); allSentTxs.push(sentTx); logger.info(`sec ${sec + 1}: sent tx ${sentSoFar + idx + 1}`); }); @@ -147,11 +178,11 @@ describe('sustained N TPS test', () => { } } - return allSentTxs; + return [allSentTxs, txSendTimes]; }; - const submitUnproven = async () => { - logger.info(`Proving transaction for each wallet to clone...`); + const submitUnproven = async (): Promise<[txs: SentTx[], txSendTime: Map]> => { + logger.info(`Creating base tx for wallets to clone...`); const sponsor = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress()); const baseTxPromises = []; for (let i = 0; i < NUM_WALLETS; i++) { @@ -172,7 +203,7 @@ describe('sustained N TPS test', () => { logger.info(`Cloning and sending benchmark transactions...`); - const cloneAndSend = async (tx: ProvenTx, index: number) => { + const cloneAndSend = async (tx: ProvenTx, _index: number): Promise<[TxHash, SentTx]> => { // Clone the transaction const clonedTxData = Tx.clone(tx, false); @@ -191,37 +222,33 @@ describe('sustained N TPS test', () => { clonedTxData.data.forPublic.nonRevertibleAccumulatedData.nullifiers[i] = Fr.random(); } } - const clonedTx = new ProvenTx(aztecNodes[index], clonedTxData, tx.offchainEffects, tx.stats); - await clonedTx.recomputeHash(); - return clonedTx.send(); + const clonedTx = new ProvenTx(aztecNode, clonedTxData, tx.offchainEffects, tx.stats); + const txHash = await clonedTx.recomputeHash(); + return [txHash, clonedTx.send()]; }; const interval = 1000 / TARGET_TPS; const sentTxPromises: Promise[] = []; + const txSendTimes = new Map(); for (let i = 0; i < TOTAL_TXS; i++) { const workerIndex = i % NUM_WALLETS; const baseTx = baseTxs[workerIndex]; const prom = workers.put(async () => { - const sentTx = await cloneAndSend(baseTx, workerIndex); + const [txHash, sentTx] = await cloneAndSend(baseTx, workerIndex); logger.info(`sent tx ${i + 1}`); + txSendTimes.set(txHash.toString(), Date.now()); return sentTx; }); sentTxPromises.push(prom); await sleep(interval); } - return Promise.all(sentTxPromises); + return [await Promise.all(sentTxPromises), txSendTimes]; }; it('can send n_tps', async () => { const TOTAL_TXS = TEST_DURATION_SECONDS * TARGET_TPS; logger.info(`Proving benchmark transactions...`); - let sentTxs: SentTx[] = []; - - if (config.REAL_VERIFIER === true) { - sentTxs = await submitProven(); - } else { - sentTxs = await submitUnproven(); - } + const [sentTxs, txSendTimes] = await (config.REAL_VERIFIER ? submitProven() : submitUnproven()); logger.info(`Sending benchmark transactions at target TPS...`); @@ -230,15 +257,28 @@ describe('sustained N TPS test', () => { const results: { success: boolean; tx: SentTx; error?: any }[] = []; + const blockHeaders = new Map>(); + const txMinedInBlock = new Map(); const waitForTx = async (sentTx: SentTx, index: number) => { try { - await sentTx.wait({ + const receipt = await sentTx.wait({ timeout: 1200, interval: 1, ignoreDroppedReceiptsFor: 2, }); - const receipt = await sentTx.getReceipt(); - logger.info(`tx ${index + 1} included in block ${receipt.blockNumber}`); + if (receipt.blockNumber) { + if (!blockHeaders.has(receipt.blockNumber)) { + blockHeaders.set( + receipt.blockNumber, + aztecNode.getBlockHeader(receipt.blockNumber) as Promise, + ); + } + + txMinedInBlock.set(receipt.txHash.toString(), receipt.blockNumber); + logger.info(`tx ${index + 1} included in block ${receipt.blockNumber}`); + } else { + throw new Error('Invalid txReceipt: ' + JSON.stringify(receipt)); + } results.push({ success: true, tx: sentTx }); } catch (error) { logger.error(`tx ${index + 1} was not included: ${error}`); @@ -259,6 +299,19 @@ describe('sustained N TPS test', () => { expect(results.length).toBe(TOTAL_TXS); + const histogram = createHistogram({ + figures: 2, + min: 1, + max: 10 * 72 * 1000, + }); + + for (const [txHash, sendEpoch] of txSendTimes.entries()) { + const blockNumber = txMinedInBlock.get(txHash); + expect(blockNumber).toBeGreaterThan(0); + const blockMineTimestamp = (await blockHeaders.get(blockNumber!)!).globalVariables.timestamp * 1000n; + histogram.record(Number(blockMineTimestamp) - sendEpoch); + } + // Log failed transactions for debugging results .filter(r => !r.success) diff --git a/yarn-project/end-to-end/src/spartan/utils.ts b/yarn-project/end-to-end/src/spartan/utils.ts index 80a7cb841aa8..0604f1678a86 100644 --- a/yarn-project/end-to-end/src/spartan/utils.ts +++ b/yarn-project/end-to-end/src/spartan/utils.ts @@ -2,6 +2,7 @@ import { createLogger } from '@aztec/aztec.js/log'; import type { RollupCheatCodes } from '@aztec/aztec/testing'; import type { L1ContractAddresses, ViemPublicClient } from '@aztec/ethereum'; import type { Logger } from '@aztec/foundation/log'; +import { promiseWithResolvers } from '@aztec/foundation/promise'; import { makeBackoff, retry } from '@aztec/foundation/retry'; import { schemas } from '@aztec/foundation/schemas'; import { sleep } from '@aztec/foundation/sleep'; @@ -160,6 +161,39 @@ export async function startPortForward({ return { process, port }; } +export function getExternalIP(namespace: string, serviceName: string): Promise { + const { promise, resolve, reject } = promiseWithResolvers(); + const process = spawn( + 'kubectl', + [ + 'get', + 'service', + '-n', + namespace, + `${namespace}-${serviceName}`, + '--output', + "jsonpath='{.status.loadBalancer.ingress[0].ip}'", + ], + { + stdio: 'pipe', + }, + ); + + let ip = ''; + process.stdout.on('data', data => { + ip += data; + }); + process.on('error', err => { + reject(err); + }); + process.on('exit', () => { + // kubectl prints JSON. Remove the quotes + resolve(ip.replace(/"|'/g, '')); + }); + + return promise; +} + export function startPortForwardForRPC(namespace: string, resourceType = 'services', index = 0) { return startPortForward({ resource: `${resourceType}/${namespace}-rpc-aztec-node-${index}`, @@ -295,6 +329,32 @@ async function execHelmCommand(args: Parameters[0]) { return stdout; } +export async function cleanHelm(instanceName: string, namespace: string, logger: Logger) { + // uninstall the helm chart if it exists + logger.info(`Uninstalling helm chart ${instanceName}`); + await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`); + // and delete the chaos-mesh resources created by this release + const deleteByLabel = async (resource: string) => { + const args = { + resource, + namespace: namespace, + label: `app.kubernetes.io/instance=${instanceName}`, + } as const; + logger.info(`Deleting ${resource} resources for release ${instanceName}`); + await deleteResourceByLabel(args).catch(e => { + logger.error(`Error deleting ${resource}: ${e}`); + logger.info(`Force deleting ${resource}`); + return deleteResourceByLabel({ ...args, force: true }); + }); + }; + + await deleteByLabel('podchaos'); + await deleteByLabel('networkchaos'); + await deleteByLabel('podnetworkchaos'); + await deleteByLabel('workflows'); + await deleteByLabel('workflownodes'); +} + /** * Installs a Helm chart with the given parameters. * @param instanceName - The name of the Helm chart instance. @@ -317,7 +377,6 @@ export async function installChaosMeshChart({ targetNamespace, valuesFile, helmChartDir, - chaosMeshNamespace = 'chaos-mesh', timeout = '10m', clean = true, values = {}, @@ -334,32 +393,13 @@ export async function installChaosMeshChart({ logger: Logger; }) { if (clean) { - // uninstall the helm chart if it exists - logger.info(`Uninstalling helm chart ${instanceName}`); - await execAsync(`helm uninstall ${instanceName} --namespace ${chaosMeshNamespace} --wait --ignore-not-found`); - // and delete the chaos-mesh resources created by this release - const deleteByLabel = async (resource: string) => { - const args = { - resource, - namespace: chaosMeshNamespace, - label: `app.kubernetes.io/instance=${instanceName}`, - } as const; - logger.info(`Deleting ${resource} resources for release ${instanceName}`); - await deleteResourceByLabel(args).catch(e => { - logger.error(`Error deleting ${resource}: ${e}`); - logger.info(`Force deleting ${resource}`); - return deleteResourceByLabel({ ...args, force: true }); - }); - }; - - await deleteByLabel('podchaos'); - await deleteByLabel('networkchaos'); + await cleanHelm(instanceName, targetNamespace, logger); } return execHelmCommand({ instanceName, helmChartDir, - namespace: chaosMeshNamespace, + namespace: targetNamespace, valuesFile, timeout, values: { ...values, 'global.targetNamespace': targetNamespace }, diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts index 6dbf0a34de9e..fbcabb47168e 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.test.ts @@ -627,6 +627,9 @@ class MockAztecNode implements AztecNode { getCurrentBaseFees(): Promise { return Promise.resolve(GasFees.empty()); } + getMaxPriorityFees(): Promise { + return Promise.resolve(GasFees.empty()); + } getBlockNumber(): Promise { return Promise.resolve(1); } diff --git a/yarn-project/stdlib/src/interfaces/aztec-node.ts b/yarn-project/stdlib/src/interfaces/aztec-node.ts index 627532625656..a1aa35864e97 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node.ts @@ -279,6 +279,12 @@ export interface AztecNode */ getCurrentBaseFees(): Promise; + /** + * Method to fetch the current max priority fee of txs in the mempool. + * @returns The current max priority fees. + */ + getMaxPriorityFees(): Promise; + /** * Method to fetch the version of the package. * @returns The node package version @@ -576,6 +582,8 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getCurrentBaseFees: z.function().returns(GasFees.schema), + getMaxPriorityFees: z.function().returns(GasFees.schema), + getNodeVersion: z.function().returns(z.string()), getVersion: z.function().returns(z.number()), diff --git a/yarn-project/stdlib/src/tx/tx.ts b/yarn-project/stdlib/src/tx/tx.ts index 90b313219d0c..cf662505faab 100644 --- a/yarn-project/stdlib/src/tx/tx.ts +++ b/yarn-project/stdlib/src/tx/tx.ts @@ -300,8 +300,9 @@ export class Tx extends Gossipable { } /** Recomputes the tx hash. Used for testing purposes only when a property of the tx was mutated. */ - public async recomputeHash() { + public async recomputeHash(): Promise { (this as any).txHash = await Tx.computeTxHash(this); + return this.txHash; } #combinePublicCallRequestWithCallData(request: PublicCallRequest) {