Skip to content

Commit

Permalink
feat: Min and max block times for sequencer (#7630)
Browse files Browse the repository at this point in the history
Adds two new config variables for the sequencer: min and max block time
in seconds. The sequencer now checks the current time against the
previous block. If it's less than min, it doesn't build a block. If it's
more than max, it builds the block even with no txs.
  • Loading branch information
spalladino authored Jul 26, 2024
1 parent 022a899 commit 2a013b8
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 13 deletions.
2 changes: 2 additions & 0 deletions docs/docs/reference/sandbox_reference/sandbox-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ ROLLUP_CONTRACT_ADDRESS=0x01234567890abcde01234567890abcde
SEQ_PUBLISHER_PRIVATE_KEY=0x01234567890abcde01234567890abcde # Private key of an ethereum account that will be used by the sequencer to publish blocks.
SEQ_MAX_TX_PER_BLOCK=32 # Maximum txs to go on a block. (default: 32)
SEQ_MIN_TX_PER_BLOCK=1 # Minimum txs to go on a block. (default: 1)
SEQ_MAX_SECONDS_BETWEEN_BLOCKS=0 # Sequencer will produce a block with less than the min number of txs once this threshold is reached. (default: 0, means disabled)
SEQ_MIN_SECONDS_BETWEEN_BLOCKS=0 # Minimum seconds to wait between consecutive blocks. (default: 0)
```

**PXE**
Expand Down
8 changes: 8 additions & 0 deletions yarn-project/aztec/terraform/node/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,14 @@ resource "aws_ecs_task_definition" "aztec-node" {
name = "SEQ_MIN_TX_PER_BLOCK"
value = var.SEQ_MIN_TX_PER_BLOCK
},
{
name = "SEQ_MAX_SECONDS_BETWEEN_BLOCKS"
value = var.SEQ_MAX_SECONDS_BETWEEN_BLOCKS
},
{
name = "SEQ_MIN_SECONDS_BETWEEN_BLOCKS"
value = var.SEQ_MIN_SECONDS_BETWEEN_BLOCKS
},
{
name = "SEQ_PUBLISHER_PRIVATE_KEY"
value = local.sequencer_private_keys[count.index]
Expand Down
12 changes: 11 additions & 1 deletion yarn-project/aztec/terraform/node/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,17 @@ variable "SEQ_MAX_TX_PER_BLOCK" {

variable "SEQ_MIN_TX_PER_BLOCK" {
type = string
default = 1
default = 0
}

variable "SEQ_MAX_SECONDS_BETWEEN_BLOCKS" {
type = string
default = 60
}

variable "SEQ_MIN_SECONDS_BETWEEN_BLOCKS" {
type = string
default = 30
}

variable "P2P_MIN_PEERS" {
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/circuit-types/src/interfaces/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export interface SequencerConfig {
maxTxsPerBlock?: number;
/** The minimum number of txs to include in a block. */
minTxsPerBlock?: number;
/** The minimum number of seconds inbetween consecutive blocks. */
minSecondsBetweenBlocks?: number;
/** The maximum number of seconds inbetween consecutive blocks. Sequencer will produce a block with less than minTxsPerBlock once this threshold is reached. */
maxSecondsBetweenBlocks?: number;
/** Recipient of block reward. */
coinbase?: EthAddress;
/** Address to receive fees. */
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/scripts/docker-compose-p2p.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ services:
WS_CHECK_INTERVAL: 50
SEQ_MAX_TX_PER_BLOCK: 32
SEQ_MIN_TX_PER_BLOCK: 1
SEQ_MAX_SECONDS_BETWEEN_BLOCKS: 0
SEQ_MIN_SECONDS_BETWEEN_BLOCKS: 0
P2P_TCP_LISTEN_ADDR: '0.0.0.0:40400'
P2P_NAT_ENABLED: 'false'
P2P_ENABLED: 'true'
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/scripts/start_p2p_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export WS_CHECK_INTERVAL=50
export SEQ_TX_POLLING_INTERVAL=50
export SEQ_MAX_TX_PER_BLOCK=32
export SEQ_MIN_TX_PER_BLOCK=32
export SEQ_MAX_SECONDS_BETWEEN_BLOCKS=0
export SEQ_MIN_SECONDS_BETWEEN_BLOCKS=0
export BOOTSTRAP_NODES='/ip4/127.0.0.1/tcp/40400/p2p/12D3KooWGBpbC6qQFkaCYphjNeY6sV99o4SnEWyTeBigoVriDn4D'
export P2P_TCP_LISTEN_ADDR='0.0.0.0:40400'
export P2P_NAT_ENABLED='false'
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/sequencer-client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export function getConfigEnvVars(): SequencerClientConfig {
SEQ_TX_POLLING_INTERVAL_MS,
SEQ_MAX_TX_PER_BLOCK,
SEQ_MIN_TX_PER_BLOCK,
SEQ_MAX_SECONDS_BETWEEN_BLOCKS,
SEQ_MIN_SECONDS_BETWEEN_BLOCKS,
SEQ_ALLOWED_SETUP_FN,
SEQ_ALLOWED_TEARDOWN_FN,
SEQ_MAX_BLOCK_SIZE_IN_BYTES,
Expand All @@ -58,6 +60,8 @@ export function getConfigEnvVars(): SequencerClientConfig {
l1Contracts: getL1ContractAddressesFromEnv(),
maxTxsPerBlock: SEQ_MAX_TX_PER_BLOCK ? +SEQ_MAX_TX_PER_BLOCK : 32,
minTxsPerBlock: SEQ_MIN_TX_PER_BLOCK ? +SEQ_MIN_TX_PER_BLOCK : 1,
maxSecondsBetweenBlocks: SEQ_MAX_SECONDS_BETWEEN_BLOCKS ? +SEQ_MAX_SECONDS_BETWEEN_BLOCKS : 0,
minSecondsBetweenBlocks: SEQ_MIN_SECONDS_BETWEEN_BLOCKS ? +SEQ_MIN_SECONDS_BETWEEN_BLOCKS : 0,
sequencerSkipSubmitProofs: ['1', 'true'].includes(SEQ_SKIP_SUBMIT_PROOFS ?? ''),
// TODO: undefined should not be allowed for the following 2 values in PROD
coinbase: COINBASE ? EthAddress.fromString(COINBASE) : undefined,
Expand Down
67 changes: 55 additions & 12 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export class Sequencer {
private pollingIntervalMs: number = 1000;
private maxTxsPerBlock = 32;
private minTxsPerBLock = 1;
private minSecondsBetweenBlocks = 0;
private maxSecondsBetweenBlocks = 0;
// TODO: zero values should not be allowed for the following 2 values in PROD
private _coinbase = EthAddress.ZERO;
private _feeRecipient = AztecAddress.ZERO;
Expand Down Expand Up @@ -78,15 +80,21 @@ export class Sequencer {
* @param config - New parameters.
*/
public updateConfig(config: SequencerConfig) {
if (config.transactionPollingIntervalMS) {
if (config.transactionPollingIntervalMS !== undefined) {
this.pollingIntervalMs = config.transactionPollingIntervalMS;
}
if (config.maxTxsPerBlock) {
if (config.maxTxsPerBlock !== undefined) {
this.maxTxsPerBlock = config.maxTxsPerBlock;
}
if (config.minTxsPerBlock) {
if (config.minTxsPerBlock !== undefined) {
this.minTxsPerBLock = config.minTxsPerBlock;
}
if (config.minSecondsBetweenBlocks !== undefined) {
this.minSecondsBetweenBlocks = config.minSecondsBetweenBlocks;
}
if (config.maxSecondsBetweenBlocks !== undefined) {
this.maxSecondsBetweenBlocks = config.maxSecondsBetweenBlocks;
}
if (config.coinbase) {
this._coinbase = config.coinbase;
}
Expand All @@ -96,7 +104,7 @@ export class Sequencer {
if (config.allowedInSetup) {
this.allowedInSetup = config.allowedInSetup;
}
if (config.maxBlockSizeInBytes) {
if (config.maxBlockSizeInBytes !== undefined) {
this.maxBlockSizeInBytes = config.maxBlockSizeInBytes;
}
// TODO(#5917) remove this. it is no longer needed since we don't need to whitelist functions in teardown
Expand Down Expand Up @@ -184,16 +192,37 @@ export class Sequencer {
return;
}

this.state = SequencerState.WAITING_FOR_TXS;
// Compute time elapsed since the previous block
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
const currentTime = Math.floor(Date.now() / 1000);
const elapsedSinceLastBlock = currentTime - lastBlockTime;

// Get txs to build the new block
const pendingTxs = this.p2pClient.getTxs('pending');
if (pendingTxs.length < this.minTxsPerBLock) {
// Do not go forward with new block if not enough time has passed since last block
if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
this.log.debug(
`Not creating block because there are not enough txs in the pool (got ${pendingTxs.length} min ${this.minTxsPerBLock})`,
`Not creating block because not enough time has passed since last block (last block at ${lastBlockTime} current time ${currentTime})`,
);
return;
}

this.state = SequencerState.WAITING_FOR_TXS;

// Get txs to build the new block.
const pendingTxs = this.p2pClient.getTxs('pending');

// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
if (pendingTxs.length < this.minTxsPerBLock) {
if (this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock)) {
this.log.debug(
`Creating block with only ${pendingTxs.length} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
);
} else {
this.log.debug(
`Not creating block because not enough txs in the pool (got ${pendingTxs.length} min ${this.minTxsPerBLock})`,
);
return;
}
}
this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);

