@aztec/simulator 0.67.1 → 0.68.1
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.
- package/dest/acvm/oracle/oracle.d.ts +1 -1
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +3 -3
- package/dest/acvm/oracle/typed_oracle.d.ts +1 -1
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +3 -3
- package/dest/avm/avm_memory_types.d.ts +1 -1
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +45 -38
- package/dest/avm/avm_simulator.d.ts +1 -0
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +38 -18
- package/dest/avm/avm_tree.d.ts +2 -23
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +27 -82
- package/dest/avm/errors.d.ts +8 -1
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +13 -3
- package/dest/avm/fixtures/index.d.ts +2 -0
- package/dest/avm/fixtures/index.d.ts.map +1 -1
- package/dest/avm/fixtures/index.js +4 -4
- package/dest/avm/journal/journal.d.ts +15 -7
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +30 -22
- package/dest/avm/journal/nullifiers.d.ts +0 -4
- package/dest/avm/journal/nullifiers.d.ts.map +1 -1
- package/dest/avm/journal/nullifiers.js +1 -11
- package/dest/avm/journal/public_storage.d.ts +1 -49
- package/dest/avm/journal/public_storage.d.ts.map +1 -1
- package/dest/avm/journal/public_storage.js +1 -19
- package/dest/avm/opcodes/addressing_mode.js +3 -3
- package/dest/avm/opcodes/conversion.d.ts +4 -4
- package/dest/avm/opcodes/conversion.d.ts.map +1 -1
- package/dest/avm/opcodes/conversion.js +22 -18
- package/dest/avm/opcodes/ec_add.d.ts.map +1 -1
- package/dest/avm/opcodes/ec_add.js +5 -4
- package/dest/avm/opcodes/external_calls.js +2 -2
- package/dest/avm/opcodes/hashing.d.ts.map +1 -1
- package/dest/avm/opcodes/hashing.js +5 -5
- package/dest/avm/opcodes/misc.d.ts.map +1 -1
- package/dest/avm/opcodes/misc.js +3 -3
- package/dest/avm/opcodes/multi_scalar_mul.d.ts.map +1 -1
- package/dest/avm/opcodes/multi_scalar_mul.js +9 -6
- package/dest/avm/test_utils.d.ts +1 -0
- package/dest/avm/test_utils.d.ts.map +1 -1
- package/dest/avm/test_utils.js +4 -1
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +2 -1
- package/dest/client/db_oracle.d.ts +7 -3
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/index.d.ts +1 -0
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +2 -1
- package/dest/client/view_data_oracle.d.ts +2 -2
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +5 -4
- package/dest/providers/acvm_wasm.js +2 -2
- package/dest/providers/acvm_wasm_with_blobs.d.ts +7 -0
- package/dest/providers/acvm_wasm_with_blobs.d.ts.map +1 -0
- package/dest/providers/acvm_wasm_with_blobs.js +15 -0
- package/dest/providers/index.d.ts +1 -1
- package/dest/providers/index.d.ts.map +1 -1
- package/dest/providers/index.js +2 -2
- package/dest/public/bytecode_errors.d.ts +4 -0
- package/dest/public/bytecode_errors.d.ts.map +1 -0
- package/dest/public/bytecode_errors.js +7 -0
- package/dest/public/enqueued_call_side_effect_trace.d.ts +10 -4
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +63 -13
- package/dest/public/execution.d.ts +2 -2
- package/dest/public/execution.d.ts.map +1 -1
- package/dest/public/execution.js +1 -1
- package/dest/public/executor_metrics.d.ts +2 -0
- package/dest/public/executor_metrics.d.ts.map +1 -1
- package/dest/public/executor_metrics.js +11 -1
- package/dest/public/fee_payment.d.ts.map +1 -1
- package/dest/public/fee_payment.js +4 -3
- package/dest/public/fixtures/index.d.ts +17 -11
- package/dest/public/fixtures/index.d.ts.map +1 -1
- package/dest/public/fixtures/index.js +103 -35
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +7 -6
- package/dest/public/public_processor.d.ts +15 -7
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +119 -75
- package/dest/public/public_processor_metrics.d.ts +10 -2
- package/dest/public/public_processor_metrics.d.ts.map +1 -1
- package/dest/public/public_processor_metrics.js +49 -2
- package/dest/public/public_tx_context.d.ts +5 -0
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +40 -20
- package/dest/public/public_tx_simulator.d.ts +2 -1
- package/dest/public/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator.js +35 -6
- package/dest/public/side_effect_errors.js +2 -2
- package/dest/public/side_effect_trace_interface.d.ts +2 -1
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts.map +1 -1
- package/dest/public/transitional_adapters.js +2 -35
- package/dest/public/unique_class_ids.d.ts +37 -0
- package/dest/public/unique_class_ids.d.ts.map +1 -0
- package/dest/public/unique_class_ids.js +66 -0
- package/package.json +12 -12
- package/src/acvm/oracle/oracle.ts +2 -2
- package/src/acvm/oracle/typed_oracle.ts +2 -2
- package/src/avm/avm_memory_types.ts +56 -38
- package/src/avm/avm_simulator.ts +48 -20
- package/src/avm/avm_tree.ts +35 -92
- package/src/avm/errors.ts +13 -2
- package/src/avm/fixtures/index.ts +4 -2
- package/src/avm/journal/journal.ts +39 -29
- package/src/avm/journal/nullifiers.ts +0 -11
- package/src/avm/journal/public_storage.ts +2 -21
- package/src/avm/opcodes/addressing_mode.ts +2 -2
- package/src/avm/opcodes/conversion.ts +21 -16
- package/src/avm/opcodes/ec_add.ts +4 -3
- package/src/avm/opcodes/external_calls.ts +1 -1
- package/src/avm/opcodes/hashing.ts +6 -4
- package/src/avm/opcodes/misc.ts +4 -3
- package/src/avm/opcodes/multi_scalar_mul.ts +10 -5
- package/src/avm/test_utils.ts +4 -0
- package/src/client/client_execution_context.ts +2 -0
- package/src/client/db_oracle.ts +8 -3
- package/src/client/index.ts +1 -0
- package/src/client/view_data_oracle.ts +6 -3
- package/src/providers/acvm_wasm.ts +1 -1
- package/src/providers/acvm_wasm_with_blobs.ts +25 -0
- package/src/providers/index.ts +1 -1
- package/src/public/bytecode_errors.ts +6 -0
- package/src/public/enqueued_call_side_effect_trace.ts +83 -19
- package/src/public/execution.ts +1 -2
- package/src/public/executor_metrics.ts +13 -0
- package/src/public/fee_payment.ts +3 -2
- package/src/public/fixtures/index.ts +152 -46
- package/src/public/public_db_sources.ts +6 -5
- package/src/public/public_processor.ts +171 -88
- package/src/public/public_processor_metrics.ts +64 -2
- package/src/public/public_tx_context.ts +57 -21
- package/src/public/public_tx_simulator.ts +34 -6
- package/src/public/side_effect_errors.ts +1 -1
- package/src/public/side_effect_trace_interface.ts +2 -1
- package/src/public/transitional_adapters.ts +0 -51
- package/src/public/unique_class_ids.ts +80 -0
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
type MerkleTreeWriteOperations,
|
|
5
5
|
NestedProcessReturnValues,
|
|
6
6
|
type ProcessedTx,
|
|
7
|
-
type ProcessedTxHandler,
|
|
8
7
|
Tx,
|
|
9
8
|
TxExecutionPhase,
|
|
10
9
|
type TxValidator,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
type BlockHeader,
|
|
17
16
|
type ContractDataSource,
|
|
18
17
|
Fr,
|
|
18
|
+
Gas,
|
|
19
19
|
type GlobalVariables,
|
|
20
20
|
MAX_NOTE_HASHES_PER_TX,
|
|
21
21
|
MAX_NULLIFIERS_PER_TX,
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
} from '@aztec/circuits.js';
|
|
25
25
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
26
26
|
import { createLogger } from '@aztec/foundation/log';
|
|
27
|
-
import { Timer } from '@aztec/foundation/timer';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
27
|
+
import { type DateProvider, Timer, elapsed, executeTimeout } from '@aztec/foundation/timer';
|
|
28
|
+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
29
|
+
import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
|
|
30
|
+
import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
30
31
|
|
|
31
32
|
import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from './fee_payment.js';
|
|
32
33
|
import { WorldStateDB } from './public_db_sources.js';
|
|
@@ -37,28 +38,35 @@ import { PublicTxSimulator } from './public_tx_simulator.js';
|
|
|
37
38
|
* Creates new instances of PublicProcessor given the provided merkle tree db and contract data source.
|
|
38
39
|
*/
|
|
39
40
|
export class PublicProcessorFactory {
|
|
40
|
-
constructor(
|
|
41
|
+
constructor(
|
|
42
|
+
private contractDataSource: ContractDataSource,
|
|
43
|
+
private dateProvider: DateProvider,
|
|
44
|
+
private telemetryClient: TelemetryClient,
|
|
45
|
+
) {}
|
|
41
46
|
|
|
42
47
|
/**
|
|
43
48
|
* Creates a new instance of a PublicProcessor.
|
|
44
49
|
* @param historicalHeader - The header of a block previous to the one in which the tx is included.
|
|
45
50
|
* @param globalVariables - The global variables for the block being processed.
|
|
51
|
+
* @param enforceFeePayment - Allows disabling balance checks for fee estimations.
|
|
46
52
|
* @returns A new instance of a PublicProcessor.
|
|
47
53
|
*/
|
|
48
54
|
public create(
|
|
49
55
|
merkleTree: MerkleTreeWriteOperations,
|
|
50
56
|
maybeHistoricalHeader: BlockHeader | undefined,
|
|
51
57
|
globalVariables: GlobalVariables,
|
|
58
|
+
enforceFeePayment: boolean,
|
|
52
59
|
): PublicProcessor {
|
|
53
60
|
const historicalHeader = maybeHistoricalHeader ?? merkleTree.getInitialHeader();
|
|
54
61
|
|
|
55
62
|
const worldStateDB = new WorldStateDB(merkleTree, this.contractDataSource);
|
|
56
|
-
const publicTxSimulator =
|
|
63
|
+
const publicTxSimulator = this.createPublicTxSimulator(
|
|
57
64
|
merkleTree,
|
|
58
65
|
worldStateDB,
|
|
59
66
|
this.telemetryClient,
|
|
60
67
|
globalVariables,
|
|
61
68
|
/*doMerkleOperations=*/ true,
|
|
69
|
+
enforceFeePayment,
|
|
62
70
|
);
|
|
63
71
|
|
|
64
72
|
return new PublicProcessor(
|
|
@@ -67,16 +75,42 @@ export class PublicProcessorFactory {
|
|
|
67
75
|
historicalHeader,
|
|
68
76
|
worldStateDB,
|
|
69
77
|
publicTxSimulator,
|
|
78
|
+
this.dateProvider,
|
|
70
79
|
this.telemetryClient,
|
|
71
80
|
);
|
|
72
81
|
}
|
|
82
|
+
|
|
83
|
+
protected createPublicTxSimulator(
|
|
84
|
+
db: MerkleTreeWriteOperations,
|
|
85
|
+
worldStateDB: WorldStateDB,
|
|
86
|
+
telemetryClient: TelemetryClient,
|
|
87
|
+
globalVariables: GlobalVariables,
|
|
88
|
+
doMerkleOperations: boolean,
|
|
89
|
+
enforceFeePayment: boolean,
|
|
90
|
+
) {
|
|
91
|
+
return new PublicTxSimulator(
|
|
92
|
+
db,
|
|
93
|
+
worldStateDB,
|
|
94
|
+
telemetryClient,
|
|
95
|
+
globalVariables,
|
|
96
|
+
doMerkleOperations,
|
|
97
|
+
enforceFeePayment,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class PublicProcessorTimeoutError extends Error {
|
|
103
|
+
constructor(message: string = 'Timed out while processing tx') {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = 'PublicProcessorTimeoutError';
|
|
106
|
+
}
|
|
73
107
|
}
|
|
74
108
|
|
|
75
109
|
/**
|
|
76
110
|
* Converts Txs lifted from the P2P module into ProcessedTx objects by executing
|
|
77
111
|
* any public function calls in them. Txs with private calls only are unaffected.
|
|
78
112
|
*/
|
|
79
|
-
export class PublicProcessor {
|
|
113
|
+
export class PublicProcessor implements Traceable {
|
|
80
114
|
private metrics: PublicProcessorMetrics;
|
|
81
115
|
constructor(
|
|
82
116
|
protected db: MerkleTreeWriteOperations,
|
|
@@ -84,6 +118,7 @@ export class PublicProcessor {
|
|
|
84
118
|
protected historicalHeader: BlockHeader,
|
|
85
119
|
protected worldStateDB: WorldStateDB,
|
|
86
120
|
protected publicTxSimulator: PublicTxSimulator,
|
|
121
|
+
private dateProvider: DateProvider,
|
|
87
122
|
telemetryClient: TelemetryClient,
|
|
88
123
|
private log = createLogger('simulator:public-processor'),
|
|
89
124
|
) {
|
|
@@ -103,14 +138,16 @@ export class PublicProcessor {
|
|
|
103
138
|
public async process(
|
|
104
139
|
txs: Tx[],
|
|
105
140
|
maxTransactions = txs.length,
|
|
106
|
-
processedTxHandler?: ProcessedTxHandler,
|
|
107
141
|
txValidator?: TxValidator<ProcessedTx>,
|
|
142
|
+
deadline?: Date,
|
|
108
143
|
): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> {
|
|
109
144
|
// The processor modifies the tx objects in place, so we need to clone them.
|
|
110
145
|
txs = txs.map(tx => Tx.clone(tx));
|
|
111
146
|
const result: ProcessedTx[] = [];
|
|
112
147
|
const failed: FailedTx[] = [];
|
|
113
148
|
let returns: NestedProcessReturnValues[] = [];
|
|
149
|
+
let totalGas = new Gas(0, 0);
|
|
150
|
+
const timer = new Timer();
|
|
114
151
|
|
|
115
152
|
for (const tx of txs) {
|
|
116
153
|
// only process up to the limit of the block
|
|
@@ -118,95 +155,141 @@ export class PublicProcessor {
|
|
|
118
155
|
break;
|
|
119
156
|
}
|
|
120
157
|
try {
|
|
121
|
-
const [processedTx, returnValues] =
|
|
122
|
-
? await this.processPrivateOnlyTx(tx)
|
|
123
|
-
: await this.processTxWithPublicCalls(tx);
|
|
124
|
-
|
|
125
|
-
this.log.verbose(
|
|
126
|
-
!tx.hasPublicCalls()
|
|
127
|
-
? `Processed tx ${processedTx.hash} with no public calls`
|
|
128
|
-
: `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls`,
|
|
129
|
-
{
|
|
130
|
-
txHash: processedTx.hash,
|
|
131
|
-
txFee: processedTx.txEffect.transactionFee.toBigInt(),
|
|
132
|
-
revertCode: processedTx.txEffect.revertCode.getCode(),
|
|
133
|
-
revertReason: processedTx.revertReason,
|
|
134
|
-
gasUsed: processedTx.gasUsed,
|
|
135
|
-
publicDataWriteCount: processedTx.txEffect.publicDataWrites.length,
|
|
136
|
-
nullifierCount: processedTx.txEffect.nullifiers.length,
|
|
137
|
-
noteHashCount: processedTx.txEffect.noteHashes.length,
|
|
138
|
-
contractClassLogCount: processedTx.txEffect.contractClassLogs.getTotalLogCount(),
|
|
139
|
-
unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
|
|
140
|
-
privateLogCount: processedTx.txEffect.privateLogs.length,
|
|
141
|
-
l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// Commit the state updates from this transaction
|
|
146
|
-
await this.worldStateDB.commit();
|
|
147
|
-
|
|
148
|
-
// Re-validate the transaction
|
|
149
|
-
if (txValidator) {
|
|
150
|
-
// Only accept processed transactions that are not double-spends,
|
|
151
|
-
// public functions emitting nullifiers would pass earlier check but fail here.
|
|
152
|
-
// Note that we're checking all nullifiers generated in the private execution twice,
|
|
153
|
-
// we could store the ones already checked and skip them here as an optimization.
|
|
154
|
-
const [_, invalid] = await txValidator.validateTxs([processedTx]);
|
|
155
|
-
if (invalid.length) {
|
|
156
|
-
throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// if we were given a handler then send the transaction to it for block building or proving
|
|
160
|
-
if (processedTxHandler) {
|
|
161
|
-
await processedTxHandler.addNewTx(processedTx);
|
|
162
|
-
}
|
|
163
|
-
// Update the state so that the next tx in the loop has the correct .startState
|
|
164
|
-
// NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
|
|
165
|
-
// a) had only 1 tx with public calls per block, so this loop had len 1
|
|
166
|
-
// b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
|
|
167
|
-
// To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
|
|
168
|
-
// The below is taken from buildBaseRollupHints:
|
|
169
|
-
await this.db.appendLeaves(
|
|
170
|
-
MerkleTreeId.NOTE_HASH_TREE,
|
|
171
|
-
padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
172
|
-
);
|
|
173
|
-
try {
|
|
174
|
-
await this.db.batchInsert(
|
|
175
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
176
|
-
padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
|
|
177
|
-
NULLIFIER_SUBTREE_HEIGHT,
|
|
178
|
-
);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
if (txValidator) {
|
|
181
|
-
// Ideally the validator has already caught this above, but just in case:
|
|
182
|
-
throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
|
|
183
|
-
} else {
|
|
184
|
-
// We have no validator and assume this call should blindly process txs with duplicates being caught later
|
|
185
|
-
this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
await this.db.sequentialInsert(
|
|
190
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
191
|
-
processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
|
|
192
|
-
);
|
|
158
|
+
const [processedTx, returnValues] = await this.processTx(tx, txValidator, deadline);
|
|
193
159
|
result.push(processedTx);
|
|
194
|
-
returns = returns.concat(returnValues
|
|
160
|
+
returns = returns.concat(returnValues);
|
|
161
|
+
totalGas = totalGas.add(processedTx.gasUsed.publicGas);
|
|
195
162
|
} catch (err: any) {
|
|
163
|
+
if (err?.name === 'PublicProcessorTimeoutError') {
|
|
164
|
+
this.log.warn(`Stopping tx processing due to timeout.`);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
196
167
|
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
197
168
|
this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage} ${err?.stack}`);
|
|
198
169
|
|
|
199
|
-
failed.push({
|
|
200
|
-
tx,
|
|
201
|
-
error: err instanceof Error ? err : new Error(errorMessage),
|
|
202
|
-
});
|
|
170
|
+
failed.push({ tx, error: err instanceof Error ? err : new Error(errorMessage) });
|
|
203
171
|
returns.push(new NestedProcessReturnValues([]));
|
|
204
172
|
}
|
|
205
173
|
}
|
|
206
174
|
|
|
175
|
+
const duration = timer.s();
|
|
176
|
+
const rate = duration > 0 ? totalGas.l2Gas / duration : 0;
|
|
177
|
+
this.metrics.recordAllTxs(totalGas, rate);
|
|
178
|
+
|
|
207
179
|
return [result, failed, returns];
|
|
208
180
|
}
|
|
209
181
|
|
|
182
|
+
@trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() }))
|
|
183
|
+
private async processTx(
|
|
184
|
+
tx: Tx,
|
|
185
|
+
txValidator?: TxValidator<ProcessedTx>,
|
|
186
|
+
deadline?: Date,
|
|
187
|
+
): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
|
|
188
|
+
const [time, [processedTx, returnValues]] = await elapsed(() => this.processTxWithinDeadline(tx, deadline));
|
|
189
|
+
|
|
190
|
+
this.log.verbose(
|
|
191
|
+
!tx.hasPublicCalls()
|
|
192
|
+
? `Processed tx ${processedTx.hash} with no public calls in ${time}ms`
|
|
193
|
+
: `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls in ${time}ms`,
|
|
194
|
+
{
|
|
195
|
+
txHash: processedTx.hash,
|
|
196
|
+
txFee: processedTx.txEffect.transactionFee.toBigInt(),
|
|
197
|
+
revertCode: processedTx.txEffect.revertCode.getCode(),
|
|
198
|
+
revertReason: processedTx.revertReason,
|
|
199
|
+
gasUsed: processedTx.gasUsed,
|
|
200
|
+
publicDataWriteCount: processedTx.txEffect.publicDataWrites.length,
|
|
201
|
+
nullifierCount: processedTx.txEffect.nullifiers.length,
|
|
202
|
+
noteHashCount: processedTx.txEffect.noteHashes.length,
|
|
203
|
+
contractClassLogCount: processedTx.txEffect.contractClassLogs.getTotalLogCount(),
|
|
204
|
+
unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
|
|
205
|
+
privateLogCount: processedTx.txEffect.privateLogs.length,
|
|
206
|
+
l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
|
|
207
|
+
durationMs: time,
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Commit the state updates from this transaction
|
|
212
|
+
await this.worldStateDB.commit();
|
|
213
|
+
|
|
214
|
+
// Re-validate the transaction
|
|
215
|
+
if (txValidator) {
|
|
216
|
+
// Only accept processed transactions that are not double-spends,
|
|
217
|
+
// public functions emitting nullifiers would pass earlier check but fail here.
|
|
218
|
+
// Note that we're checking all nullifiers generated in the private execution twice,
|
|
219
|
+
// we could store the ones already checked and skip them here as an optimization.
|
|
220
|
+
const [_, invalid] = await txValidator.validateTxs([processedTx]);
|
|
221
|
+
if (invalid.length) {
|
|
222
|
+
throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Update the state so that the next tx in the loop has the correct .startState
|
|
226
|
+
// NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
|
|
227
|
+
// a) had only 1 tx with public calls per block, so this loop had len 1
|
|
228
|
+
// b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
|
|
229
|
+
// To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
|
|
230
|
+
// The below is taken from buildBaseRollupHints:
|
|
231
|
+
const treeInsertionStart = process.hrtime.bigint();
|
|
232
|
+
await this.db.appendLeaves(
|
|
233
|
+
MerkleTreeId.NOTE_HASH_TREE,
|
|
234
|
+
padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
235
|
+
);
|
|
236
|
+
try {
|
|
237
|
+
await this.db.batchInsert(
|
|
238
|
+
MerkleTreeId.NULLIFIER_TREE,
|
|
239
|
+
padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
|
|
240
|
+
NULLIFIER_SUBTREE_HEIGHT,
|
|
241
|
+
);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (txValidator) {
|
|
244
|
+
// Ideally the validator has already caught this above, but just in case:
|
|
245
|
+
throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
|
|
246
|
+
} else {
|
|
247
|
+
// We have no validator and assume this call should blindly process txs with duplicates being caught later
|
|
248
|
+
this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await this.db.sequentialInsert(
|
|
253
|
+
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
254
|
+
processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
|
|
255
|
+
);
|
|
256
|
+
const treeInsertionEnd = process.hrtime.bigint();
|
|
257
|
+
this.metrics.recordTreeInsertions(Number(treeInsertionEnd - treeInsertionStart) / 1_000);
|
|
258
|
+
|
|
259
|
+
return [processedTx, returnValues ?? []];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Processes the given tx within deadline. Returns timeout if deadline is hit. */
|
|
263
|
+
private async processTxWithinDeadline(
|
|
264
|
+
tx: Tx,
|
|
265
|
+
deadline?: Date,
|
|
266
|
+
): Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> {
|
|
267
|
+
const processFn: () => Promise<[ProcessedTx, NestedProcessReturnValues[] | undefined]> = tx.hasPublicCalls()
|
|
268
|
+
? () => this.processTxWithPublicCalls(tx)
|
|
269
|
+
: () => this.processPrivateOnlyTx(tx);
|
|
270
|
+
|
|
271
|
+
if (!deadline) {
|
|
272
|
+
return await processFn();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const timeout = +deadline - this.dateProvider.now();
|
|
276
|
+
this.log.debug(`Processing tx ${tx.getTxHash().toString()} within ${timeout}ms`, {
|
|
277
|
+
deadline: deadline.toISOString(),
|
|
278
|
+
now: new Date(this.dateProvider.now()).toISOString(),
|
|
279
|
+
txHash: tx.getTxHash().toString(),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (timeout < 0) {
|
|
283
|
+
throw new PublicProcessorTimeoutError();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return await executeTimeout(
|
|
287
|
+
() => processFn(),
|
|
288
|
+
timeout,
|
|
289
|
+
() => new PublicProcessorTimeoutError(),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
210
293
|
/**
|
|
211
294
|
* Creates the public data write for paying the tx fee.
|
|
212
295
|
* This is used in private only txs, since for txs with public calls
|
|
@@ -241,7 +324,7 @@ export class PublicProcessor {
|
|
|
241
324
|
@trackSpan('PublicProcessor.processPrivateOnlyTx', (tx: Tx) => ({
|
|
242
325
|
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
243
326
|
}))
|
|
244
|
-
private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx]> {
|
|
327
|
+
private async processPrivateOnlyTx(tx: Tx): Promise<[ProcessedTx, undefined]> {
|
|
245
328
|
const gasFees = this.globalVariables.gasFees;
|
|
246
329
|
const transactionFee = tx.data.gasUsed.computeFee(gasFees);
|
|
247
330
|
|
|
@@ -260,7 +343,7 @@ export class PublicProcessor {
|
|
|
260
343
|
.filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log.data))
|
|
261
344
|
.map(log => ContractClassRegisteredEvent.fromLog(log.data)),
|
|
262
345
|
);
|
|
263
|
-
return [processedTx];
|
|
346
|
+
return [processedTx, undefined];
|
|
264
347
|
}
|
|
265
348
|
|
|
266
349
|
@trackSpan('PublicProcessor.processTxWithPublicCalls', tx => ({
|
|
@@ -294,7 +377,7 @@ export class PublicProcessor {
|
|
|
294
377
|
|
|
295
378
|
const phaseCount = processedPhases.length;
|
|
296
379
|
const durationMs = timer.ms();
|
|
297
|
-
this.metrics.recordTx(phaseCount, durationMs);
|
|
380
|
+
this.metrics.recordTx(phaseCount, durationMs, gasUsed.publicGas);
|
|
298
381
|
|
|
299
382
|
const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason);
|
|
300
383
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type TxExecutionPhase } from '@aztec/circuit-types';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Gas } from '@aztec/circuits.js';
|
|
3
|
+
import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
|
|
3
4
|
import {
|
|
4
5
|
Attributes,
|
|
6
|
+
type Gauge,
|
|
5
7
|
type Histogram,
|
|
6
8
|
Metrics,
|
|
7
9
|
type TelemetryClient,
|
|
@@ -21,6 +23,12 @@ export class PublicProcessorMetrics {
|
|
|
21
23
|
private phaseCount: UpDownCounter;
|
|
22
24
|
|
|
23
25
|
private bytecodeDeployed: Histogram;
|
|
26
|
+
private totalGas: Gauge;
|
|
27
|
+
private totalGasHistogram: Histogram;
|
|
28
|
+
private gasRate: Histogram;
|
|
29
|
+
private txGas: Histogram;
|
|
30
|
+
|
|
31
|
+
private treeInsertionDuration: Histogram;
|
|
24
32
|
|
|
25
33
|
constructor(client: TelemetryClient, name = 'PublicProcessor') {
|
|
26
34
|
this.tracer = client.getTracer(name);
|
|
@@ -54,6 +62,32 @@ export class PublicProcessorMetrics {
|
|
|
54
62
|
description: 'Size of deployed bytecode',
|
|
55
63
|
unit: 'By',
|
|
56
64
|
});
|
|
65
|
+
|
|
66
|
+
this.totalGas = meter.createGauge(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS, {
|
|
67
|
+
description: 'Total gas used in block',
|
|
68
|
+
unit: 'gas',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.totalGasHistogram = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TOTAL_GAS_HISTOGRAM, {
|
|
72
|
+
description: 'Total gas used in block as histogram',
|
|
73
|
+
unit: 'gas/block',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.txGas = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TX_GAS, {
|
|
77
|
+
description: 'Gas used in transaction',
|
|
78
|
+
unit: 'gas/tx',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.gasRate = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_GAS_RATE, {
|
|
82
|
+
description: 'L2 gas per second for complete block',
|
|
83
|
+
unit: 'gas/s',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
this.treeInsertionDuration = meter.createHistogram(Metrics.PUBLIC_PROCESSOR_TREE_INSERTION, {
|
|
87
|
+
description: 'How long it takes for tree insertion',
|
|
88
|
+
unit: 'us',
|
|
89
|
+
valueType: ValueType.INT,
|
|
90
|
+
});
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
recordPhaseDuration(phaseName: TxExecutionPhase, durationMs: number) {
|
|
@@ -61,12 +95,36 @@ export class PublicProcessorMetrics {
|
|
|
61
95
|
this.phaseDuration.record(Math.ceil(durationMs), { [Attributes.TX_PHASE_NAME]: phaseName });
|
|
62
96
|
}
|
|
63
97
|
|
|
64
|
-
recordTx(phaseCount: number, durationMs: number) {
|
|
98
|
+
recordTx(phaseCount: number, durationMs: number, gasUsed: Gas) {
|
|
65
99
|
this.txPhaseCount.add(phaseCount);
|
|
66
100
|
this.txDuration.record(Math.ceil(durationMs));
|
|
67
101
|
this.txCount.add(1, {
|
|
68
102
|
[Attributes.OK]: true,
|
|
69
103
|
});
|
|
104
|
+
this.txGas.record(gasUsed.daGas, {
|
|
105
|
+
[Attributes.GAS_DIMENSION]: 'DA',
|
|
106
|
+
});
|
|
107
|
+
this.txGas.record(gasUsed.l2Gas, {
|
|
108
|
+
[Attributes.GAS_DIMENSION]: 'L2',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
recordAllTxs(totalGas: Gas, gasRate: number) {
|
|
113
|
+
this.totalGas.record(totalGas.daGas, {
|
|
114
|
+
[Attributes.GAS_DIMENSION]: 'DA',
|
|
115
|
+
});
|
|
116
|
+
this.totalGas.record(totalGas.l2Gas, {
|
|
117
|
+
[Attributes.GAS_DIMENSION]: 'L2',
|
|
118
|
+
});
|
|
119
|
+
this.gasRate.record(gasRate, {
|
|
120
|
+
[Attributes.GAS_DIMENSION]: 'L2',
|
|
121
|
+
});
|
|
122
|
+
this.totalGasHistogram.record(totalGas.daGas, {
|
|
123
|
+
[Attributes.GAS_DIMENSION]: 'DA',
|
|
124
|
+
});
|
|
125
|
+
this.totalGasHistogram.record(totalGas.l2Gas, {
|
|
126
|
+
[Attributes.GAS_DIMENSION]: 'L2',
|
|
127
|
+
});
|
|
70
128
|
}
|
|
71
129
|
|
|
72
130
|
recordFailedTx() {
|
|
@@ -89,4 +147,8 @@ export class PublicProcessorMetrics {
|
|
|
89
147
|
this.bytecodeDeployed.record(totalBytecode);
|
|
90
148
|
}
|
|
91
149
|
}
|
|
150
|
+
|
|
151
|
+
recordTreeInsertions(durationUs: number) {
|
|
152
|
+
this.treeInsertionDuration.record(Math.ceil(durationUs));
|
|
153
|
+
}
|
|
92
154
|
}
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
TxHash,
|
|
11
11
|
} from '@aztec/circuit-types';
|
|
12
12
|
import {
|
|
13
|
-
AppendOnlyTreeSnapshot,
|
|
14
13
|
AvmCircuitInputs,
|
|
15
14
|
type AvmCircuitPublicInputs,
|
|
16
15
|
type AztecAddress,
|
|
@@ -19,12 +18,15 @@ import {
|
|
|
19
18
|
type GasSettings,
|
|
20
19
|
type GlobalVariables,
|
|
21
20
|
MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
|
|
21
|
+
MAX_NOTE_HASHES_PER_TX,
|
|
22
|
+
MAX_NULLIFIERS_PER_TX,
|
|
22
23
|
type PrivateToPublicAccumulatedData,
|
|
23
24
|
type PublicCallRequest,
|
|
24
25
|
PublicCircuitPublicInputs,
|
|
25
26
|
RevertCode,
|
|
26
27
|
type StateReference,
|
|
27
28
|
TreeSnapshots,
|
|
29
|
+
computeTransactionFee,
|
|
28
30
|
countAccumulatedItems,
|
|
29
31
|
} from '@aztec/circuits.js';
|
|
30
32
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
@@ -91,7 +93,7 @@ export class PublicTxContext {
|
|
|
91
93
|
const previousAccumulatedDataArrayLengths = new SideEffectArrayLengths(
|
|
92
94
|
/*publicDataWrites*/ 0,
|
|
93
95
|
/*protocolPublicDataWrites*/ 0,
|
|
94
|
-
|
|
96
|
+
/*noteHashes*/ 0,
|
|
95
97
|
/*nullifiers=*/ 0,
|
|
96
98
|
countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.l2ToL1Msgs),
|
|
97
99
|
/*unencryptedLogsHashes*/ 0,
|
|
@@ -102,7 +104,12 @@ export class PublicTxContext {
|
|
|
102
104
|
);
|
|
103
105
|
|
|
104
106
|
// Transaction level state manager that will be forked for revertible phases.
|
|
105
|
-
const txStateManager = await AvmPersistableStateManager.create(
|
|
107
|
+
const txStateManager = await AvmPersistableStateManager.create(
|
|
108
|
+
worldStateDB,
|
|
109
|
+
enqueuedCallTrace,
|
|
110
|
+
doMerkleOperations,
|
|
111
|
+
fetchTxHash(nonRevertibleAccumulatedDataFromPrivate),
|
|
112
|
+
);
|
|
106
113
|
|
|
107
114
|
const gasSettings = tx.data.constants.txContext.gasSettings;
|
|
108
115
|
const gasUsedByPrivate = tx.data.gasUsed;
|
|
@@ -186,12 +193,7 @@ export class PublicTxContext {
|
|
|
186
193
|
* @returns The transaction's hash.
|
|
187
194
|
*/
|
|
188
195
|
getTxHash(): TxHash {
|
|
189
|
-
|
|
190
|
-
const firstNullifier = this.nonRevertibleAccumulatedDataFromPrivate.nullifiers[0];
|
|
191
|
-
if (!firstNullifier || firstNullifier.isZero()) {
|
|
192
|
-
throw new Error(`Cannot get tx hash since first nullifier is missing`);
|
|
193
|
-
}
|
|
194
|
-
return new TxHash(firstNullifier.toBuffer());
|
|
196
|
+
return fetchTxHash(this.nonRevertibleAccumulatedDataFromPrivate);
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
/**
|
|
@@ -280,6 +282,15 @@ export class PublicTxContext {
|
|
|
280
282
|
return this.getTotalGasUsed().sub(teardownGasLimits).add(this.teardownGasUsed);
|
|
281
283
|
}
|
|
282
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Compute the public gas used using the actual gas used during teardown instead
|
|
287
|
+
* of the teardown gas limit.
|
|
288
|
+
*/
|
|
289
|
+
getActualPublicGasUsed(): Gas {
|
|
290
|
+
assert(this.halted, 'Can only compute actual gas used after tx execution ends');
|
|
291
|
+
return this.gasUsedByPublic.add(this.teardownGasUsed);
|
|
292
|
+
}
|
|
293
|
+
|
|
283
294
|
/**
|
|
284
295
|
* Get the transaction fee as is available to the specified phase.
|
|
285
296
|
* Only teardown should have access to the actual transaction fee.
|
|
@@ -297,12 +308,15 @@ export class PublicTxContext {
|
|
|
297
308
|
* Should only be called during or after teardown.
|
|
298
309
|
*/
|
|
299
310
|
private getTransactionFeeUnsafe(): Fr {
|
|
300
|
-
const
|
|
311
|
+
const gasUsed = this.getTotalGasUsed();
|
|
312
|
+
const txFee = computeTransactionFee(this.globalVariables.gasFees, this.gasSettings, gasUsed);
|
|
313
|
+
|
|
301
314
|
this.log.debug(`Computed tx fee`, {
|
|
302
315
|
txFee,
|
|
303
|
-
gasUsed: inspect(
|
|
316
|
+
gasUsed: inspect(gasUsed),
|
|
304
317
|
gasFees: inspect(this.globalVariables.gasFees),
|
|
305
318
|
});
|
|
319
|
+
|
|
306
320
|
return txFee;
|
|
307
321
|
}
|
|
308
322
|
|
|
@@ -311,16 +325,29 @@ export class PublicTxContext {
|
|
|
311
325
|
*/
|
|
312
326
|
private generateAvmCircuitPublicInputs(endStateReference: StateReference): AvmCircuitPublicInputs {
|
|
313
327
|
assert(this.halted, 'Can only get AvmCircuitPublicInputs after tx execution ends');
|
|
314
|
-
const ephemeralTrees = this.state.getActiveStateManager().merkleTrees
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
const ephemeralTrees = this.state.getActiveStateManager().merkleTrees;
|
|
329
|
+
|
|
330
|
+
const noteHashTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE);
|
|
331
|
+
const nullifierTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE);
|
|
332
|
+
const publicDataTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE);
|
|
333
|
+
// Pad the note hash and nullifier trees
|
|
334
|
+
const paddedNoteHashTreeSize =
|
|
335
|
+
this.startStateReference.partial.noteHashTree.nextAvailableLeafIndex + MAX_NOTE_HASHES_PER_TX;
|
|
336
|
+
if (noteHashTree.nextAvailableLeafIndex > paddedNoteHashTreeSize) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Inserted too many leaves in note hash tree: ${noteHashTree.nextAvailableLeafIndex} > ${paddedNoteHashTreeSize}`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
noteHashTree.nextAvailableLeafIndex = paddedNoteHashTreeSize;
|
|
342
|
+
|
|
343
|
+
const paddedNullifierTreeSize =
|
|
344
|
+
this.startStateReference.partial.nullifierTree.nextAvailableLeafIndex + MAX_NULLIFIERS_PER_TX;
|
|
345
|
+
if (nullifierTree.nextAvailableLeafIndex > paddedNullifierTreeSize) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`Inserted too many leaves in nullifier tree: ${nullifierTree.nextAvailableLeafIndex} > ${paddedNullifierTreeSize}`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
nullifierTree.nextAvailableLeafIndex = paddedNullifierTreeSize;
|
|
324
351
|
|
|
325
352
|
const endTreeSnapshots = new TreeSnapshots(
|
|
326
353
|
endStateReference.l1ToL2MessageTree,
|
|
@@ -425,3 +452,12 @@ function applyMaxToAvailableGas(availableGas: Gas) {
|
|
|
425
452
|
/*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION),
|
|
426
453
|
);
|
|
427
454
|
}
|
|
455
|
+
|
|
456
|
+
function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedData): TxHash {
|
|
457
|
+
// Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier
|
|
458
|
+
const firstNullifier = nonRevertibleAccumulatedData.nullifiers[0];
|
|
459
|
+
if (!firstNullifier || firstNullifier.isZero()) {
|
|
460
|
+
throw new Error(`Cannot get tx hash since first nullifier is missing`);
|
|
461
|
+
}
|
|
462
|
+
return new TxHash(firstNullifier.toBuffer());
|
|
463
|
+
}
|