@aztec/simulator 0.66.0 → 0.67.1-devnet
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/acvm.js +3 -3
- package/dest/acvm/oracle/oracle.d.ts +2 -2
- package/dest/acvm/oracle/oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/oracle.js +5 -5
- package/dest/acvm/oracle/typed_oracle.d.ts +3 -3
- package/dest/acvm/oracle/typed_oracle.d.ts.map +1 -1
- package/dest/acvm/oracle/typed_oracle.js +5 -5
- package/dest/acvm/serialize.js +2 -2
- package/dest/avm/avm_context.d.ts +2 -2
- package/dest/avm/avm_context.d.ts.map +1 -1
- package/dest/avm/avm_context.js +3 -4
- package/dest/avm/avm_execution_environment.d.ts +4 -6
- package/dest/avm/avm_execution_environment.d.ts.map +1 -1
- package/dest/avm/avm_execution_environment.js +8 -13
- package/dest/avm/avm_memory_types.d.ts +3 -3
- package/dest/avm/avm_memory_types.d.ts.map +1 -1
- package/dest/avm/avm_memory_types.js +29 -29
- package/dest/avm/avm_simulator.d.ts +3 -3
- package/dest/avm/avm_simulator.d.ts.map +1 -1
- package/dest/avm/avm_simulator.js +27 -15
- package/dest/avm/avm_tree.d.ts +2 -1
- package/dest/avm/avm_tree.d.ts.map +1 -1
- package/dest/avm/avm_tree.js +6 -2
- package/dest/avm/errors.d.ts +3 -3
- package/dest/avm/errors.d.ts.map +1 -1
- package/dest/avm/errors.js +8 -15
- 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 +7 -7
- package/dest/avm/journal/journal.d.ts +31 -8
- package/dest/avm/journal/journal.d.ts.map +1 -1
- package/dest/avm/journal/journal.js +139 -42
- 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/external_calls.d.ts.map +1 -1
- package/dest/avm/opcodes/external_calls.js +4 -11
- package/dest/avm/opcodes/misc.d.ts.map +1 -1
- package/dest/avm/opcodes/misc.js +3 -3
- 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 +3 -3
- package/dest/client/client_execution_context.d.ts.map +1 -1
- package/dest/client/client_execution_context.js +15 -8
- package/dest/client/db_oracle.d.ts +9 -5
- package/dest/client/db_oracle.d.ts.map +1 -1
- package/dest/client/execution_note_cache.d.ts +9 -1
- package/dest/client/execution_note_cache.d.ts.map +1 -1
- package/dest/client/execution_note_cache.js +10 -3
- 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/private_execution.d.ts.map +1 -1
- package/dest/client/private_execution.js +4 -4
- package/dest/client/simulator.d.ts.map +1 -1
- package/dest/client/simulator.js +4 -4
- package/dest/client/unconstrained_execution.d.ts.map +1 -1
- package/dest/client/unconstrained_execution.js +3 -3
- package/dest/client/view_data_oracle.d.ts +4 -4
- package/dest/client/view_data_oracle.d.ts.map +1 -1
- package/dest/client/view_data_oracle.js +9 -9
- package/dest/common/debug_fn_name.d.ts +2 -2
- package/dest/common/debug_fn_name.d.ts.map +1 -1
- package/dest/common/debug_fn_name.js +8 -14
- package/dest/providers/acvm_native.js +4 -4
- package/dest/providers/acvm_wasm.js +2 -2
- package/dest/providers/factory.d.ts +2 -2
- package/dest/providers/factory.d.ts.map +1 -1
- package/dest/providers/factory.js +4 -4
- package/dest/providers/index.d.ts +0 -1
- package/dest/providers/index.d.ts.map +1 -1
- package/dest/providers/index.js +1 -2
- package/dest/public/enqueued_call_side_effect_trace.d.ts +15 -26
- package/dest/public/enqueued_call_side_effect_trace.d.ts.map +1 -1
- package/dest/public/enqueued_call_side_effect_trace.js +42 -62
- 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.map +1 -1
- package/dest/public/executor_metrics.js +2 -5
- 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 +24 -1
- package/dest/public/fixtures/index.d.ts.map +1 -1
- package/dest/public/fixtures/index.js +17 -11
- package/dest/public/index.d.ts +0 -1
- package/dest/public/index.d.ts.map +1 -1
- package/dest/public/index.js +1 -2
- package/dest/public/public_db_sources.d.ts.map +1 -1
- package/dest/public/public_db_sources.js +10 -9
- package/dest/public/public_processor.d.ts +12 -12
- package/dest/public/public_processor.d.ts.map +1 -1
- package/dest/public/public_processor.js +80 -68
- package/dest/public/public_processor_metrics.d.ts +1 -1
- package/dest/public/public_processor_metrics.d.ts.map +1 -1
- package/dest/public/public_tx_context.d.ts +13 -10
- package/dest/public/public_tx_context.d.ts.map +1 -1
- package/dest/public/public_tx_context.js +72 -45
- package/dest/public/public_tx_simulator.d.ts +2 -2
- package/dest/public/public_tx_simulator.d.ts.map +1 -1
- package/dest/public/public_tx_simulator.js +54 -23
- package/dest/public/side_effect_trace_interface.d.ts +6 -18
- package/dest/public/side_effect_trace_interface.d.ts.map +1 -1
- package/dest/public/transitional_adapters.d.ts +2 -2
- package/dest/public/transitional_adapters.d.ts.map +1 -1
- package/dest/public/transitional_adapters.js +10 -39
- package/package.json +16 -9
- package/src/acvm/acvm.ts +2 -2
- package/src/acvm/oracle/oracle.ts +4 -4
- package/src/acvm/oracle/typed_oracle.ts +5 -5
- package/src/acvm/serialize.ts +1 -1
- package/src/avm/avm_context.ts +2 -3
- package/src/avm/avm_execution_environment.ts +6 -31
- package/src/avm/avm_memory_types.ts +31 -29
- package/src/avm/avm_simulator.ts +28 -22
- package/src/avm/avm_tree.ts +6 -1
- package/src/avm/errors.ts +12 -14
- package/src/avm/fixtures/index.ts +6 -5
- package/src/avm/journal/journal.ts +230 -71
- package/src/avm/opcodes/conversion.ts +21 -16
- package/src/avm/opcodes/external_calls.ts +3 -19
- package/src/avm/opcodes/misc.ts +2 -2
- package/src/avm/test_utils.ts +4 -0
- package/src/client/client_execution_context.ts +19 -9
- package/src/client/db_oracle.ts +10 -5
- package/src/client/execution_note_cache.ts +13 -3
- package/src/client/index.ts +1 -0
- package/src/client/private_execution.ts +3 -3
- package/src/client/simulator.ts +4 -4
- package/src/client/unconstrained_execution.ts +2 -2
- package/src/client/view_data_oracle.ts +11 -9
- package/src/common/debug_fn_name.ts +7 -13
- package/src/providers/acvm_native.ts +3 -3
- package/src/providers/acvm_wasm.ts +2 -2
- package/src/providers/factory.ts +3 -3
- package/src/providers/index.ts +0 -1
- package/src/public/enqueued_call_side_effect_trace.ts +62 -86
- package/src/public/execution.ts +1 -2
- package/src/public/executor_metrics.ts +0 -4
- package/src/public/fee_payment.ts +3 -2
- package/src/public/fixtures/index.ts +25 -12
- package/src/public/index.ts +0 -1
- package/src/public/public_db_sources.ts +9 -8
- package/src/public/public_processor.ts +109 -105
- package/src/public/public_processor_metrics.ts +1 -1
- package/src/public/public_tx_context.ts +97 -50
- package/src/public/public_tx_simulator.ts +69 -38
- package/src/public/side_effect_trace_interface.ts +10 -16
- package/src/public/transitional_adapters.ts +12 -48
- package/dest/public/dual_side_effect_trace.d.ts +0 -77
- package/dest/public/dual_side_effect_trace.d.ts.map +0 -1
- package/dest/public/dual_side_effect_trace.js +0 -119
- package/dest/public/side_effect_trace.d.ts +0 -96
- package/dest/public/side_effect_trace.d.ts.map +0 -1
- package/dest/public/side_effect_trace.js +0 -309
- package/src/public/dual_side_effect_trace.ts +0 -242
- package/src/public/side_effect_trace.ts +0 -536
|
@@ -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,
|
|
@@ -13,20 +12,21 @@ import {
|
|
|
13
12
|
} from '@aztec/circuit-types';
|
|
14
13
|
import {
|
|
15
14
|
type AztecAddress,
|
|
15
|
+
type BlockHeader,
|
|
16
16
|
type ContractDataSource,
|
|
17
17
|
Fr,
|
|
18
18
|
type GlobalVariables,
|
|
19
|
-
type Header,
|
|
20
19
|
MAX_NOTE_HASHES_PER_TX,
|
|
21
20
|
MAX_NULLIFIERS_PER_TX,
|
|
22
21
|
NULLIFIER_SUBTREE_HEIGHT,
|
|
23
22
|
PublicDataWrite,
|
|
24
23
|
} from '@aztec/circuits.js';
|
|
25
24
|
import { padArrayEnd } from '@aztec/foundation/collection';
|
|
26
|
-
import {
|
|
25
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
27
26
|
import { Timer } from '@aztec/foundation/timer';
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
27
|
+
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
28
|
+
import { ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
|
|
29
|
+
import { Attributes, type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
30
30
|
|
|
31
31
|
import { computeFeePayerBalanceLeafSlot, computeFeePayerBalanceStorageSlot } from './fee_payment.js';
|
|
32
32
|
import { WorldStateDB } from './public_db_sources.js';
|
|
@@ -47,13 +47,19 @@ export class PublicProcessorFactory {
|
|
|
47
47
|
*/
|
|
48
48
|
public create(
|
|
49
49
|
merkleTree: MerkleTreeWriteOperations,
|
|
50
|
-
maybeHistoricalHeader:
|
|
50
|
+
maybeHistoricalHeader: BlockHeader | undefined,
|
|
51
51
|
globalVariables: GlobalVariables,
|
|
52
52
|
): PublicProcessor {
|
|
53
53
|
const historicalHeader = maybeHistoricalHeader ?? merkleTree.getInitialHeader();
|
|
54
54
|
|
|
55
55
|
const worldStateDB = new WorldStateDB(merkleTree, this.contractDataSource);
|
|
56
|
-
const publicTxSimulator = new PublicTxSimulator(
|
|
56
|
+
const publicTxSimulator = new PublicTxSimulator(
|
|
57
|
+
merkleTree,
|
|
58
|
+
worldStateDB,
|
|
59
|
+
this.telemetryClient,
|
|
60
|
+
globalVariables,
|
|
61
|
+
/*doMerkleOperations=*/ true,
|
|
62
|
+
);
|
|
57
63
|
|
|
58
64
|
return new PublicProcessor(
|
|
59
65
|
merkleTree,
|
|
@@ -70,16 +76,16 @@ export class PublicProcessorFactory {
|
|
|
70
76
|
* Converts Txs lifted from the P2P module into ProcessedTx objects by executing
|
|
71
77
|
* any public function calls in them. Txs with private calls only are unaffected.
|
|
72
78
|
*/
|
|
73
|
-
export class PublicProcessor {
|
|
79
|
+
export class PublicProcessor implements Traceable {
|
|
74
80
|
private metrics: PublicProcessorMetrics;
|
|
75
81
|
constructor(
|
|
76
82
|
protected db: MerkleTreeWriteOperations,
|
|
77
83
|
protected globalVariables: GlobalVariables,
|
|
78
|
-
protected historicalHeader:
|
|
84
|
+
protected historicalHeader: BlockHeader,
|
|
79
85
|
protected worldStateDB: WorldStateDB,
|
|
80
86
|
protected publicTxSimulator: PublicTxSimulator,
|
|
81
87
|
telemetryClient: TelemetryClient,
|
|
82
|
-
private log =
|
|
88
|
+
private log = createLogger('simulator:public-processor'),
|
|
83
89
|
) {
|
|
84
90
|
this.metrics = new PublicProcessorMetrics(telemetryClient, 'PublicProcessor');
|
|
85
91
|
}
|
|
@@ -97,7 +103,6 @@ export class PublicProcessor {
|
|
|
97
103
|
public async process(
|
|
98
104
|
txs: Tx[],
|
|
99
105
|
maxTransactions = txs.length,
|
|
100
|
-
processedTxHandler?: ProcessedTxHandler,
|
|
101
106
|
txValidator?: TxValidator<ProcessedTx>,
|
|
102
107
|
): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> {
|
|
103
108
|
// The processor modifies the tx objects in place, so we need to clone them.
|
|
@@ -112,66 +117,9 @@ export class PublicProcessor {
|
|
|
112
117
|
break;
|
|
113
118
|
}
|
|
114
119
|
try {
|
|
115
|
-
const [processedTx, returnValues] =
|
|
116
|
-
? await this.processPrivateOnlyTx(tx)
|
|
117
|
-
: await this.processTxWithPublicCalls(tx);
|
|
118
|
-
this.log.debug(`Processed tx`, {
|
|
119
|
-
txHash: processedTx.hash,
|
|
120
|
-
historicalHeaderHash: processedTx.constants.historicalHeader.hash(),
|
|
121
|
-
blockNumber: processedTx.constants.globalVariables.blockNumber,
|
|
122
|
-
lastArchiveRoot: processedTx.constants.historicalHeader.lastArchive.root,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Commit the state updates from this transaction
|
|
126
|
-
await this.worldStateDB.commit();
|
|
127
|
-
|
|
128
|
-
// Re-validate the transaction
|
|
129
|
-
if (txValidator) {
|
|
130
|
-
// Only accept processed transactions that are not double-spends,
|
|
131
|
-
// public functions emitting nullifiers would pass earlier check but fail here.
|
|
132
|
-
// Note that we're checking all nullifiers generated in the private execution twice,
|
|
133
|
-
// we could store the ones already checked and skip them here as an optimization.
|
|
134
|
-
const [_, invalid] = await txValidator.validateTxs([processedTx]);
|
|
135
|
-
if (invalid.length) {
|
|
136
|
-
throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// if we were given a handler then send the transaction to it for block building or proving
|
|
140
|
-
if (processedTxHandler) {
|
|
141
|
-
await processedTxHandler.addNewTx(processedTx);
|
|
142
|
-
}
|
|
143
|
-
// Update the state so that the next tx in the loop has the correct .startState
|
|
144
|
-
// NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
|
|
145
|
-
// a) had only 1 tx with public calls per block, so this loop had len 1
|
|
146
|
-
// b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
|
|
147
|
-
// To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
|
|
148
|
-
// The below is taken from buildBaseRollupHints:
|
|
149
|
-
await this.db.appendLeaves(
|
|
150
|
-
MerkleTreeId.NOTE_HASH_TREE,
|
|
151
|
-
padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
152
|
-
);
|
|
153
|
-
try {
|
|
154
|
-
await this.db.batchInsert(
|
|
155
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
156
|
-
padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
|
|
157
|
-
NULLIFIER_SUBTREE_HEIGHT,
|
|
158
|
-
);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (txValidator) {
|
|
161
|
-
// Ideally the validator has already caught this above, but just in case:
|
|
162
|
-
throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
|
|
163
|
-
} else {
|
|
164
|
-
// We have no validator and assume this call should blindly process txs with duplicates being caught later
|
|
165
|
-
this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
await this.db.sequentialInsert(
|
|
170
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
171
|
-
processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
|
|
172
|
-
);
|
|
120
|
+
const [processedTx, returnValues] = await this.processTx(tx, txValidator);
|
|
173
121
|
result.push(processedTx);
|
|
174
|
-
returns = returns.concat(returnValues
|
|
122
|
+
returns = returns.concat(returnValues);
|
|
175
123
|
} catch (err: any) {
|
|
176
124
|
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
177
125
|
this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage} ${err?.stack}`);
|
|
@@ -187,17 +135,89 @@ export class PublicProcessor {
|
|
|
187
135
|
return [result, failed, returns];
|
|
188
136
|
}
|
|
189
137
|
|
|
138
|
+
@trackSpan('PublicProcessor.processTx', tx => ({ [Attributes.TX_HASH]: tx.tryGetTxHash()?.toString() }))
|
|
139
|
+
private async processTx(
|
|
140
|
+
tx: Tx,
|
|
141
|
+
txValidator?: TxValidator<ProcessedTx>,
|
|
142
|
+
): Promise<[ProcessedTx, NestedProcessReturnValues[]]> {
|
|
143
|
+
const [processedTx, returnValues] = !tx.hasPublicCalls()
|
|
144
|
+
? await this.processPrivateOnlyTx(tx)
|
|
145
|
+
: await this.processTxWithPublicCalls(tx);
|
|
146
|
+
|
|
147
|
+
this.log.verbose(
|
|
148
|
+
!tx.hasPublicCalls()
|
|
149
|
+
? `Processed tx ${processedTx.hash} with no public calls`
|
|
150
|
+
: `Processed tx ${processedTx.hash} with ${tx.enqueuedPublicFunctionCalls.length} public calls`,
|
|
151
|
+
{
|
|
152
|
+
txHash: processedTx.hash,
|
|
153
|
+
txFee: processedTx.txEffect.transactionFee.toBigInt(),
|
|
154
|
+
revertCode: processedTx.txEffect.revertCode.getCode(),
|
|
155
|
+
revertReason: processedTx.revertReason,
|
|
156
|
+
gasUsed: processedTx.gasUsed,
|
|
157
|
+
publicDataWriteCount: processedTx.txEffect.publicDataWrites.length,
|
|
158
|
+
nullifierCount: processedTx.txEffect.nullifiers.length,
|
|
159
|
+
noteHashCount: processedTx.txEffect.noteHashes.length,
|
|
160
|
+
contractClassLogCount: processedTx.txEffect.contractClassLogs.getTotalLogCount(),
|
|
161
|
+
unencryptedLogCount: processedTx.txEffect.unencryptedLogs.getTotalLogCount(),
|
|
162
|
+
privateLogCount: processedTx.txEffect.privateLogs.length,
|
|
163
|
+
l2ToL1MessageCount: processedTx.txEffect.l2ToL1Msgs.length,
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Commit the state updates from this transaction
|
|
168
|
+
await this.worldStateDB.commit();
|
|
169
|
+
|
|
170
|
+
// Re-validate the transaction
|
|
171
|
+
if (txValidator) {
|
|
172
|
+
// Only accept processed transactions that are not double-spends,
|
|
173
|
+
// public functions emitting nullifiers would pass earlier check but fail here.
|
|
174
|
+
// Note that we're checking all nullifiers generated in the private execution twice,
|
|
175
|
+
// we could store the ones already checked and skip them here as an optimization.
|
|
176
|
+
const [_, invalid] = await txValidator.validateTxs([processedTx]);
|
|
177
|
+
if (invalid.length) {
|
|
178
|
+
throw new Error(`Transaction ${invalid[0].hash} invalid after processing public functions`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Update the state so that the next tx in the loop has the correct .startState
|
|
182
|
+
// NB: before this change, all .startStates were actually incorrect, but the issue was never caught because we either:
|
|
183
|
+
// a) had only 1 tx with public calls per block, so this loop had len 1
|
|
184
|
+
// b) always had a txHandler with the same db passed to it as this.db, which updated the db in buildBaseRollupHints in this loop
|
|
185
|
+
// To see how this ^ happens, move back to one shared db in test_context and run orchestrator_multi_public_functions.test.ts
|
|
186
|
+
// The below is taken from buildBaseRollupHints:
|
|
187
|
+
await this.db.appendLeaves(
|
|
188
|
+
MerkleTreeId.NOTE_HASH_TREE,
|
|
189
|
+
padArrayEnd(processedTx.txEffect.noteHashes, Fr.ZERO, MAX_NOTE_HASHES_PER_TX),
|
|
190
|
+
);
|
|
191
|
+
try {
|
|
192
|
+
await this.db.batchInsert(
|
|
193
|
+
MerkleTreeId.NULLIFIER_TREE,
|
|
194
|
+
padArrayEnd(processedTx.txEffect.nullifiers, Fr.ZERO, MAX_NULLIFIERS_PER_TX).map(n => n.toBuffer()),
|
|
195
|
+
NULLIFIER_SUBTREE_HEIGHT,
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (txValidator) {
|
|
199
|
+
// Ideally the validator has already caught this above, but just in case:
|
|
200
|
+
throw new Error(`Transaction ${processedTx.hash} invalid after processing public functions`);
|
|
201
|
+
} else {
|
|
202
|
+
// We have no validator and assume this call should blindly process txs with duplicates being caught later
|
|
203
|
+
this.log.warn(`Detected duplicate nullifier after public processing for: ${processedTx.hash}.`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await this.db.sequentialInsert(
|
|
208
|
+
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
209
|
+
processedTx.txEffect.publicDataWrites.map(x => x.toBuffer()),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
return [processedTx, returnValues ?? []];
|
|
213
|
+
}
|
|
214
|
+
|
|
190
215
|
/**
|
|
191
|
-
* Creates the
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* See build_or_patch_payment_update_request in base_rollup_inputs.nr for more details.
|
|
216
|
+
* Creates the public data write for paying the tx fee.
|
|
217
|
+
* This is used in private only txs, since for txs with public calls
|
|
218
|
+
* the avm handles the fee payment itself.
|
|
195
219
|
*/
|
|
196
|
-
private async getFeePaymentPublicDataWrite(
|
|
197
|
-
publicDataWrites: PublicDataWrite[],
|
|
198
|
-
txFee: Fr,
|
|
199
|
-
feePayer: AztecAddress,
|
|
200
|
-
): Promise<PublicDataWrite | undefined> {
|
|
220
|
+
private async getFeePaymentPublicDataWrite(txFee: Fr, feePayer: AztecAddress): Promise<PublicDataWrite | undefined> {
|
|
201
221
|
if (feePayer.isZero()) {
|
|
202
222
|
this.log.debug(`No one is paying the fee of ${txFee.toBigInt()}`);
|
|
203
223
|
return;
|
|
@@ -209,11 +229,7 @@ export class PublicProcessor {
|
|
|
209
229
|
|
|
210
230
|
this.log.debug(`Deducting ${txFee.toBigInt()} balance in Fee Juice for ${feePayer}`);
|
|
211
231
|
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
const balance = existingBalanceWrite
|
|
215
|
-
? existingBalanceWrite.value
|
|
216
|
-
: await this.worldStateDB.storageRead(feeJuiceAddress, balanceSlot);
|
|
232
|
+
const balance = await this.worldStateDB.storageRead(feeJuiceAddress, balanceSlot);
|
|
217
233
|
|
|
218
234
|
if (balance.lt(txFee)) {
|
|
219
235
|
throw new Error(
|
|
@@ -234,12 +250,7 @@ export class PublicProcessor {
|
|
|
234
250
|
const gasFees = this.globalVariables.gasFees;
|
|
235
251
|
const transactionFee = tx.data.gasUsed.computeFee(gasFees);
|
|
236
252
|
|
|
237
|
-
const
|
|
238
|
-
const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite(
|
|
239
|
-
accumulatedData.publicDataWrites,
|
|
240
|
-
transactionFee,
|
|
241
|
-
tx.data.feePayer,
|
|
242
|
-
);
|
|
253
|
+
const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite(transactionFee, tx.data.feePayer);
|
|
243
254
|
|
|
244
255
|
const processedTx = makeProcessedTxFromPrivateOnlyTx(
|
|
245
256
|
tx,
|
|
@@ -247,6 +258,13 @@ export class PublicProcessor {
|
|
|
247
258
|
feePaymentPublicDataWrite,
|
|
248
259
|
this.globalVariables,
|
|
249
260
|
);
|
|
261
|
+
|
|
262
|
+
this.metrics.recordClassRegistration(
|
|
263
|
+
...tx.contractClassLogs
|
|
264
|
+
.unrollLogs()
|
|
265
|
+
.filter(log => ContractClassRegisteredEvent.isContractClassRegisteredEvent(log.data))
|
|
266
|
+
.map(log => ContractClassRegisteredEvent.fromLog(log.data)),
|
|
267
|
+
);
|
|
250
268
|
return [processedTx];
|
|
251
269
|
}
|
|
252
270
|
|
|
@@ -283,21 +301,7 @@ export class PublicProcessor {
|
|
|
283
301
|
const durationMs = timer.ms();
|
|
284
302
|
this.metrics.recordTx(phaseCount, durationMs);
|
|
285
303
|
|
|
286
|
-
const
|
|
287
|
-
const feePaymentPublicDataWrite = await this.getFeePaymentPublicDataWrite(
|
|
288
|
-
data.accumulatedData.publicDataWrites,
|
|
289
|
-
data.transactionFee,
|
|
290
|
-
tx.data.feePayer,
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const processedTx = makeProcessedTxFromTxWithPublicCalls(
|
|
294
|
-
tx,
|
|
295
|
-
avmProvingRequest,
|
|
296
|
-
feePaymentPublicDataWrite,
|
|
297
|
-
gasUsed,
|
|
298
|
-
revertCode,
|
|
299
|
-
revertReason,
|
|
300
|
-
);
|
|
304
|
+
const processedTx = makeProcessedTxFromTxWithPublicCalls(tx, avmProvingRequest, gasUsed, revertCode, revertReason);
|
|
301
305
|
|
|
302
306
|
const returnValues = processedPhases.find(({ phase }) => phase === TxExecutionPhase.APP_LOGIC)?.returnValues ?? [];
|
|
303
307
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TxExecutionPhase } from '@aztec/circuit-types';
|
|
2
|
-
import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts';
|
|
2
|
+
import { type ContractClassRegisteredEvent } from '@aztec/protocol-contracts/class-registerer';
|
|
3
3
|
import {
|
|
4
4
|
Attributes,
|
|
5
5
|
type Histogram,
|
|
@@ -10,31 +10,33 @@ import {
|
|
|
10
10
|
TxHash,
|
|
11
11
|
} from '@aztec/circuit-types';
|
|
12
12
|
import {
|
|
13
|
-
AppendOnlyTreeSnapshot,
|
|
14
13
|
AvmCircuitInputs,
|
|
15
14
|
type AvmCircuitPublicInputs,
|
|
15
|
+
type AztecAddress,
|
|
16
16
|
Fr,
|
|
17
17
|
Gas,
|
|
18
18
|
type GasSettings,
|
|
19
19
|
type GlobalVariables,
|
|
20
|
+
MAX_L2_GAS_PER_TX_PUBLIC_PORTION,
|
|
21
|
+
MAX_NOTE_HASHES_PER_TX,
|
|
22
|
+
MAX_NULLIFIERS_PER_TX,
|
|
20
23
|
type PrivateToPublicAccumulatedData,
|
|
21
24
|
type PublicCallRequest,
|
|
22
25
|
PublicCircuitPublicInputs,
|
|
23
26
|
RevertCode,
|
|
24
27
|
type StateReference,
|
|
25
28
|
TreeSnapshots,
|
|
29
|
+
computeTransactionFee,
|
|
26
30
|
countAccumulatedItems,
|
|
27
31
|
} from '@aztec/circuits.js';
|
|
28
|
-
import { type
|
|
32
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
29
33
|
|
|
30
34
|
import { strict as assert } from 'assert';
|
|
31
35
|
import { inspect } from 'util';
|
|
32
36
|
|
|
33
37
|
import { AvmPersistableStateManager } from '../avm/index.js';
|
|
34
|
-
import { DualSideEffectTrace } from './dual_side_effect_trace.js';
|
|
35
38
|
import { PublicEnqueuedCallSideEffectTrace, SideEffectArrayLengths } from './enqueued_call_side_effect_trace.js';
|
|
36
39
|
import { type WorldStateDB } from './public_db_sources.js';
|
|
37
|
-
import { PublicSideEffectTrace } from './side_effect_trace.js';
|
|
38
40
|
import { generateAvmCircuitPublicInputs } from './transitional_adapters.js';
|
|
39
41
|
import { getCallRequestsByPhase, getExecutionRequestsByPhase } from './utils.js';
|
|
40
42
|
|
|
@@ -42,10 +44,10 @@ import { getCallRequestsByPhase, getExecutionRequestsByPhase } from './utils.js'
|
|
|
42
44
|
* The transaction-level context for public execution.
|
|
43
45
|
*/
|
|
44
46
|
export class PublicTxContext {
|
|
45
|
-
private log:
|
|
47
|
+
private log: Logger;
|
|
46
48
|
|
|
47
49
|
/* Gas used including private, teardown gas _limit_, setup and app logic */
|
|
48
|
-
private
|
|
50
|
+
private gasUsedByPublic: Gas = Gas.empty();
|
|
49
51
|
/* Gas actually used during teardown (different from limit) */
|
|
50
52
|
public teardownGasUsed: Gas = Gas.empty();
|
|
51
53
|
|
|
@@ -62,8 +64,9 @@ export class PublicTxContext {
|
|
|
62
64
|
public readonly state: PhaseStateManager,
|
|
63
65
|
private readonly globalVariables: GlobalVariables,
|
|
64
66
|
private readonly startStateReference: StateReference,
|
|
65
|
-
private readonly startGasUsed: Gas,
|
|
66
67
|
private readonly gasSettings: GasSettings,
|
|
68
|
+
private readonly gasUsedByPrivate: Gas,
|
|
69
|
+
private readonly gasAllocatedToPublic: Gas,
|
|
67
70
|
private readonly setupCallRequests: PublicCallRequest[],
|
|
68
71
|
private readonly appLogicCallRequests: PublicCallRequest[],
|
|
69
72
|
private readonly teardownCallRequests: PublicCallRequest[],
|
|
@@ -72,10 +75,10 @@ export class PublicTxContext {
|
|
|
72
75
|
private readonly teardownExecutionRequests: PublicExecutionRequest[],
|
|
73
76
|
public readonly nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData,
|
|
74
77
|
public readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData,
|
|
78
|
+
public readonly feePayer: AztecAddress,
|
|
75
79
|
public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private
|
|
76
80
|
) {
|
|
77
|
-
this.log =
|
|
78
|
-
this.gasUsed = startGasUsed;
|
|
81
|
+
this.log = createLogger(`simulator:public_tx_context`);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
public static async create(
|
|
@@ -87,10 +90,10 @@ export class PublicTxContext {
|
|
|
87
90
|
) {
|
|
88
91
|
const nonRevertibleAccumulatedDataFromPrivate = tx.data.forPublic!.nonRevertibleAccumulatedData;
|
|
89
92
|
|
|
90
|
-
const innerCallTrace = new PublicSideEffectTrace();
|
|
91
93
|
const previousAccumulatedDataArrayLengths = new SideEffectArrayLengths(
|
|
92
94
|
/*publicDataWrites*/ 0,
|
|
93
|
-
|
|
95
|
+
/*protocolPublicDataWrites*/ 0,
|
|
96
|
+
/*noteHashes*/ 0,
|
|
94
97
|
/*nullifiers=*/ 0,
|
|
95
98
|
countAccumulatedItems(nonRevertibleAccumulatedDataFromPrivate.l2ToL1Msgs),
|
|
96
99
|
/*unencryptedLogsHashes*/ 0,
|
|
@@ -99,17 +102,27 @@ export class PublicTxContext {
|
|
|
99
102
|
/*startSideEffectCounter=*/ 0,
|
|
100
103
|
previousAccumulatedDataArrayLengths,
|
|
101
104
|
);
|
|
102
|
-
const trace = new DualSideEffectTrace(innerCallTrace, enqueuedCallTrace);
|
|
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
|
+
);
|
|
113
|
+
|
|
114
|
+
const gasSettings = tx.data.constants.txContext.gasSettings;
|
|
115
|
+
const gasUsedByPrivate = tx.data.gasUsed;
|
|
116
|
+
// Gas allocated to public is "whatever's left" after private, but with some max applied.
|
|
117
|
+
const gasAllocatedToPublic = applyMaxToAvailableGas(gasSettings.gasLimits.sub(gasUsedByPrivate));
|
|
106
118
|
|
|
107
119
|
return new PublicTxContext(
|
|
108
120
|
new PhaseStateManager(txStateManager),
|
|
109
121
|
globalVariables,
|
|
110
122
|
await db.getStateReference(),
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
gasSettings,
|
|
124
|
+
gasUsedByPrivate,
|
|
125
|
+
gasAllocatedToPublic,
|
|
113
126
|
getCallRequestsByPhase(tx, TxExecutionPhase.SETUP),
|
|
114
127
|
getCallRequestsByPhase(tx, TxExecutionPhase.APP_LOGIC),
|
|
115
128
|
getCallRequestsByPhase(tx, TxExecutionPhase.TEARDOWN),
|
|
@@ -118,6 +131,7 @@ export class PublicTxContext {
|
|
|
118
131
|
getExecutionRequestsByPhase(tx, TxExecutionPhase.TEARDOWN),
|
|
119
132
|
tx.data.forPublic!.nonRevertibleAccumulatedData,
|
|
120
133
|
tx.data.forPublic!.revertibleAccumulatedData,
|
|
134
|
+
tx.data.feePayer,
|
|
121
135
|
enqueuedCallTrace,
|
|
122
136
|
);
|
|
123
137
|
}
|
|
@@ -179,12 +193,7 @@ export class PublicTxContext {
|
|
|
179
193
|
* @returns The transaction's hash.
|
|
180
194
|
*/
|
|
181
195
|
getTxHash(): TxHash {
|
|
182
|
-
|
|
183
|
-
const firstNullifier = this.nonRevertibleAccumulatedDataFromPrivate.nullifiers[0];
|
|
184
|
-
if (!firstNullifier || firstNullifier.isZero()) {
|
|
185
|
-
throw new Error(`Cannot get tx hash since first nullifier is missing`);
|
|
186
|
-
}
|
|
187
|
-
return new TxHash(firstNullifier.toBuffer());
|
|
196
|
+
return fetchTxHash(this.nonRevertibleAccumulatedDataFromPrivate);
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
/**
|
|
@@ -230,13 +239,14 @@ export class PublicTxContext {
|
|
|
230
239
|
}
|
|
231
240
|
|
|
232
241
|
/**
|
|
233
|
-
* How much gas is left
|
|
242
|
+
* How much gas is left as of the specified phase?
|
|
234
243
|
*/
|
|
235
|
-
|
|
244
|
+
getGasLeftAtPhase(phase: TxExecutionPhase): Gas {
|
|
236
245
|
if (phase === TxExecutionPhase.TEARDOWN) {
|
|
237
|
-
return this.gasSettings.teardownGasLimits;
|
|
246
|
+
return applyMaxToAvailableGas(this.gasSettings.teardownGasLimits);
|
|
238
247
|
} else {
|
|
239
|
-
|
|
248
|
+
const gasLeftForPublic = this.gasAllocatedToPublic.sub(this.gasUsedByPublic);
|
|
249
|
+
return gasLeftForPublic;
|
|
240
250
|
}
|
|
241
251
|
}
|
|
242
252
|
|
|
@@ -247,10 +257,18 @@ export class PublicTxContext {
|
|
|
247
257
|
if (phase === TxExecutionPhase.TEARDOWN) {
|
|
248
258
|
this.teardownGasUsed = this.teardownGasUsed.add(gas);
|
|
249
259
|
} else {
|
|
250
|
-
this.
|
|
260
|
+
this.gasUsedByPublic = this.gasUsedByPublic.add(gas);
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
/**
|
|
265
|
+
* The gasUsed by public and private,
|
|
266
|
+
* as if the entire teardown gas limit was consumed.
|
|
267
|
+
*/
|
|
268
|
+
getTotalGasUsed(): Gas {
|
|
269
|
+
return this.gasUsedByPrivate.add(this.gasUsedByPublic);
|
|
270
|
+
}
|
|
271
|
+
|
|
254
272
|
/**
|
|
255
273
|
* Compute the gas used using the actual gas used during teardown instead
|
|
256
274
|
* of the teardown gas limit.
|
|
@@ -261,14 +279,7 @@ export class PublicTxContext {
|
|
|
261
279
|
assert(this.halted, 'Can only compute actual gas used after tx execution ends');
|
|
262
280
|
const requireTeardown = this.teardownCallRequests.length > 0;
|
|
263
281
|
const teardownGasLimits = requireTeardown ? this.gasSettings.teardownGasLimits : Gas.empty();
|
|
264
|
-
return this.
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* The gasUsed as if the entire teardown gas limit was consumed.
|
|
269
|
-
*/
|
|
270
|
-
getGasUsedForFee(): Gas {
|
|
271
|
-
return this.gasUsed;
|
|
282
|
+
return this.getTotalGasUsed().sub(teardownGasLimits).add(this.teardownGasUsed);
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
/**
|
|
@@ -288,12 +299,15 @@ export class PublicTxContext {
|
|
|
288
299
|
* Should only be called during or after teardown.
|
|
289
300
|
*/
|
|
290
301
|
private getTransactionFeeUnsafe(): Fr {
|
|
291
|
-
const
|
|
302
|
+
const gasUsed = this.getTotalGasUsed();
|
|
303
|
+
const txFee = computeTransactionFee(this.globalVariables.gasFees, this.gasSettings, gasUsed);
|
|
304
|
+
|
|
292
305
|
this.log.debug(`Computed tx fee`, {
|
|
293
306
|
txFee,
|
|
294
|
-
gasUsed: inspect(
|
|
307
|
+
gasUsed: inspect(gasUsed),
|
|
295
308
|
gasFees: inspect(this.globalVariables.gasFees),
|
|
296
309
|
});
|
|
310
|
+
|
|
297
311
|
return txFee;
|
|
298
312
|
}
|
|
299
313
|
|
|
@@ -302,16 +316,29 @@ export class PublicTxContext {
|
|
|
302
316
|
*/
|
|
303
317
|
private generateAvmCircuitPublicInputs(endStateReference: StateReference): AvmCircuitPublicInputs {
|
|
304
318
|
assert(this.halted, 'Can only get AvmCircuitPublicInputs after tx execution ends');
|
|
305
|
-
const ephemeralTrees = this.state.getActiveStateManager().merkleTrees
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
319
|
+
const ephemeralTrees = this.state.getActiveStateManager().merkleTrees;
|
|
320
|
+
|
|
321
|
+
const noteHashTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NOTE_HASH_TREE);
|
|
322
|
+
const nullifierTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.NULLIFIER_TREE);
|
|
323
|
+
const publicDataTree = ephemeralTrees.getTreeSnapshot(MerkleTreeId.PUBLIC_DATA_TREE);
|
|
324
|
+
// Pad the note hash and nullifier trees
|
|
325
|
+
const paddedNoteHashTreeSize =
|
|
326
|
+
this.startStateReference.partial.noteHashTree.nextAvailableLeafIndex + MAX_NOTE_HASHES_PER_TX;
|
|
327
|
+
if (noteHashTree.nextAvailableLeafIndex > paddedNoteHashTreeSize) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`Inserted too many leaves in note hash tree: ${noteHashTree.nextAvailableLeafIndex} > ${paddedNoteHashTreeSize}`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
noteHashTree.nextAvailableLeafIndex = paddedNoteHashTreeSize;
|
|
333
|
+
|
|
334
|
+
const paddedNullifierTreeSize =
|
|
335
|
+
this.startStateReference.partial.nullifierTree.nextAvailableLeafIndex + MAX_NULLIFIERS_PER_TX;
|
|
336
|
+
if (nullifierTree.nextAvailableLeafIndex > paddedNullifierTreeSize) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Inserted too many leaves in nullifier tree: ${nullifierTree.nextAvailableLeafIndex} > ${paddedNullifierTreeSize}`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
nullifierTree.nextAvailableLeafIndex = paddedNullifierTreeSize;
|
|
315
342
|
|
|
316
343
|
const endTreeSnapshots = new TreeSnapshots(
|
|
317
344
|
endStateReference.l1ToL2MessageTree,
|
|
@@ -324,15 +351,16 @@ export class PublicTxContext {
|
|
|
324
351
|
this.trace,
|
|
325
352
|
this.globalVariables,
|
|
326
353
|
this.startStateReference,
|
|
327
|
-
this.
|
|
354
|
+
/*startGasUsed=*/ this.gasUsedByPrivate,
|
|
328
355
|
this.gasSettings,
|
|
356
|
+
this.feePayer,
|
|
329
357
|
this.setupCallRequests,
|
|
330
358
|
this.appLogicCallRequests,
|
|
331
359
|
this.teardownCallRequests,
|
|
332
360
|
this.nonRevertibleAccumulatedDataFromPrivate,
|
|
333
361
|
this.revertibleAccumulatedDataFromPrivate,
|
|
334
362
|
endTreeSnapshots,
|
|
335
|
-
/*endGasUsed=*/ this.
|
|
363
|
+
/*endGasUsed=*/ this.getTotalGasUsed(),
|
|
336
364
|
this.getTransactionFeeUnsafe(),
|
|
337
365
|
this.revertCode,
|
|
338
366
|
);
|
|
@@ -367,12 +395,12 @@ export class PublicTxContext {
|
|
|
367
395
|
* transaction level one.
|
|
368
396
|
*/
|
|
369
397
|
class PhaseStateManager {
|
|
370
|
-
private log:
|
|
398
|
+
private log: Logger;
|
|
371
399
|
|
|
372
400
|
private currentlyActiveStateManager: AvmPersistableStateManager | undefined;
|
|
373
401
|
|
|
374
402
|
constructor(private readonly txStateManager: AvmPersistableStateManager) {
|
|
375
|
-
this.log =
|
|
403
|
+
this.log = createLogger(`simulator:public_phase_state_manager`);
|
|
376
404
|
}
|
|
377
405
|
|
|
378
406
|
fork() {
|
|
@@ -405,3 +433,22 @@ class PhaseStateManager {
|
|
|
405
433
|
this.currentlyActiveStateManager = undefined;
|
|
406
434
|
}
|
|
407
435
|
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Apply L2 gas maximum.
|
|
439
|
+
*/
|
|
440
|
+
function applyMaxToAvailableGas(availableGas: Gas) {
|
|
441
|
+
return new Gas(
|
|
442
|
+
/*daGas=*/ availableGas.daGas,
|
|
443
|
+
/*l2Gas=*/ Math.min(availableGas.l2Gas, MAX_L2_GAS_PER_TX_PUBLIC_PORTION),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function fetchTxHash(nonRevertibleAccumulatedData: PrivateToPublicAccumulatedData): TxHash {
|
|
448
|
+
// Private kernel functions are executed client side and for this reason tx hash is already set as first nullifier
|
|
449
|
+
const firstNullifier = nonRevertibleAccumulatedData.nullifiers[0];
|
|
450
|
+
if (!firstNullifier || firstNullifier.isZero()) {
|
|
451
|
+
throw new Error(`Cannot get tx hash since first nullifier is missing`);
|
|
452
|
+
}
|
|
453
|
+
return new TxHash(firstNullifier.toBuffer());
|
|
454
|
+
}
|