@aztec/sequencer-client 0.0.0-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +45 -0
  2. package/dest/client/index.d.ts +2 -0
  3. package/dest/client/index.d.ts.map +1 -0
  4. package/dest/client/index.js +1 -0
  5. package/dest/client/sequencer-client.d.ts +71 -0
  6. package/dest/client/sequencer-client.d.ts.map +1 -0
  7. package/dest/client/sequencer-client.js +117 -0
  8. package/dest/config.d.ts +29 -0
  9. package/dest/config.d.ts.map +1 -0
  10. package/dest/config.js +143 -0
  11. package/dest/global_variable_builder/global_builder.d.ts +32 -0
  12. package/dest/global_variable_builder/global_builder.d.ts.map +1 -0
  13. package/dest/global_variable_builder/global_builder.js +79 -0
  14. package/dest/global_variable_builder/index.d.ts +2 -0
  15. package/dest/global_variable_builder/index.d.ts.map +1 -0
  16. package/dest/global_variable_builder/index.js +1 -0
  17. package/dest/index.d.ts +8 -0
  18. package/dest/index.d.ts.map +1 -0
  19. package/dest/index.js +9 -0
  20. package/dest/publisher/config.d.ts +31 -0
  21. package/dest/publisher/config.d.ts.map +1 -0
  22. package/dest/publisher/config.js +35 -0
  23. package/dest/publisher/index.d.ts +2 -0
  24. package/dest/publisher/index.d.ts.map +1 -0
  25. package/dest/publisher/index.js +1 -0
  26. package/dest/publisher/sequencer-publisher-metrics.d.ts +25 -0
  27. package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -0
  28. package/dest/publisher/sequencer-publisher-metrics.js +129 -0
  29. package/dest/publisher/sequencer-publisher.d.ts +152 -0
  30. package/dest/publisher/sequencer-publisher.d.ts.map +1 -0
  31. package/dest/publisher/sequencer-publisher.js +481 -0
  32. package/dest/sequencer/allowed.d.ts +3 -0
  33. package/dest/sequencer/allowed.d.ts.map +1 -0
  34. package/dest/sequencer/allowed.js +27 -0
  35. package/dest/sequencer/config.d.ts +2 -0
  36. package/dest/sequencer/config.d.ts.map +1 -0
  37. package/dest/sequencer/config.js +1 -0
  38. package/dest/sequencer/index.d.ts +4 -0
  39. package/dest/sequencer/index.d.ts.map +1 -0
  40. package/dest/sequencer/index.js +3 -0
  41. package/dest/sequencer/metrics.d.ts +24 -0
  42. package/dest/sequencer/metrics.d.ts.map +1 -0
  43. package/dest/sequencer/metrics.js +102 -0
  44. package/dest/sequencer/sequencer.d.ts +180 -0
  45. package/dest/sequencer/sequencer.d.ts.map +1 -0
  46. package/dest/sequencer/sequencer.js +623 -0
  47. package/dest/sequencer/timetable.d.ts +38 -0
  48. package/dest/sequencer/timetable.d.ts.map +1 -0
  49. package/dest/sequencer/timetable.js +110 -0
  50. package/dest/sequencer/utils.d.ts +48 -0
  51. package/dest/sequencer/utils.d.ts.map +1 -0
  52. package/dest/sequencer/utils.js +53 -0
  53. package/dest/slasher/factory.d.ts +7 -0
  54. package/dest/slasher/factory.d.ts.map +1 -0
  55. package/dest/slasher/factory.js +8 -0
  56. package/dest/slasher/index.d.ts +3 -0
  57. package/dest/slasher/index.d.ts.map +1 -0
  58. package/dest/slasher/index.js +2 -0
  59. package/dest/slasher/slasher_client.d.ts +75 -0
  60. package/dest/slasher/slasher_client.d.ts.map +1 -0
  61. package/dest/slasher/slasher_client.js +132 -0
  62. package/dest/test/index.d.ts +17 -0
  63. package/dest/test/index.d.ts.map +1 -0
  64. package/dest/test/index.js +10 -0
  65. package/dest/tx_validator/archive_cache.d.ts +14 -0
  66. package/dest/tx_validator/archive_cache.d.ts.map +1 -0
  67. package/dest/tx_validator/archive_cache.js +22 -0
  68. package/dest/tx_validator/gas_validator.d.ts +14 -0
  69. package/dest/tx_validator/gas_validator.d.ts.map +1 -0
  70. package/dest/tx_validator/gas_validator.js +78 -0
  71. package/dest/tx_validator/nullifier_cache.d.ts +16 -0
  72. package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
  73. package/dest/tx_validator/nullifier_cache.js +24 -0
  74. package/dest/tx_validator/phases_validator.d.ts +12 -0
  75. package/dest/tx_validator/phases_validator.d.ts.map +1 -0
  76. package/dest/tx_validator/phases_validator.js +80 -0
  77. package/dest/tx_validator/test_utils.d.ts +23 -0
  78. package/dest/tx_validator/test_utils.d.ts.map +1 -0
  79. package/dest/tx_validator/test_utils.js +26 -0
  80. package/dest/tx_validator/tx_validator_factory.d.ts +18 -0
  81. package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
  82. package/dest/tx_validator/tx_validator_factory.js +50 -0
  83. package/package.json +121 -0
  84. package/src/client/index.ts +1 -0
  85. package/src/client/sequencer-client.ts +219 -0
  86. package/src/config.ts +179 -0
  87. package/src/global_variable_builder/global_builder.ts +108 -0
  88. package/src/global_variable_builder/index.ts +1 -0
  89. package/src/index.ts +10 -0
  90. package/src/publisher/config.ts +75 -0
  91. package/src/publisher/index.ts +1 -0
  92. package/src/publisher/sequencer-publisher-metrics.ts +176 -0
  93. package/src/publisher/sequencer-publisher.ts +625 -0
  94. package/src/sequencer/allowed.ts +36 -0
  95. package/src/sequencer/config.ts +1 -0
  96. package/src/sequencer/index.ts +3 -0
  97. package/src/sequencer/metrics.ts +137 -0
  98. package/src/sequencer/sequencer.ts +759 -0
  99. package/src/sequencer/timetable.ts +123 -0
  100. package/src/sequencer/utils.ts +74 -0
  101. package/src/slasher/factory.ts +15 -0
  102. package/src/slasher/index.ts +2 -0
  103. package/src/slasher/slasher_client.ts +193 -0
  104. package/src/test/index.ts +20 -0
  105. package/src/tx_validator/archive_cache.ts +28 -0
  106. package/src/tx_validator/gas_validator.ts +101 -0
  107. package/src/tx_validator/nullifier_cache.ts +30 -0
  108. package/src/tx_validator/phases_validator.ts +98 -0
  109. package/src/tx_validator/test_utils.ts +48 -0
  110. package/src/tx_validator/tx_validator_factory.ts +120 -0
