diff --git a/docs/docs/reference/sandbox_reference/sandbox-reference.md b/docs/docs/reference/sandbox_reference/sandbox-reference.md index ff16d411ad9..00496d916f5 100644 --- a/docs/docs/reference/sandbox_reference/sandbox-reference.md +++ b/docs/docs/reference/sandbox_reference/sandbox-reference.md @@ -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** diff --git a/yarn-project/aztec/terraform/node/main.tf b/yarn-project/aztec/terraform/node/main.tf index 0c75330c9e2..71963fd8074 100644 --- a/yarn-project/aztec/terraform/node/main.tf +++ b/yarn-project/aztec/terraform/node/main.tf @@ -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] diff --git a/yarn-project/aztec/terraform/node/variables.tf b/yarn-project/aztec/terraform/node/variables.tf index 9f644c89139..a9a69fff3c5 100644 --- a/yarn-project/aztec/terraform/node/variables.tf +++ b/yarn-project/aztec/terraform/node/variables.tf @@ -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" { diff --git a/yarn-project/circuit-types/src/interfaces/configs.ts b/yarn-project/circuit-types/src/interfaces/configs.ts index cd75cef56da..bfbc4058ed6 100644 --- a/yarn-project/circuit-types/src/interfaces/configs.ts +++ b/yarn-project/circuit-types/src/interfaces/configs.ts @@ -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. */ diff --git a/yarn-project/end-to-end/scripts/docker-compose-p2p.yml b/yarn-project/end-to-end/scripts/docker-compose-p2p.yml index 788a5ad02f1..dd07cd209f2 100644 --- a/yarn-project/end-to-end/scripts/docker-compose-p2p.yml +++ b/yarn-project/end-to-end/scripts/docker-compose-p2p.yml @@ -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' diff --git a/yarn-project/end-to-end/scripts/start_p2p_e2e.sh b/yarn-project/end-to-end/scripts/start_p2p_e2e.sh index bd04e091e96..88b21e9f6ee 100755 --- a/yarn-project/end-to-end/scripts/start_p2p_e2e.sh +++ b/yarn-project/end-to-end/scripts/start_p2p_e2e.sh @@ -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' diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index 5fe566e154f..cf24b865947 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -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, @@ -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, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index a1241e9ee50..3a6ccb0cf72 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -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; @@ -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; } @@ -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 @@ -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( @@ -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()); @@ -233,6 +266,11 @@ 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(), })) @@ -240,6 +278,7 @@ export class Sequencer { validTxs: Tx[], newGlobalVariables: GlobalVariables, historicalHeader: Header | undefined, + elapsedSinceLastBlock: number, ): Promise { const workTimer = new Timer(); this.state = SequencerState.CREATING_BLOCK; @@ -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;