@aztec/sequencer-client 0.0.1-commit.e0f15ab9b → 0.0.1-commit.e304674f1
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 +1 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +0 -4
- package/dest/global_variable_builder/global_builder.d.ts +3 -3
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +7 -4
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -1
- package/dest/publisher/sequencer-publisher.d.ts +52 -31
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +106 -87
- package/dest/sequencer/checkpoint_proposal_job.d.ts +31 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +179 -108
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/sequencer.d.ts +14 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +67 -18
- package/dest/sequencer/timetable.d.ts +4 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +15 -5
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +0 -7
- package/src/global_variable_builder/global_builder.ts +15 -3
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +174 -124
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +215 -117
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/sequencer.ts +97 -20
- package/src/sequencer/timetable.ts +19 -8
|
@@ -13,7 +13,7 @@ import type { TypedEventEmitter } from '@aztec/foundation/types';
|
|
|
13
13
|
import type { P2P } from '@aztec/p2p';
|
|
14
14
|
import type { SlasherClientInterface } from '@aztec/slasher';
|
|
15
15
|
import type { BlockData, L2BlockSink, L2BlockSource, ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
16
|
-
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
16
|
+
import type { Checkpoint, ProposedCheckpointData } from '@aztec/stdlib/checkpoint';
|
|
17
17
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
18
18
|
import {
|
|
19
19
|
type ResolvedSequencerConfig,
|
|
@@ -72,6 +72,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
72
72
|
/** The last epoch for which we logged strategy comparison in fisherman mode. */
|
|
73
73
|
private lastEpochForStrategyComparison: EpochNumber | undefined;
|
|
74
74
|
|
|
75
|
+
/** The last checkpoint proposal job, tracked so we can await its pending L1 submission during shutdown. */
|
|
76
|
+
private lastCheckpointProposalJob: CheckpointProposalJob | undefined;
|
|
77
|
+
|
|
75
78
|
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
76
79
|
protected timetable!: SequencerTimetable;
|
|
77
80
|
|
|
@@ -120,6 +123,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
120
123
|
p2pPropagationTime: this.config.attestationPropagationTime,
|
|
121
124
|
blockDurationMs: this.config.blockDurationMs,
|
|
122
125
|
enforce: this.config.enforceTimeTable,
|
|
126
|
+
pipelining: this.epochCache.isProposerPipeliningEnabled(),
|
|
123
127
|
},
|
|
124
128
|
this.metrics,
|
|
125
129
|
this.log,
|
|
@@ -149,6 +153,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
149
153
|
this.setState(SequencerState.STOPPING, undefined, { force: true });
|
|
150
154
|
await this.publisherFactory.stopAll();
|
|
151
155
|
await this.runningPromise?.stop();
|
|
156
|
+
await this.lastCheckpointProposalJob?.awaitPendingSubmission();
|
|
152
157
|
this.setState(SequencerState.STOPPED, undefined, { force: true });
|
|
153
158
|
this.log.info('Stopped sequencer');
|
|
154
159
|
}
|
|
@@ -208,6 +213,9 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
208
213
|
return;
|
|
209
214
|
}
|
|
210
215
|
|
|
216
|
+
// Track the job so we can await its pending L1 submission during shutdown
|
|
217
|
+
this.lastCheckpointProposalJob = checkpointProposalJob;
|
|
218
|
+
|
|
211
219
|
// Execute the checkpoint proposal job
|
|
212
220
|
const checkpoint = await checkpointProposalJob.execute();
|
|
213
221
|
|
|
@@ -234,7 +242,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
234
242
|
* @returns CheckpointProposalJob if successful, undefined if we are not yet synced or are not the proposer.
|
|
235
243
|
*/
|
|
236
244
|
@trackSpan('Sequencer.prepareCheckpointProposal')
|
|
237
|
-
|
|
245
|
+
protected async prepareCheckpointProposal(
|
|
238
246
|
slot: SlotNumber,
|
|
239
247
|
targetSlot: SlotNumber,
|
|
240
248
|
epoch: EpochNumber,
|
|
@@ -312,6 +320,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
312
320
|
return undefined;
|
|
313
321
|
}
|
|
314
322
|
|
|
323
|
+
// Guard: don't exceed 1-deep pipeline. Without a proposed checkpoint, we can only build
|
|
324
|
+
// confirmed + 1. With a proposed checkpoint, we can build confirmed + 2.
|
|
325
|
+
const confirmedCkpt = syncedTo.checkpointedCheckpointNumber;
|
|
326
|
+
if (checkpointNumber > confirmedCkpt + 2) {
|
|
327
|
+
this.log.verbose(
|
|
328
|
+
`Skipping slot ${targetSlot}: checkpoint ${checkpointNumber} exceeds max pipeline depth (confirmed=${confirmedCkpt})`,
|
|
329
|
+
);
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
|
|
315
333
|
// Check that the target slot is not taken by a block already (should never happen, since only us can propose for this slot)
|
|
316
334
|
if (syncedTo.blockData && syncedTo.blockData.header.getSlot() >= targetSlot) {
|
|
317
335
|
this.log.warn(
|
|
@@ -337,13 +355,41 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
337
355
|
}
|
|
338
356
|
|
|
339
357
|
// Prepare invalidation request if the pending chain is invalid (returns undefined if no need)
|
|
340
|
-
|
|
358
|
+
let invalidateCheckpoint = await publisher.simulateInvalidateCheckpoint(syncedTo.pendingChainValidationStatus);
|
|
359
|
+
|
|
360
|
+
// Determine the correct archive and L1 state overrides for the canProposeAt check.
|
|
361
|
+
// The L1 contract reads archives[proposedCheckpointNumber] and compares it with the provided archive.
|
|
362
|
+
// When invalidating or pipelining, the local archive may differ from L1's, so we adjust accordingly.
|
|
363
|
+
let archiveForCheck = syncedTo.archive;
|
|
364
|
+
const l1Overrides: {
|
|
365
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
366
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
367
|
+
} = {};
|
|
368
|
+
|
|
369
|
+
if (this.epochCache.isProposerPipeliningEnabled() && syncedTo.hasProposedCheckpoint) {
|
|
370
|
+
// Parent checkpoint hasn't landed on L1 yet. Override both the proposed checkpoint number
|
|
371
|
+
// and the archive at that checkpoint so L1 simulation sees the correct chain tip.
|
|
372
|
+
const parentCheckpointNumber = CheckpointNumber(checkpointNumber - 1);
|
|
373
|
+
l1Overrides.forcePendingCheckpointNumber = parentCheckpointNumber;
|
|
374
|
+
l1Overrides.forceArchive = { checkpointNumber: parentCheckpointNumber, archive: syncedTo.archive };
|
|
375
|
+
this.metrics.recordPipelineDepth(1);
|
|
376
|
+
|
|
377
|
+
this.log.verbose(
|
|
378
|
+
`Building on top of proposed checkpoint (pending=${syncedTo.proposedCheckpointData?.checkpointNumber})`,
|
|
379
|
+
);
|
|
380
|
+
// Clear the invalidation - the proposed checkpoint should handle it.
|
|
381
|
+
invalidateCheckpoint = undefined;
|
|
382
|
+
} else if (invalidateCheckpoint) {
|
|
383
|
+
// After invalidation, L1 will roll back to checkpoint N-1. The archive at N-1 already
|
|
384
|
+
// exists on L1, so we just pass the matching archive (the lastArchive of the invalid checkpoint).
|
|
385
|
+
archiveForCheck = invalidateCheckpoint.lastArchive;
|
|
386
|
+
l1Overrides.forcePendingCheckpointNumber = invalidateCheckpoint.forcePendingCheckpointNumber;
|
|
387
|
+
this.metrics.recordPipelineDepth(0);
|
|
388
|
+
} else {
|
|
389
|
+
this.metrics.recordPipelineDepth(0);
|
|
390
|
+
}
|
|
341
391
|
|
|
342
|
-
|
|
343
|
-
// if all the previous checks are good, but we do it just in case.
|
|
344
|
-
const canProposeCheck = await publisher.canProposeAt(syncedTo.archive, proposer ?? EthAddress.ZERO, {
|
|
345
|
-
...invalidateCheckpoint,
|
|
346
|
-
});
|
|
392
|
+
const canProposeCheck = await publisher.canProposeAt(archiveForCheck, proposer ?? EthAddress.ZERO, l1Overrides);
|
|
347
393
|
|
|
348
394
|
if (canProposeCheck === undefined) {
|
|
349
395
|
this.log.warn(
|
|
@@ -391,7 +437,6 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
391
437
|
return this.createCheckpointProposalJob(
|
|
392
438
|
slot,
|
|
393
439
|
targetSlot,
|
|
394
|
-
epoch,
|
|
395
440
|
targetEpoch,
|
|
396
441
|
checkpointNumber,
|
|
397
442
|
syncedTo.blockNumber,
|
|
@@ -399,13 +444,13 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
399
444
|
publisher,
|
|
400
445
|
attestorAddress,
|
|
401
446
|
invalidateCheckpoint,
|
|
447
|
+
syncedTo.proposedCheckpointData,
|
|
402
448
|
);
|
|
403
449
|
}
|
|
404
450
|
|
|
405
451
|
protected createCheckpointProposalJob(
|
|
406
452
|
slot: SlotNumber,
|
|
407
453
|
targetSlot: SlotNumber,
|
|
408
|
-
epoch: EpochNumber,
|
|
409
454
|
targetEpoch: EpochNumber,
|
|
410
455
|
checkpointNumber: CheckpointNumber,
|
|
411
456
|
syncedToBlockNumber: BlockNumber,
|
|
@@ -413,11 +458,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
413
458
|
publisher: SequencerPublisher,
|
|
414
459
|
attestorAddress: EthAddress,
|
|
415
460
|
invalidateCheckpoint: InvalidateCheckpointRequest | undefined,
|
|
461
|
+
proposedCheckpointData?: ProposedCheckpointData,
|
|
416
462
|
): CheckpointProposalJob {
|
|
417
463
|
return new CheckpointProposalJob(
|
|
418
464
|
slot,
|
|
419
465
|
targetSlot,
|
|
420
|
-
epoch,
|
|
421
466
|
targetEpoch,
|
|
422
467
|
checkpointNumber,
|
|
423
468
|
syncedToBlockNumber,
|
|
@@ -444,6 +489,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
444
489
|
this.setState.bind(this),
|
|
445
490
|
this.tracer,
|
|
446
491
|
this.log.getBindings(),
|
|
492
|
+
proposedCheckpointData,
|
|
447
493
|
);
|
|
448
494
|
}
|
|
449
495
|
|
|
@@ -518,25 +564,43 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
518
564
|
number: syncSummary.latestBlockNumber,
|
|
519
565
|
hash: syncSummary.latestBlockHash,
|
|
520
566
|
})),
|
|
521
|
-
this.l2BlockSource
|
|
567
|
+
this.l2BlockSource
|
|
568
|
+
.getL2Tips()
|
|
569
|
+
.then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed, proposedCheckpoint: t.proposedCheckpoint })),
|
|
522
570
|
this.p2pClient.getStatus().then(p2p => p2p.syncedToL2Block),
|
|
523
|
-
this.l1ToL2MessageSource.getL2Tips().then(t => t.proposed),
|
|
571
|
+
this.l1ToL2MessageSource.getL2Tips().then(t => ({ proposed: t.proposed, checkpointed: t.checkpointed })),
|
|
524
572
|
this.l2BlockSource.getPendingChainValidationStatus(),
|
|
573
|
+
this.l2BlockSource.getProposedCheckpointOnly(),
|
|
525
574
|
] as const);
|
|
526
575
|
|
|
527
|
-
const [worldState,
|
|
576
|
+
const [worldState, l2Tips, p2p, l1ToL2MessageSourceTips, pendingChainValidationStatus, proposedCheckpointData] =
|
|
577
|
+
syncedBlocks;
|
|
528
578
|
|
|
529
579
|
// Handle zero as a special case, since the block hash won't match across services if we're changing the prefilled data for the genesis block,
|
|
530
580
|
// as the world state can compute the new genesis block hash, but other components use the hardcoded constant.
|
|
531
581
|
// TODO(palla/mbps): Fix the above. All components should be able to handle dynamic genesis block hashes.
|
|
532
582
|
const result =
|
|
533
|
-
(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
583
|
+
(l2Tips.proposed.number === 0 &&
|
|
584
|
+
l2Tips.checkpointed.block.number === 0 &&
|
|
585
|
+
l2Tips.checkpointed.checkpoint.number === 0 &&
|
|
586
|
+
worldState.number === 0 &&
|
|
587
|
+
p2p.number === 0 &&
|
|
588
|
+
l1ToL2MessageSourceTips.proposed.number === 0 &&
|
|
589
|
+
l1ToL2MessageSourceTips.checkpointed.block.number === 0 &&
|
|
590
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.number === 0) ||
|
|
591
|
+
(worldState.hash === l2Tips.proposed.hash &&
|
|
592
|
+
p2p.hash === l2Tips.proposed.hash &&
|
|
593
|
+
l1ToL2MessageSourceTips.proposed.hash === l2Tips.proposed.hash &&
|
|
594
|
+
l1ToL2MessageSourceTips.checkpointed.block.hash === l2Tips.checkpointed.block.hash &&
|
|
595
|
+
l1ToL2MessageSourceTips.checkpointed.checkpoint.hash === l2Tips.checkpointed.checkpoint.hash);
|
|
537
596
|
|
|
538
597
|
if (!result) {
|
|
539
|
-
this.log.debug(`Sequencer sync check failed`, {
|
|
598
|
+
this.log.debug(`Sequencer sync check failed`, {
|
|
599
|
+
worldState,
|
|
600
|
+
l2BlockSource: l2Tips.proposed,
|
|
601
|
+
p2p,
|
|
602
|
+
l1ToL2MessageSourceTips,
|
|
603
|
+
});
|
|
540
604
|
return undefined;
|
|
541
605
|
}
|
|
542
606
|
|
|
@@ -546,8 +610,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
546
610
|
const archive = new Fr((await this.worldState.getCommitted().getTreeInfo(MerkleTreeId.ARCHIVE)).root);
|
|
547
611
|
return {
|
|
548
612
|
checkpointNumber: CheckpointNumber.ZERO,
|
|
613
|
+
checkpointedCheckpointNumber: CheckpointNumber.ZERO,
|
|
549
614
|
blockNumber: BlockNumber.ZERO,
|
|
550
615
|
archive,
|
|
616
|
+
hasProposedCheckpoint: false,
|
|
551
617
|
syncedL2Slot,
|
|
552
618
|
pendingChainValidationStatus,
|
|
553
619
|
};
|
|
@@ -560,11 +626,16 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
560
626
|
return undefined;
|
|
561
627
|
}
|
|
562
628
|
|
|
629
|
+
const hasProposedCheckpoint = l2Tips.proposedCheckpoint.checkpoint.number > l2Tips.checkpointed.checkpoint.number;
|
|
630
|
+
|
|
563
631
|
return {
|
|
564
632
|
blockData,
|
|
565
633
|
blockNumber: blockData.header.getBlockNumber(),
|
|
566
634
|
checkpointNumber: blockData.checkpointNumber,
|
|
635
|
+
checkpointedCheckpointNumber: l2Tips.checkpointed.checkpoint.number,
|
|
567
636
|
archive: blockData.archive.root,
|
|
637
|
+
hasProposedCheckpoint,
|
|
638
|
+
proposedCheckpointData,
|
|
568
639
|
syncedL2Slot,
|
|
569
640
|
pendingChainValidationStatus,
|
|
570
641
|
};
|
|
@@ -612,7 +683,10 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
612
683
|
return [false, proposer];
|
|
613
684
|
}
|
|
614
685
|
|
|
615
|
-
this.log.
|
|
686
|
+
this.log.info(`We are the proposer for pipeline slot ${targetSlot}`, {
|
|
687
|
+
targetSlot,
|
|
688
|
+
proposer,
|
|
689
|
+
});
|
|
616
690
|
return [true, proposer];
|
|
617
691
|
}
|
|
618
692
|
|
|
@@ -909,8 +983,11 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
909
983
|
type SequencerSyncCheckResult = {
|
|
910
984
|
blockData?: BlockData;
|
|
911
985
|
checkpointNumber: CheckpointNumber;
|
|
986
|
+
checkpointedCheckpointNumber: CheckpointNumber;
|
|
912
987
|
blockNumber: BlockNumber;
|
|
913
988
|
archive: Fr;
|
|
989
|
+
hasProposedCheckpoint: boolean;
|
|
990
|
+
proposedCheckpointData?: ProposedCheckpointData;
|
|
914
991
|
syncedL2Slot: SlotNumber;
|
|
915
992
|
pendingChainValidationStatus: ValidateCheckpointResult;
|
|
916
993
|
};
|
|
@@ -70,6 +70,9 @@ export class SequencerTimetable {
|
|
|
70
70
|
/** Maximum number of blocks that can be built in this slot configuration */
|
|
71
71
|
public readonly maxNumberOfBlocks: number;
|
|
72
72
|
|
|
73
|
+
/** Whether pipelining is enabled (checkpoint finalization deferred to next slot). */
|
|
74
|
+
public readonly pipelining: boolean;
|
|
75
|
+
|
|
73
76
|
constructor(
|
|
74
77
|
opts: {
|
|
75
78
|
ethereumSlotDuration: number;
|
|
@@ -78,6 +81,7 @@ export class SequencerTimetable {
|
|
|
78
81
|
p2pPropagationTime?: number;
|
|
79
82
|
blockDurationMs?: number;
|
|
80
83
|
enforce: boolean;
|
|
84
|
+
pipelining?: boolean;
|
|
81
85
|
},
|
|
82
86
|
private readonly metrics?: SequencerMetrics,
|
|
83
87
|
private readonly log?: Logger,
|
|
@@ -88,6 +92,7 @@ export class SequencerTimetable {
|
|
|
88
92
|
this.p2pPropagationTime = opts.p2pPropagationTime ?? DEFAULT_P2P_PROPAGATION_TIME;
|
|
89
93
|
this.blockDuration = opts.blockDurationMs ? opts.blockDurationMs / 1000 : undefined;
|
|
90
94
|
this.enforce = opts.enforce;
|
|
95
|
+
this.pipelining = opts.pipelining ?? false;
|
|
91
96
|
|
|
92
97
|
// Assume zero-cost propagation time and faster runs in test environments where L1 slot duration is shortened
|
|
93
98
|
if (this.ethereumSlotDuration < 8) {
|
|
@@ -116,18 +121,23 @@ export class SequencerTimetable {
|
|
|
116
121
|
if (!this.blockDuration) {
|
|
117
122
|
this.maxNumberOfBlocks = 1; // Single block per slot
|
|
118
123
|
} else {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
// When pipelining, finalization is deferred to the next slot, but we still need
|
|
125
|
+
// a sub-slot for validator re-execution so they can produce attestations.
|
|
126
|
+
let timeReservedAtEnd = this.blockDuration; // Validatior re-execution only
|
|
127
|
+
if (!this.pipelining) {
|
|
128
|
+
timeReservedAtEnd += this.checkpointFinalizationTime;
|
|
129
|
+
}
|
|
130
|
+
|
|
122
131
|
const timeAvailableForBlocks = this.aztecSlotDuration - this.initializationOffset - timeReservedAtEnd;
|
|
123
132
|
this.maxNumberOfBlocks = Math.floor(timeAvailableForBlocks / this.blockDuration);
|
|
124
133
|
}
|
|
125
134
|
|
|
126
|
-
// Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.checkpointFinalizationTime;
|
|
135
|
+
// Minimum work to do within a slot for building a block with the minimum time for execution and publishing its checkpoint.
|
|
136
|
+
// When pipelining, finalization is deferred, but we still need time for execution and validator re-execution.
|
|
137
|
+
let minWorkToDo = this.initializationOffset + this.minExecutionTime * 2;
|
|
138
|
+
if (!this.pipelining) {
|
|
139
|
+
minWorkToDo += this.checkpointFinalizationTime;
|
|
140
|
+
}
|
|
131
141
|
|
|
132
142
|
const initializeDeadline = this.aztecSlotDuration - minWorkToDo;
|
|
133
143
|
this.initializeDeadline = initializeDeadline;
|
|
@@ -144,6 +154,7 @@ export class SequencerTimetable {
|
|
|
144
154
|
blockAssembleTime: this.checkpointAssembleTime,
|
|
145
155
|
initializeDeadline: this.initializeDeadline,
|
|
146
156
|
enforce: this.enforce,
|
|
157
|
+
pipelining: this.pipelining,
|
|
147
158
|
minWorkToDo,
|
|
148
159
|
blockDuration: this.blockDuration,
|
|
149
160
|
maxNumberOfBlocks: this.maxNumberOfBlocks,
|