@aztec/sequencer-client 0.84.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +7 -5
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +2 -1
- package/dest/sequencer/sequencer.d.ts +1 -1
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +22 -25
- package/dest/sequencer/timetable.d.ts +15 -9
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +18 -7
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +1 -2
- package/package.json +25 -29
- package/src/client/sequencer-client.ts +7 -5
- package/src/config.ts +10 -2
- package/src/sequencer/sequencer.ts +31 -31
- package/src/sequencer/timetable.ts +33 -12
- package/src/tx_validator/tx_validator_factory.ts +1 -1
- package/dest/tx_validator/archive_cache.d.ts +0 -14
- package/dest/tx_validator/archive_cache.d.ts.map +0 -1
- package/dest/tx_validator/archive_cache.js +0 -22
- package/src/tx_validator/archive_cache.ts +0 -28
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequencer-client.d.ts","sourceRoot":"","sources":["../../src/client/sequencer-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;GAEG;AACH,qBAAa,eAAe;IACd,SAAS,CAAC,SAAS,EAAE,SAAS;gBAApB,SAAS,EAAE,SAAS;IAE1C;;;;;;;;;;;;OAYG;WACiB,GAAG,CACrB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE;QACJ,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;QAC7C,SAAS,EAAE,GAAG,CAAC;QACf,sBAAsB,EAAE,sBAAsB,CAAC;QAC/C,aAAa,EAAE,aAAa,CAAC;QAC7B,kBAAkB,EAAE,kBAAkB,CAAC;QACvC,aAAa,EAAE,aAAa,CAAC;QAC7B,mBAAmB,EAAE,mBAAmB,CAAC;QACzC,SAAS,EAAE,eAAe,CAAC;QAC3B,SAAS,CAAC,EAAE,kBAAkB,CAAC;QAC/B,cAAc,CAAC,EAAE,uBAAuB,CAAC;QACzC,YAAY,EAAE,YAAY,CAAC;QAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;QACxB,SAAS,CAAC,EAAE,kBAAkB,CAAC;KAChC;
|
|
1
|
+
{"version":3,"file":"sequencer-client.d.ts","sourceRoot":"","sources":["../../src/client/sequencer-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUhD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;GAEG;AACH,qBAAa,eAAe;IACd,SAAS,CAAC,SAAS,EAAE,SAAS;gBAApB,SAAS,EAAE,SAAS;IAE1C;;;;;;;;;;;;OAYG;WACiB,GAAG,CACrB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE;QACJ,eAAe,EAAE,eAAe,GAAG,SAAS,CAAC;QAC7C,SAAS,EAAE,GAAG,CAAC;QACf,sBAAsB,EAAE,sBAAsB,CAAC;QAC/C,aAAa,EAAE,aAAa,CAAC;QAC7B,kBAAkB,EAAE,kBAAkB,CAAC;QACvC,aAAa,EAAE,aAAa,CAAC;QAC7B,mBAAmB,EAAE,mBAAmB,CAAC;QACzC,SAAS,EAAE,eAAe,CAAC;QAC3B,SAAS,CAAC,EAAE,kBAAkB,CAAC;QAC/B,cAAc,CAAC,EAAE,uBAAuB,CAAC;QACzC,YAAY,EAAE,YAAY,CAAC;QAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;QACxB,SAAS,CAAC,EAAE,kBAAkB,CAAC;KAChC;IA2HH;;;OAGG;IACI,qBAAqB,CAAC,MAAM,EAAE,eAAe;IAIpD;;OAEG;IACU,IAAI;IAIjB,uGAAuG;IAChG,KAAK;IAIZ;;OAEG;IACI,OAAO;IAId,IAAI,QAAQ,IAAI,UAAU,CAEzB;IAED,IAAI,YAAY,IAAI,YAAY,CAE/B;IAED,IAAI,gBAAgB,IAAI,UAAU,CAEjC;IAED,IAAI,gBAAgB,IAAI,UAAU,GAAG,SAAS,CAE7C;IAED,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;CACF"}
|
|
@@ -49,7 +49,8 @@ import { Sequencer } from '../sequencer/index.js';
|
|
|
49
49
|
viemPollingIntervalMS: config.viemPollingIntervalMS,
|
|
50
50
|
aztecSlotDuration: config.aztecSlotDuration,
|
|
51
51
|
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
52
|
-
aztecEpochDuration: config.aztecEpochDuration
|
|
52
|
+
aztecEpochDuration: config.aztecEpochDuration,
|
|
53
|
+
aztecProofSubmissionWindow: config.aztecProofSubmissionWindow
|
|
53
54
|
}, {
|
|
54
55
|
dateProvider: deps.dateProvider
|
|
55
56
|
});
|
|
@@ -66,10 +67,11 @@ import { Sequencer } from '../sequencer/index.js';
|
|
|
66
67
|
const globalsBuilder = new GlobalVariableBuilder(config);
|
|
67
68
|
const publicProcessorFactory = new PublicProcessorFactory(contractDataSource, deps.dateProvider, telemetryClient);
|
|
68
69
|
const ethereumSlotDuration = config.ethereumSlotDuration;
|
|
69
|
-
const rollupManaLimit = await rollupContract.getManaLimit();
|
|
70
|
-
|
|
71
|
-
if (sequencerManaLimit >
|
|
72
|
-
|
|
70
|
+
const rollupManaLimit = Number(await rollupContract.getManaLimit());
|
|
71
|
+
let sequencerManaLimit = config.maxL2BlockGas ?? rollupManaLimit;
|
|
72
|
+
if (sequencerManaLimit > rollupManaLimit) {
|
|
73
|
+
log.warn(`Provided maxL2BlockGas of ${sequencerManaLimit} is greater than the maximum allowed by the L1 (${rollupManaLimit}), setting limit to ${rollupManaLimit}`);
|
|
74
|
+
sequencerManaLimit = rollupManaLimit;
|
|
73
75
|
}
|
|
74
76
|
// When running in anvil, assume we can post a tx up until the very last second of an L1 slot.
|
|
75
77
|
// Otherwise, assume we must have broadcasted the tx before the slot started (we use a default
|
package/dest/config.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { SequencerConfig };
|
|
|
9
9
|
/**
|
|
10
10
|
* Configuration settings for the SequencerClient.
|
|
11
11
|
*/
|
|
12
|
-
export type SequencerClientConfig = PublisherConfig & ValidatorClientConfig & TxSenderConfig & SequencerConfig & L1ReaderConfig & ChainConfig & Pick<P2PConfig, 'txPublicSetupAllowList'> & Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration'>;
|
|
12
|
+
export type SequencerClientConfig = PublisherConfig & ValidatorClientConfig & TxSenderConfig & SequencerConfig & L1ReaderConfig & ChainConfig & Pick<P2PConfig, 'txPublicSetupAllowList'> & Pick<L1ContractsConfig, 'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration' | 'aztecProofSubmissionWindow'>;
|
|
13
13
|
export declare const sequencerConfigMappings: ConfigMappingsType<SequencerConfig>;
|
|
14
14
|
export declare const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientConfig>;
|
|
15
15
|
/**
|
package/dest/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAuB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,KAAK,qBAAqB,EAAiC,MAAM,yBAAyB,CAAC;AAEpG,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAC;AAE/B,cAAc,uBAAuB,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,GACjD,qBAAqB,GACrB,cAAc,GACd,eAAe,GACf,cAAc,GACd,WAAW,GACX,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,GACzC,IAAI,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAuB,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,KAAK,qBAAqB,EAAiC,MAAM,yBAAyB,CAAC;AAEpG,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGpB,MAAM,uBAAuB,CAAC;AAE/B,cAAc,uBAAuB,CAAC;AACtC,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,eAAe,GACjD,qBAAqB,GACrB,cAAc,GACd,eAAe,GACf,cAAc,GACd,WAAW,GACX,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,GACzC,IAAI,CACF,iBAAiB,EACjB,sBAAsB,GAAG,mBAAmB,GAAG,oBAAoB,GAAG,4BAA4B,CACnG,CAAC;AAEJ,eAAO,MAAM,uBAAuB,EAAE,kBAAkB,CAAC,eAAe,CAmEvE,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,kBAAkB,CAAC,qBAAqB,CAanF,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,qBAAqB,CAExD"}
|
package/dest/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
-
import type
|
|
3
|
+
import { type L2Block } from '@aztec/aztec.js';
|
|
4
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
5
|
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
6
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequencer.d.ts","sourceRoot":"","sources":["../../src/sequencer/sequencer.ts"],"names":[],"mappings":";;AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"sequencer.d.ts","sourceRoot":"","sources":["../../src/sequencer/sequencer.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
4
4
|
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
}
|
|
7
|
+
import { retryUntil } from '@aztec/aztec.js';
|
|
7
8
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
8
9
|
import { omit } from '@aztec/foundation/collection';
|
|
9
10
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -232,7 +233,7 @@ export { SequencerState };
|
|
|
232
233
|
const enqueueGovernanceVotePromise = this.publisher.enqueueCastVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
|
|
233
234
|
const enqueueSlashingVotePromise = this.publisher.enqueueCastVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
|
|
234
235
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
235
|
-
this.log.
|
|
236
|
+
this.log.debug(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
236
237
|
chainTipArchive,
|
|
237
238
|
blockNumber: newBlockNumber,
|
|
238
239
|
slot
|
|
@@ -253,7 +254,11 @@ export { SequencerState };
|
|
|
253
254
|
});
|
|
254
255
|
finishedFlushing = true;
|
|
255
256
|
} else {
|
|
256
|
-
this.log.
|
|
257
|
+
this.log.verbose(`Not enough txs to build block ${newBlockNumber} at slot ${slot} (got ${pendingTxCount} txs, need ${this.minTxsPerBlock})`, {
|
|
258
|
+
chainTipArchive,
|
|
259
|
+
blockNumber: newBlockNumber,
|
|
260
|
+
slot
|
|
261
|
+
});
|
|
257
262
|
}
|
|
258
263
|
await enqueueGovernanceVotePromise.catch((err)=>{
|
|
259
264
|
this.log.error(`Error enqueuing governance vote`, err, {
|
|
@@ -347,8 +352,10 @@ export { SequencerState };
|
|
|
347
352
|
msgCount,
|
|
348
353
|
validator: opts.validateOnly
|
|
349
354
|
});
|
|
350
|
-
// Sync to the previous block at least
|
|
351
|
-
|
|
355
|
+
// Sync to the previous block at least. If we cannot sync to that block because the archiver hasn't caught up,
|
|
356
|
+
// we keep retrying until the reexecution deadline. Note that this could only happen when we are a validator,
|
|
357
|
+
// for if we are the proposer, then world-state should already be caught up, as we check this earlier.
|
|
358
|
+
await retryUntil(()=>this.worldState.syncImmediate(blockNumber - 1, true).then((syncedTo)=>syncedTo >= blockNumber - 1), 'sync to previous block', this.timetable.getValidatorReexecTimeEnd(), 0.1);
|
|
352
359
|
this.log.debug(`Synced to previous block ${blockNumber - 1}`);
|
|
353
360
|
// NB: separating the dbs because both should update the state
|
|
354
361
|
const publicProcessorDBFork = await this.worldState.fork();
|
|
@@ -544,33 +551,23 @@ export { SequencerState };
|
|
|
544
551
|
* @returns Boolean indicating if our dependencies are synced to the latest block.
|
|
545
552
|
*/ async getChainTip() {
|
|
546
553
|
const syncedBlocks = await Promise.all([
|
|
547
|
-
this.worldState.status().then((
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
};
|
|
552
|
-
}),
|
|
554
|
+
this.worldState.status().then(({ syncSummary })=>({
|
|
555
|
+
number: syncSummary.latestBlockNumber,
|
|
556
|
+
hash: syncSummary.latestBlockHash
|
|
557
|
+
})),
|
|
553
558
|
this.l2BlockSource.getL2Tips().then((t)=>t.latest),
|
|
554
559
|
this.p2pClient.getStatus().then((p2p)=>p2p.syncedToL2Block),
|
|
555
|
-
this.l1ToL2MessageSource.
|
|
560
|
+
this.l1ToL2MessageSource.getL2Tips().then((t)=>t.latest)
|
|
556
561
|
]);
|
|
557
562
|
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource] = syncedBlocks;
|
|
558
|
-
|
|
559
|
-
// note that the archiver reports undefined hash for the genesis block
|
|
563
|
+
// The archiver reports 'undefined' hash for the genesis block
|
|
560
564
|
// because it doesn't have access to world state to compute it (facepalm)
|
|
561
|
-
|
|
562
|
-
// this should change to hashes once p2p client handles reorgs
|
|
563
|
-
// and once we stop pretending that the l1tol2message source is not
|
|
564
|
-
// just the archiver under a different name
|
|
565
|
-
(!l2BlockSource.hash || p2p.hash === l2BlockSource.hash) && l1ToL2MessageSource === l2BlockSource.number;
|
|
565
|
+
const result = l2BlockSource.hash === undefined ? worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0 : worldState.hash === l2BlockSource.hash && p2p.hash === l2BlockSource.hash && l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
566
566
|
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
p2pNumber: p2p.number,
|
|
572
|
-
p2pHash: p2p.hash,
|
|
573
|
-
l1ToL2MessageSourceNumber: l1ToL2MessageSource
|
|
567
|
+
worldState,
|
|
568
|
+
l2BlockSource,
|
|
569
|
+
p2p,
|
|
570
|
+
l1ToL2MessageSource
|
|
574
571
|
});
|
|
575
572
|
if (!result) {
|
|
576
573
|
return undefined;
|
|
@@ -7,25 +7,31 @@ export declare class SequencerTimetable {
|
|
|
7
7
|
private readonly enforce;
|
|
8
8
|
private readonly metrics?;
|
|
9
9
|
private readonly log;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
readonly
|
|
16
|
-
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */
|
|
17
|
-
readonly blockValidationTime = 1;
|
|
10
|
+
/**
|
|
11
|
+
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
12
|
+
* assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
|
|
13
|
+
* starts building at this time, and all times hold, it will have at least `minExecutionTime` to execute txs for the block.
|
|
14
|
+
*/
|
|
15
|
+
readonly initializeDeadline: number;
|
|
18
16
|
/**
|
|
19
17
|
* How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
20
18
|
* but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
21
19
|
* we can just post in the very last second of the L1 slot and still expect the tx to be accepted.
|
|
22
20
|
*/
|
|
23
21
|
readonly l1PublishingTime: number;
|
|
22
|
+
/** What's the minimum time we want to leave available for execution and reexecution (used to derive init deadline) */
|
|
23
|
+
readonly minExecutionTime = 1;
|
|
24
|
+
/** How long it takes to get ready to start building */
|
|
25
|
+
readonly blockPrepareTime = 1;
|
|
26
|
+
/** How long it takes to for proposals and attestations to travel across the p2p layer (one-way) */
|
|
27
|
+
readonly attestationPropagationTime = 2;
|
|
28
|
+
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */
|
|
29
|
+
readonly blockValidationTime = 1;
|
|
24
30
|
constructor(ethereumSlotDuration: number, aztecSlotDuration: number, maxL1TxInclusionTimeIntoSlot: number, enforce?: boolean, metrics?: SequencerMetrics | undefined, log?: import("@aztec/aztec.js").Logger);
|
|
25
31
|
private get afterBlockBuildingTimeNeededWithoutReexec();
|
|
26
32
|
getBlockProposalExecTimeEnd(secondsIntoSlot: number): number;
|
|
27
33
|
private get afterBlockReexecTimeNeeded();
|
|
28
|
-
getValidatorReexecTimeEnd(secondsIntoSlot
|
|
34
|
+
getValidatorReexecTimeEnd(secondsIntoSlot?: number): number;
|
|
29
35
|
getMaxAllowedTime(state: SequencerState): number | undefined;
|
|
30
36
|
assertTimeLeft(newState: SequencerState, secondsIntoSlot: number): void;
|
|
31
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,qBAAa,kBAAkB;
|
|
1
|
+
{"version":3,"file":"timetable.d.ts","sourceRoot":"","sources":["../../src/sequencer/timetable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,qBAAa,kBAAkB;IA4B3B,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IACrC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,4BAA4B;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAhCtB;;;;OAIG;IACH,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;OAIG;IACH,SAAgB,gBAAgB,SAAC;IAEjC,sHAAsH;IACtH,SAAgB,gBAAgB,KAAK;IAErC,uDAAuD;IACvD,SAAgB,gBAAgB,KAAK;IAErC,mGAAmG;IACnG,SAAgB,0BAA0B,KAAK;IAE/C,mIAAmI;IACnI,SAAgB,mBAAmB,KAAK;gBAGrB,oBAAoB,EAAE,MAAM,EAC5B,iBAAiB,EAAE,MAAM,EACzB,4BAA4B,EAAE,MAAM,EACpC,OAAO,GAAE,OAAc,EACvB,OAAO,CAAC,8BAAkB,EAC1B,GAAG,mCAAsC;IAmB5D,OAAO,KAAK,yCAAyC,GAEpD;IAEM,2BAA2B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM;IAenE,OAAO,KAAK,0BAA0B,GAErC;IAEM,yBAAyB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM;IAU3D,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS;IAsB5D,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM;CAkBxE;AAED,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,aAAa,EAAE,cAAc;aAC7B,cAAc,EAAE,MAAM;aACtB,WAAW,EAAE,MAAM;gBAFnB,aAAa,EAAE,cAAc,EAC7B,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM;CAOtC"}
|
|
@@ -7,15 +7,20 @@ export class SequencerTimetable {
|
|
|
7
7
|
enforce;
|
|
8
8
|
metrics;
|
|
9
9
|
log;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
12
|
+
* assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
|
|
13
|
+
* starts building at this time, and all times hold, it will have at least `minExecutionTime` to execute txs for the block.
|
|
14
|
+
*/ initializeDeadline;
|
|
14
15
|
/**
|
|
15
16
|
* How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
16
17
|
* but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
17
18
|
* we can just post in the very last second of the L1 slot and still expect the tx to be accepted.
|
|
18
19
|
*/ l1PublishingTime;
|
|
20
|
+
/** What's the minimum time we want to leave available for execution and reexecution (used to derive init deadline) */ minExecutionTime;
|
|
21
|
+
/** How long it takes to get ready to start building */ blockPrepareTime;
|
|
22
|
+
/** How long it takes to for proposals and attestations to travel across the p2p layer (one-way) */ attestationPropagationTime;
|
|
23
|
+
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */ blockValidationTime;
|
|
19
24
|
constructor(ethereumSlotDuration, aztecSlotDuration, maxL1TxInclusionTimeIntoSlot, enforce = true, metrics, log = createLogger('sequencer:timetable')){
|
|
20
25
|
this.ethereumSlotDuration = ethereumSlotDuration;
|
|
21
26
|
this.aztecSlotDuration = aztecSlotDuration;
|
|
@@ -23,11 +28,17 @@ export class SequencerTimetable {
|
|
|
23
28
|
this.enforce = enforce;
|
|
24
29
|
this.metrics = metrics;
|
|
25
30
|
this.log = log;
|
|
26
|
-
this.
|
|
31
|
+
this.minExecutionTime = 1;
|
|
27
32
|
this.blockPrepareTime = 1;
|
|
28
33
|
this.attestationPropagationTime = 2;
|
|
29
34
|
this.blockValidationTime = 1;
|
|
30
35
|
this.l1PublishingTime = this.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
36
|
+
const allWorkToDo = this.blockPrepareTime + this.minExecutionTime * 2 + this.attestationPropagationTime * 2 + this.blockValidationTime + this.l1PublishingTime;
|
|
37
|
+
const initializeDeadline = this.aztecSlotDuration - allWorkToDo;
|
|
38
|
+
if (initializeDeadline <= 0) {
|
|
39
|
+
throw new Error(`Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${allWorkToDo} and a slot duration of ${this.aztecSlotDuration}).`);
|
|
40
|
+
}
|
|
41
|
+
this.initializeDeadline = initializeDeadline;
|
|
31
42
|
}
|
|
32
43
|
get afterBlockBuildingTimeNeededWithoutReexec() {
|
|
33
44
|
return this.blockValidationTime + this.attestationPropagationTime * 2 + this.l1PublishingTime;
|
|
@@ -66,9 +77,9 @@ export class SequencerTimetable {
|
|
|
66
77
|
case SequencerState.PROPOSER_CHECK:
|
|
67
78
|
return; // We don't really care about times for this states
|
|
68
79
|
case SequencerState.INITIALIZING_PROPOSAL:
|
|
69
|
-
return this.
|
|
80
|
+
return this.initializeDeadline;
|
|
70
81
|
case SequencerState.CREATING_BLOCK:
|
|
71
|
-
return this.
|
|
82
|
+
return this.initializeDeadline + this.blockPrepareTime;
|
|
72
83
|
case SequencerState.COLLECTING_ATTESTATIONS:
|
|
73
84
|
return this.aztecSlotDuration - this.l1PublishingTime - 2 * this.attestationPropagationTime;
|
|
74
85
|
case SequencerState.PUBLISHING_BLOCK:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tx_validator_factory.d.ts","sourceRoot":"","sources":["../../src/tx_validator/tx_validator_factory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tx_validator_factory.d.ts","sourceRoot":"","sources":["../../src/tx_validator/tx_validator_factory.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EACV,cAAc,EACd,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE9E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,QAAQ,EAAE,6BAA6B,GAAG,SAAS,EACnD,EACE,WAAW,EACX,SAAS,EACT,aAAa,EACb,cAAc,EACd,OAAO,EACP,kBAAkB,GACnB,EAAE;IACD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GACA,WAAW,CAAC,EAAE,CAAC,CAkBjB;AAED,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,wBAAwB,EAC5B,kBAAkB,EAAE,kBAAkB,EACtC,eAAe,EAAE,eAAe,EAChC,cAAc,EAAE,cAAc,EAAE,GAC/B;IACD,mBAAmB,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;IACrC,cAAc,EAAE,cAAc,CAAC;CAChC,CAgBA"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import { AggregateTxValidator, BlockHeaderTxValidator, DataTxValidator, DoubleSpendTxValidator, GasTxValidator, MetadataTxValidator, PhasesTxValidator, TxProofValidator } from '@aztec/p2p';
|
|
2
|
+
import { AggregateTxValidator, ArchiveCache, BlockHeaderTxValidator, DataTxValidator, DoubleSpendTxValidator, GasTxValidator, MetadataTxValidator, PhasesTxValidator, TxProofValidator } from '@aztec/p2p';
|
|
3
3
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
4
4
|
import { DatabasePublicStateSource } from '@aztec/stdlib/trees';
|
|
5
|
-
import { ArchiveCache } from './archive_cache.js';
|
|
6
5
|
import { NullifierCache } from './nullifier_cache.js';
|
|
7
6
|
export function createValidatorForAcceptingTxs(db, contractDataSource, verifier, { blockNumber, l1ChainId, rollupVersion, setupAllowList, gasFees, skipFeeEnforcement }) {
|
|
8
7
|
const validators = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/sequencer-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -28,50 +28,46 @@
|
|
|
28
28
|
"test:integration:run": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --config jest.integration.config.json"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@aztec/aztec.js": "0.
|
|
32
|
-
"@aztec/bb-prover": "0.
|
|
33
|
-
"@aztec/blob-lib": "0.
|
|
34
|
-
"@aztec/blob-sink": "0.
|
|
35
|
-
"@aztec/constants": "0.
|
|
36
|
-
"@aztec/epoch-cache": "0.
|
|
37
|
-
"@aztec/ethereum": "0.
|
|
38
|
-
"@aztec/foundation": "0.
|
|
39
|
-
"@aztec/l1-artifacts": "0.
|
|
40
|
-
"@aztec/merkle-tree": "0.
|
|
41
|
-
"@aztec/noir-acvm_js": "0.
|
|
42
|
-
"@aztec/noir-contracts.js": "0.
|
|
43
|
-
"@aztec/noir-protocol-circuits-types": "0.
|
|
44
|
-
"@aztec/noir-types": "0.
|
|
45
|
-
"@aztec/p2p": "0.
|
|
46
|
-
"@aztec/protocol-contracts": "0.
|
|
47
|
-
"@aztec/prover-client": "0.
|
|
48
|
-
"@aztec/simulator": "0.
|
|
49
|
-
"@aztec/stdlib": "0.
|
|
50
|
-
"@aztec/telemetry-client": "0.
|
|
51
|
-
"@aztec/validator-client": "0.
|
|
52
|
-
"@aztec/world-state": "0.
|
|
31
|
+
"@aztec/aztec.js": "0.85.0",
|
|
32
|
+
"@aztec/bb-prover": "0.85.0",
|
|
33
|
+
"@aztec/blob-lib": "0.85.0",
|
|
34
|
+
"@aztec/blob-sink": "0.85.0",
|
|
35
|
+
"@aztec/constants": "0.85.0",
|
|
36
|
+
"@aztec/epoch-cache": "0.85.0",
|
|
37
|
+
"@aztec/ethereum": "0.85.0",
|
|
38
|
+
"@aztec/foundation": "0.85.0",
|
|
39
|
+
"@aztec/l1-artifacts": "0.85.0",
|
|
40
|
+
"@aztec/merkle-tree": "0.85.0",
|
|
41
|
+
"@aztec/noir-acvm_js": "0.85.0",
|
|
42
|
+
"@aztec/noir-contracts.js": "0.85.0",
|
|
43
|
+
"@aztec/noir-protocol-circuits-types": "0.85.0",
|
|
44
|
+
"@aztec/noir-types": "0.85.0",
|
|
45
|
+
"@aztec/p2p": "0.85.0",
|
|
46
|
+
"@aztec/protocol-contracts": "0.85.0",
|
|
47
|
+
"@aztec/prover-client": "0.85.0",
|
|
48
|
+
"@aztec/simulator": "0.85.0",
|
|
49
|
+
"@aztec/stdlib": "0.85.0",
|
|
50
|
+
"@aztec/telemetry-client": "0.85.0",
|
|
51
|
+
"@aztec/validator-client": "0.85.0",
|
|
52
|
+
"@aztec/world-state": "0.85.0",
|
|
53
53
|
"lodash.chunk": "^4.2.0",
|
|
54
54
|
"lodash.pick": "^4.4.0",
|
|
55
55
|
"tslib": "^2.4.0",
|
|
56
56
|
"viem": "2.23.7"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@aztec/archiver": "0.
|
|
60
|
-
"@aztec/kv-store": "0.
|
|
59
|
+
"@aztec/archiver": "0.85.0",
|
|
60
|
+
"@aztec/kv-store": "0.85.0",
|
|
61
61
|
"@jest/globals": "^29.5.0",
|
|
62
62
|
"@types/jest": "^29.5.0",
|
|
63
|
-
"@types/levelup": "^5.1.2",
|
|
64
63
|
"@types/lodash.chunk": "^4.2.7",
|
|
65
64
|
"@types/lodash.pick": "^4.4.7",
|
|
66
|
-
"@types/memdown": "^3.0.0",
|
|
67
65
|
"@types/node": "^18.7.23",
|
|
68
66
|
"concurrently": "^7.6.0",
|
|
69
67
|
"eslint": "^8.37.0",
|
|
70
68
|
"express": "^4.21.1",
|
|
71
69
|
"jest": "^29.5.0",
|
|
72
70
|
"jest-mock-extended": "^3.0.3",
|
|
73
|
-
"levelup": "^5.1.1",
|
|
74
|
-
"memdown": "^6.1.1",
|
|
75
71
|
"prettier": "^2.8.7",
|
|
76
72
|
"ts-node": "^10.9.1",
|
|
77
73
|
"typescript": "^5.0.4"
|
|
@@ -119,6 +119,7 @@ export class SequencerClient {
|
|
|
119
119
|
aztecSlotDuration: config.aztecSlotDuration,
|
|
120
120
|
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
121
121
|
aztecEpochDuration: config.aztecEpochDuration,
|
|
122
|
+
aztecProofSubmissionWindow: config.aztecProofSubmissionWindow,
|
|
122
123
|
},
|
|
123
124
|
{ dateProvider: deps.dateProvider },
|
|
124
125
|
));
|
|
@@ -141,12 +142,13 @@ export class SequencerClient {
|
|
|
141
142
|
|
|
142
143
|
const ethereumSlotDuration = config.ethereumSlotDuration;
|
|
143
144
|
|
|
144
|
-
const rollupManaLimit = await rollupContract.getManaLimit();
|
|
145
|
-
|
|
146
|
-
if (sequencerManaLimit >
|
|
147
|
-
|
|
148
|
-
`
|
|
145
|
+
const rollupManaLimit = Number(await rollupContract.getManaLimit());
|
|
146
|
+
let sequencerManaLimit = config.maxL2BlockGas ?? rollupManaLimit;
|
|
147
|
+
if (sequencerManaLimit > rollupManaLimit) {
|
|
148
|
+
log.warn(
|
|
149
|
+
`Provided maxL2BlockGas of ${sequencerManaLimit} is greater than the maximum allowed by the L1 (${rollupManaLimit}), setting limit to ${rollupManaLimit}`,
|
|
149
150
|
);
|
|
151
|
+
sequencerManaLimit = rollupManaLimit;
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
// When running in anvil, assume we can post a tx up until the very last second of an L1 slot.
|
package/src/config.ts
CHANGED
|
@@ -37,7 +37,10 @@ export type SequencerClientConfig = PublisherConfig &
|
|
|
37
37
|
L1ReaderConfig &
|
|
38
38
|
ChainConfig &
|
|
39
39
|
Pick<P2PConfig, 'txPublicSetupAllowList'> &
|
|
40
|
-
Pick<
|
|
40
|
+
Pick<
|
|
41
|
+
L1ContractsConfig,
|
|
42
|
+
'ethereumSlotDuration' | 'aztecSlotDuration' | 'aztecEpochDuration' | 'aztecProofSubmissionWindow'
|
|
43
|
+
>;
|
|
41
44
|
|
|
42
45
|
export const sequencerConfigMappings: ConfigMappingsType<SequencerConfig> = {
|
|
43
46
|
transactionPollingIntervalMS: {
|
|
@@ -115,7 +118,12 @@ export const sequencerClientConfigMappings: ConfigMappingsType<SequencerClientCo
|
|
|
115
118
|
...getTxSenderConfigMappings('SEQ'),
|
|
116
119
|
...getPublisherConfigMappings('SEQ'),
|
|
117
120
|
...chainConfigMappings,
|
|
118
|
-
...pickConfigMappings(l1ContractsConfigMappings, [
|
|
121
|
+
...pickConfigMappings(l1ContractsConfigMappings, [
|
|
122
|
+
'ethereumSlotDuration',
|
|
123
|
+
'aztecSlotDuration',
|
|
124
|
+
'aztecEpochDuration',
|
|
125
|
+
'aztecProofSubmissionWindow',
|
|
126
|
+
]),
|
|
119
127
|
};
|
|
120
128
|
|
|
121
129
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type L2Block, retryUntil } from '@aztec/aztec.js';
|
|
2
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
3
|
import { omit } from '@aztec/foundation/collection';
|
|
4
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
type AllowedElement,
|
|
21
21
|
SequencerConfigSchema,
|
|
22
22
|
type WorldStateSynchronizer,
|
|
23
|
-
type WorldStateSynchronizerStatus,
|
|
24
23
|
} from '@aztec/stdlib/interfaces/server';
|
|
25
24
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
26
25
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
@@ -293,7 +292,7 @@ export class Sequencer {
|
|
|
293
292
|
);
|
|
294
293
|
|
|
295
294
|
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
296
|
-
this.log.
|
|
295
|
+
this.log.debug(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
297
296
|
chainTipArchive,
|
|
298
297
|
blockNumber: newBlockNumber,
|
|
299
298
|
slot,
|
|
@@ -321,8 +320,9 @@ export class Sequencer {
|
|
|
321
320
|
});
|
|
322
321
|
finishedFlushing = true;
|
|
323
322
|
} else {
|
|
324
|
-
this.log.
|
|
325
|
-
`Not enough txs to build block ${newBlockNumber} at slot ${slot}
|
|
323
|
+
this.log.verbose(
|
|
324
|
+
`Not enough txs to build block ${newBlockNumber} at slot ${slot} (got ${pendingTxCount} txs, need ${this.minTxsPerBlock})`,
|
|
325
|
+
{ chainTipArchive, blockNumber: newBlockNumber, slot },
|
|
326
326
|
);
|
|
327
327
|
}
|
|
328
328
|
|
|
@@ -433,8 +433,15 @@ export class Sequencer {
|
|
|
433
433
|
validator: opts.validateOnly,
|
|
434
434
|
});
|
|
435
435
|
|
|
436
|
-
// Sync to the previous block at least
|
|
437
|
-
|
|
436
|
+
// Sync to the previous block at least. If we cannot sync to that block because the archiver hasn't caught up,
|
|
437
|
+
// we keep retrying until the reexecution deadline. Note that this could only happen when we are a validator,
|
|
438
|
+
// for if we are the proposer, then world-state should already be caught up, as we check this earlier.
|
|
439
|
+
await retryUntil(
|
|
440
|
+
() => this.worldState.syncImmediate(blockNumber - 1, true).then(syncedTo => syncedTo >= blockNumber - 1),
|
|
441
|
+
'sync to previous block',
|
|
442
|
+
this.timetable.getValidatorReexecTimeEnd(),
|
|
443
|
+
0.1,
|
|
444
|
+
);
|
|
438
445
|
this.log.debug(`Synced to previous block ${blockNumber - 1}`);
|
|
439
446
|
|
|
440
447
|
// NB: separating the dbs because both should update the state
|
|
@@ -704,44 +711,37 @@ export class Sequencer {
|
|
|
704
711
|
*/
|
|
705
712
|
protected async getChainTip(): Promise<{ blockNumber: number; archive: Fr } | undefined> {
|
|
706
713
|
const syncedBlocks = await Promise.all([
|
|
707
|
-
this.worldState.status().then((
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
};
|
|
712
|
-
}),
|
|
714
|
+
this.worldState.status().then(({ syncSummary }) => ({
|
|
715
|
+
number: syncSummary.latestBlockNumber,
|
|
716
|
+
hash: syncSummary.latestBlockHash,
|
|
717
|
+
})),
|
|
713
718
|
this.l2BlockSource.getL2Tips().then(t => t.latest),
|
|
714
719
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
715
|
-
this.l1ToL2MessageSource.
|
|
720
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => t.latest),
|
|
716
721
|
] as const);
|
|
717
722
|
|
|
718
723
|
const [worldState, l2BlockSource, p2p, l1ToL2MessageSource] = syncedBlocks;
|
|
719
724
|
|
|
725
|
+
// The archiver reports 'undefined' hash for the genesis block
|
|
726
|
+
// because it doesn't have access to world state to compute it (facepalm)
|
|
720
727
|
const result =
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
// this should change to hashes once p2p client handles reorgs
|
|
727
|
-
// and once we stop pretending that the l1tol2message source is not
|
|
728
|
-
// just the archiver under a different name
|
|
729
|
-
(!l2BlockSource.hash || p2p.hash === l2BlockSource.hash) &&
|
|
730
|
-
l1ToL2MessageSource === l2BlockSource.number;
|
|
728
|
+
l2BlockSource.hash === undefined
|
|
729
|
+
? worldState.number === 0 && p2p.number === 0 && l1ToL2MessageSource.number === 0
|
|
730
|
+
: worldState.hash === l2BlockSource.hash &&
|
|
731
|
+
p2p.hash === l2BlockSource.hash &&
|
|
732
|
+
l1ToL2MessageSource.hash === l2BlockSource.hash;
|
|
731
733
|
|
|
732
734
|
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
p2pNumber: p2p.number,
|
|
738
|
-
p2pHash: p2p.hash,
|
|
739
|
-
l1ToL2MessageSourceNumber: l1ToL2MessageSource,
|
|
735
|
+
worldState,
|
|
736
|
+
l2BlockSource,
|
|
737
|
+
p2p,
|
|
738
|
+
l1ToL2MessageSource,
|
|
740
739
|
});
|
|
741
740
|
|
|
742
741
|
if (!result) {
|
|
743
742
|
return undefined;
|
|
744
743
|
}
|
|
744
|
+
|
|
745
745
|
if (worldState.number >= INITIAL_L2_BLOCK_NUM) {
|
|
746
746
|
const block = await this.l2BlockSource.getBlock(worldState.number);
|
|
747
747
|
if (!block) {
|
|
@@ -4,8 +4,22 @@ import type { SequencerMetrics } from './metrics.js';
|
|
|
4
4
|
import { SequencerState } from './utils.js';
|
|
5
5
|
|
|
6
6
|
export class SequencerTimetable {
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* How late into the slot can we be to start working. Computed as the total time needed for assembling and publishing a block,
|
|
9
|
+
* assuming an execution time equal to `minExecutionTime`, subtracted from the slot duration. This means that, if the proposer
|
|
10
|
+
* starts building at this time, and all times hold, it will have at least `minExecutionTime` to execute txs for the block.
|
|
11
|
+
*/
|
|
12
|
+
public readonly initializeDeadline: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
16
|
+
* but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
17
|
+
* we can just post in the very last second of the L1 slot and still expect the tx to be accepted.
|
|
18
|
+
*/
|
|
19
|
+
public readonly l1PublishingTime;
|
|
20
|
+
|
|
21
|
+
/** What's the minimum time we want to leave available for execution and reexecution (used to derive init deadline) */
|
|
22
|
+
public readonly minExecutionTime = 1;
|
|
9
23
|
|
|
10
24
|
/** How long it takes to get ready to start building */
|
|
11
25
|
public readonly blockPrepareTime = 1;
|
|
@@ -16,13 +30,6 @@ export class SequencerTimetable {
|
|
|
16
30
|
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */
|
|
17
31
|
public readonly blockValidationTime = 1;
|
|
18
32
|
|
|
19
|
-
/**
|
|
20
|
-
* How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
21
|
-
* but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
22
|
-
* we can just post in the very last second of the L1 slot and still expect the tx to be accepted.
|
|
23
|
-
*/
|
|
24
|
-
public readonly l1PublishingTime;
|
|
25
|
-
|
|
26
33
|
constructor(
|
|
27
34
|
private readonly ethereumSlotDuration: number,
|
|
28
35
|
private readonly aztecSlotDuration: number,
|
|
@@ -32,6 +39,20 @@ export class SequencerTimetable {
|
|
|
32
39
|
private readonly log = createLogger('sequencer:timetable'),
|
|
33
40
|
) {
|
|
34
41
|
this.l1PublishingTime = this.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
42
|
+
|
|
43
|
+
const allWorkToDo =
|
|
44
|
+
this.blockPrepareTime +
|
|
45
|
+
this.minExecutionTime * 2 +
|
|
46
|
+
this.attestationPropagationTime * 2 +
|
|
47
|
+
this.blockValidationTime +
|
|
48
|
+
this.l1PublishingTime;
|
|
49
|
+
const initializeDeadline = this.aztecSlotDuration - allWorkToDo;
|
|
50
|
+
if (initializeDeadline <= 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Block proposal initialize deadline cannot be negative (got ${initializeDeadline} from total time needed ${allWorkToDo} and a slot duration of ${this.aztecSlotDuration}).`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
this.initializeDeadline = initializeDeadline;
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
private get afterBlockBuildingTimeNeededWithoutReexec() {
|
|
@@ -57,7 +78,7 @@ export class SequencerTimetable {
|
|
|
57
78
|
return this.attestationPropagationTime + this.l1PublishingTime;
|
|
58
79
|
}
|
|
59
80
|
|
|
60
|
-
public getValidatorReexecTimeEnd(secondsIntoSlot
|
|
81
|
+
public getValidatorReexecTimeEnd(secondsIntoSlot?: number): number {
|
|
61
82
|
// We need to leave for `afterBlockReexecTimeNeeded` seconds available.
|
|
62
83
|
const validationTimeEnd = this.aztecSlotDuration - this.afterBlockReexecTimeNeeded;
|
|
63
84
|
this.log.debug(`Validator re-execution time deadline is ${validationTimeEnd}`, {
|
|
@@ -75,9 +96,9 @@ export class SequencerTimetable {
|
|
|
75
96
|
case SequencerState.PROPOSER_CHECK:
|
|
76
97
|
return; // We don't really care about times for this states
|
|
77
98
|
case SequencerState.INITIALIZING_PROPOSAL:
|
|
78
|
-
return this.
|
|
99
|
+
return this.initializeDeadline;
|
|
79
100
|
case SequencerState.CREATING_BLOCK:
|
|
80
|
-
return this.
|
|
101
|
+
return this.initializeDeadline + this.blockPrepareTime;
|
|
81
102
|
case SequencerState.COLLECTING_ATTESTATIONS:
|
|
82
103
|
return this.aztecSlotDuration - this.l1PublishingTime - 2 * this.attestationPropagationTime;
|
|
83
104
|
case SequencerState.PUBLISHING_BLOCK:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/fields';
|
|
2
2
|
import {
|
|
3
3
|
AggregateTxValidator,
|
|
4
|
+
ArchiveCache,
|
|
4
5
|
BlockHeaderTxValidator,
|
|
5
6
|
DataTxValidator,
|
|
6
7
|
DoubleSpendTxValidator,
|
|
@@ -20,7 +21,6 @@ import type {
|
|
|
20
21
|
import { DatabasePublicStateSource, type PublicStateSource } from '@aztec/stdlib/trees';
|
|
21
22
|
import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
|
|
22
23
|
|
|
23
|
-
import { ArchiveCache } from './archive_cache.js';
|
|
24
24
|
import { NullifierCache } from './nullifier_cache.js';
|
|
25
25
|
|
|
26
26
|
export function createValidatorForAcceptingTxs(
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import type { ArchiveSource } from '@aztec/p2p';
|
|
3
|
-
import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
|
|
4
|
-
/**
|
|
5
|
-
* Implements an archive source by checking a DB and an in-memory collection.
|
|
6
|
-
* Intended for validating transactions as they are added to a block.
|
|
7
|
-
*/
|
|
8
|
-
export declare class ArchiveCache implements ArchiveSource {
|
|
9
|
-
private db;
|
|
10
|
-
archives: Map<string, bigint>;
|
|
11
|
-
constructor(db: MerkleTreeReadOperations);
|
|
12
|
-
getArchiveIndices(archives: Fr[]): Promise<(bigint | undefined)[]>;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=archive_cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"archive_cache.d.ts","sourceRoot":"","sources":["../../src/tx_validator/archive_cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAGhF;;;GAGG;AACH,qBAAa,YAAa,YAAW,aAAa;IAGpC,OAAO,CAAC,EAAE;IAFtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAEV,EAAE,EAAE,wBAAwB;IAInC,iBAAiB,CAAC,QAAQ,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;CAWhF"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
2
|
-
/**
|
|
3
|
-
* Implements an archive source by checking a DB and an in-memory collection.
|
|
4
|
-
* Intended for validating transactions as they are added to a block.
|
|
5
|
-
*/ export class ArchiveCache {
|
|
6
|
-
db;
|
|
7
|
-
archives;
|
|
8
|
-
constructor(db){
|
|
9
|
-
this.db = db;
|
|
10
|
-
this.archives = new Map();
|
|
11
|
-
}
|
|
12
|
-
async getArchiveIndices(archives) {
|
|
13
|
-
const toCheckDb = archives.filter((n)=>!this.archives.has(n.toString()));
|
|
14
|
-
const dbHits = await this.db.findLeafIndices(MerkleTreeId.ARCHIVE, toCheckDb);
|
|
15
|
-
dbHits.forEach((x, index)=>{
|
|
16
|
-
if (x !== undefined) {
|
|
17
|
-
this.archives.set(toCheckDb[index].toString(), x);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
return archives.map((n)=>this.archives.get(n.toString()));
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Fr } from '@aztec/foundation/fields';
|
|
2
|
-
import type { ArchiveSource } from '@aztec/p2p';
|
|
3
|
-
import type { MerkleTreeReadOperations } from '@aztec/stdlib/interfaces/server';
|
|
4
|
-
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Implements an archive source by checking a DB and an in-memory collection.
|
|
8
|
-
* Intended for validating transactions as they are added to a block.
|
|
9
|
-
*/
|
|
10
|
-
export class ArchiveCache implements ArchiveSource {
|
|
11
|
-
archives: Map<string, bigint>;
|
|
12
|
-
|
|
13
|
-
constructor(private db: MerkleTreeReadOperations) {
|
|
14
|
-
this.archives = new Map<string, bigint>();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
public async getArchiveIndices(archives: Fr[]): Promise<(bigint | undefined)[]> {
|
|
18
|
-
const toCheckDb = archives.filter(n => !this.archives.has(n.toString()));
|
|
19
|
-
const dbHits = await this.db.findLeafIndices(MerkleTreeId.ARCHIVE, toCheckDb);
|
|
20
|
-
dbHits.forEach((x, index) => {
|
|
21
|
-
if (x !== undefined) {
|
|
22
|
-
this.archives.set(toCheckDb[index].toString(), x);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
return archives.map(n => this.archives.get(n.toString()));
|
|
27
|
-
}
|
|
28
|
-
}
|