diff --git a/Cargo.lock b/Cargo.lock index 130b3bff21..a7b2887e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "astria-conductor" -version = "1.0.0" +version = "1.0.1" dependencies = [ "astria-build-info", "astria-config", diff --git a/charts/evm-rollup/Chart.yaml b/charts/evm-rollup/Chart.yaml index 482a162569..ab83b13991 100644 --- a/charts/evm-rollup/Chart.yaml +++ b/charts/evm-rollup/Chart.yaml @@ -15,13 +15,13 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.1 +version: 1.0.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.0.0" +appVersion: "1.0.2" maintainers: - name: wafflesvonmaple diff --git a/charts/evm-rollup/files/genesis/geth-genesis.json b/charts/evm-rollup/files/genesis/geth-genesis.json index 68027198d0..0a562717a7 100644 --- a/charts/evm-rollup/files/genesis/geth-genesis.json +++ b/charts/evm-rollup/files/genesis/geth-genesis.json @@ -15,10 +15,10 @@ {{- if .Values.genesis.cancunTime }} "cancunTime": {{ toString .Values.genesis.cancunTime | replace "\"" "" }}, {{- end }} - {{- if .Values.genesis.cancunTime }} + {{- if .Values.genesis.pragueTime }} "pragueTime": {{ toString .Values.genesis.pragueTime | replace "\"" "" }}, {{- end }} - {{- if .Values.genesis.cancunTime }} + {{- if .Values.genesis.verkleTime }} "verkleTime": {{ toString .Values.genesis.verkleTime | replace "\"" "" }}, {{- end }} "terminalTotalDifficulty": 0, @@ -27,18 +27,41 @@ {{- range $key, $value := .Values.genesis.extra }} "{{ $key }}": {{ toPrettyJson $value | indent 8 | trim }}, {{- end }} - {{- if .Values.genesis.extraDataOverride }} - "astriaExtraDataOverride": "{{ .Values.genesis.extraDataOverride }}", - {{- end }} "astriaOverrideGenesisExtraData": {{ .Values.genesis.overrideGenesisExtraData }}, - "astriaSequencerInitialHeight": {{ toString .Values.genesis.sequencerInitialHeight | replace "\"" "" }}, "astriaRollupName": "{{ tpl .Values.genesis.rollupName . }}", - "astriaCelestiaInitialHeight": {{ toString .Values.genesis.celestiaInitialHeight | replace "\"" "" }}, - "astriaCelestiaHeightVariance": {{ toString .Values.genesis.celestiaHeightVariance | replace "\"" "" }}, - "astriaBridgeAddresses": {{ toPrettyJson .Values.genesis.bridgeAddresses | indent 8 | trim }}, - "astriaFeeCollectors": {{ toPrettyJson .Values.genesis.feeCollectors | indent 8 | trim }}, - "astriaEIP1559Params": {{ toPrettyJson .Values.genesis.eip1559Params | indent 8 | trim }}, - "astriaSequencerAddressPrefix": "{{ .Values.genesis.sequencerAddressPrefix }}" + "astriaForks": { + "genesis": { + "height": {{ toString .Values.genesis.forks.launch.height | replace "\"" "" }}, + {{- if .Values.genesis.forks.launch.halt }} + "halt": {{ toString .Values.genesis.forks.launch.halt | replace "\"" "" }}, + {{- end }} + {{- if .Values.genesis.forks.launch.snapshotChecksum }} + "snapshotChecksum": {{ toString .Values.genesis.forks.launch.snapshotChecksum }}, + {{- end }} + {{- if .Values.genesis.forks.launch.extraDataOverride }} + "extraDataOverride": {{ toString .Values.genesis.forks.launch.extraDataOverride }}, + {{- end }} + "feeCollector": "{{ .Values.genesis.forks.launch.feeCollector }}", + {{- if .Values.genesis.forks.launch.eip1559Params }} + "eip1559Params": {{ toPrettyJson .Values.genesis.forks.launch.eip1559Params | indent 8 | trim }}, + {{- end }} + "sequencer": { + "chainId": "{{ tpl .Values.genesis.forks.launch.sequencerChainId . }}", + "addressPrefix": "{{ .Values.genesis.forks.launch.sequencerAddressPrefix }}", + "startHeight": {{ toString .Values.genesis.forks.launch.sequencerStartHeight | replace "\"" "" }}, + "stopHeight": {{ toString .Values.genesis.forks.launch.sequencerStopHeight | replace "\"" "" }} + }, + "celestia": { + "chainId": "{{ tpl .Values.genesis.forks.launch.celestiaChainId . }}", + "startHeight": {{ toString .Values.genesis.forks.launch.celestiaStartHeight | replace "\"" "" }}, + "heightVariance": {{ toString .Values.genesis.forks.launch.celestiaHeightVariance | replace "\"" "" }} + }, + "bridgeAddresses": {{ toPrettyJson .Values.genesis.forks.launch.bridgeAddresses | indent 8 | trim }} + } + {{- if .Values.genesis.forks.additionalForks }} + , {{ toPrettyJson .Values.genesis.forks.additionalForks | indent 8 | trim }} + {{- end }} + } {{- if not .Values.global.dev }} {{- else }} {{- end }} diff --git a/charts/evm-rollup/templates/configmap.yaml b/charts/evm-rollup/templates/configmap.yaml index bf733912bf..dc5524acaf 100644 --- a/charts/evm-rollup/templates/configmap.yaml +++ b/charts/evm-rollup/templates/configmap.yaml @@ -6,14 +6,12 @@ metadata: data: ASTRIA_CONDUCTOR_LOG: "astria_conductor={{ .Values.config.logLevel }}" ASTRIA_CONDUCTOR_CELESTIA_NODE_HTTP_URL: "{{ .Values.config.celestia.rpc }}" - ASTRIA_CONDUCTOR_EXPECTED_CELESTIA_CHAIN_ID: "{{ tpl .Values.config.conductor.celestiaChainId . }}" ASTRIA_CONDUCTOR_CELESTIA_BEARER_TOKEN: "{{ .Values.config.celestia.token }}" ASTRIA_CONDUCTOR_CELESTIA_BLOCK_TIME_MS: "{{ .Values.config.conductor.celestiaBlockTimeMs }}" ASTRIA_CONDUCTOR_EXECUTION_RPC_URL: "http://127.0.0.1:{{ .Values.ports.executionGRPC }}" ASTRIA_CONDUCTOR_EXECUTION_COMMIT_LEVEL: "{{ .Values.config.conductor.executionCommitLevel }}" ASTRIA_CONDUCTOR_SEQUENCER_GRPC_URL: "{{ tpl .Values.config.conductor.sequencerGrpc . }}" ASTRIA_CONDUCTOR_SEQUENCER_COMETBFT_URL: "{{ tpl .Values.config.conductor.sequencerRpc . }}" - ASTRIA_CONDUCTOR_EXPECTED_SEQUENCER_CHAIN_ID: "{{ tpl .Values.config.conductor.sequencerChainId . }}" ASTRIA_CONDUCTOR_SEQUENCER_BLOCK_TIME_MS: "{{ .Values.config.conductor.sequencerBlockTimeMs }}" ASTRIA_CONDUCTOR_NO_METRICS: "{{ not .Values.metrics.enabled }}" ASTRIA_CONDUCTOR_METRICS_HTTP_LISTENER_ADDR: "0.0.0.0:{{ .Values.ports.conductorMetrics }}" diff --git a/charts/evm-rollup/values.yaml b/charts/evm-rollup/values.yaml index 5437e0c07d..2a30d9af4b 100644 --- a/charts/evm-rollup/values.yaml +++ b/charts/evm-rollup/values.yaml @@ -11,30 +11,72 @@ images: repo: ghcr.io/astriaorg/astria-geth pullPolicy: IfNotPresent tag: 1.0.0 - devTag: latest + devTag: pr-59 overrideTag: "" conductor: repo: ghcr.io/astriaorg/conductor pullPolicy: IfNotPresent - tag: 1.0.0 + tag: 1.0.1 devTag: latest genesis: - ## These values are used to configure the genesis block of the rollup chain - ## no defaults as they are unique to each chain - # The name of the rollup chain, used to generate the Rollup ID rollupName: "" - # Block height to start syncing rollup from, lowest possible is 2 - sequencerInitialHeight: "" - # The first Celestia height to utilize when looking for rollup data - celestiaInitialHeight: "" - # The variance in Celestia height to allow before halting the chain - celestiaHeightVariance: "" - # Will fill the extra data in each block, can be left empty - # can also fill with something unique for your chain. - extraDataOverride: "" + + # The "forks" for upgrading the chain. Contains necessary information for starting + # and, if desired, restarting the chain at a given height. The necessary fields + # for the genesis fork are provided, and additional forks can be added as needed. + forks: + ## These values are used to configure the genesis block of the rollup chain + ## no defaults as they are unique to each chain + launch: + # The block height to start the chain at, 0 for genesis + height: "0" + # Whether to halt the rollup chain at the given height. False for genesis + halt: "false" + # Checksum of the snapshot to use upon restart + snapshotChecksum: "" + # Will fill the extra data in each block, can be left empty + # can also fill with something unique for your chain. + extraDataOverride: "" + # Configure the fee collector for the evm tx fees, activated at block heights. + # If not configured, all tx fees will be burned. + feeCollector: "" + # 1: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" + # Configure EIP-1559 params, activated at block heights. + eip1559Params: {} + # 1: + # minBaseFee: 0 + # elasticityMultiplier: 2 + # baseFeeChangeDenominator: 8 + # The chain id of the sequencer chain + sequencerChainId: "" + # The hrp for bech32m addresses, unlikely to be changed + sequencerAddressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + sequencerStartHeight: "" + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork. + # A height of 0 indicates no stop height. + sequencerStopHeight: "" + # The chain id of the celestia chain + celestiaChainId: "" + # The first Celestia height to utilize when looking for rollup data + celestiaStartHeight: "" + # The variance in Celestia height to allow before halting the chain + celestiaHeightVariance: "" + # Configure the sequencer bridge addresses and allowed assets if using + # the astria canonical bridge. Recommend removing alloc values if so. + bridgeAddresses: [] + # - address: "684ae50c49a434199199c9c698115391152d7b3f" + # startHeight: 1 + # assetDenom: "nria" + # senderAddress: "0x0000000000000000000000000000000000000000" + # assetPrecision: 9 + # JSON object with any additional desired forks. Can be used with any or all + # of the above fields. Any fields left empty will be populated with the previous + # fork's values. + additionalForks: {} ## These are general configuration values with some recommended defaults @@ -42,34 +84,7 @@ genesis: gasLimit: "50000000" # If set to true the genesis block will contain extra data overrideGenesisExtraData: true - # The hrp for bech32m addresses, unlikely to be changed - sequencerAddressPrefix: "astria" - - ## These values are used to configure astria native bridging - ## Many of the fields have commented out example fields - - # Configure the sequencer bridge addresses and allowed assets if using - # the astria canonical bridge. Recommend removing alloc values if so. - bridgeAddresses: [] - # - address: "684ae50c49a434199199c9c698115391152d7b3f" - # startHeight: 1 - # assetDenom: "nria" - # senderAddress: "0x0000000000000000000000000000000000000000" - # assetPrecision: 9 - - - ## Fee configuration - # Configure the fee collector for the evm tx fees, activated at block heights. - # If not configured, all tx fees will be burned. - feeCollectors: {} - # 1: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" - # Configure EIP-1559 params, activated at block heights - eip1559Params: {} - # 1: - # minBaseFee: 0 - # elasticityMultiplier: 2 - # baseFeeChangeDenominator: 8 ## Standard Eth Genesis config values # An EVM chain number id, different from the astria rollup name @@ -167,8 +182,6 @@ config: # - "FirmOnly" -> blocks are only pulled from DA # - "SoftAndFirm" -> blocks are pulled from both the sequencer and DA executionCommitLevel: 'SoftAndFirm' - # The chain id of the Astria sequencer chain conductor communicates with - sequencerChainId: "" # The expected fastest block time possible from sequencer, determines polling # rate. sequencerBlockTimeMs: 2000 @@ -180,8 +193,6 @@ config: sequencerGrpc: "" # The maximum number of requests to make to the sequencer per second sequencerRequestsPerSecond: 500 - # The chain id of the celestia network the conductor communicates with - celestiaChainId: "" celestia: # if config.rollup.executionLevel is NOT 'SoftOnly' AND celestia-node is not enabled diff --git a/charts/evm-stack/Chart.lock b/charts/evm-stack/Chart.lock index 7de99729cf..e2bc6c505f 100644 --- a/charts/evm-stack/Chart.lock +++ b/charts/evm-stack/Chart.lock @@ -4,7 +4,7 @@ dependencies: version: 0.4.0 - name: evm-rollup repository: file://../evm-rollup - version: 1.0.1 + version: 1.0.2 - name: composer repository: file://../composer version: 1.0.0 @@ -20,5 +20,5 @@ dependencies: - name: blockscout-stack repository: https://blockscout.github.io/helm-charts version: 1.6.8 -digest: sha256:4715e557b6ceb0fa85c9efe86f5b26d665783f0be9162728efe808fa3a35d727 -generated: "2024-12-12T19:52:24.992658+02:00" +digest: sha256:cabd3646c4478bb720c86bdc692243c20717d667ad8230154656d9c965197c6a +generated: "2024-12-13T08:32:31.235575-06:00" diff --git a/charts/evm-stack/Chart.yaml b/charts/evm-stack/Chart.yaml index dea0641cf7..a1990ea05e 100644 --- a/charts/evm-stack/Chart.yaml +++ b/charts/evm-stack/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.6 +version: 1.0.7 dependencies: - name: celestia-node @@ -23,7 +23,7 @@ dependencies: repository: "file://../celestia-node" condition: celestia-node.enabled - name: evm-rollup - version: 1.0.1 + version: 1.0.2 repository: "file://../evm-rollup" - name: composer version: 1.0.0 diff --git a/charts/evm-stack/values.yaml b/charts/evm-stack/values.yaml index e18df9b10c..6729a570dd 100644 --- a/charts/evm-stack/values.yaml +++ b/charts/evm-stack/values.yaml @@ -13,7 +13,6 @@ global: rollupName: "" evmChainId: "" sequencerChainId: "" - celestiaChainId: "" otel: endpoint: "" tracesEndpoint: "" @@ -29,8 +28,6 @@ evm-rollup: chainId: "{{ .Values.global.evmChainId }}" config: conductor: - sequencerChainId: "{{ .Values.global.sequencerChainId }}" - celestiaChainId: "{{ .Values.global.celestiaChainId }}" sequencerRpc: "{{ .Values.global.sequencerRpc }}" sequencerGrpc: "{{ .Values.global.sequencerGrpc }}" otel: diff --git a/crates/astria-conductor/CHANGELOG.md b/crates/astria-conductor/CHANGELOG.md index b9de90e6d4..333e07938d 100644 --- a/crates/astria-conductor/CHANGELOG.md +++ b/crates/astria-conductor/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Add stop height logic, remove chain id env vars, accomodate new genesis info +shape [#1843](https://github.com/astriaorg/astria/pull/1843). - Bump penumbra dependencies [#1740](https://github.com/astriaorg/astria/pull/1740). ## [1.0.0-rc.2] - 2024-10-23 diff --git a/crates/astria-conductor/Cargo.toml b/crates/astria-conductor/Cargo.toml index 8dc3f608a8..d69d9b6558 100644 --- a/crates/astria-conductor/Cargo.toml +++ b/crates/astria-conductor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astria-conductor" -version = "1.0.0" +version = "1.0.1" edition = "2021" rust-version = "1.81.0" license = "MIT OR Apache-2.0" diff --git a/crates/astria-conductor/local.env.example b/crates/astria-conductor/local.env.example index 9a1eb3e43d..6237d3109b 100644 --- a/crates/astria-conductor/local.env.example +++ b/crates/astria-conductor/local.env.example @@ -73,12 +73,6 @@ ASTRIA_CONDUCTOR_SEQUENCER_BLOCK_TIME_MS=2000 # CometBFT node. ASTRIA_CONDUCTOR_SEQUENCER_REQUESTS_PER_SECOND=500 -# The chain ID of the sequencer network the conductor should be communicating with. -ASTRIA_CONDUCTOR_EXPECTED_SEQUENCER_CHAIN_ID="test-sequencer-1000" - -# The chain ID of the Celestia network the conductor should be communicating with. -ASTRIA_CONDUCTOR_EXPECTED_CELESTIA_CHAIN_ID="test-celestia-1000" - # Set to true to enable prometheus metrics. ASTRIA_CONDUCTOR_NO_METRICS=true diff --git a/crates/astria-conductor/src/celestia/builder.rs b/crates/astria-conductor/src/celestia/builder.rs index ef33a28cbd..7860d439ee 100644 --- a/crates/astria-conductor/src/celestia/builder.rs +++ b/crates/astria-conductor/src/celestia/builder.rs @@ -23,8 +23,6 @@ pub(crate) struct Builder { pub(crate) executor: executor::Handle, pub(crate) sequencer_cometbft_client: SequencerClient, pub(crate) sequencer_requests_per_second: u32, - pub(crate) expected_celestia_chain_id: String, - pub(crate) expected_sequencer_chain_id: String, pub(crate) shutdown: CancellationToken, pub(crate) metrics: &'static Metrics, } @@ -39,8 +37,6 @@ impl Builder { executor, sequencer_cometbft_client, sequencer_requests_per_second, - expected_celestia_chain_id, - expected_sequencer_chain_id, shutdown, metrics, } = self; @@ -54,8 +50,6 @@ impl Builder { executor, sequencer_cometbft_client, sequencer_requests_per_second, - expected_celestia_chain_id, - expected_sequencer_chain_id, shutdown, metrics, }) diff --git a/crates/astria-conductor/src/celestia/mod.rs b/crates/astria-conductor/src/celestia/mod.rs index 85146dc645..0a0a0df9d7 100644 --- a/crates/astria-conductor/src/celestia/mod.rs +++ b/crates/astria-conductor/src/celestia/mod.rs @@ -141,12 +141,6 @@ pub(crate) struct Reader { /// (usually to verify block data retrieved from Celestia blobs). sequencer_requests_per_second: u32, - /// The chain ID of the Celestia network the reader should be communicating with. - expected_celestia_chain_id: String, - - /// The chain ID of the Sequencer the reader should be communicating with. - expected_sequencer_chain_id: String, - /// Token to listen for Conductor being shut down. shutdown: CancellationToken, @@ -174,49 +168,50 @@ impl Reader { .await } + // TODO(https://github.com/astriaorg/astria/issues/1879): refactor to not return an empty tuple #[instrument(skip_all, err)] async fn initialize( &mut self, ) -> eyre::Result<((), executor::Handle, tendermint::chain::Id)> { + let executor = self + .executor + .wait_for_init() + .await + .wrap_err("handle to executor failed while waiting for it being initialized")?; + let validate_celestia_chain_id = async { let actual_celestia_chain_id = get_celestia_chain_id(&self.celestia_client) .await .wrap_err("failed to fetch Celestia chain ID")?; - let expected_celestia_chain_id = &self.expected_celestia_chain_id; + let expected_celestia_chain_id = executor.celestia_chain_id(); ensure!( - self.expected_celestia_chain_id == actual_celestia_chain_id.as_str(), + expected_celestia_chain_id == actual_celestia_chain_id.as_str(), "expected Celestia chain id `{expected_celestia_chain_id}` does not match actual: \ `{actual_celestia_chain_id}`" ); Ok(()) }; - let wait_for_init_executor = async { - self.executor - .wait_for_init() - .await - .wrap_err("handle to executor failed while waiting for it being initialized") - }; - let get_and_validate_sequencer_chain_id = async { let actual_sequencer_chain_id = get_sequencer_chain_id(self.sequencer_cometbft_client.clone()) .await .wrap_err("failed to get sequencer chain ID")?; - let expected_sequencer_chain_id = &self.expected_sequencer_chain_id; + let expected_sequencer_chain_id = executor.sequencer_chain_id(); ensure!( - self.expected_sequencer_chain_id == actual_sequencer_chain_id.to_string(), + expected_sequencer_chain_id == actual_sequencer_chain_id.as_str(), "expected Celestia chain id `{expected_sequencer_chain_id}` does not match \ actual: `{actual_sequencer_chain_id}`" ); Ok(actual_sequencer_chain_id) }; - try_join!( + let ((), sequencer_chain_id) = try_join!( validate_celestia_chain_id, - wait_for_init_executor, get_and_validate_sequencer_chain_id - ) + )?; + + Ok(((), executor, sequencer_chain_id)) } } diff --git a/crates/astria-conductor/src/conductor/inner.rs b/crates/astria-conductor/src/conductor/inner.rs index c52bd32536..8f6c0c467d 100644 --- a/crates/astria-conductor/src/conductor/inner.rs +++ b/crates/astria-conductor/src/conductor/inner.rs @@ -28,6 +28,7 @@ use tracing::{ warn, }; +use self::executor::StopHeightExceded; use crate::{ celestia, executor, @@ -133,7 +134,6 @@ impl ConductorInner { sequencer_grpc_client, sequencer_cometbft_client: sequencer_cometbft_client.clone(), sequencer_block_time: Duration::from_millis(cfg.sequencer_block_time_ms), - expected_sequencer_chain_id: cfg.expected_sequencer_chain_id.clone(), shutdown: shutdown_token.clone(), executor: executor_handle.clone(), } @@ -155,8 +155,6 @@ impl ConductorInner { executor: executor_handle.clone(), sequencer_cometbft_client: sequencer_cometbft_client.clone(), sequencer_requests_per_second: cfg.sequencer_requests_per_second, - expected_celestia_chain_id: cfg.expected_celestia_chain_id, - expected_sequencer_chain_id: cfg.expected_sequencer_chain_id, shutdown: shutdown_token.clone(), metrics, } @@ -311,6 +309,8 @@ fn check_for_restart(name: &str, err: &eyre::Report) -> bool { if status.code() == tonic::Code::PermissionDenied { return true; } + } else if err.downcast_ref::().is_some() { + return true; } current = err.source(); } @@ -329,5 +329,25 @@ mod tests { let err = err.wrap_err("wrapper_2"); let err = err.wrap_err("wrapper_3"); assert!(super::check_for_restart("executor", &err.unwrap_err())); + + let celestia_height_error: Result<&str, super::StopHeightExceded> = + Err(super::StopHeightExceded::Celestia { + firm_height: 1, + stop_height: 1, + }); + let err = celestia_height_error.wrap_err("wrapper_1"); + let err = err.wrap_err("wrapper_2"); + let err = err.wrap_err("wrapper_3"); + assert!(super::check_for_restart("executor", &err.unwrap_err())); + + let sequencer_height_error: Result<&str, super::StopHeightExceded> = + Err(super::StopHeightExceded::Sequencer { + soft_height: 1, + stop_height: 1, + }); + let err = sequencer_height_error.wrap_err("wrapper_1"); + let err = err.wrap_err("wrapper_2"); + let err = err.wrap_err("wrapper_3"); + assert!(super::check_for_restart("executor", &err.unwrap_err())); } } diff --git a/crates/astria-conductor/src/config.rs b/crates/astria-conductor/src/config.rs index e8211b1714..0cb8c0969d 100644 --- a/crates/astria-conductor/src/config.rs +++ b/crates/astria-conductor/src/config.rs @@ -63,12 +63,6 @@ pub struct Config { /// The number of requests per second that will be sent to Sequencer. pub sequencer_requests_per_second: u32, - /// The chain ID of the sequencer network the conductor should be communiacting with. - pub expected_sequencer_chain_id: String, - - /// The chain ID of the Celestia network the conductor should be communicating with. - pub expected_celestia_chain_id: String, - /// Address of the RPC server for execution pub execution_rpc_url: String, diff --git a/crates/astria-conductor/src/executor/mod.rs b/crates/astria-conductor/src/executor/mod.rs index 8c3155ca8b..4277adc843 100644 --- a/crates/astria-conductor/src/executor/mod.rs +++ b/crates/astria-conductor/src/executor/mod.rs @@ -67,6 +67,30 @@ pub(crate) struct StateNotInit; #[derive(Clone, Debug)] pub(crate) struct StateIsInit; +#[derive(Debug, thiserror::Error)] +pub(super) enum StopHeightExceded { + #[error("firm height {firm_height} exceeded sequencer stop height {stop_height}")] + Celestia { firm_height: u64, stop_height: u64 }, + #[error("soft height {soft_height} met or exceeded sequencer stop height {stop_height}")] + Sequencer { soft_height: u64, stop_height: u64 }, +} + +impl StopHeightExceded { + fn celestia(firm_height: u64, stop_height: u64) -> Self { + StopHeightExceded::Celestia { + firm_height, + stop_height, + } + } + + fn sequencer(soft_height: u64, stop_height: u64) -> Self { + StopHeightExceded::Sequencer { + soft_height, + stop_height, + } + } +} + #[derive(Debug, thiserror::Error)] pub(crate) enum FirmSendError { #[error("executor was configured without firm commitments")] @@ -214,6 +238,14 @@ impl Handle { pub(crate) fn celestia_block_variance(&mut self) -> u64 { self.state.celestia_block_variance() } + + pub(crate) fn sequencer_chain_id(&self) -> String { + self.state.sequencer_chain_id() + } + + pub(crate) fn celestia_chain_id(&self) -> String { + self.state.celestia_chain_id() + } } pub(crate) struct Executor { @@ -396,6 +428,30 @@ impl Executor { // TODO(https://github.com/astriaorg/astria/issues/624): add retry logic before failing hard. let executable_block = ExecutableBlock::from_sequencer(block, self.state.rollup_id()); + // Stop executing soft blocks at the sequencer stop block height (exclusive). If we are also + // executing firm blocks, we let execution continue since one more firm block will be + // executed before `execute_firm` initiates a restart. If we are in soft-only mode, we + // return a `StopHeightExceded::Sequencer` error to signal a restart. + if self.is_soft_block_height_exceded(&executable_block) { + if self.mode.is_with_firm() { + info!( + height = %executable_block.height, + "received soft block whose height is greater than or equal to stop block height. \ + dropping soft block and waiting for next firm block before attempting restart. \ + will continue logging info of soft blocks until restart", + ); + return Ok(()); + } + return Err(StopHeightExceded::sequencer( + executable_block.height.value(), + self.state.sequencer_stop_block_height(), + )) + .wrap_err( + "soft height met or exceeded sequencer stop height, attempting restart with new \ + genesis info", + ); + } + let expected_height = self.state.next_expected_soft_sequencer_height(); match executable_block.height.cmp(&expected_height) { std::cmp::Ordering::Less => { @@ -413,11 +469,14 @@ impl Executor { std::cmp::Ordering::Equal => {} } - let genesis_height = self.state.sequencer_genesis_block_height(); + let genesis_height = self.state.sequencer_start_block_height(); let block_height = executable_block.height; - let Some(block_number) = - state::map_sequencer_height_to_rollup_height(genesis_height, block_height) - else { + let rollup_start_block_height = self.state.rollup_start_block_height(); + let Some(block_number) = state::map_sequencer_height_to_rollup_height( + genesis_height, + block_height, + rollup_start_block_height, + ) else { bail!( "failed to map block height rollup number. This means the operation `sequencer_height - sequencer_genesis_height` underflowed or was not a valid @@ -457,19 +516,33 @@ impl Executor { err, ))] async fn execute_firm(&mut self, block: ReconstructedBlock) -> eyre::Result<()> { + if self.is_firm_block_height_exceded(&block) { + return Err(StopHeightExceded::celestia( + block.header.height().value(), + self.state.sequencer_stop_block_height(), + )) + .wrap_err( + "firm height exceeded sequencer stop height, attempting restart with new genesis \ + info", + ); + } + let celestia_height = block.celestia_height; let executable_block = ExecutableBlock::from_reconstructed(block); let expected_height = self.state.next_expected_firm_sequencer_height(); let block_height = executable_block.height; + let rollup_start_block_height = self.state.rollup_start_block_height(); ensure!( block_height == expected_height, "expected block at sequencer height {expected_height}, but got {block_height}", ); - let genesis_height = self.state.sequencer_genesis_block_height(); - let Some(block_number) = - state::map_sequencer_height_to_rollup_height(genesis_height, block_height) - else { + let genesis_height = self.state.sequencer_start_block_height(); + let Some(block_number) = state::map_sequencer_height_to_rollup_height( + genesis_height, + block_height, + rollup_start_block_height, + ) else { bail!( "failed to map block height rollup number. This means the operation `sequencer_height - sequencer_genesis_height` underflowed or was not a valid @@ -666,6 +739,19 @@ impl Executor { self.mode, ) } + + /// Returns whether the height of the soft block is greater than or equal to the stop block + /// height. + fn is_soft_block_height_exceded(&self, block: &ExecutableBlock) -> bool { + let stop_height = self.state.sequencer_stop_block_height(); + stop_height > 0 && block.height.value() >= stop_height + } + + /// Returns whether the height of the firm block is greater than the stop block height. + fn is_firm_block_height_exceded(&self, block: &ReconstructedBlock) -> bool { + let stop_height = self.state.sequencer_stop_block_height(); + stop_height > 0 && block.header.height().value() > stop_height + } } #[instrument(skip_all)] diff --git a/crates/astria-conductor/src/executor/state.rs b/crates/astria-conductor/src/executor/state.rs index 2ae4e2c4b2..a9337b6d0c 100644 --- a/crates/astria-conductor/src/executor/state.rs +++ b/crates/astria-conductor/src/executor/state.rs @@ -35,13 +35,13 @@ pub(super) fn channel() -> (StateSender, StateReceiver) { #[derive(Debug, thiserror::Error)] #[error( - "adding sequencer genesis height `{sequencer_genesis_height}` and `{commitment_type}` rollup \ - number `{rollup_number}` overflowed unsigned u32::MAX, the maximum permissible cometbft \ - height" + "adding sequencer genesis height `{sequencer_start_block_height}` and `{commitment_type}` \ + rollup number `{rollup_number}` overflowed unsigned u32::MAX, the maximum permissible \ + cometbft height" )] pub(super) struct InvalidState { commitment_type: &'static str, - sequencer_genesis_height: u64, + sequencer_start_block_height: u64, rollup_number: u64, } @@ -98,15 +98,22 @@ pub(super) struct StateSender { } fn can_map_firm_to_sequencer_height( - genesis_info: GenesisInfo, + genesis_info: &GenesisInfo, commitment_state: &CommitmentState, ) -> Result<(), InvalidState> { - let sequencer_genesis_height = genesis_info.sequencer_genesis_block_height(); + let sequencer_start_block_height = genesis_info.sequencer_start_block_height(); let rollup_number = commitment_state.firm().number(); - if map_rollup_number_to_sequencer_height(sequencer_genesis_height, rollup_number).is_none() { + let rollup_start_height = genesis_info.rollup_start_block_height(); + if map_rollup_number_to_sequencer_height( + sequencer_start_block_height, + rollup_number, + rollup_start_height, + ) + .is_none() + { Err(InvalidState { commitment_type: "firm", - sequencer_genesis_height: sequencer_genesis_height.value(), + sequencer_start_block_height, rollup_number: rollup_number.into(), }) } else { @@ -115,15 +122,22 @@ fn can_map_firm_to_sequencer_height( } fn can_map_soft_to_sequencer_height( - genesis_info: GenesisInfo, + genesis_info: &GenesisInfo, commitment_state: &CommitmentState, ) -> Result<(), InvalidState> { - let sequencer_genesis_height = genesis_info.sequencer_genesis_block_height(); + let sequencer_start_block_height = genesis_info.sequencer_start_block_height(); let rollup_number = commitment_state.soft().number(); - if map_rollup_number_to_sequencer_height(sequencer_genesis_height, rollup_number).is_none() { + let rollup_start_height = genesis_info.rollup_start_block_height(); + if map_rollup_number_to_sequencer_height( + sequencer_start_block_height, + rollup_number, + rollup_start_height, + ) + .is_none() + { Err(InvalidState { commitment_type: "soft", - sequencer_genesis_height: sequencer_genesis_height.value(), + sequencer_start_block_height, rollup_number: rollup_number.into(), }) } else { @@ -137,8 +151,8 @@ impl StateSender { genesis_info: GenesisInfo, commitment_state: CommitmentState, ) -> Result<(), InvalidState> { - can_map_firm_to_sequencer_height(genesis_info, &commitment_state)?; - can_map_soft_to_sequencer_height(genesis_info, &commitment_state)?; + can_map_firm_to_sequencer_height(&genesis_info, &commitment_state)?; + can_map_soft_to_sequencer_height(&genesis_info, &commitment_state)?; self.inner.send_modify(move |state| { let old_state = state.replace(State::new(genesis_info, commitment_state)); assert!( @@ -154,8 +168,8 @@ impl StateSender { commitment_state: CommitmentState, ) -> Result<(), InvalidState> { let genesis_info = self.genesis_info(); - can_map_firm_to_sequencer_height(genesis_info, &commitment_state)?; - can_map_soft_to_sequencer_height(genesis_info, &commitment_state)?; + can_map_firm_to_sequencer_height(&genesis_info, &commitment_state)?; + can_map_soft_to_sequencer_height(&genesis_info, &commitment_state)?; self.inner.send_modify(move |state| { state .as_mut() @@ -222,8 +236,10 @@ forward_impls!( [soft_hash -> Bytes], [celestia_block_variance -> u64], [rollup_id -> RollupId], - [sequencer_genesis_block_height -> SequencerHeight], + [sequencer_start_block_height -> u64], [celestia_base_block_height -> u64], + [sequencer_stop_block_height -> u64], + [rollup_start_block_height -> u64] ); forward_impls!( @@ -231,6 +247,8 @@ forward_impls!( [celestia_base_block_height -> u64], [celestia_block_variance -> u64], [rollup_id -> RollupId], + [sequencer_chain_id -> String], + [celestia_chain_id -> String], ); /// `State` tracks the genesis info and commitment state of the remote rollup node. @@ -253,8 +271,8 @@ impl State { self.commitment_state = commitment_state; } - fn genesis_info(&self) -> GenesisInfo { - self.genesis_info + fn genesis_info(&self) -> &GenesisInfo { + &self.genesis_info } fn firm(&self) -> &Block { @@ -289,54 +307,76 @@ impl State { self.genesis_info.celestia_block_variance() } - fn sequencer_genesis_block_height(&self) -> SequencerHeight { - self.genesis_info.sequencer_genesis_block_height() + fn sequencer_start_block_height(&self) -> u64 { + self.genesis_info.sequencer_start_block_height() + } + + fn sequencer_stop_block_height(&self) -> u64 { + self.genesis_info.sequencer_stop_block_height() + } + + fn sequencer_chain_id(&self) -> String { + self.genesis_info.sequencer_chain_id().to_string() + } + + fn celestia_chain_id(&self) -> String { + self.genesis_info.celestia_chain_id().to_string() } fn rollup_id(&self) -> RollupId { self.genesis_info.rollup_id() } + fn rollup_start_block_height(&self) -> u64 { + self.genesis_info.rollup_start_block_height() + } + fn next_expected_firm_sequencer_height(&self) -> Option { map_rollup_number_to_sequencer_height( - self.sequencer_genesis_block_height(), + self.sequencer_start_block_height(), self.firm_number().saturating_add(1), + self.rollup_start_block_height(), ) } fn next_expected_soft_sequencer_height(&self) -> Option { map_rollup_number_to_sequencer_height( - self.sequencer_genesis_block_height(), + self.sequencer_start_block_height(), self.soft_number().saturating_add(1), + self.rollup_start_block_height(), ) } } /// Maps a rollup height to a sequencer height. /// -/// Returns `None` if `sequencer_genesis_height + rollup_number` overflows -/// `u32::MAX`. +/// Returns `None` if `sequencer_start_block_height + rollup_number - rollup_start_block_height` +/// overflows `u32::MAX`. fn map_rollup_number_to_sequencer_height( - sequencer_genesis_height: SequencerHeight, + sequencer_start_block_height: u64, rollup_number: u32, + rollup_start_block_height: u64, ) -> Option { - let sequencer_genesis_height = sequencer_genesis_height.value(); let rollup_number: u64 = rollup_number.into(); - let sequencer_height = sequencer_genesis_height.checked_add(rollup_number)?; + let sequencer_height = sequencer_start_block_height + .checked_add(rollup_number)? + .checked_sub(rollup_start_block_height)?; sequencer_height.try_into().ok() } /// Maps a sequencer height to a rollup height. /// -/// Returns `None` if `sequencer_height - sequencer_genesis_height` underflows or if -/// the result does not fit in `u32`. +/// Returns `None` if `sequencer_height - sequencer_start_block_height + rollup_start_block_height` +/// underflows or if the result does not fit in `u32`. pub(super) fn map_sequencer_height_to_rollup_height( - sequencer_genesis_height: SequencerHeight, + sequencer_start_height: u64, sequencer_height: SequencerHeight, + rollup_start_block_height: u64, ) -> Option { sequencer_height .value() - .checked_sub(sequencer_genesis_height.value())? + .checked_sub(sequencer_start_height)? + .checked_add(rollup_start_block_height)? .try_into() .ok() } @@ -384,8 +424,12 @@ mod tests { let rollup_id = RollupId::new([24; 32]); GenesisInfo::try_from_raw(raw::GenesisInfo { rollup_id: Some(rollup_id.to_raw()), - sequencer_genesis_block_height: 10, + sequencer_start_block_height: 10, + sequencer_stop_block_height: 100, celestia_block_variance: 0, + rollup_start_block_height: 0, + sequencer_chain_id: "test-sequencer-0".to_string(), + celestia_chain_id: "test-celestia-0".to_string(), }) .unwrap() } @@ -419,7 +463,7 @@ mod tests { fn assert_height_is_correct(left: u32, right: u32, expected: u32) { assert_eq!( SequencerHeight::from(expected), - map_rollup_number_to_sequencer_height(SequencerHeight::from(left), right) + map_rollup_number_to_sequencer_height(left.into(), right, 0) .expect("left + right is so small, they should never overflow"), ); } diff --git a/crates/astria-conductor/src/executor/tests.rs b/crates/astria-conductor/src/executor/tests.rs index e8ead9dc71..0d9f4f6409 100644 --- a/crates/astria-conductor/src/executor/tests.rs +++ b/crates/astria-conductor/src/executor/tests.rs @@ -47,8 +47,12 @@ fn make_state( ) -> (StateSender, StateReceiver) { let genesis_info = GenesisInfo::try_from_raw(raw::GenesisInfo { rollup_id: Some(ROLLUP_ID.to_raw()), - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 100, celestia_block_variance: 1, + rollup_start_block_height: 1, + sequencer_chain_id: "test_sequencer-0".to_string(), + celestia_chain_id: "test_celestia-0".to_string(), }) .unwrap(); let commitment_state = CommitmentState::try_from_raw(raw::CommitmentState { diff --git a/crates/astria-conductor/src/sequencer/builder.rs b/crates/astria-conductor/src/sequencer/builder.rs index c71aa0e7d8..2cdaf4bf62 100644 --- a/crates/astria-conductor/src/sequencer/builder.rs +++ b/crates/astria-conductor/src/sequencer/builder.rs @@ -10,7 +10,6 @@ pub(crate) struct Builder { pub(crate) sequencer_grpc_client: SequencerGrpcClient, pub(crate) sequencer_cometbft_client: sequencer_client::HttpClient, pub(crate) sequencer_block_time: Duration, - pub(crate) expected_sequencer_chain_id: String, pub(crate) shutdown: CancellationToken, } @@ -21,7 +20,6 @@ impl Builder { sequencer_grpc_client, sequencer_cometbft_client, sequencer_block_time, - expected_sequencer_chain_id, shutdown, } = self; super::Reader { @@ -29,7 +27,6 @@ impl Builder { sequencer_grpc_client, sequencer_cometbft_client, sequencer_block_time, - expected_sequencer_chain_id, shutdown, } } diff --git a/crates/astria-conductor/src/sequencer/mod.rs b/crates/astria-conductor/src/sequencer/mod.rs index df9d11b5a1..1d399b84b5 100644 --- a/crates/astria-conductor/src/sequencer/mod.rs +++ b/crates/astria-conductor/src/sequencer/mod.rs @@ -78,9 +78,6 @@ pub(crate) struct Reader { /// height. sequencer_block_time: Duration, - /// The chain ID of the sequencer network the reader should be communicating with. - expected_sequencer_chain_id: String, - /// Token to listen for Conductor being shut down. shutdown: CancellationToken, } @@ -107,9 +104,14 @@ impl Reader { get_sequencer_chain_id(self.sequencer_cometbft_client.clone()) .await .wrap_err("failed to get chain ID from Sequencer")?; - let expected_sequencer_chain_id = &self.expected_sequencer_chain_id; + let expected_sequencer_chain_id = self + .executor + .wait_for_init() + .await + .wrap_err("handle to executor failed while waiting for it being initialized")? + .sequencer_chain_id(); ensure!( - self.expected_sequencer_chain_id == actual_sequencer_chain_id.as_str(), + expected_sequencer_chain_id == actual_sequencer_chain_id.as_str(), "expected chain id `{expected_sequencer_chain_id}` does not match actual: \ `{actual_sequencer_chain_id}`" ); diff --git a/crates/astria-conductor/tests/blackbox/firm_only.rs b/crates/astria-conductor/tests/blackbox/firm_only.rs index e634292a1c..0cc2779a30 100644 --- a/crates/astria-conductor/tests/blackbox/firm_only.rs +++ b/crates/astria-conductor/tests/blackbox/firm_only.rs @@ -15,7 +15,10 @@ use futures::future::{ }; use serde_json::json; use telemetry::metrics; -use tokio::time::timeout; +use tokio::time::{ + sleep, + timeout, +}; use wiremock::{ matchers::{ body_partial_json, @@ -54,8 +57,10 @@ async fn simple() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -135,8 +140,10 @@ async fn submits_two_heights_in_succession() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -236,7 +243,7 @@ async fn submits_two_heights_in_succession() { ) .await .expect( - "conductor should have executed the soft block and updated the soft commitment state \ + "conductor should have executed the firm block and updated the firm commitment state \ within 2000ms", ); } @@ -247,8 +254,10 @@ async fn skips_already_executed_heights() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -320,7 +329,7 @@ async fn skips_already_executed_heights() { ) .await .expect( - "conductor should have executed the soft block and updated the soft commitment state \ + "conductor should have executed the firm block and updated the firm commitment state \ within 1000ms", ); } @@ -331,8 +340,10 @@ async fn fetch_from_later_celestia_height() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -447,8 +458,10 @@ async fn exits_on_celestia_chain_id_mismatch() { matcher::message_type::(), ) .respond_with(GrpcResponse::constant_response( - genesis_info!(sequencer_genesis_block_height: 1, - celestia_block_variance: 10,), + genesis_info!(sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, + celestia_block_variance: 10, + rollup_start_block_height: 0,), )) .expect(0..) .mount(&mock_grpc.mock_server) @@ -513,3 +526,177 @@ async fn exits_on_celestia_chain_id_mismatch() { } } } + +/// Tests that the conductor correctly stops at the stop block height and executes the firm block +/// for that height before restarting and continuing after fetching new genesis info and commitment +/// state. +/// +/// It consists of the following steps: +/// 1. Mount commitment state and genesis info with a stop height of 3 for the first height, only +/// responding up to 1 time so that the same info is not provided after conductor restart. +/// 2. Mount sequencer genesis and celestia header network head. +/// 3. Mount firm blocks for heights 3 and 4. +/// 4. Mount `execute_block` and `update_commitment_state` for firm block 3, expecting only one call +/// since they should not be called after restarting. +/// 5. Wait ample time for conductor to restart before performing the next set of mounts. +/// 6. Mount new genesis info and updated commitment state with rollup start block height of 2 to +/// reflect that the first block has already been executed. +/// 7. Mount `execute_block` and `update_commitment_state` for firm block 4, awaiting their +/// satisfaction. +#[expect(clippy::too_many_lines, reason = "All lines reasonably necessary")] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn conductor_restarts_after_reaching_stop_block_height() { + let test_conductor = spawn_conductor(CommitLevel::FirmOnly).await; + + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 3, + celestia_block_variance: 10, + rollup_start_block_height: 0, + up_to_n_times: 1, // Only respond once, since updated information is needed after restart. + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + base_celestia_height: 1, + up_to_n_times: 1, + ); + + mount_sequencer_genesis!(test_conductor); + mount_celestia_header_network_head!( + test_conductor, + height: 2u32, + ); + + mount_celestia_blobs!( + test_conductor, + celestia_height: 1, + sequencer_heights: [3, 4], + ); + mount_sequencer_commit!( + test_conductor, + height: 3u32, + ); + mount_sequencer_commit!( + test_conductor, + height: 4u32, + ); + mount_sequencer_validator_set!(test_conductor, height: 2u32); + mount_sequencer_validator_set!(test_conductor, height: 3u32); + + let execute_block_1 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_1", + number: 2, + hash: [2; 64], + parent: [1; 64], + expected_calls: 1, // should not be called again upon restart + ); + + let update_commitment_state_1 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_1", + firm: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + expected_calls: 1, // should not be called again upon restart + ); + + timeout( + Duration::from_millis(1000), + join( + execute_block_1.wait_until_satisfied(), + update_commitment_state_1.wait_until_satisfied(), + ), + ) + .await + .expect( + "conductor should have executed the first firm block and updated the first firm \ + commitment state twice within 1000ms", + ); + + // Wait for conductor to restart before performing the next set of mounts + sleep(Duration::from_millis(1000)).await; + + // Mount new genesis info and commitment state with updated heights + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, + celestia_block_variance: 10, + rollup_start_block_height: 1, + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + ); + + let execute_block_2 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_2", + number: 3, + hash: [3; 64], + parent: [2; 64], + expected_calls: 1, + ); + + let update_commitment_state_2 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_2", + firm: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + soft: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + base_celestia_height: 1, + expected_calls: 1, + ); + + timeout( + Duration::from_millis(2000), + join( + execute_block_2.wait_until_satisfied(), + update_commitment_state_2.wait_until_satisfied(), + ), + ) + .await + .expect( + "conductor should have executed the second firm block and updated the second firm \ + commitment state twice within 2000ms", + ); +} diff --git a/crates/astria-conductor/tests/blackbox/helpers/macros.rs b/crates/astria-conductor/tests/blackbox/helpers/macros.rs index 981f4c31dc..3594db488b 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/macros.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/macros.rs @@ -94,14 +94,20 @@ macro_rules! filtered_sequencer_block { #[macro_export] macro_rules! genesis_info { ( - sequencer_genesis_block_height: - $sequencer_height:expr,celestia_block_variance: - $variance:expr $(,)? + sequencer_start_block_height: + $start_height:expr,sequencer_stop_block_height: + $stop_height:expr,celestia_block_variance: + $variance:expr,rollup_start_block_height: + $rollup_start_block_height:expr $(,)? ) => { ::astria_core::generated::astria::execution::v1::GenesisInfo { rollup_id: Some($crate::ROLLUP_ID.to_raw()), - sequencer_genesis_block_height: $sequencer_height, + sequencer_start_block_height: $start_height, + sequencer_stop_block_height: $stop_height, celestia_block_variance: $variance, + rollup_start_block_height: $rollup_start_block_height, + sequencer_chain_id: $crate::SEQUENCER_CHAIN_ID.to_string(), + celestia_chain_id: $crate::helpers::CELESTIA_CHAIN_ID.to_string(), } }; } @@ -175,6 +181,22 @@ macro_rules! mount_get_commitment_state { soft: ( number: $soft_number:expr, hash: $soft_hash:expr, parent: $soft_parent:expr$(,)? ), base_celestia_height: $base_celestia_height:expr $(,)? + ) => { + mount_get_commitment_state!( + $test_env, + firm: ( number: $firm_number, hash: $firm_hash, parent: $firm_parent, ), + soft: ( number: $soft_number, hash: $soft_hash, parent: $soft_parent, ), + base_celestia_height: $base_celestia_height, + up_to_n_times: 1, + ) + }; + ( + $test_env:ident, + firm: ( number: $firm_number:expr, hash: $firm_hash:expr, parent: $firm_parent:expr$(,)? ), + soft: ( number: $soft_number:expr, hash: $soft_hash:expr, parent: $soft_parent:expr$(,)? ), + base_celestia_height: $base_celestia_height:expr, + up_to_n_times: $up_to_n_times:expr + $(,)? ) => { $test_env .mount_get_commitment_state($crate::commitment_state!( @@ -189,7 +211,7 @@ macro_rules! mount_get_commitment_state { parent: $soft_parent, ), base_celestia_height: $base_celestia_height, - )) + ), $up_to_n_times) .await }; } @@ -274,7 +296,8 @@ macro_rules! mount_executed_block { mock_name: $mock_name:expr, number: $number:expr, hash: $hash:expr, - parent: $parent:expr $(,)? + parent: $parent:expr, + expected_calls: $expected_calls:expr $(,)? ) => {{ use ::base64::prelude::*; $test_env.mount_execute_block( @@ -287,10 +310,27 @@ macro_rules! mount_executed_block { number: $number, hash: $hash, parent: $parent, - ) + ), + $expected_calls, ) .await }}; + ( + $test_env:ident, + mock_name: $mock_name:expr, + number: $number:expr, + hash: $hash:expr, + parent: $parent:expr, + ) => { + mount_executed_block!( + $test_env, + mock_name: None, + number: $number, + hash: $hash, + parent: $parent, + expected_calls: 1, + ) + }; ( $test_env:ident, number: $number:expr, @@ -334,15 +374,38 @@ macro_rules! mount_get_filtered_sequencer_block { macro_rules! mount_get_genesis_info { ( $test_env:ident, - sequencer_genesis_block_height: $sequencer_height:expr, - celestia_block_variance: $variance:expr + sequencer_start_block_height: $start_height:expr, + sequencer_stop_block_height: $stop_height:expr, + celestia_block_variance: $variance:expr, + rollup_start_block_height: $rollup_start_block_height:expr + $(,)? + ) => { + mount_get_genesis_info!( + $test_env, + sequencer_start_block_height: $start_height, + sequencer_stop_block_height: $stop_height, + celestia_block_variance: $variance, + rollup_start_block_height: $rollup_start_block_height, + up_to_n_times: 1, + ) + }; + ( + $test_env:ident, + sequencer_start_block_height: $start_height:expr, + sequencer_stop_block_height: $stop_height:expr, + celestia_block_variance: $variance:expr, + rollup_start_block_height: $rollup_start_block_height:expr, + up_to_n_times: $up_to_n_times:expr $(,)? ) => { $test_env.mount_get_genesis_info( $crate::genesis_info!( - sequencer_genesis_block_height: $sequencer_height, + sequencer_start_block_height: $start_height, + sequencer_stop_block_height: $stop_height, celestia_block_variance: $variance, - ) + rollup_start_block_height: $rollup_start_block_height, + ), + $up_to_n_times, ).await; }; } diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index 8890bc3f20..ceede6e553 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -305,19 +305,24 @@ impl TestConductor { mount_genesis(&self.mock_http, chain_id).await; } - pub async fn mount_get_genesis_info(&self, genesis_info: GenesisInfo) { + pub async fn mount_get_genesis_info(&self, genesis_info: GenesisInfo, up_to_n_times: u64) { use astria_core::generated::astria::execution::v1::GetGenesisInfoRequest; astria_grpc_mock::Mock::for_rpc_given( "get_genesis_info", astria_grpc_mock::matcher::message_type::(), ) .respond_with(astria_grpc_mock::response::constant_response(genesis_info)) + .up_to_n_times(up_to_n_times) .expect(1..) .mount(&self.mock_grpc.mock_server) .await; } - pub async fn mount_get_commitment_state(&self, commitment_state: CommitmentState) { + pub async fn mount_get_commitment_state( + &self, + commitment_state: CommitmentState, + up_to_n_times: u64, + ) { use astria_core::generated::astria::execution::v1::GetCommitmentStateRequest; astria_grpc_mock::Mock::for_rpc_given( @@ -327,6 +332,7 @@ impl TestConductor { .respond_with(astria_grpc_mock::response::constant_response( commitment_state, )) + .up_to_n_times(up_to_n_times) .expect(1..) .mount(&self.mock_grpc.mock_server) .await; @@ -337,6 +343,7 @@ impl TestConductor { mock_name: Option<&str>, expected_pbjson: S, response: Block, + expected_calls: u64, ) -> astria_grpc_mock::MockGuard { use astria_grpc_mock::{ matcher::message_partial_pbjson, @@ -349,7 +356,7 @@ impl TestConductor { if let Some(name) = mock_name { mock = mock.with_name(name); } - mock.expect(1) + mock.expect(expected_calls) .mount_as_scoped(&self.mock_grpc.mock_server) .await } @@ -522,8 +529,6 @@ pub(crate) fn make_config() -> Config { sequencer_cometbft_url: "http://127.0.0.1:26657".into(), sequencer_requests_per_second: 500, sequencer_block_time_ms: 2000, - expected_celestia_chain_id: CELESTIA_CHAIN_ID.into(), - expected_sequencer_chain_id: SEQUENCER_CHAIN_ID.into(), execution_rpc_url: "http://127.0.0.1:50051".into(), log: "info".into(), execution_commit_level: astria_conductor::config::CommitLevel::SoftAndFirm, diff --git a/crates/astria-conductor/tests/blackbox/soft_and_firm.rs b/crates/astria-conductor/tests/blackbox/soft_and_firm.rs index 1054c43f80..85ac6afaff 100644 --- a/crates/astria-conductor/tests/blackbox/soft_and_firm.rs +++ b/crates/astria-conductor/tests/blackbox/soft_and_firm.rs @@ -5,7 +5,10 @@ use futures::future::{ join, join3, }; -use tokio::time::timeout; +use tokio::time::{ + sleep, + timeout, +}; use crate::{ helpers::spawn_conductor, @@ -40,8 +43,10 @@ async fn executes_soft_first_then_updates_firm() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -174,8 +179,10 @@ async fn executes_firm_then_soft_at_next_height() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -333,8 +340,10 @@ async fn missing_block_is_fetched_for_updating_firm_commitment() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -462,8 +471,11 @@ async fn conductor_restarts_on_permission_denied() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, + up_to_n_times: 2, ); mount_get_commitment_state!( @@ -479,6 +491,7 @@ async fn conductor_restarts_on_permission_denied() { parent: [0; 64], ), base_celestia_height: 1, + up_to_n_times: 2, ); mount_sequencer_genesis!(test_conductor); @@ -578,3 +591,229 @@ async fn conductor_restarts_on_permission_denied() { within 1000ms", ); } + +/// Tests if the conductor correctly stops and procedes to restart after reaching the sequencer stop +/// height (from genesis info provided by rollup). In `SoftAndFirm` mode executor should not call +/// `execute_block` or `update_commitment_state` for any soft blocks at or above the stop height. +/// The conductor DOES call these on the firm block at the stop height, then proceeds to restart. +/// +/// This test consists of the following steps: +/// 1. Mount commitment state and genesis info with a sequencer stop height of 3, only responding up +/// to 1 time so that Conductor will not receive the same response after restart. +/// 2. Mount Celestia network head and sequencer genesis. +/// 3. Mount ABCI info and sequencer (soft blocks) for heights 3 and 4. +/// 4. Mount firm blocks at heights 3 and 4, with corresponding `update_commitment_state` mounts, +/// which should both be called. These are mounted with a slight delay to ensure that the soft +/// block arrives first after restart. +/// 5. Mount `execute_block` and `update_commitment_state` for both soft and firm blocks at height +/// 3. The soft block should not be executed since it is at the stop height, but the firm should +/// be. +/// 6. Await satisfaction of the `execute_block` and `update_commitment_state` for the firm block at +/// height 3 with a timeout of 1000ms. The test sleeps during this time, so that the following +/// mounts do not occur before the conductor restarts. +/// 7. Mount new genesis info with a sequencer stop height of 10 and a rollup start block height of +/// 2, along with corresponding commitment state, reflecting that block 1 has already been +/// executed and the commitment state updated. +/// 8. Mount `execute_block` and `update_commitment_state` for both soft and firm blocks at height 4 +/// and await their satisfaction. +#[expect( + clippy::too_many_lines, + reason = "All lines reasonably necessary for the thoroughness of this test" +)] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn conductor_restarts_after_reaching_stop_height() { + let test_conductor = spawn_conductor(CommitLevel::SoftAndFirm).await; + + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 3, + celestia_block_variance: 10, + rollup_start_block_height: 0, + up_to_n_times: 1, // We only respond once since this needs to be updated after restart + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + base_celestia_height: 1, + up_to_n_times: 1, // We only respond once since this needs to be updated after restart + ); + + mount_sequencer_genesis!(test_conductor); + mount_celestia_header_network_head!( + test_conductor, + height: 1u32, + ); + mount_abci_info!( + test_conductor, + latest_sequencer_height: 4, + ); + + // Mount soft blocks for heights 3 and 4 + mount_get_filtered_sequencer_block!( + test_conductor, + sequencer_height: 3, + ); + + // Mount firm blocks for heights 3 and 4 + mount_celestia_blobs!( + test_conductor, + celestia_height: 1, + sequencer_heights: [3, 4], + delay: Some(Duration::from_millis(200)) // short delay to ensure soft block at height 4 gets executed first after restart + ); + mount_sequencer_commit!( + test_conductor, + height: 3u32, + ); + mount_sequencer_commit!( + test_conductor, + height: 4u32, + ); + mount_sequencer_validator_set!(test_conductor, height: 2u32); + mount_sequencer_validator_set!(test_conductor, height: 3u32); + + let execute_block_1 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_1", + number: 2, + hash: [2; 64], + parent: [1; 64], + expected_calls: 1, // This should not be called again after restart + ); + + // This should not be called, since it is at the sequencer stop height + let _update_commitment_state_soft_1 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_soft_1", + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + expected_calls: 0, + ); + + let update_commitment_state_firm_1 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_firm_1", + firm: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + expected_calls: 1, // Should not be called again after restart + ); + + timeout( + Duration::from_millis(1000), + join( + execute_block_1.wait_until_satisfied(), + update_commitment_state_firm_1.wait_until_satisfied(), + ), + ) + .await + .expect("conductor should have updated the firm commitment state within 1000ms"); + + // Wait until conductor is restarted before performing next set of mounts + sleep(Duration::from_millis(1000)).await; + + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, + celestia_block_variance: 10, + rollup_start_block_height: 1, + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + ); + + let execute_block_2 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_2", + number: 3, + hash: [3; 64], + parent: [2; 64], + expected_calls: 1, + ); + + let update_commitment_state_soft_2 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_soft_2", + firm: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + soft: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + base_celestia_height: 1, + expected_calls: 1, + ); + + let update_commitment_state_firm_2 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_firm_2", + firm: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + soft: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + base_celestia_height: 1, + expected_calls: 1, + ); + + timeout( + Duration::from_millis(1000), + join3( + execute_block_2.wait_until_satisfied(), + update_commitment_state_soft_2.wait_until_satisfied(), + update_commitment_state_firm_2.wait_until_satisfied(), + ), + ) + .await + .expect("conductor should have updated the firm commitment state within 1000ms"); +} diff --git a/crates/astria-conductor/tests/blackbox/soft_only.rs b/crates/astria-conductor/tests/blackbox/soft_only.rs index 8fff4e8c1c..cda9e431b7 100644 --- a/crates/astria-conductor/tests/blackbox/soft_only.rs +++ b/crates/astria-conductor/tests/blackbox/soft_only.rs @@ -14,7 +14,10 @@ use futures::future::{ join4, }; use telemetry::metrics; -use tokio::time::timeout; +use tokio::time::{ + sleep, + timeout, +}; use crate::{ commitment_state, @@ -41,8 +44,10 @@ async fn simple() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -114,8 +119,10 @@ async fn submits_two_heights_in_succession() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -220,8 +227,10 @@ async fn skips_already_executed_heights() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 1, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -293,8 +302,10 @@ async fn requests_from_later_genesis_height() { mount_get_genesis_info!( test_conductor, - sequencer_genesis_block_height: 10, + sequencer_start_block_height: 10, + sequencer_stop_block_height: 20, celestia_block_variance: 10, + rollup_start_block_height: 0, ); mount_get_commitment_state!( @@ -401,8 +412,10 @@ async fn exits_on_sequencer_chain_id_mismatch() { matcher::message_type::(), ) .respond_with(GrpcResponse::constant_response( - genesis_info!(sequencer_genesis_block_height: 1, - celestia_block_variance: 10,), + genesis_info!(sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, + celestia_block_variance: 10, + rollup_start_block_height: 0,), )) .expect(0..) .mount(&mock_grpc.mock_server) @@ -452,3 +465,174 @@ async fn exits_on_sequencer_chain_id_mismatch() { } } } + +/// Tests that the conductor correctly stops at the sequencer stop block height in soft only mode, +/// not executing the soft block at that height. Then, tests that the conductor correctly restarts +/// and continues executing soft blocks after receiving updated genesis info and commitment state. +/// +/// It consists of the following steps: +/// 1. Mount commitment state and genesis info with a stop height of 4, responding only up to 1 time +/// so that the same information is not retrieved after restarting. +/// 2. Mount sequencer genesis, ABCI info, and sequencer blocks for heights 3 and 4. +/// 3. Mount `execute_block` and `update_commitment_state` mocks for the soft block at height 3, +/// expecting only 1 call and timing out after 1000ms. During this time, the test sleeps so that +/// the following mounts are not performed before the conductor restarts. +/// 4. Mount updated commitment state and genesis info with a stop height of 10 (more than high +/// enough) and a rollup start block height of 2, reflecting that the first block has already +/// been executed. +/// 5. Mount `execute_block` and `update_commitment_state` mocks for the soft block at height 4, +/// awaiting their satisfaction. +#[expect( + clippy::too_many_lines, + reason = "All lines reasonably necessary for the thoroughness of this test" +)] +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn conductor_restarts_after_reaching_stop_block_height() { + let test_conductor = spawn_conductor(CommitLevel::SoftOnly).await; + + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 4, + celestia_block_variance: 10, + rollup_start_block_height: 0, + up_to_n_times: 1, // We need to mount a new genesis info after restart + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + base_celestia_height: 1, + up_to_n_times: 1, // We need to mount a new commitment state after restart + ); + + mount_sequencer_genesis!(test_conductor); + + mount_abci_info!( + test_conductor, + latest_sequencer_height: 4, + ); + + mount_get_filtered_sequencer_block!( + test_conductor, + sequencer_height: 3, + ); + + mount_get_filtered_sequencer_block!( + test_conductor, + sequencer_height: 4, + ); + + let execute_block_1 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_1", + number: 2, + hash: [2; 64], + parent: [1; 64], + expected_calls: 1, + ); + + let update_commitment_state_1 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_1", + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + expected_calls: 1, + ); + + timeout( + Duration::from_millis(1000), + join( + execute_block_1.wait_until_satisfied(), + update_commitment_state_1.wait_until_satisfied(), + ), + ) + .await + .expect( + "conductor should have executed the first soft block and updated the first soft \ + commitment state within 1000ms", + ); + + // Wait until conductor is restarted before performing next set of mounts + sleep(Duration::from_millis(1000)).await; + + mount_get_genesis_info!( + test_conductor, + sequencer_start_block_height: 1, + sequencer_stop_block_height: 10, + celestia_block_variance: 10, + rollup_start_block_height: 1, + ); + + mount_get_commitment_state!( + test_conductor, + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 2, + hash: [2; 64], + parent: [1; 64], + ), + base_celestia_height: 1, + ); + + let execute_block_2 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_2", + number: 3, + hash: [3; 64], + parent: [2; 64], + expected_calls: 1, + ); + + let update_commitment_state_2 = mount_update_commitment_state!( + test_conductor, + mock_name: "update_commitment_state_2", + firm: ( + number: 1, + hash: [1; 64], + parent: [0; 64], + ), + soft: ( + number: 3, + hash: [3; 64], + parent: [2; 64], + ), + base_celestia_height: 1, + expected_calls: 1, + ); + + timeout( + Duration::from_millis(1000), + join( + execute_block_2.wait_until_satisfied(), + update_commitment_state_2.wait_until_satisfied(), + ), + ) + .await + .expect( + "conductor should have executed the second soft block and updated the second soft \ + commitment state within 1000ms", + ); +} diff --git a/crates/astria-core/src/execution/v1/mod.rs b/crates/astria-core/src/execution/v1/mod.rs index 3327cd0edc..7bfa96e4b4 100644 --- a/crates/astria-core/src/execution/v1/mod.rs +++ b/crates/astria-core/src/execution/v1/mod.rs @@ -23,6 +23,14 @@ impl GenesisInfoError { fn no_rollup_id() -> Self { Self(GenesisInfoErrorKind::NoRollupId) } + + fn invalid_sequencer_id(source: tendermint::Error) -> Self { + Self(GenesisInfoErrorKind::InvalidSequencerId(source)) + } + + fn invalid_celestia_id(source: celestia_tendermint::Error) -> Self { + Self(GenesisInfoErrorKind::InvalidCelestiaId(source)) + } } #[derive(Debug, thiserror::Error)] @@ -31,6 +39,10 @@ enum GenesisInfoErrorKind { IncorrectRollupIdLength(IncorrectRollupIdLength), #[error("`rollup_id` was not set")] NoRollupId, + #[error("invalid tendermint chain ID for sequencer")] + InvalidSequencerId(#[source] tendermint::Error), + #[error("invalid celestia-tendermint chain ID for celestia")] + InvalidCelestiaId(#[source] celestia_tendermint::Error), } /// Genesis Info required from a rollup to start an execution client. @@ -39,7 +51,7 @@ enum GenesisInfoErrorKind { /// /// Usually constructed its [`Protobuf`] implementation from a /// [`raw::GenesisInfo`]. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr( feature = "serde", @@ -49,9 +61,17 @@ pub struct GenesisInfo { /// The rollup id which is used to identify the rollup txs. rollup_id: RollupId, /// The Sequencer block height which contains the first block of the rollup. - sequencer_genesis_block_height: tendermint::block::Height, + sequencer_start_block_height: tendermint::block::Height, + /// The Sequencer block height to stop at. + sequencer_stop_block_height: tendermint::block::Height, /// The allowed variance in the block height of celestia when looking for sequencer blocks. celestia_block_variance: u64, + /// The rollup block number to map to the sequencer start block height. + rollup_start_block_height: u64, + /// The chain ID of the sequencer network. + sequencer_chain_id: tendermint::chain::Id, + /// The chain ID of the celestia network. + celestia_chain_id: celestia_tendermint::chain::Id, } impl GenesisInfo { @@ -61,14 +81,34 @@ impl GenesisInfo { } #[must_use] - pub fn sequencer_genesis_block_height(&self) -> tendermint::block::Height { - self.sequencer_genesis_block_height + pub fn sequencer_start_block_height(&self) -> u64 { + self.sequencer_start_block_height.into() + } + + #[must_use] + pub fn sequencer_stop_block_height(&self) -> u64 { + self.sequencer_stop_block_height.into() } #[must_use] pub fn celestia_block_variance(&self) -> u64 { self.celestia_block_variance } + + #[must_use] + pub fn sequencer_chain_id(&self) -> &tendermint::chain::Id { + &self.sequencer_chain_id + } + + #[must_use] + pub fn celestia_chain_id(&self) -> &str { + self.celestia_chain_id.as_str() + } + + #[must_use] + pub fn rollup_start_block_height(&self) -> u64 { + self.rollup_start_block_height + } } impl From for raw::GenesisInfo { @@ -84,38 +124,68 @@ impl Protobuf for GenesisInfo { fn try_from_raw_ref(raw: &Self::Raw) -> Result { let raw::GenesisInfo { rollup_id, - sequencer_genesis_block_height, + sequencer_start_block_height, + sequencer_stop_block_height, celestia_block_variance, + rollup_start_block_height, + sequencer_chain_id, + celestia_chain_id, } = raw; let Some(rollup_id) = rollup_id else { return Err(Self::Error::no_rollup_id()); }; let rollup_id = RollupId::try_from_raw_ref(rollup_id) .map_err(Self::Error::incorrect_rollup_id_length)?; + let sequencer_chain_id = sequencer_chain_id + .clone() + .try_into() + .map_err(Self::Error::invalid_sequencer_id)?; + let celestia_chain_id = celestia_chain_id + .clone() + .try_into() + .map_err(Self::Error::invalid_celestia_id)?; Ok(Self { rollup_id, - sequencer_genesis_block_height: (*sequencer_genesis_block_height).into(), + sequencer_start_block_height: (*sequencer_start_block_height).into(), + sequencer_stop_block_height: (*sequencer_stop_block_height).into(), celestia_block_variance: *celestia_block_variance, + rollup_start_block_height: *rollup_start_block_height, + sequencer_chain_id, + celestia_chain_id, }) } fn to_raw(&self) -> Self::Raw { let Self { rollup_id, - sequencer_genesis_block_height, + sequencer_start_block_height, + sequencer_stop_block_height, celestia_block_variance, + rollup_start_block_height, + sequencer_chain_id, + celestia_chain_id, } = self; - let sequencer_genesis_block_height: u32 = - (*sequencer_genesis_block_height).value().try_into().expect( + let sequencer_start_block_height: u32 = + (*sequencer_start_block_height).value().try_into().expect( "block height overflow, this should not happen since tendermint heights are i64 \ under the hood", ); + let sequencer_stop_block_height: u32 = + (*sequencer_stop_block_height).value().try_into().expect( + "block height overflow, this should not happen since tendermint heights are i64 \ + under the hood", + ); + Self::Raw { rollup_id: Some(rollup_id.to_raw()), - sequencer_genesis_block_height, + sequencer_start_block_height, + sequencer_stop_block_height, celestia_block_variance: *celestia_block_variance, + rollup_start_block_height: *rollup_start_block_height, + sequencer_chain_id: sequencer_chain_id.to_string(), + celestia_chain_id: celestia_chain_id.to_string(), } } } diff --git a/crates/astria-core/src/generated/astria.execution.v1.rs b/crates/astria-core/src/generated/astria.execution.v1.rs index 35b5d20480..3de5b64bc8 100644 --- a/crates/astria-core/src/generated/astria.execution.v1.rs +++ b/crates/astria-core/src/generated/astria.execution.v1.rs @@ -10,10 +10,20 @@ pub struct GenesisInfo { pub rollup_id: ::core::option::Option, /// The first block height of sequencer chain to use for rollup transactions. #[prost(uint32, tag = "2")] - pub sequencer_genesis_block_height: u32, + pub sequencer_start_block_height: u32, + /// The last block height of sequencer chain to use for rollup transactions. + #[prost(uint32, tag = "3")] + pub sequencer_stop_block_height: u32, /// The allowed variance in celestia for sequencer blocks to have been posted. #[prost(uint64, tag = "4")] pub celestia_block_variance: u64, + /// The rollup block number to map to the sequencer start block height. + #[prost(uint64, tag = "5")] + pub rollup_start_block_height: u64, + #[prost(string, tag = "6")] + pub sequencer_chain_id: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub celestia_chain_id: ::prost::alloc::string::String, } impl ::prost::Name for GenesisInfo { const NAME: &'static str = "GenesisInfo"; diff --git a/crates/astria-core/src/generated/astria.execution.v1.serde.rs b/crates/astria-core/src/generated/astria.execution.v1.serde.rs index 970146cc46..5454cac369 100644 --- a/crates/astria-core/src/generated/astria.execution.v1.serde.rs +++ b/crates/astria-core/src/generated/astria.execution.v1.serde.rs @@ -710,23 +710,48 @@ impl serde::Serialize for GenesisInfo { if self.rollup_id.is_some() { len += 1; } - if self.sequencer_genesis_block_height != 0 { + if self.sequencer_start_block_height != 0 { + len += 1; + } + if self.sequencer_stop_block_height != 0 { len += 1; } if self.celestia_block_variance != 0 { len += 1; } + if self.rollup_start_block_height != 0 { + len += 1; + } + if !self.sequencer_chain_id.is_empty() { + len += 1; + } + if !self.celestia_chain_id.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("astria.execution.v1.GenesisInfo", len)?; if let Some(v) = self.rollup_id.as_ref() { struct_ser.serialize_field("rollupId", v)?; } - if self.sequencer_genesis_block_height != 0 { - struct_ser.serialize_field("sequencerGenesisBlockHeight", &self.sequencer_genesis_block_height)?; + if self.sequencer_start_block_height != 0 { + struct_ser.serialize_field("sequencerStartBlockHeight", &self.sequencer_start_block_height)?; + } + if self.sequencer_stop_block_height != 0 { + struct_ser.serialize_field("sequencerStopBlockHeight", &self.sequencer_stop_block_height)?; } if self.celestia_block_variance != 0 { #[allow(clippy::needless_borrow)] struct_ser.serialize_field("celestiaBlockVariance", ToString::to_string(&self.celestia_block_variance).as_str())?; } + if self.rollup_start_block_height != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("rollupStartBlockHeight", ToString::to_string(&self.rollup_start_block_height).as_str())?; + } + if !self.sequencer_chain_id.is_empty() { + struct_ser.serialize_field("sequencerChainId", &self.sequencer_chain_id)?; + } + if !self.celestia_chain_id.is_empty() { + struct_ser.serialize_field("celestiaChainId", &self.celestia_chain_id)?; + } struct_ser.end() } } @@ -739,17 +764,29 @@ impl<'de> serde::Deserialize<'de> for GenesisInfo { const FIELDS: &[&str] = &[ "rollup_id", "rollupId", - "sequencer_genesis_block_height", - "sequencerGenesisBlockHeight", + "sequencer_start_block_height", + "sequencerStartBlockHeight", + "sequencer_stop_block_height", + "sequencerStopBlockHeight", "celestia_block_variance", "celestiaBlockVariance", + "rollup_start_block_height", + "rollupStartBlockHeight", + "sequencer_chain_id", + "sequencerChainId", + "celestia_chain_id", + "celestiaChainId", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { RollupId, - SequencerGenesisBlockHeight, + SequencerStartBlockHeight, + SequencerStopBlockHeight, CelestiaBlockVariance, + RollupStartBlockHeight, + SequencerChainId, + CelestiaChainId, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -772,8 +809,12 @@ impl<'de> serde::Deserialize<'de> for GenesisInfo { { match value { "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), - "sequencerGenesisBlockHeight" | "sequencer_genesis_block_height" => Ok(GeneratedField::SequencerGenesisBlockHeight), + "sequencerStartBlockHeight" | "sequencer_start_block_height" => Ok(GeneratedField::SequencerStartBlockHeight), + "sequencerStopBlockHeight" | "sequencer_stop_block_height" => Ok(GeneratedField::SequencerStopBlockHeight), "celestiaBlockVariance" | "celestia_block_variance" => Ok(GeneratedField::CelestiaBlockVariance), + "rollupStartBlockHeight" | "rollup_start_block_height" => Ok(GeneratedField::RollupStartBlockHeight), + "sequencerChainId" | "sequencer_chain_id" => Ok(GeneratedField::SequencerChainId), + "celestiaChainId" | "celestia_chain_id" => Ok(GeneratedField::CelestiaChainId), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -794,8 +835,12 @@ impl<'de> serde::Deserialize<'de> for GenesisInfo { V: serde::de::MapAccess<'de>, { let mut rollup_id__ = None; - let mut sequencer_genesis_block_height__ = None; + let mut sequencer_start_block_height__ = None; + let mut sequencer_stop_block_height__ = None; let mut celestia_block_variance__ = None; + let mut rollup_start_block_height__ = None; + let mut sequencer_chain_id__ = None; + let mut celestia_chain_id__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::RollupId => { @@ -804,11 +849,19 @@ impl<'de> serde::Deserialize<'de> for GenesisInfo { } rollup_id__ = map_.next_value()?; } - GeneratedField::SequencerGenesisBlockHeight => { - if sequencer_genesis_block_height__.is_some() { - return Err(serde::de::Error::duplicate_field("sequencerGenesisBlockHeight")); + GeneratedField::SequencerStartBlockHeight => { + if sequencer_start_block_height__.is_some() { + return Err(serde::de::Error::duplicate_field("sequencerStartBlockHeight")); } - sequencer_genesis_block_height__ = + sequencer_start_block_height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::SequencerStopBlockHeight => { + if sequencer_stop_block_height__.is_some() { + return Err(serde::de::Error::duplicate_field("sequencerStopBlockHeight")); + } + sequencer_stop_block_height__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } @@ -820,12 +873,36 @@ impl<'de> serde::Deserialize<'de> for GenesisInfo { Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } + GeneratedField::RollupStartBlockHeight => { + if rollup_start_block_height__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupStartBlockHeight")); + } + rollup_start_block_height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::SequencerChainId => { + if sequencer_chain_id__.is_some() { + return Err(serde::de::Error::duplicate_field("sequencerChainId")); + } + sequencer_chain_id__ = Some(map_.next_value()?); + } + GeneratedField::CelestiaChainId => { + if celestia_chain_id__.is_some() { + return Err(serde::de::Error::duplicate_field("celestiaChainId")); + } + celestia_chain_id__ = Some(map_.next_value()?); + } } } Ok(GenesisInfo { rollup_id: rollup_id__, - sequencer_genesis_block_height: sequencer_genesis_block_height__.unwrap_or_default(), + sequencer_start_block_height: sequencer_start_block_height__.unwrap_or_default(), + sequencer_stop_block_height: sequencer_stop_block_height__.unwrap_or_default(), celestia_block_variance: celestia_block_variance__.unwrap_or_default(), + rollup_start_block_height: rollup_start_block_height__.unwrap_or_default(), + sequencer_chain_id: sequencer_chain_id__.unwrap_or_default(), + celestia_chain_id: celestia_chain_id__.unwrap_or_default(), }) } } diff --git a/dev/values/rollup/dev.yaml b/dev/values/rollup/dev.yaml index 0d9be85422..7f7af87cd9 100644 --- a/dev/values/rollup/dev.yaml +++ b/dev/values/rollup/dev.yaml @@ -10,18 +10,43 @@ global: evm-rollup: genesis: - ## These values are used to configure the genesis block of the rollup chain - ## no defaults as they are unique to each chain - - # Block height to start syncing rollup from, lowest possible is 2 - sequencerInitialHeight: 2 - # The first Celestia height to utilize when looking for rollup data - celestiaInitialHeight: 2 - # The variance in Celestia height to allow before halting the chain - celestiaHeightVariance: 10 - # Will fill the extra data in each block, can be left empty - # can also fill with something unique for your chain. - extraDataOverride: "" + # The name of the rollup chain, used to generate the Rollup ID + rollupName: "{{ .Values.global.rollupName }}" + + # The "forks" for upgrading the chain. Contains necessary information for starting + # and, if desired, restarting the chain at a given height. The necessary fields + # for the genesis fork are provided, and additional forks can be added as needed. + forks: + ## These values are used to configure the genesis block of the rollup chain + ## no defaults as they are unique to each chain + launch: + # The block height to start the chain at, 0 for genesis + height: 0 + # Configure the fee collector for the evm tx fees, activated at block heights. + # If not configured, all tx fees will be burned. + feeCollector: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" + # The chain id of the sequencer chain + sequencerChainId: "{{ .Values.global.sequencerChainId }}" + # The hrp for bech32m addresses, unlikely to be changed + sequencerAddressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + sequencerStartHeight: 2 + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork + sequencerStopHeight: 0 + # The chain id of the celestia chain + celestiaChainId: "{{ .Values.global.celestiaChainId }}" + # The first Celestia height to utilize when looking for rollup data + celestiaStartHeight: 2 + # The variance in Celestia height to allow before halting the chain + celestiaHeightVariance: 10 + # Configure the sequencer bridge addresses and allowed assets if using + # the astria canonical bridge. Recommend removing alloc values if so. + bridgeAddresses: + - bridgeAddress: "astria13ahqz4pjqfmynk9ylrqv4fwe4957x2p0h5782u" + startHeight: 1 + senderAddress: "0x0000000000000000000000000000000000000000" + assetDenom: "nria" + assetPrecision: 9 ## These are general configuration values with some recommended defaults @@ -29,34 +54,6 @@ evm-rollup: gasLimit: "50000000" # If set to true the genesis block will contain extra data overrideGenesisExtraData: true - # The hrp for bech32m addresses, unlikely to be changed - sequencerAddressPrefix: "astria" - - ## These values are used to configure astria native bridging - ## Many of the fields have commented out example fields - - # Configure the sequencer bridge addresses and allowed assets if using - # the astria canonical bridge. Recommend removing alloc values if so. - bridgeAddresses: - - bridgeAddress: "astria13ahqz4pjqfmynk9ylrqv4fwe4957x2p0h5782u" - startHeight: 1 - senderAddress: "0x0000000000000000000000000000000000000000" - assetDenom: "nria" - assetPrecision: 9 - - - ## Fee configuration - - # Configure the fee collector for the evm tx fees, activated at block heights. - # If not configured, all tx fees will be burned. - feeCollectors: - 1: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" - # Configure EIP-1559 params, activated at block heights - eip1559Params: {} - # 1: - # minBaseFee: 0 - # elasticityMultiplier: 2 - # baseFeeChangeDenominator: 8 ## Standard Eth Genesis config values # Configuration of Eth forks, setting to 0 will enable from height, diff --git a/dev/values/rollup/ibc-bridge-test.yaml b/dev/values/rollup/ibc-bridge-test.yaml index b5f198927c..9d546a37a2 100644 --- a/dev/values/rollup/ibc-bridge-test.yaml +++ b/dev/values/rollup/ibc-bridge-test.yaml @@ -1,12 +1,46 @@ # this file contains overrides that are used for the ibc bridge tests +global: + rollupName: astria + sequencerChainId: sequencer-test-chain-0 + celestiaChainId: celestia-local-0 evm-rollup: genesis: - bridgeAddresses: - - bridgeAddress: "astria1d7zjjljc0dsmxa545xkpwxym86g8uvvwhtezcr" - startHeight: 1 - assetDenom: "transfer/channel-0/utia" - assetPrecision: 6 + # The name of the rollup chain, used to generate the Rollup ID + rollupName: "{{ .Values.global.rollupName }}" + + # The "forks" for upgrading the chain. Contains necessary information for starting + # and, if desired, restarting the chain at a given height. The necessary fields + # for the genesis fork are provided, and additional forks can be added as needed. + forks: + ## These values are used to configure the genesis block of the rollup chain + ## no defaults as they are unique to each chain + launch: + # The block height to start the chain at, 0 for genesis + height: 0 + # Configure the fee collector for the evm tx fees, activated at block heights. + # If not configured, all tx fees will be burned. + feeCollector: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" + # The chain id of the sequencer chain + sequencerChainId: "{{ .Values.global.sequencerChainId }}" + # The hrp for bech32m addresses, unlikely to be changed + sequencerAddressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + sequencerStartHeight: 2 + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork + sequencerStopHeight: 0 + # The chain id of the celestia chain + celestiaChainId: "{{ .Values.global.celestiaChainId }}" + # The first Celestia height to utilize when looking for rollup data + celestiaStartHeight: 2 + # The variance in Celestia height to allow before halting the chain + celestiaHeightVariance: 10 + bridgeAddresses: + - bridgeAddress: "astria1d7zjjljc0dsmxa545xkpwxym86g8uvvwhtezcr" + startHeight: 1 + assetDenom: "transfer/channel-0/utia" + assetPrecision: 6 + alloc: - address: "0x4e59b44847b379578588920cA78FbF26c0B4956C" value: diff --git a/proto/executionapis/astria/execution/v1/execution.proto b/proto/executionapis/astria/execution/v1/execution.proto index e68083db4d..62a108a7d3 100644 --- a/proto/executionapis/astria/execution/v1/execution.proto +++ b/proto/executionapis/astria/execution/v1/execution.proto @@ -14,9 +14,15 @@ message GenesisInfo { // The rollup_id is the unique identifier for the rollup chain. astria.primitive.v1.RollupId rollup_id = 1; // The first block height of sequencer chain to use for rollup transactions. - uint32 sequencer_genesis_block_height = 2; + uint32 sequencer_start_block_height = 2; + // The last block height of sequencer chain to use for rollup transactions. + uint32 sequencer_stop_block_height = 3; // The allowed variance in celestia for sequencer blocks to have been posted. uint64 celestia_block_variance = 4; + // The rollup block number to map to the sequencer start block height. + uint64 rollup_start_block_height = 5; + string sequencer_chain_id = 6; + string celestia_chain_id = 7; } // The set of information which deterministic driver of block production