package/src/config.ts ADDED
@@ -0,0 +1,179 @@
1
+ import {
2
+ type L1ContractsConfig,
3
+ type L1ReaderConfig,
4
+ l1ContractsConfigMappings,
5
+ l1ReaderConfigMappings,
6
+ } from '@aztec/ethereum';
7
+ import {
8
+ type ConfigMappingsType,
9
+ booleanConfigHelper,
10
+ getConfigFromMappings,
11
+ numberConfigHelper,
12
+ pickConfigMappings,
13
+ } from '@aztec/foundation/config';
14
+ import { EthAddress } from '@aztec/foundation/eth-address';
15
+ import { Fr } from '@aztec/foundation/fields';
16
+ import { FunctionSelector } from '@aztec/stdlib/abi';
17
+ import { AztecAddress } from '@aztec/stdlib/aztec-address';
18
+ import { type AllowedElement, type ChainConfig, type SequencerConfig, chainConfigMappings } from '@aztec/stdlib/config';
19
+
20
+ import {
21
+ type PublisherConfig,
22
+ type TxSenderConfig,
23
+ getPublisherConfigMappings,
24
+ getTxSenderConfigMappings,
25
+ } from './publisher/config.js';
26
+
27
+ export * from './publisher/config.js';
28
+ export type { SequencerConfig };
29
+
30
+ /**
31
+ * Configuration settings for the SequencerClient.
32
+ */
33
+ export type SequencerClientConfig = PublisherConfig &
34
+ TxSenderConfig &
35
+ SequencerConfig &
36
+ L1ReaderConfig &
37
+ ChainConfig &
38
+ Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
39
+
40
+ export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
41
+ transactionPollingIntervalMS: {
42
+ env: 'SEQ_TX_POLLING_INTERVAL_MS',
43
+ description: 'The number of ms to wait between polling for pending txs.',
44
+ ...numberConfigHelper(500),
45
+ },
46
+ maxTxsPerBlock: {
47
+ env: 'SEQ_MAX_TX_PER_BLOCK',
48
+ description: 'The maximum number of txs to include in a block.',
49
+ ...numberConfigHelper(32),
50
+ },
51
+ minTxsPerBlock: {
52
+ env: 'SEQ_MIN_TX_PER_BLOCK',
53
+ description: 'The minimum number of txs to include in a block.',
54
+ ...numberConfigHelper(1),
55
+ },
56
+ maxL2BlockGas: {
57
+ env: 'SEQ_MAX_L2_BLOCK_GAS',
58
+ description: 'The maximum L2 block gas.',
59
+ ...numberConfigHelper(10e9),
60
+ },
61
+ maxDABlockGas: {
62
+ env: 'SEQ_MAX_DA_BLOCK_GAS',
63
+ description: 'The maximum DA block gas.',
64
+ ...numberConfigHelper(10e9),
65
+ },
66
+ coinbase: {
67
+ env: 'COINBASE',
68
+ parseEnv: (val: string) => (val ? EthAddress.fromString(val) : undefined),
69
+ description: 'Recipient of block reward.',
70
+ },
71
+ feeRecipient: {
72
+ env: 'FEE_RECIPIENT',
73
+ parseEnv: (val: string) => AztecAddress.fromString(val),
74
+ description: 'Address to receive fees.',
75
+ },
76
+ acvmWorkingDirectory: {
77
+ env: 'ACVM_WORKING_DIRECTORY',
78
+ description: 'The working directory to use for simulation/proving',
79
+ },
80
+ acvmBinaryPath: {
81
+ env: 'ACVM_BINARY_PATH',
82
+ description: 'The path to the ACVM binary',
83
+ },
84
+ allowedInSetup: {
85
+ env: 'SEQ_ALLOWED_SETUP_FN',
86
+ parseEnv: (val: string) => parseSequencerAllowList(val),
87
+ description: 'The list of functions calls allowed to run in setup',
88
+ printDefault: () =>
89
+ 'AuthRegistry, FeeJuice.increase_public_balance, Token.increase_public_balance, FPC.prepare_fee',
90
+ },
91
+ maxBlockSizeInBytes: {
92
+ env: 'SEQ_MAX_BLOCK_SIZE_IN_BYTES',
93
+ description: 'Max block size',
94
+ ...numberConfigHelper(1024 * 1024),
95
+ },
96
+ enforceTimeTable: {
97
+ env: 'SEQ_ENFORCE_TIME_TABLE',
98
+ description: 'Whether to enforce the time table when building blocks',
99
+ ...booleanConfigHelper(),
100
+ defaultValue: false,
101
+ },
102
+ governanceProposerPayload: {
103
+ env: 'GOVERNANCE_PROPOSER_PAYLOAD_ADDRESS',
104
+ description: 'The address of the payload for the governanceProposer',
105
+ parseEnv: (val: string) => EthAddress.fromString(val),
106
+ defaultValue: EthAddress.ZERO,
107
+ },
108
+ maxL1TxInclusionTimeIntoSlot: {
109
+ env: 'SEQ_MAX_L1_TX_INCLUSION_TIME_INTO_SLOT',
110
+ description: 'How many seconds into an L1 slot we can still send a tx and get it mined.',
111
+ parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
112
+ },
113
+ };
114
+
115
+ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientConfig> = {
116
+ ...sequencerConfigMappings,
117
+ ...l1ReaderConfigMappings,
118
+ ...getTxSenderConfigMappings('SEQ'),
119
+ ...getPublisherConfigMappings('SEQ'),
120
+ ...chainConfigMappings,
121
+ ...pickConfigMappings(l1ContractsConfigMappings, ['ethereumSlotDuration', 'aztecSlotDuration', 'aztecEpochDuration']),
122
+ };
123
+
124
+ /**
125
+ * Creates an instance of SequencerClientConfig out of environment variables using sensible defaults for integration testing if not set.
126
+ */
127
+ export function getConfigEnvVars(): SequencerClientConfig {
128
+ return getConfigFromMappings<SequencerClientConfig>(sequencerClientConfigMappings);
129
+ }
130
+
131
+ /**
132
+ * Parses a string to a list of allowed elements.
133
+ * Each encoded is expected to be of one of the following formats
134
+ * `I:${address}`
135
+ * `I:${address}:${selector}`
136
+ * `C:${classId}`
137
+ * `C:${classId}:${selector}`
138
+ *
139
+ * @param value The string to parse
140
+ * @returns A list of allowed elements
141
+ */
142
+ export function parseSequencerAllowList(value: string): AllowedElement[] {
143
+ const entries: AllowedElement[] = [];
144
+
145
+ if (!value) {
146
+ return entries;
147
+ }
148
+
149
+ for (const val of value.split(',')) {
150
+ const [typeString, identifierString, selectorString] = val.split(':');
151
+ const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined;
152
+
153
+ if (typeString === 'I') {
154
+ if (selector) {
155
+ entries.push({
156
+ address: AztecAddress.fromString(identifierString),
157
+ selector,
158
+ });
159
+ } else {
160
+ entries.push({
161
+ address: AztecAddress.fromString(identifierString),
162
+ });
163
+ }
164
+ } else if (typeString === 'C') {
165
+ if (selector) {
166
+ entries.push({
167
+ classId: Fr.fromHexString(identifierString),
168
+ selector,
169
+ });
170
+ } else {
171
+ entries.push({
172
+ classId: Fr.fromHexString(identifierString),
173
+ });
174
+ }
175
+ }
176
+ }
177
+
178
+ return entries;
179
+ }
@@ -0,0 +1,108 @@
1
+ import {
2
+ type L1ContractsConfig,
3
+ type L1ReaderConfig,
4
+ type ViemPublicClient,
5
+ createEthereumChain,
6
+ } from '@aztec/ethereum';
7
+ import type { EthAddress } from '@aztec/foundation/eth-address';
8
+ import { Fr } from '@aztec/foundation/fields';
9
+ import { createLogger } from '@aztec/foundation/log';
10
+ import { RollupAbi } from '@aztec/l1-artifacts';
11
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
12
+ import { GasFees } from '@aztec/stdlib/gas';
13
+ import type { GlobalVariableBuilder as GlobalVariableBuilderInterface } from '@aztec/stdlib/tx';
14
+ import { GlobalVariables } from '@aztec/stdlib/tx';
15
+
16
+ import { type GetContractReturnType, createPublicClient, fallback, getAddress, getContract, http } from 'viem';
17
+
18
+ /**
19
+ * Simple global variables builder.
20
+ */
21
+ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
22
+ private log = createLogger('sequencer:global_variable_builder');
23
+
24
+ private rollupContract: GetContractReturnType<typeof RollupAbi, ViemPublicClient>;
25
+ private publicClient: ViemPublicClient;
26
+ private ethereumSlotDuration: number;
27
+
28
+ constructor(config: L1ReaderConfig & Pick<L1ContractsConfig, 'ethereumSlotDuration'>) {
29
+ const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
30
+
31
+ const chain = createEthereumChain(l1RpcUrls, chainId);
32
+
33
+ this.ethereumSlotDuration = config.ethereumSlotDuration;
34
+
35
+ this.publicClient = createPublicClient({
36
+ chain: chain.chainInfo,
37
+ transport: fallback(chain.rpcUrls.map(url => http(url))),
38
+ pollingInterval: config.viemPollingIntervalMS,
39
+ });
40
+
41
+ this.rollupContract = getContract({
42
+ address: getAddress(l1Contracts.rollupAddress.toString()),
43
+ abi: RollupAbi,
44
+ client: this.publicClient,
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Computes the "current" base fees, e.g., the price that you currently should pay to get include in the next block
50
+ * @returns Base fees for the expected next block
51
+ */
52
+ public async getCurrentBaseFees(): Promise<GasFees> {
53
+ // Since this might be called in the middle of a slot where a block might have been published,
54
+ // we need to fetch the last block written, and estimate the earliest timestamp for the next block.
55
+ // The timestamp of that last block will act as a lower bound for the next block.
56
+
57
+ const lastBlock = await this.rollupContract.read.getBlock([await this.rollupContract.read.getPendingBlockNumber()]);
58
+ const earliestTimestamp = await this.rollupContract.read.getTimestampForSlot([lastBlock.slotNumber + 1n]);
59
+ const nextEthTimestamp = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
60
+ const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
61
+
62
+ return new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFeeAt([timestamp, true])));
63
+ }
64
+
65
+ /**
66
+ * Simple builder of global variables that use the minimum time possible.
67
+ * @param blockNumber - The block number to build global variables for.
68
+ * @param coinbase - The address to receive block reward.
69
+ * @param feeRecipient - The address to receive fees.
70
+ * @param slotNumber - The slot number to use for the global variables, if undefined it will be calculated.
71
+ * @returns The global variables for the given block number.
72
+ */
73
+ public async buildGlobalVariables(
74
+ blockNumber: Fr,
75
+ coinbase: EthAddress,
76
+ feeRecipient: AztecAddress,
77
+ slotNumber?: bigint,
78
+ ): Promise<GlobalVariables> {
79
+ const version = new Fr(await this.rollupContract.read.getVersion());
80
+ const chainId = new Fr(this.publicClient.chain.id);
81
+
82
+ if (slotNumber === undefined) {
83
+ const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(this.ethereumSlotDuration));
84
+ slotNumber = await this.rollupContract.read.getSlotAt([ts]);
85
+ }
86
+
87
+ const timestamp = await this.rollupContract.read.getTimestampForSlot([slotNumber]);
88
+
89
+ const slotFr = new Fr(slotNumber);
90
+ const timestampFr = new Fr(timestamp);
91
+
92
+ // We can skip much of the logic in getCurrentBaseFees since it we already check that we are not within a slot elsewhere.
93
+ const gasFees = new GasFees(Fr.ZERO, new Fr(await this.rollupContract.read.getManaBaseFeeAt([timestamp, true])));
94
+
95
+ const globalVariables = new GlobalVariables(
96
+ chainId,
97
+ version,
98
+ blockNumber,
99
+ slotFr,
100
+ timestampFr,
101
+ coinbase,
102
+ feeRecipient,
103
+ gasFees,
104
+ );
105
+
106
+ return globalVariables;
107
+ }
108
+ }
@@ -0,0 +1 @@
1
+ export { GlobalVariableBuilder } from './global_builder.js';
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './client/index.js';
2
+ export * from './config.js';
3
+ export * from './publisher/index.js';
4
+ export * from './tx_validator/tx_validator_factory.js';
5
+ export * from './slasher/index.js';
6
+ export { Sequencer, SequencerState, getDefaultAllowedSetupFunctions } from './sequencer/index.js';
7
+
8
+ // Used by the node to simulate public parts of transactions. Should these be moved to a shared library?
9
+ // ISSUE(#9832)
10
+ export * from './global_variable_builder/index.js';
@@ -0,0 +1,75 @@
1
+ import { type BlobSinkConfig, blobSinkConfigMapping } from '@aztec/blob-sink/client';
2
+ import {
3
+ type L1ReaderConfig,
4
+ type L1TxUtilsConfig,
5
+ NULL_KEY,
6
+ l1ReaderConfigMappings,
7
+ l1TxUtilsConfigMappings,
8
+ } from '@aztec/ethereum';
9
+ import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config';
10
+ import { EthAddress } from '@aztec/foundation/eth-address';
11
+
12
+ /**
13
+ * The configuration of the rollup transaction publisher.
14
+ */
15
+ export type TxSenderConfig = L1ReaderConfig & {
16
+ /**
17
+ * The private key to be used by the publisher.
18
+ */
19
+ publisherPrivateKey: `0x${string}`;
20
+
21
+ /**
22
+ * The address of the custom forwarder contract.
23
+ */
24
+ customForwarderContractAddress: EthAddress;
25
+ };
26
+
27
+ /**
28
+ * Configuration of the L1Publisher.
29
+ */
30
+ export type PublisherConfig = L1TxUtilsConfig &
31
+ BlobSinkConfig & {
32
+ /**
33
+ * The interval to wait between publish retries.
34
+ */
35
+ l1PublishRetryIntervalMS: number;
36
+ };
37
+
38
+ export const getTxSenderConfigMappings: (
39
+ scope: 'PROVER' | 'SEQ',
40
+ ) => ConfigMappingsType<Omit<TxSenderConfig, 'l1Contracts'>> = (scope: 'PROVER' | 'SEQ') => ({
41
+ ...l1ReaderConfigMappings,
42
+ customForwarderContractAddress: {
43
+ env: `CUSTOM_FORWARDER_CONTRACT_ADDRESS`,
44
+ parseEnv: (val: string) => EthAddress.fromString(val),
45
+ description: 'The address of the custom forwarder contract.',
46
+ defaultValue: EthAddress.ZERO,
47
+ },
48
+ publisherPrivateKey: {
49
+ env: scope === 'PROVER' ? `PROVER_PUBLISHER_PRIVATE_KEY` : `SEQ_PUBLISHER_PRIVATE_KEY`,
50
+ description: 'The private key to be used by the publisher.',
51
+ parseEnv: (val: string) => (val ? `0x${val.replace('0x', '')}` : NULL_KEY),
52
+ defaultValue: NULL_KEY,
53
+ },
54
+ });
55
+
56
+ export function getTxSenderConfigFromEnv(scope: 'PROVER' | 'SEQ'): Omit<TxSenderConfig, 'l1Contracts'> {
57
+ return getConfigFromMappings(getTxSenderConfigMappings(scope));
58
+ }
59
+
60
+ export const getPublisherConfigMappings: (
61
+ scope: 'PROVER' | 'SEQ',
62
+ ) => ConfigMappingsType<PublisherConfig & L1TxUtilsConfig> = scope => ({
63
+ l1PublishRetryIntervalMS: {
64
+ env: scope === `PROVER` ? `PROVER_PUBLISH_RETRY_INTERVAL_MS` : `SEQ_PUBLISH_RETRY_INTERVAL_MS`,
65
+ parseEnv: (val: string) => +val,
66
+ defaultValue: 1000,
67
+ description: 'The interval to wait between publish retries.',
68
+ },
69
+ ...l1TxUtilsConfigMappings,
70
+ ...blobSinkConfigMapping,
71
+ });
72
+
73
+ export function getPublisherConfigFromEnv(scope: 'PROVER' | 'SEQ'): PublisherConfig {
74
+ return getConfigFromMappings(getPublisherConfigMappings(scope));
75
+ }
@@ -0,0 +1 @@
1
+ export { SequencerPublisher } from './sequencer-publisher.js';
@@ -0,0 +1,176 @@
1
+ import { createLogger } from '@aztec/aztec.js';
2
+ import type { L1PublishBlockStats, L1PublishStats } from '@aztec/stdlib/stats';
3
+ import {
4
+ Attributes,
5
+ type Gauge,
6
+ type Histogram,
7
+ Metrics,
8
+ type TelemetryClient,
9
+ type UpDownCounter,
10
+ ValueType,
11
+ } from '@aztec/telemetry-client';
12
+
13
+ import { formatEther } from 'viem/utils';
14
+
15
+ export type L1TxType = 'process';
16
+
17
+ export class SequencerPublisherMetrics {
18
+ private gasPrice: Histogram;
19
+
20
+ private txCount: UpDownCounter;
21
+ private txDuration: Histogram;
22
+ private txGas: Histogram;
23
+ private txCalldataSize: Histogram;
24
+ private txCalldataGas: Histogram;
25
+ private txBlobDataGasUsed: Histogram;
26
+ private txBlobDataGasCost: Histogram;
27
+
28
+ private readonly blobCountHistogram: Histogram;
29
+ private readonly blobInclusionBlocksHistogram: Histogram;
30
+ private readonly blobTxSuccessCounter: UpDownCounter;
31
+ private readonly blobTxFailureCounter: UpDownCounter;
32
+
33
+ private senderBalance: Gauge;
34
+
35
+ constructor(
36
+ client: TelemetryClient,
37
+ name = 'SequencerPublisher',
38
+ private logger = createLogger('sequencer:publisher:metrics'),
39
+ ) {
40
+ const meter = client.getMeter(name);
41
+
42
+ this.gasPrice = meter.createHistogram(Metrics.L1_PUBLISHER_GAS_PRICE, {
43
+ description: 'The gas price used for transactions',
44
+ unit: 'gwei',
45
+ valueType: ValueType.DOUBLE,
46
+ });
47
+
48
+ this.txCount = meter.createUpDownCounter(Metrics.L1_PUBLISHER_TX_COUNT, {
49
+ description: 'The number of transactions processed',
50
+ });
51
+
52
+ this.txDuration = meter.createHistogram(Metrics.L1_PUBLISHER_TX_DURATION, {
53
+ description: 'The duration of transaction processing',
54
+ unit: 'ms',
55
+ valueType: ValueType.INT,
56
+ });
57
+
58
+ this.txGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_GAS, {
59
+ description: 'The gas consumed by transactions',
60
+ unit: 'gas',
61
+ valueType: ValueType.INT,
62
+ });
63
+
64
+ this.txCalldataSize = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_SIZE, {
65
+ description: 'The size of the calldata in transactions',
66
+ unit: 'By',
67
+ valueType: ValueType.INT,
68
+ });
69
+
70
+ this.txCalldataGas = meter.createHistogram(Metrics.L1_PUBLISHER_TX_CALLDATA_GAS, {
71
+ description: 'The gas consumed by the calldata in transactions',
72
+ unit: 'gas',
73
+ valueType: ValueType.INT,
74
+ });
75
+
76
+ this.txBlobDataGasUsed = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_USED, {
77
+ description: 'The amount of blob gas used in transactions',
78
+ unit: 'gas',
79
+ valueType: ValueType.INT,
80
+ });
81
+
82
+ this.txBlobDataGasCost = meter.createHistogram(Metrics.L1_PUBLISHER_TX_BLOBDATA_GAS_COST, {
83
+ description: 'The gas cost of blobs in transactions',
84
+ unit: 'gwei',
85
+ valueType: ValueType.INT,
86
+ });
87
+
88
+ this.blobCountHistogram = meter.createHistogram(Metrics.L1_PUBLISHER_BLOB_COUNT, {
89
+ description: 'Number of blobs in L1 transactions',
90
+ unit: 'blobs',
91
+ valueType: ValueType.INT,
92
+ });
93
+
94
+ this.blobInclusionBlocksHistogram = meter.createHistogram(Metrics.L1_PUBLISHER_BLOB_INCLUSION_BLOCKS, {
95
+ description: 'Number of L1 blocks between blob tx submission and inclusion',
96
+ unit: 'blocks',
97
+ valueType: ValueType.INT,
98
+ });
99
+
100
+ this.blobTxSuccessCounter = meter.createUpDownCounter(Metrics.L1_PUBLISHER_BLOB_TX_SUCCESS, {
101
+ description: 'Number of successful L1 transactions with blobs',
102
+ });
103
+
104
+ this.blobTxFailureCounter = meter.createUpDownCounter(Metrics.L1_PUBLISHER_BLOB_TX_FAILURE, {
105
+ description: 'Number of failed L1 transactions with blobs',
106
+ });
107
+
108
+ this.senderBalance = meter.createGauge(Metrics.L1_PUBLISHER_BALANCE, {
109
+ unit: 'eth',
110
+ description: 'The balance of the sender address',
111
+ valueType: ValueType.DOUBLE,
112
+ });
113
+ }
114
+
115
+ recordFailedTx(txType: L1TxType) {
116
+ this.txCount.add(1, {
117
+ [Attributes.L1_TX_TYPE]: txType,
118
+ [Attributes.OK]: false,
119
+ });
120
+
121
+ if (txType === 'process') {
122
+ this.blobTxFailureCounter.add(1);
123
+ }
124
+ }
125
+
126
+ recordProcessBlockTx(durationMs: number, stats: L1PublishBlockStats) {
127
+ this.recordTx('process', durationMs, stats);
128
+
129
+ if (stats.blobCount && stats.blobCount > 0) {
130
+ this.blobCountHistogram.record(stats.blobCount);
131
+
132
+ if (stats.inclusionBlocks !== undefined) {
133
+ this.blobInclusionBlocksHistogram.record(stats.inclusionBlocks);
134
+ }
135
+
136
+ this.blobTxSuccessCounter.add(1);
137
+ }
138
+ }
139
+
140
+ recordSenderBalance(wei: bigint, senderAddress: string) {
141
+ const eth = parseFloat(formatEther(wei, 'wei'));
142
+ this.senderBalance.record(eth, {
143
+ [Attributes.SENDER_ADDRESS]: senderAddress,
144
+ });
145
+ }
146
+
147
+ private recordTx(txType: L1TxType, durationMs: number, stats: L1PublishStats) {
148
+ const attributes = {
149
+ [Attributes.L1_TX_TYPE]: txType,
150
+ [Attributes.L1_SENDER]: stats.sender,
151
+ } as const;
152
+
153
+ this.txCount.add(1, {
154
+ ...attributes,
155
+ [Attributes.OK]: true,
156
+ });
157
+
158
+ this.txDuration.record(Math.ceil(durationMs), attributes);
159
+ this.txGas.record(
160
+ // safe to downcast - total block limit is 30M gas which fits in a JS number
161
+ Number(stats.gasUsed),
162
+ attributes,
163
+ );
164
+ this.txCalldataGas.record(stats.calldataGas, attributes);
165
+ this.txCalldataSize.record(stats.calldataSize, attributes);
166
+
167
+ this.txBlobDataGasCost.record(Number(stats.blobDataGas), attributes);
168
+ this.txBlobDataGasUsed.record(Number(stats.blobGasUsed), attributes);
169
+
170
+ try {
171
+ this.gasPrice.record(parseInt(formatEther(stats.gasPrice, 'gwei'), 10));
172
+ } catch (e) {
173
+ // ignore
174
+ }
175
+ }
176
+ }