@aztec/sequencer-client 0.69.0-devnet → 0.69.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/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +2 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +11 -1
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -2
- package/dest/publisher/config.d.ts +4 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +6 -1
- package/dest/publisher/l1-publisher.d.ts +13 -0
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +60 -50
- package/dest/sequencer/index.d.ts +1 -0
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +2 -1
- package/dest/sequencer/sequencer.d.ts +7 -16
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +53 -128
- package/dest/sequencer/utils.d.ts +2 -2
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -3
- package/dest/tx_validator/gas_validator.d.ts +2 -3
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +9 -22
- package/dest/tx_validator/nullifier_cache.d.ts +16 -0
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/tx_validator/nullifier_cache.js +24 -0
- package/dest/tx_validator/phases_validator.d.ts +2 -3
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +15 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +38 -24
- package/package.json +20 -19
- package/src/client/sequencer-client.ts +1 -2
- package/src/config.ts +10 -0
- package/src/index.ts +2 -1
- package/src/publisher/config.ts +10 -0
- package/src/publisher/l1-publisher.ts +70 -46
- package/src/sequencer/index.ts +1 -0
- package/src/sequencer/sequencer.ts +60 -177
- package/src/sequencer/utils.ts +2 -2
- package/src/tx_validator/gas_validator.ts +11 -24
- package/src/tx_validator/nullifier_cache.ts +29 -0
- package/src/tx_validator/phases_validator.ts +22 -33
- package/src/tx_validator/tx_validator_factory.ts +82 -40
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type EpochProofClaim,
|
|
4
4
|
type EpochProofQuote,
|
|
5
5
|
type L2Block,
|
|
6
|
-
|
|
6
|
+
SignatureDomainSeparator,
|
|
7
7
|
type TxHash,
|
|
8
8
|
getHashedSignaturePayload,
|
|
9
9
|
} from '@aztec/circuit-types';
|
|
@@ -27,7 +27,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
|
27
27
|
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
28
28
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
29
29
|
import { Timer } from '@aztec/foundation/timer';
|
|
30
|
-
import { EmpireBaseAbi,
|
|
30
|
+
import { EmpireBaseAbi, RollupAbi, SlasherAbi } from '@aztec/l1-artifacts';
|
|
31
31
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
32
32
|
|
|
33
33
|
import pick from 'lodash.pick';
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
type BaseError,
|
|
36
36
|
type Chain,
|
|
37
37
|
type Client,
|
|
38
|
-
ContractFunctionExecutionError,
|
|
38
|
+
type ContractFunctionExecutionError,
|
|
39
39
|
ContractFunctionRevertedError,
|
|
40
40
|
type GetContractReturnType,
|
|
41
41
|
type Hex,
|
|
@@ -95,6 +95,8 @@ export type MinimalTransactionReceipt = {
|
|
|
95
95
|
logs: any[];
|
|
96
96
|
/** Block number in which this tx was mined. */
|
|
97
97
|
blockNumber: bigint;
|
|
98
|
+
/** The block hash in which this tx was mined */
|
|
99
|
+
blockHash: `0x${string}`;
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
/** Arguments to the process method of the rollup contract */
|
|
@@ -175,6 +177,8 @@ export class L1Publisher {
|
|
|
175
177
|
protected account: PrivateKeyAccount;
|
|
176
178
|
protected ethereumSlotDuration: bigint;
|
|
177
179
|
|
|
180
|
+
private blobSinkUrl: string | undefined;
|
|
181
|
+
|
|
178
182
|
// @note - with blobs, the below estimate seems too large.
|
|
179
183
|
// Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob)
|
|
180
184
|
// Total used for emptier block from above test: 429k (of which 84k is 1x blob)
|
|
@@ -189,6 +193,7 @@ export class L1Publisher {
|
|
|
189
193
|
) {
|
|
190
194
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
191
195
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
196
|
+
this.blobSinkUrl = config.blobSinkUrl;
|
|
192
197
|
this.metrics = new L1PublisherMetrics(client, 'L1Publisher');
|
|
193
198
|
|
|
194
199
|
const { l1RpcUrl: rpcUrl, l1ChainId: chainId, publisherPrivateKey, l1Contracts } = config;
|
|
@@ -416,38 +421,6 @@ export class L1Publisher {
|
|
|
416
421
|
if (error instanceof ContractFunctionRevertedError) {
|
|
417
422
|
const err = error as ContractFunctionRevertedError;
|
|
418
423
|
this.log.debug(`Validation failed: ${err.message}`, err.data);
|
|
419
|
-
} else if (error instanceof ContractFunctionExecutionError) {
|
|
420
|
-
let err = error as ContractFunctionRevertedError;
|
|
421
|
-
if (!tryGetCustomErrorName(err)) {
|
|
422
|
-
// If we get here, it's because the custom error no longer exists in Rollup.sol,
|
|
423
|
-
// but in another lib. The below reconstructs the error message.
|
|
424
|
-
try {
|
|
425
|
-
await this.publicClient.estimateGas({
|
|
426
|
-
data: encodeFunctionData({
|
|
427
|
-
abi: this.rollupContract.abi,
|
|
428
|
-
functionName: 'validateHeader',
|
|
429
|
-
args,
|
|
430
|
-
}),
|
|
431
|
-
account: this.account,
|
|
432
|
-
to: this.rollupContract.address,
|
|
433
|
-
});
|
|
434
|
-
} catch (estGasErr: unknown) {
|
|
435
|
-
const possibleAbis = [ExtRollupLibAbi, LeonidasLibAbi];
|
|
436
|
-
possibleAbis.forEach(abi => {
|
|
437
|
-
const possibleErr = getContractError(estGasErr as BaseError, {
|
|
438
|
-
args: [],
|
|
439
|
-
abi: abi,
|
|
440
|
-
functionName: 'validateHeader',
|
|
441
|
-
address: this.rollupContract.address,
|
|
442
|
-
sender: this.account.address,
|
|
443
|
-
});
|
|
444
|
-
err = tryGetCustomErrorName(possibleErr) ? possibleErr : err;
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
throw err;
|
|
448
|
-
}
|
|
449
|
-
} else {
|
|
450
|
-
this.log.debug(`Unexpected error during validation: ${error}`);
|
|
451
424
|
}
|
|
452
425
|
throw error;
|
|
453
426
|
}
|
|
@@ -593,16 +566,19 @@ export class L1Publisher {
|
|
|
593
566
|
|
|
594
567
|
const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
|
|
595
568
|
|
|
596
|
-
const digest = getHashedSignaturePayload(consensusPayload,
|
|
569
|
+
const digest = getHashedSignaturePayload(consensusPayload, SignatureDomainSeparator.blockAttestation);
|
|
570
|
+
|
|
571
|
+
const blobs = Blob.getBlobs(block.body.toBlobFields());
|
|
597
572
|
const proposeTxArgs = {
|
|
598
573
|
header: block.header.toBuffer(),
|
|
599
574
|
archive: block.archive.root.toBuffer(),
|
|
600
575
|
blockHash: block.header.hash().toBuffer(),
|
|
601
576
|
body: block.body.toBuffer(),
|
|
602
|
-
blobs
|
|
577
|
+
blobs,
|
|
603
578
|
attestations,
|
|
604
579
|
txHashes: txHashes ?? [],
|
|
605
580
|
};
|
|
581
|
+
|
|
606
582
|
// Publish body and propose block (if not already published)
|
|
607
583
|
if (this.interrupted) {
|
|
608
584
|
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
@@ -647,6 +623,12 @@ export class L1Publisher {
|
|
|
647
623
|
};
|
|
648
624
|
this.log.verbose(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
649
625
|
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
626
|
+
|
|
627
|
+
// Send the blobs to the blob sink
|
|
628
|
+
this.sendBlobsToBlobSink(receipt.blockHash, blobs).catch(_err => {
|
|
629
|
+
this.log.error('Failed to send blobs to blob sink');
|
|
630
|
+
});
|
|
631
|
+
|
|
650
632
|
return true;
|
|
651
633
|
}
|
|
652
634
|
|
|
@@ -661,7 +643,7 @@ export class L1Publisher {
|
|
|
661
643
|
address: this.rollupContract.address,
|
|
662
644
|
},
|
|
663
645
|
{
|
|
664
|
-
blobs: proposeTxArgs.blobs.map(b => b.
|
|
646
|
+
blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros),
|
|
665
647
|
kzg,
|
|
666
648
|
maxFeePerBlobGas: 10000000000n,
|
|
667
649
|
},
|
|
@@ -757,8 +739,7 @@ export class L1Publisher {
|
|
|
757
739
|
},
|
|
758
740
|
],
|
|
759
741
|
});
|
|
760
|
-
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors
|
|
761
|
-
// and viem provides no way to get the revert reason from a given tx.
|
|
742
|
+
// If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
|
|
762
743
|
// Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
|
|
763
744
|
// See: https://github.com/wevm/viem/issues/2075
|
|
764
745
|
// This throws a EstimateGasExecutionError with the custom error information:
|
|
@@ -770,13 +751,13 @@ export class L1Publisher {
|
|
|
770
751
|
});
|
|
771
752
|
return undefined;
|
|
772
753
|
} catch (simulationErr: any) {
|
|
773
|
-
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use
|
|
754
|
+
// If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
|
|
774
755
|
const contractErr =
|
|
775
756
|
simulationErr.name === 'ContractFunctionExecutionError'
|
|
776
757
|
? simulationErr
|
|
777
758
|
: getContractError(simulationErr as BaseError, {
|
|
778
759
|
args: [],
|
|
779
|
-
abi:
|
|
760
|
+
abi: RollupAbi,
|
|
780
761
|
functionName: args.functionName,
|
|
781
762
|
address: args.address,
|
|
782
763
|
sender: this.account.address,
|
|
@@ -966,7 +947,7 @@ export class L1Publisher {
|
|
|
966
947
|
},
|
|
967
948
|
{},
|
|
968
949
|
{
|
|
969
|
-
blobs: encodedData.blobs.map(b => b.
|
|
950
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
970
951
|
kzg,
|
|
971
952
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
972
953
|
},
|
|
@@ -1056,7 +1037,7 @@ export class L1Publisher {
|
|
|
1056
1037
|
fixedGas: gas,
|
|
1057
1038
|
},
|
|
1058
1039
|
{
|
|
1059
|
-
blobs: encodedData.blobs.map(b => b.
|
|
1040
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1060
1041
|
kzg,
|
|
1061
1042
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1062
1043
|
},
|
|
@@ -1095,7 +1076,7 @@ export class L1Publisher {
|
|
|
1095
1076
|
},
|
|
1096
1077
|
{ fixedGas: gas },
|
|
1097
1078
|
{
|
|
1098
|
-
blobs: encodedData.blobs.map(b => b.
|
|
1079
|
+
blobs: encodedData.blobs.map(b => b.dataWithZeros),
|
|
1099
1080
|
kzg,
|
|
1100
1081
|
maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS
|
|
1101
1082
|
},
|
|
@@ -1137,6 +1118,7 @@ export class L1Publisher {
|
|
|
1137
1118
|
gasPrice: receipt.effectiveGasPrice,
|
|
1138
1119
|
logs: receipt.logs,
|
|
1139
1120
|
blockNumber: receipt.blockNumber,
|
|
1121
|
+
blockHash: receipt.blockHash,
|
|
1140
1122
|
};
|
|
1141
1123
|
}
|
|
1142
1124
|
|
|
@@ -1152,9 +1134,51 @@ export class L1Publisher {
|
|
|
1152
1134
|
protected async sleepOrInterrupted() {
|
|
1153
1135
|
await this.interruptibleSleep.sleep(this.sleepTimeMs);
|
|
1154
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
/**
|
|
1139
|
+
* Send blobs to the blob sink
|
|
1140
|
+
*
|
|
1141
|
+
* If a blob sink url is configured, then we send blobs to the blob sink
|
|
1142
|
+
* - for now we use the blockHash as the identifier for the blobs;
|
|
1143
|
+
* In the future this will move to be the beacon block id - which takes a bit more work
|
|
1144
|
+
* to calculate and will need to be mocked in e2e tests
|
|
1145
|
+
*/
|
|
1146
|
+
protected async sendBlobsToBlobSink(blockHash: string, blobs: Blob[]): Promise<boolean> {
|
|
1147
|
+
// TODO(md): for now we are assuming the indexes of the blobs will be 0, 1, 2
|
|
1148
|
+
// When in reality they will not, but for testing purposes this is fine
|
|
1149
|
+
if (!this.blobSinkUrl) {
|
|
1150
|
+
this.log.verbose('No blob sink url configured');
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
this.log.verbose(`Sending ${blobs.length} blobs to blob sink`);
|
|
1155
|
+
try {
|
|
1156
|
+
const res = await fetch(`${this.blobSinkUrl}/blob_sidecar`, {
|
|
1157
|
+
method: 'POST',
|
|
1158
|
+
headers: {
|
|
1159
|
+
'Content-Type': 'application/json',
|
|
1160
|
+
},
|
|
1161
|
+
body: JSON.stringify({
|
|
1162
|
+
// eslint-disable-next-line camelcase
|
|
1163
|
+
block_id: blockHash,
|
|
1164
|
+
blobs: blobs.map((b, i) => ({ blob: b.toBuffer(), index: i })),
|
|
1165
|
+
}),
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
if (res.ok) {
|
|
1169
|
+
return true;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
this.log.error('Failed to send blobs to blob sink', res.status);
|
|
1173
|
+
return false;
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
this.log.error(`Error sending blobs to blob sink`, err);
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1155
1179
|
}
|
|
1156
1180
|
|
|
1157
|
-
|
|
1181
|
+
/*
|
|
1158
1182
|
* Returns cost of calldata usage in Ethereum.
|
|
1159
1183
|
* @param data - Calldata.
|
|
1160
1184
|
* @returns 4 for each zero byte, 16 for each nonzero.
|
package/src/sequencer/index.ts
CHANGED
|
@@ -4,11 +4,9 @@ import {
|
|
|
4
4
|
type L1ToL2MessageSource,
|
|
5
5
|
type L2Block,
|
|
6
6
|
type L2BlockSource,
|
|
7
|
-
type ProcessedTx,
|
|
8
7
|
SequencerConfigSchema,
|
|
9
8
|
Tx,
|
|
10
9
|
type TxHash,
|
|
11
|
-
type TxValidator,
|
|
12
10
|
type WorldStateSynchronizer,
|
|
13
11
|
} from '@aztec/circuit-types';
|
|
14
12
|
import type { AllowedElement, Signature, WorldStateSynchronizerStatus } from '@aztec/circuit-types/interfaces';
|
|
@@ -17,7 +15,9 @@ import {
|
|
|
17
15
|
AppendOnlyTreeSnapshot,
|
|
18
16
|
BlockHeader,
|
|
19
17
|
ContentCommitment,
|
|
18
|
+
type ContractDataSource,
|
|
20
19
|
GENESIS_ARCHIVE_ROOT,
|
|
20
|
+
Gas,
|
|
21
21
|
type GlobalVariables,
|
|
22
22
|
StateReference,
|
|
23
23
|
} from '@aztec/circuits.js';
|
|
@@ -39,7 +39,7 @@ import { type GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
39
39
|
import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
|
|
40
40
|
import { prettyLogViemErrorMsg } from '../publisher/utils.js';
|
|
41
41
|
import { type SlasherClient } from '../slasher/slasher_client.js';
|
|
42
|
-
import {
|
|
42
|
+
import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
43
43
|
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
44
44
|
import { type SequencerConfig } from './config.js';
|
|
45
45
|
import { SequencerMetrics } from './metrics.js';
|
|
@@ -47,12 +47,6 @@ import { SequencerState, orderAttestations } from './utils.js';
|
|
|
47
47
|
|
|
48
48
|
export { SequencerState };
|
|
49
49
|
|
|
50
|
-
export type ShouldProposeArgs = {
|
|
51
|
-
pendingTxsCount?: number;
|
|
52
|
-
validTxsCount?: number;
|
|
53
|
-
processedTxsCount?: number;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
50
|
export class SequencerTooSlowError extends Error {
|
|
57
51
|
constructor(
|
|
58
52
|
public readonly currentState: SequencerState,
|
|
@@ -90,6 +84,7 @@ export class Sequencer {
|
|
|
90
84
|
private state = SequencerState.STOPPED;
|
|
91
85
|
private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
|
|
92
86
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
87
|
+
private maxBlockGas: Gas = new Gas(10e9, 10e9);
|
|
93
88
|
private processTxTime: number = 12;
|
|
94
89
|
private metrics: SequencerMetrics;
|
|
95
90
|
private isFlushing: boolean = false;
|
|
@@ -112,7 +107,7 @@ export class Sequencer {
|
|
|
112
107
|
private l2BlockSource: L2BlockSource,
|
|
113
108
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
114
109
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
115
|
-
private
|
|
110
|
+
private contractDataSource: ContractDataSource,
|
|
116
111
|
protected l1Constants: SequencerRollupConstants,
|
|
117
112
|
private dateProvider: DateProvider,
|
|
118
113
|
telemetry: TelemetryClient,
|
|
@@ -149,6 +144,12 @@ export class Sequencer {
|
|
|
149
144
|
if (config.minTxsPerBlock !== undefined) {
|
|
150
145
|
this.minTxsPerBLock = config.minTxsPerBlock;
|
|
151
146
|
}
|
|
147
|
+
if (config.maxDABlockGas !== undefined) {
|
|
148
|
+
this.maxBlockGas = new Gas(config.maxDABlockGas, this.maxBlockGas.l2Gas);
|
|
149
|
+
}
|
|
150
|
+
if (config.maxL2BlockGas !== undefined) {
|
|
151
|
+
this.maxBlockGas = new Gas(this.maxBlockGas.daGas, config.maxL2BlockGas);
|
|
152
|
+
}
|
|
152
153
|
if (config.coinbase) {
|
|
153
154
|
this._coinbase = config.coinbase;
|
|
154
155
|
}
|
|
@@ -179,7 +180,7 @@ export class Sequencer {
|
|
|
179
180
|
// How late into the slot can we be to start working
|
|
180
181
|
const initialTime = 2;
|
|
181
182
|
|
|
182
|
-
// How long it takes to
|
|
183
|
+
// How long it takes to get ready to start building
|
|
183
184
|
const blockPrepareTime = 1;
|
|
184
185
|
|
|
185
186
|
// How long it takes to for attestations to travel across the p2p layer.
|
|
@@ -218,9 +219,9 @@ export class Sequencer {
|
|
|
218
219
|
[SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
|
|
219
220
|
// We always want to allow the full slot to check if we are the proposer
|
|
220
221
|
[SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
|
|
221
|
-
//
|
|
222
|
-
[SequencerState.
|
|
223
|
-
//
|
|
222
|
+
// How late we can start initializing a new block proposal
|
|
223
|
+
[SequencerState.INITIALIZING_PROPOSAL]: initialTime,
|
|
224
|
+
// When we start building a block
|
|
224
225
|
[SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
|
|
225
226
|
// We start collecting attestations after building the block
|
|
226
227
|
[SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
|
|
@@ -323,25 +324,27 @@ export class Sequencer {
|
|
|
323
324
|
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
|
|
324
325
|
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
// Check the pool has enough txs to build a block
|
|
328
|
+
const pendingTxCount = this.p2pClient.getPendingTxCount();
|
|
329
|
+
if (pendingTxCount < this.minTxsPerBLock && !this.isFlushing) {
|
|
330
|
+
this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBLock}.`, {
|
|
331
|
+
slot,
|
|
332
|
+
blockNumber: newBlockNumber,
|
|
333
|
+
});
|
|
334
|
+
await this.claimEpochProofRightIfAvailable(slot);
|
|
327
335
|
return;
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
330
339
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
331
340
|
chainTipArchive: new Fr(chainTipArchive),
|
|
332
341
|
blockNumber: newBlockNumber,
|
|
333
342
|
slot,
|
|
334
343
|
});
|
|
335
344
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const pendingTxs = await this.p2pClient.getPendingTxs();
|
|
340
|
-
|
|
341
|
-
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
342
|
-
await this.claimEpochProofRightIfAvailable(slot);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
+
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
346
|
+
// and also we may need to fetch more if we don't have enough valid txs.
|
|
347
|
+
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
345
348
|
|
|
346
349
|
// If I created a "partial" header here that should make our job much easier.
|
|
347
350
|
const proposalHeader = new BlockHeader(
|
|
@@ -353,35 +356,12 @@ export class Sequencer {
|
|
|
353
356
|
Fr.ZERO,
|
|
354
357
|
);
|
|
355
358
|
|
|
356
|
-
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here.
|
|
357
|
-
// TODO: We should validate only the number of txs we need to speed up this process.
|
|
358
|
-
const allValidTxs = await this.takeValidTxs(
|
|
359
|
-
pendingTxs,
|
|
360
|
-
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
364
|
-
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
365
|
-
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
366
|
-
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
367
|
-
// may break if we start emitting lots of log data from public-land.
|
|
368
|
-
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
369
|
-
|
|
370
|
-
this.log.verbose(
|
|
371
|
-
`Collected ${validTxs.length} txs out of ${allValidTxs.length} valid txs out of ${pendingTxs.length} total pending txs for block ${newBlockNumber}`,
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
// Bail if we don't have enough valid txs
|
|
375
|
-
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
376
|
-
await this.claimEpochProofRightIfAvailable(slot);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
359
|
try {
|
|
360
|
+
// TODO(palla/txs) Is the note below still valid? We don't seem to be doing any rollback in there.
|
|
381
361
|
// @note It is very important that the following function will FAIL and not just return early
|
|
382
362
|
// if it have made any state changes. If not, we won't rollback the state, and you will
|
|
383
363
|
// be in for a world of pain.
|
|
384
|
-
await this.buildBlockAndAttemptToPublish(
|
|
364
|
+
await this.buildBlockAndAttemptToPublish(pendingTxs, proposalHeader, historicalHeader);
|
|
385
365
|
} catch (err) {
|
|
386
366
|
this.log.error(`Error assembling block`, err, { blockNumber: newBlockNumber, slot });
|
|
387
367
|
}
|
|
@@ -469,64 +449,20 @@ export class Sequencer {
|
|
|
469
449
|
this.state = proposedState;
|
|
470
450
|
}
|
|
471
451
|
|
|
472
|
-
shouldProposeBlock(historicalHeader: BlockHeader | undefined, args: ShouldProposeArgs): boolean {
|
|
473
|
-
if (this.isFlushing) {
|
|
474
|
-
this.log.verbose(`Flushing all pending txs in new block`);
|
|
475
|
-
return true;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Compute time elapsed since the previous block
|
|
479
|
-
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
480
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
481
|
-
const elapsedSinceLastBlock = currentTime - lastBlockTime;
|
|
482
|
-
this.log.debug(
|
|
483
|
-
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
// We need to have at least minTxsPerBLock txs.
|
|
487
|
-
if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
|
|
488
|
-
this.log.verbose(
|
|
489
|
-
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
490
|
-
);
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Bail if we don't have enough valid txs
|
|
495
|
-
if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
|
|
496
|
-
this.log.verbose(
|
|
497
|
-
`Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
|
|
498
|
-
);
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
|
|
503
|
-
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
|
|
504
|
-
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
505
|
-
// we should bail.
|
|
506
|
-
if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
|
|
507
|
-
this.log.verbose('No txs processed correctly to build block.');
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
452
|
/**
|
|
515
453
|
* Build a block
|
|
516
454
|
*
|
|
517
455
|
* Shared between the sequencer and the validator for re-execution
|
|
518
456
|
*
|
|
519
|
-
* @param
|
|
457
|
+
* @param pendingTxs - The pending transactions to construct the block from
|
|
520
458
|
* @param newGlobalVariables - The global variables for the new block
|
|
521
459
|
* @param historicalHeader - The historical header of the parent
|
|
522
|
-
* @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
|
|
523
460
|
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
524
461
|
*/
|
|
525
462
|
private async buildBlock(
|
|
526
|
-
|
|
463
|
+
pendingTxs: Iterable<Tx>,
|
|
527
464
|
newGlobalVariables: GlobalVariables,
|
|
528
465
|
historicalHeader?: BlockHeader,
|
|
529
|
-
interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
|
|
530
466
|
opts: { validateOnly?: boolean } = {},
|
|
531
467
|
) {
|
|
532
468
|
const blockNumber = newGlobalVariables.blockNumber.toBigInt();
|
|
@@ -534,19 +470,9 @@ export class Sequencer {
|
|
|
534
470
|
|
|
535
471
|
this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
|
|
536
472
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
473
|
+
const msgCount = l1ToL2Messages.length;
|
|
537
474
|
|
|
538
|
-
this.log.verbose(
|
|
539
|
-
`Building block ${blockNumber} with ${validTxs.length} txs and ${l1ToL2Messages.length} messages`,
|
|
540
|
-
{
|
|
541
|
-
msgCount: l1ToL2Messages.length,
|
|
542
|
-
txCount: validTxs.length,
|
|
543
|
-
slot,
|
|
544
|
-
blockNumber,
|
|
545
|
-
},
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
const numRealTxs = validTxs.length;
|
|
549
|
-
const blockSize = Math.max(2, numRealTxs);
|
|
475
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, { slot, blockNumber, msgCount });
|
|
550
476
|
|
|
551
477
|
// Sync to the previous block at least
|
|
552
478
|
await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
|
|
@@ -570,18 +496,30 @@ export class Sequencer {
|
|
|
570
496
|
// We set the deadline for tx processing to the start of the CREATING_BLOCK phase, plus the expected time for tx processing.
|
|
571
497
|
// Deadline is only set if enforceTimeTable is enabled.
|
|
572
498
|
const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
|
|
573
|
-
const
|
|
499
|
+
const deadline = this.enforceTimeTable
|
|
574
500
|
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
575
501
|
: undefined;
|
|
576
|
-
this.log.verbose(`Processing
|
|
502
|
+
this.log.verbose(`Processing pending txs`, {
|
|
577
503
|
slot,
|
|
578
504
|
slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
|
|
579
505
|
now: new Date(this.dateProvider.now()),
|
|
580
|
-
deadline
|
|
506
|
+
deadline,
|
|
581
507
|
});
|
|
582
|
-
|
|
508
|
+
|
|
509
|
+
const validators = createValidatorsForBlockBuilding(
|
|
510
|
+
publicProcessorFork,
|
|
511
|
+
this.contractDataSource,
|
|
512
|
+
newGlobalVariables,
|
|
513
|
+
!!this.config.enforceFees,
|
|
514
|
+
this.allowedInSetup,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
// REFACTOR: Public processor should just handle processing, one tx at a time. It should be responsibility
|
|
518
|
+
// of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
|
|
519
|
+
// publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
|
|
520
|
+
const limits = { deadline, maxTransactions: this.maxTxsPerBlock, maxBlockSize: this.maxBlockSizeInBytes };
|
|
583
521
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
584
|
-
processor.process(
|
|
522
|
+
processor.process(pendingTxs, limits, validators),
|
|
585
523
|
);
|
|
586
524
|
|
|
587
525
|
if (failedTxs.length > 0) {
|
|
@@ -609,8 +547,6 @@ export class Sequencer {
|
|
|
609
547
|
const duration = Number(end - start) / 1_000;
|
|
610
548
|
this.metrics.recordBlockBuilderTreeInsertions(duration);
|
|
611
549
|
|
|
612
|
-
await interrupt?.(processedTxs);
|
|
613
|
-
|
|
614
550
|
// All real transactions have been added, set the block as full and pad if needed
|
|
615
551
|
const block = await blockBuilder.setBlockCompleted();
|
|
616
552
|
|
|
@@ -618,7 +554,7 @@ export class Sequencer {
|
|
|
618
554
|
block,
|
|
619
555
|
publicProcessorDuration,
|
|
620
556
|
numMsgs: l1ToL2Messages.length,
|
|
621
|
-
|
|
557
|
+
numTxs: processedTxs.length,
|
|
622
558
|
blockBuildingTimer,
|
|
623
559
|
};
|
|
624
560
|
} finally {
|
|
@@ -642,7 +578,7 @@ export class Sequencer {
|
|
|
642
578
|
* @dev MUST throw instead of exiting early to ensure that world-state
|
|
643
579
|
* is being rolled back if the block is dropped.
|
|
644
580
|
*
|
|
645
|
-
* @param
|
|
581
|
+
* @param pendingTxs - Iterable of pending transactions to construct the block from
|
|
646
582
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
647
583
|
* @param historicalHeader - The historical header of the parent
|
|
648
584
|
*/
|
|
@@ -650,7 +586,7 @@ export class Sequencer {
|
|
|
650
586
|
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
651
587
|
}))
|
|
652
588
|
private async buildBlockAndAttemptToPublish(
|
|
653
|
-
|
|
589
|
+
pendingTxs: Iterable<Tx>,
|
|
654
590
|
proposalHeader: BlockHeader,
|
|
655
591
|
historicalHeader: BlockHeader | undefined,
|
|
656
592
|
): Promise<void> {
|
|
@@ -660,40 +596,19 @@ export class Sequencer {
|
|
|
660
596
|
const blockNumber = newGlobalVariables.blockNumber.toNumber();
|
|
661
597
|
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
662
598
|
|
|
663
|
-
this.metrics.recordNewBlock(blockNumber, validTxs.length);
|
|
599
|
+
// this.metrics.recordNewBlock(blockNumber, validTxs.length);
|
|
664
600
|
const workTimer = new Timer();
|
|
665
601
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
666
602
|
|
|
667
|
-
/**
|
|
668
|
-
* BuildBlock is shared between the sequencer and the validator for re-execution
|
|
669
|
-
* We use the interrupt callback to validate the block for submission and check if we should propose the block
|
|
670
|
-
*
|
|
671
|
-
* If we fail, we throw an error in order to roll back
|
|
672
|
-
*/
|
|
673
|
-
const interrupt = async (processedTxs: ProcessedTx[]) => {
|
|
674
|
-
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
-
!this.shouldProposeBlock(historicalHeader, {
|
|
678
|
-
validTxsCount: validTxs.length,
|
|
679
|
-
processedTxsCount: processedTxs.length,
|
|
680
|
-
})
|
|
681
|
-
) {
|
|
682
|
-
// TODO: Roll back changes to world state
|
|
683
|
-
throw new Error('Should not propose the block');
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
|
|
687
603
|
// Start collecting proof quotes for the previous epoch if needed in the background
|
|
688
604
|
const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
|
|
689
605
|
|
|
690
606
|
try {
|
|
691
|
-
const buildBlockRes = await this.buildBlock(
|
|
692
|
-
const { block, publicProcessorDuration,
|
|
607
|
+
const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables, historicalHeader);
|
|
608
|
+
const { block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
693
609
|
|
|
694
610
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
695
611
|
// block being published before ours instead of just waiting on our block
|
|
696
|
-
|
|
697
612
|
await this.publisher.validateBlockForSubmission(block.header);
|
|
698
613
|
|
|
699
614
|
const workDuration = workTimer.ms();
|
|
@@ -707,8 +622,8 @@ export class Sequencer {
|
|
|
707
622
|
};
|
|
708
623
|
|
|
709
624
|
const blockHash = block.hash();
|
|
710
|
-
const txHashes =
|
|
711
|
-
this.log.info(`Built block ${block.number}
|
|
625
|
+
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
626
|
+
this.log.info(`Built block ${block.number} for slot ${slot} with ${numTxs} txs`, {
|
|
712
627
|
blockHash,
|
|
713
628
|
globalVariables: block.header.globalVariables.toInspect(),
|
|
714
629
|
txHashes,
|
|
@@ -734,14 +649,12 @@ export class Sequencer {
|
|
|
734
649
|
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
735
650
|
this.metrics.recordPublishedBlock(workDuration);
|
|
736
651
|
this.log.info(
|
|
737
|
-
`Published
|
|
738
|
-
block.number
|
|
739
|
-
} with ${numProcessedTxs} transactions and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
|
|
652
|
+
`Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
|
|
740
653
|
{
|
|
741
654
|
blockNumber: block.number,
|
|
742
655
|
blockHash: blockHash,
|
|
743
656
|
slot,
|
|
744
|
-
txCount:
|
|
657
|
+
txCount: txHashes.length,
|
|
745
658
|
msgCount: numMsgs,
|
|
746
659
|
duration: Math.ceil(workDuration),
|
|
747
660
|
submitter: this.publisher.getSenderAddress().toString(),
|
|
@@ -865,36 +778,6 @@ export class Sequencer {
|
|
|
865
778
|
}
|
|
866
779
|
}
|
|
867
780
|
|
|
868
|
-
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator<T>): Promise<T[]> {
|
|
869
|
-
const [valid, invalid] = await validator.validateTxs(txs);
|
|
870
|
-
if (invalid.length > 0) {
|
|
871
|
-
this.log.debug(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
|
|
872
|
-
await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return valid.slice(0, this.maxTxsPerBlock);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
protected takeTxsWithinMaxSize(txs: Tx[]): Tx[] {
|
|
879
|
-
const maxSize = this.maxBlockSizeInBytes;
|
|
880
|
-
let totalSize = 0;
|
|
881
|
-
|
|
882
|
-
const toReturn: Tx[] = [];
|
|
883
|
-
for (const tx of txs) {
|
|
884
|
-
const txSize = tx.getSize() - tx.clientIvcProof.clientIvcProofBuffer.length;
|
|
885
|
-
if (totalSize + txSize > maxSize) {
|
|
886
|
-
this.log.debug(
|
|
887
|
-
`Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
|
|
888
|
-
);
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
toReturn.push(tx);
|
|
892
|
-
totalSize += txSize;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
return toReturn;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
781
|
@trackSpan(
|
|
899
782
|
'Sequencer.claimEpochProofRightIfAvailable',
|
|
900
783
|
slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
|