const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
Expand All @@ -215,11 +244,15 @@ export class Sequencer {
// may break if we start emitting lots of log data from public-land.
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);

if (validTxs.length < this.minTxsPerBLock) {
// Bail if we don't have enough valid txs
if (!this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock) && validTxs.length < this.minTxsPerBLock) {
this.log.debug(
`Not creating block because not enough valid txs loaded from the pool (got ${validTxs.length} min ${this.minTxsPerBLock})`,
);
return;
}

await this.buildBlockAndPublish(validTxs, newGlobalVariables, historicalHeader);
await this.buildBlockAndPublish(validTxs, newGlobalVariables, historicalHeader, elapsedSinceLastBlock);
} catch (err) {
if (BlockProofError.isBlockProofError(err)) {
const txHashes = err.txHashes.filter(h => !h.isZero());
Expand All @@ -233,13 +266,19 @@ export class Sequencer {
}
}

/** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
private skipMinTxsPerBlockCheck(elapsed: number): boolean {
return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
}

@trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, newGlobalVariables, _historicalHeader) => ({
[Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber.toNumber(),
}))
private async buildBlockAndPublish(
validTxs: Tx[],
newGlobalVariables: GlobalVariables,
historicalHeader: Header | undefined,
elapsedSinceLastBlock: number,
): Promise<void> {
const workTimer = new Timer();
this.state = SequencerState.CREATING_BLOCK;
Expand Down Expand Up @@ -281,7 +320,11 @@ export class Sequencer {
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
}

if (processedTxs.length === 0) {
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
// we should bail.
if (processedTxs.length === 0 && !this.skipMinTxsPerBlockCheck(elapsedSinceLastBlock)) {
this.log.verbose('No txs processed correctly to build block. Exiting');
this.prover.cancelBlock();
return;
Expand Down

0 comments on commit 2a013b8

Please sign in to comment.