diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 232ae7c07c..e4a56925da 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -165,6 +165,37 @@ jobs: run: | TAG=sha-$(git rev-parse --short HEAD) just run-smoke-test $TAG + smoke-test-evm-rollup-restart: + needs: [run_checker, composer, conductor, sequencer, sequencer-relayer, evm-bridge-withdrawer, cli] + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'astriaorg/astria') && (github.event_name == 'merge_group' || needs.run_checker.outputs.run_docker == 'true') + runs-on: buildjet-8vcpu-ubuntu-2204 + steps: + - uses: actions/checkout@v4 + - name: Install just + uses: taiki-e/install-action@just + - name: Install kind + uses: helm/kind-action@v1 + with: + install_only: true + - name: Log in to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Smoke Test Environment + timeout-minutes: 10 + run: | + TAG=sha-$(git rev-parse --short HEAD) + just deploy cluster + kubectl create secret generic regcred --from-file=.dockerconfigjson=$HOME/.docker/config.json --type=kubernetes.io/dockerconfigjson + echo -e "\n\nDeploying with astria images tagged $TAG" + just --set defaultValues "dev/values/rollup/evm-restart-test.yaml" deploy smoke-test $TAG + - name: Run Smoke test + timeout-minutes: 3 + run: | + TAG=sha-$(git rev-parse --short HEAD) + just run-smoke-test $TAG smoke-cli: needs: [run_checker, composer, conductor, sequencer, sequencer-relayer, evm-bridge-withdrawer, cli] diff --git a/Cargo.lock b/Cargo.lock index cf483afff4..50e242d5e1 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/deploy.just b/charts/deploy.just index 301620431e..279434c685 100644 --- a/charts/deploy.just +++ b/charts/deploy.just @@ -170,8 +170,9 @@ clean-persisted-data: deploy-local-metrics: kubectl apply -f kubernetes/metrics-server-local.yml +defaultValues := "dev/values/rollup/dev.yaml" defaultTag := "" -deploy-smoke-test tag=defaultTag: +deploy-smoke-test tag=defaultTag values=defaultValues: @echo "Deploying ingress controller..." && just deploy ingress-controller > /dev/null @just wait-for-ingress-controller > /dev/null @echo "Deploying local celestia instance..." && just deploy celestia-local > /dev/null @@ -184,7 +185,7 @@ deploy-smoke-test tag=defaultTag: {{ if tag != '' { replace('--set images.sequencer.devTag=# --set sequencer-relayer.images.sequencerRelayer.devTag=#', '#', tag) } else { '' } }} \ --create-namespace > /dev/null @just wait-for-sequencer > /dev/null - @echo "Starting EVM rollup..." && helm install -n astria-dev-cluster astria-chain-chart ./charts/evm-stack -f dev/values/rollup/dev.yaml \ + @echo "Starting EVM rollup..." && helm install -n astria-dev-cluster astria-chain-chart ./charts/evm-stack -f {{ values }} \ {{ if tag != '' { replace('--set evm-rollup.images.conductor.devTag=# --set composer.images.composer.devTag=# --set evm-bridge-withdrawer.images.evmBridgeWithdrawer.devTag=#', '#', tag) } else { '' } }} \ --set blockscout-stack.enabled=false \ --set postgresql.enabled=false \ 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..83618bf3ce 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,22 +27,98 @@ {{- 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 }}" - {{- if not .Values.global.dev }} - {{- else }} - {{- end }} + "astriaForks": { + {{- $forks := .Values.genesis.forks }} + {{- $index := 0 }} + {{- $lastIndex := sub (len $forks) 1 }} + {{- range $key, $value := .Values.genesis.forks }} + "{{ $key }}": { + {{- $fields := list }} + {{- with $value }} + + {{- if .height }} + {{- $fields = append $fields (printf "\"height\": %s" (toString .height | replace "\"" "")) }} + {{- end }} + + {{- if .halt }} + {{- $fields = append $fields (printf "\"halt\": %s" (toString .halt | replace "\"" "")) }} + {{- end }} + + {{- if .snapshotChecksum }} + {{- $fields = append $fields (printf "\"snapshotChecksum\": %s" (toString .snapshotChecksum)) }} + {{- end }} + + {{- if .extraDataOverride }} + {{- $fields = append $fields (printf "\"extraDataOverride\": %s" (toString .extraDataOverride)) }} + {{- end }} + + {{- if .feeCollector }} + {{- $fields = append $fields (printf "\"feeCollector\": \"%s\"" (toString .feeCollector)) }} + {{- end }} + + {{- if .eip1559Params }} + {{- $fields = append $fields (printf "\"eip1559Params\": %s" (toPrettyJson .eip1559Params | indent 8 | trim)) }} + {{- end }} + + {{- if .sequencer }} + {{- $sequencerFields := list }} + + {{- if .sequencer.chainId }} + {{- $sequencerFields = append $sequencerFields (printf "\"chainId\": \"%s\"" (tpl .sequencer.chainId .)) }} + {{- end }} + + {{- if .sequencer.addressPrefix }} + {{- $sequencerFields = append $sequencerFields (printf "\"addressPrefix\": \"%s\"" .sequencer.addressPrefix) }} + {{- end }} + + {{- if .sequencer.startHeight }} + {{- $sequencerFields = append $sequencerFields (printf "\"startHeight\": %s" (toString .sequencer.startHeight | replace "\"" "")) }} + {{- end }} + + {{- if .sequencer.stopHeight }} + {{- $sequencerFields = append $sequencerFields (printf "\"stopHeight\": %s" (toString .sequencer.stopHeight | replace "\"" "")) }} + {{- end }} + + {{- $fields = append $fields (printf "\"sequencer\": {\n%s\n}" (join ",\n" $sequencerFields | indent 4)) }} + {{- end }} + + {{- if .celestia }} + {{- $celestiaFields := list }} + + {{- if .celestia.chainId }} + {{- $celestiaFields = append $celestiaFields (printf "\"chainId\": \"%s\"" (tpl .celestia.chainId .)) }} + {{- end }} + + {{- if .celestia.startHeight }} + {{- $celestiaFields = append $celestiaFields (printf "\"startHeight\": %s" (toString .celestia.startHeight | replace "\"" "")) }} + {{- end }} + + {{- if .celestia.heightVariance }} + {{- $celestiaFields = append $celestiaFields (printf "\"heightVariance\": %s" (toString .celestia.heightVariance | replace "\"" "")) }} + {{- end }} + + {{- if $celestiaFields | len }} + {{- $fields = append $fields (printf "\"celestia\": {\n%s\n}" (join ",\n" $celestiaFields | indent 4)) }} + {{- end }} + {{- end }} + + {{- if .bridgeAddresses }} + {{- $fields = append $fields (printf "\"bridgeAddresses\": %s" (toPrettyJson .bridgeAddresses | indent 4 | trim)) }} + {{- end }} + + {{- join ",\n" $fields | indent 16 }} + } + {{- if ne $index $lastIndex }},{{ end }} + {{- $index := add $index 1 }} + {{- end }} + {{- end }} + } }, + {{- if not .Values.global.dev }} + {{- else }} + {{- end }} "difficulty": "0", "gasLimit": "{{ toString .Values.genesis.gasLimit | replace "\"" "" }}", "alloc": { 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..bb494e5dbf 100644 --- a/charts/evm-rollup/values.yaml +++ b/charts/evm-rollup/values.yaml @@ -11,30 +11,68 @@ 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: + 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 + sequencer: + # The chain id of the sequencer chain + chainId: "" + # The hrp for bech32m addresses, unlikely to be changed + addressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + startHeight: "" + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork. + # A height of 0 indicates no stop height. + stopHeight: "" + celestia: + # The chain id of the celestia chain + chainId: "" + # The first Celestia height to utilize when looking for rollup data + startHeight: "" + # The variance in Celestia height to allow before halting the chain + heightVariance: "" + # 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 ## These are general configuration values with some recommended defaults @@ -42,34 +80,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 +178,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 +189,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 55dd9cec3d..d40164ef6f 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:371c35af96fc5d82aa2b4d894dc7d2e11e150380fd6f09eb0ca94b4202b24698 -generated: "2025-01-08T11:22:41.273867-08:00" +digest: sha256:fe5a1fc6ae84217c619ef95494f0ce8999f332c373ee127bc78935628d3167b4 +generated: "2025-01-23T13:58:38.350609-06:00" diff --git a/charts/evm-stack/Chart.yaml b/charts/evm-stack/Chart.yaml index 7e1d7cad59..d395265569 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.7 +version: 1.0.8 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 687145fa53..0682d22d46 100644 --- a/crates/astria-conductor/CHANGELOG.md +++ b/crates/astria-conductor/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump MSRV to 1.83.0 [#1857](https://github.com/astriaorg/astria/pull/1857). +- 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 7934612df9..0cd03866fe 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.83.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 9acfc2c50f..1357617796 100644 --- a/crates/astria-conductor/src/celestia/mod.rs +++ b/crates/astria-conductor/src/celestia/mod.rs @@ -62,6 +62,7 @@ use tracing::{ use crate::{ block_cache::GetSequencerHeight, + conductor::ExitReason, executor::{ FirmSendError, FirmTrySendError, @@ -148,12 +149,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, @@ -161,13 +156,13 @@ pub(crate) struct Reader { } impl Reader { - pub(crate) async fn run_until_stopped(mut self) -> eyre::Result<()> { + pub(crate) async fn run_until_stopped(mut self) -> eyre::Result { let (executor, sequencer_chain_id) = select!( () = self.shutdown.clone().cancelled_owned() => { info_span!("conductor::celestia::Reader::run_until_stopped").in_scope(|| info!("received shutdown signal while waiting for Celestia reader task to initialize") ); - return Ok(()); + return Ok(ExitReason::ShutdownSignal); } res = self.initialize() => { @@ -185,46 +180,45 @@ impl Reader { 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 - ) - .map(|((), executor_init, sequencer_chain_id)| (executor_init, sequencer_chain_id)) + )?; + + Ok((executor, sequencer_chain_id)) } } @@ -375,7 +369,7 @@ impl RunningReader { }) } - async fn run_until_stopped(mut self) -> eyre::Result<()> { + async fn run_until_stopped(mut self) -> eyre::Result { info_span!("conductor::celestia::RunningReader::run_until_stopped").in_scope(|| { info!( initial_celestia_height = self.celestia_next_height, @@ -396,7 +390,7 @@ impl RunningReader { biased; () = self.shutdown.cancelled() => { - break Ok("received shutdown signal"); + break Ok(ExitReason::ShutdownSignal); } res = &mut self.enqueued_block, if self.waiting_for_executor_capacity() => { @@ -714,11 +708,11 @@ fn max_permitted_celestia_height(reference: u64, variance: u64) -> u64 { } #[instrument(skip_all)] -fn report_exit(exit_reason: eyre::Result<&str>, message: &str) -> eyre::Result<()> { +fn report_exit(exit_reason: eyre::Result, message: &str) -> eyre::Result { match exit_reason { Ok(reason) => { info!(%reason, message); - Ok(()) + Ok(reason) } Err(reason) => { error!(%reason, message); diff --git a/crates/astria-conductor/src/conductor/inner.rs b/crates/astria-conductor/src/conductor/inner.rs index c52bd32536..6f99514b80 100644 --- a/crates/astria-conductor/src/conductor/inner.rs +++ b/crates/astria-conductor/src/conductor/inner.rs @@ -5,7 +5,6 @@ use std::{ use astria_eyre::eyre::{ self, - eyre, Result, WrapErr as _, }; @@ -28,6 +27,7 @@ use tracing::{ warn, }; +use self::executor::StopHeightExceded; use crate::{ celestia, executor, @@ -44,12 +44,30 @@ pub(super) enum RestartOrShutdown { Shutdown, } -enum ExitReason { +pub(crate) enum ExitReason { ShutdownSignal, TaskFailed { name: &'static str, error: eyre::Report, }, + StopHeightExceded(StopHeightExceded), + ChannelsClosed, +} + +impl std::fmt::Display for ExitReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ExitReason::ShutdownSignal => write!(f, "received shutdown signal"), + ExitReason::TaskFailed { + name, + error, + } => { + write!(f, "task `{name}` failed: {error}") + } + ExitReason::StopHeightExceded(reason) => write!(f, "{reason}"), + ExitReason::ChannelsClosed => write!(f, "firm and soft block channels are both closed"), + } + } } pin_project! { @@ -82,7 +100,7 @@ pub(super) struct ConductorInner { shutdown_token: CancellationToken, /// The different long-running tasks that make up the conductor; - tasks: JoinMap<&'static str, eyre::Result<()>>, + tasks: JoinMap<&'static str, eyre::Result>, } impl ConductorInner { @@ -133,7 +151,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 +172,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, } @@ -188,7 +203,7 @@ impl ConductorInner { Some((name, res)) = self.tasks.join_next() => { match flatten(res) { - Ok(()) => ExitReason::TaskFailed{name, error: eyre!("task `{name}` exited unexpectedly")}, + Ok(exit_reason) => exit_reason, Err(err) => ExitReason::TaskFailed{name, error: err.wrap_err(format!("task `{name}` failed"))}, } } @@ -235,10 +250,18 @@ impl ConductorInner { name, error, } => { - if check_for_restart(name, error) { + if check_err_for_restart(name, error) { restart_or_shutdown = RestartOrShutdown::Restart; } } + ExitReason::StopHeightExceded(stop_height_exceded) => { + if !stop_height_exceded.halt() { + restart_or_shutdown = RestartOrShutdown::Restart; + } + } + ExitReason::ChannelsClosed => { + info!("firm and soft block channels are both closed, shutting down"); + } } info!("signalled all tasks to shut down; waiting for 25 seconds to exit"); @@ -247,11 +270,11 @@ impl ConductorInner { while let Some((name, res)) = self.tasks.join_next().await { let message = "task shut down"; match flatten(res) { - Ok(()) => { - info!(name, message); + Ok(exit_reason) => { + info!(name, message, %exit_reason); } Err(error) => { - if check_for_restart(name, &error) + if check_err_for_restart(name, &error) && !matches!(exit_reason, ExitReason::ShutdownSignal) { restart_or_shutdown = RestartOrShutdown::Restart; @@ -297,11 +320,18 @@ fn report_exit(exit_reason: &ExitReason, message: &str) { name: task, error: reason, } => error!(%reason, %task, message), + ExitReason::StopHeightExceded(stop_height_exceded) => { + info!(%stop_height_exceded, "restarting"); + } + ExitReason::ChannelsClosed => info!( + reason = "firm and soft block channels are both closed", + message + ), } } #[instrument(skip_all)] -fn check_for_restart(name: &str, err: &eyre::Report) -> bool { +fn check_err_for_restart(name: &str, err: &eyre::Report) -> bool { if name != ConductorInner::EXECUTOR { return false; } @@ -322,12 +352,12 @@ mod tests { use astria_eyre::eyre::WrapErr as _; #[test] - fn check_for_restart_ok() { + fn check_err_for_restart_ok() { let tonic_error: Result<&str, tonic::Status> = Err(tonic::Status::new(tonic::Code::PermissionDenied, "error")); let err = tonic_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())); + assert!(super::check_err_for_restart("executor", &err.unwrap_err())); } } diff --git a/crates/astria-conductor/src/conductor/mod.rs b/crates/astria-conductor/src/conductor/mod.rs index f7450d0668..b9c28c09f5 100644 --- a/crates/astria-conductor/src/conductor/mod.rs +++ b/crates/astria-conductor/src/conductor/mod.rs @@ -9,6 +9,7 @@ use astria_eyre::eyre::{ self, Result, }; +pub(crate) use inner::ExitReason; use inner::{ ConductorInner, InnerHandle, 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 fb3525ead1..b28c8f6ba7 100644 --- a/crates/astria-conductor/src/executor/mod.rs +++ b/crates/astria-conductor/src/executor/mod.rs @@ -41,6 +41,7 @@ use tracing::{ use crate::{ celestia::ReconstructedBlock, + conductor::ExitReason, config::CommitLevel, metrics::Metrics, }; @@ -68,6 +69,81 @@ pub(crate) struct StateNotInit; #[derive(Clone, Debug)] pub(crate) struct StateIsInit; +#[derive(Debug)] +pub(crate) enum StopHeightExceded { + Celestia { + firm_height: u64, + stop_height: u64, + halt: bool, + }, + Sequencer { + soft_height: u64, + stop_height: u64, + halt: bool, + }, +} + +impl StopHeightExceded { + fn celestia(firm_height: u64, stop_height: u64, halt: bool) -> Self { + StopHeightExceded::Celestia { + firm_height, + stop_height, + halt, + } + } + + fn sequencer(soft_height: u64, stop_height: u64, halt: bool) -> Self { + StopHeightExceded::Sequencer { + soft_height, + stop_height, + halt, + } + } + + /// Returns whether the conductor should halt at the stop height. + pub(crate) fn halt(&self) -> bool { + match self { + StopHeightExceded::Celestia { + halt, .. + } + | StopHeightExceded::Sequencer { + halt, .. + } => *halt, + } + } +} + +impl std::fmt::Display for StopHeightExceded { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StopHeightExceded::Celestia { + firm_height, + stop_height, + halt, + } => { + let halt_msg = if *halt { "halting" } else { "restarting" }; + write!( + f, + "firm block height {firm_height} exceded stop height {stop_height}, \ + {halt_msg}...", + ) + } + StopHeightExceded::Sequencer { + soft_height, + stop_height, + halt, + } => { + let halt_msg = if *halt { "halting" } else { "restarting" }; + write!( + f, + "soft block height {soft_height} exceded stop height {stop_height}, \ + {halt_msg}...", + ) + } + } + } +} + #[derive(Debug, thiserror::Error)] pub(crate) enum FirmSendError { #[error("executor was configured without firm commitments")] @@ -213,6 +289,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 { @@ -251,13 +335,12 @@ pub(crate) struct Executor { } impl Executor { - pub(crate) async fn run_until_stopped(mut self) -> eyre::Result<()> { + pub(crate) async fn run_until_stopped(mut self) -> eyre::Result { select!( () = self.shutdown.clone().cancelled_owned() => { return report_exit(Ok( - "received shutdown signal while initializing task; \ - aborting intialization and exiting" - ), ""); + ExitReason::ShutdownSignal + ), "aborting initialization due to shutdown signal"); } res = self.init() => { res.wrap_err("initialization failed")?; @@ -265,6 +348,10 @@ impl Executor { ); let reason = loop { + if self.firm_blocks.is_none() && self.soft_blocks.is_none() { + break Ok(ExitReason::ChannelsClosed); + } + let spread_not_too_large = !self.is_spread_too_large(); if spread_not_too_large { if let Some(channel) = self.soft_blocks.as_mut() { @@ -276,7 +363,7 @@ impl Executor { biased; () = self.shutdown.cancelled() => { - break Ok("received shutdown signal"); + break Ok(ExitReason::ShutdownSignal); } Some(block) = async { self.firm_blocks.as_mut().unwrap().recv().await }, @@ -287,8 +374,14 @@ impl Executor { block.hash = %block.block_hash(), "received block from celestia reader", )); - if let Err(error) = self.execute_firm(block).await { - break Err(error).wrap_err("failed executing firm block"); + match self.execute_firm(block).await { + Ok(None) => {}, + Ok(Some(stop_height_exceded)) => { + break Ok(ExitReason::StopHeightExceded(stop_height_exceded)); + } + Err(error) => { + break Err(error).wrap_err("failed executing firm block"); + } } } @@ -300,8 +393,14 @@ impl Executor { block.hash = %block.block_hash(), "received block from sequencer reader", )); - if let Err(error) = self.execute_soft(block).await { - break Err(error).wrap_err("failed executing soft block"); + match self.execute_soft(block).await { + Ok(None) => {}, + Ok(Some(stop_height_exceded)) => { + break Ok(ExitReason::StopHeightExceded(stop_height_exceded)); + } + Err(error) => { + break Err(error).wrap_err("failed executing soft block"); + } } } ); @@ -391,10 +490,34 @@ impl Executor { block.height = block.height().value(), err, ))] - async fn execute_soft(&mut self, block: FilteredSequencerBlock) -> eyre::Result<()> { + async fn execute_soft( + &mut self, + block: FilteredSequencerBlock, + ) -> eyre::Result> { // 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 (inclusive). If we are also + // executing firm blocks, we let execution continue so that the firm block at the stop + // height is executed before restarting. If we are in soft-only mode, we return a + // `StopHeightExceded::Sequencer` value to signal a restart. + if self.is_soft_block_height_exceded(&executable_block) { + if self.mode.is_with_firm() { + // If we are continuing to execute firm blocks, we should close the soft block + // channel so as to not keep hitting the sequencer RPC endpoint. + if let Some(channel) = self.soft_blocks.take() { + drop(channel); + } + self.soft_blocks = None; + return Ok(None); + } + return Ok(Some(StopHeightExceded::sequencer( + executable_block.height.value(), + self.state.sequencer_stop_block_height(), + self.state.genesis_info().halt_at_stop_height(), + ))); + } + let expected_height = self.state.next_expected_soft_sequencer_height(); match executable_block.height.cmp(&expected_height) { std::cmp::Ordering::Less => { @@ -402,7 +525,7 @@ impl Executor { expected_height.sequencer_block = %expected_height, "block received was stale because firm blocks were executed first; dropping", ); - return Ok(()); + return Ok(None); } std::cmp::Ordering::Greater => bail!( "block received was out-of-order; was a block skipped? expected: \ @@ -412,11 +535,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 @@ -447,7 +573,7 @@ impl Executor { self.metrics .absolute_set_executed_soft_block_number(block_number); - Ok(()) + Ok(None) } #[instrument(skip_all, fields( @@ -455,20 +581,34 @@ impl Executor { block.height = block.sequencer_height().value(), err, ))] - async fn execute_firm(&mut self, block: Box) -> eyre::Result<()> { + async fn execute_firm( + &mut self, + block: Box, + ) -> eyre::Result> { + if self.is_firm_block_height_exceded(&block) { + return Ok(Some(StopHeightExceded::celestia( + block.header.height().value(), + self.state.sequencer_stop_block_height(), + self.state.genesis_info().halt_at_stop_height(), + ))); + } + 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 @@ -523,7 +663,7 @@ impl Executor { self.metrics .absolute_set_executed_soft_block_number(block_number); - Ok(()) + Ok(None) } /// Executes `block` on top of its `parent_hash`. @@ -665,14 +805,27 @@ impl Executor { self.mode, ) } + + /// Returns whether the height of the soft block is greater than 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)] -fn report_exit(reason: eyre::Result<&str>, message: &str) -> eyre::Result<()> { +fn report_exit(reason: eyre::Result, message: &str) -> eyre::Result { match reason { Ok(reason) => { info!(%reason, message); - Ok(()) + Ok(reason) } Err(error) => { error!(%error, message); diff --git a/crates/astria-conductor/src/executor/state.rs b/crates/astria-conductor/src/executor/state.rs index 2ae4e2c4b2..c2131a20c0 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,13 @@ 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(), + halt_at_stop_height: false, }) .unwrap() } @@ -419,7 +464,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..85d0aa95d4 100644 --- a/crates/astria-conductor/src/executor/tests.rs +++ b/crates/astria-conductor/src/executor/tests.rs @@ -47,8 +47,13 @@ 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(), + halt_at_stop_height: false, }) .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..ddecb87703 100644 --- a/crates/astria-conductor/src/sequencer/mod.rs +++ b/crates/astria-conductor/src/sequencer/mod.rs @@ -40,6 +40,7 @@ use tracing::{ use crate::{ block_cache::BlockCache, + conductor::ExitReason, executor::{ self, SoftSendError, @@ -78,18 +79,15 @@ 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, } impl Reader { - pub(crate) async fn run_until_stopped(mut self) -> eyre::Result<()> { + pub(crate) async fn run_until_stopped(mut self) -> eyre::Result { let executor = select!( () = self.shutdown.clone().cancelled_owned() => { - return report_exit(Ok("received shutdown signal while waiting for Sequencer reader task to initialize"), ""); + return report_exit(Ok(ExitReason::ShutdownSignal), "received shutdown signal while waiting for Sequencer reader task to initialize"); } res = self.initialize() => { res? @@ -107,9 +105,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}`" ); @@ -187,7 +190,7 @@ impl RunningReader { }) } - async fn run_until_stopped(mut self) -> eyre::Result<()> { + async fn run_until_stopped(mut self) -> eyre::Result { let stop_reason = self.run_loop().await; // XXX: explicitly setting the message (usually implicitly set by tracing) @@ -195,7 +198,7 @@ impl RunningReader { report_exit(stop_reason, message) } - async fn run_loop(&mut self) -> eyre::Result<&'static str> { + async fn run_loop(&mut self) -> eyre::Result { use futures::future::FusedFuture as _; loop { @@ -203,7 +206,7 @@ impl RunningReader { biased; () = self.shutdown.cancelled() => { - return Ok("received shutdown signal"); + return Ok(ExitReason::ShutdownSignal); } // Process block execution which was enqueued due to executor channel being full. @@ -316,11 +319,11 @@ impl RunningReader { } #[instrument(skip_all)] -fn report_exit(reason: eyre::Result<&str>, message: &str) -> eyre::Result<()> { +fn report_exit(reason: eyre::Result, message: &str) -> eyre::Result { match reason { Ok(reason) => { info!(%reason, message); - Ok(()) + Ok(reason) } Err(reason) => { error!(%reason, message); 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..a10e56438e 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/macros.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/macros.rs @@ -94,14 +94,37 @@ 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, + ) => { + genesis_info!( + sequencer_start_block_height: $start_height, + sequencer_stop_block_height: $stop_height, + celestia_block_variance: $variance, + rollup_start_block_height: $rollup_start_block_height, + halt_at_stop_height: false, + ) + }; + ( + 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, + halt_at_stop_height: $halt_at_stop_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(), + halt_at_stop_height: $halt_at_stop_height, } }; } @@ -175,6 +198,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 +228,7 @@ macro_rules! mount_get_commitment_state { parent: $soft_parent, ), base_celestia_height: $base_celestia_height, - )) + ), $up_to_n_times) .await }; } @@ -274,7 +313,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 +327,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 +391,62 @@ 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 + $(,)? + ) => { + 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: $up_to_n_times, + halt_at_stop_height: false, + expected_calls: 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, + halt_at_stop_height: $halt_at_stop_height:expr, + expected_calls: $expected_calls: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, + halt_at_stop_height: $halt_at_stop_height, + ), + $up_to_n_times, + $expected_calls, ).await; }; } diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index c14ffff018..3c4e274434 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -305,19 +305,29 @@ 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, + expected_calls: 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)) - .expect(1..) + .up_to_n_times(up_to_n_times) + .expect(expected_calls) .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 +337,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 +348,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 +361,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 +534,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..d6a278efb6 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,13 @@ 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, + halt_at_stop_height: false, + expected_calls: 2, ); mount_get_commitment_state!( @@ -479,6 +493,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 +593,355 @@ 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 execute +/// both the soft and firm blocks at the stop height and then perform a 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. +/// 6. Await satisfaction of the `execute_block` and `update_commitment_state` for the soft and firm +/// blocks 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 + ); + + 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: 1, + ); + + 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), + join3( + execute_block_1.wait_until_satisfied(), + update_commitment_state_firm_1.wait_until_satisfied(), + update_commitment_state_soft_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(2000), + 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"); +} + +/// Tests if the conductor correctly stops and does not restart after reaching the sequencer stop +/// height if genesis info's `halt_at_stop_height` is `true`. +/// +/// This test consists of the following steps: +/// 1. Mount commitment state and genesis info with a sequencer stop height of 3, expecting only 1 +/// response. +/// 2. Mount Celestia network head and sequencer genesis. +/// 3. Mount ABCI info and sequencer (soft blocks) for height 3. +/// 4. Mount firm blocks at height 3, with corresponding `update_commitment_state` mount. +/// 5. Mount `execute_block` and `update_commitment_state` for soft and firm blocks at height 3. +/// 6. Await satisfaction of the `execute_block` and `update_commitment_state` for the soft and firm +/// block height 3 with a timeout of 1000ms. +/// 7. Allow ample time for the conductor to potentially restart erroneously. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn conductor_stops_at_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: 2, // allow for calls after an potential erroneous restart + halt_at_stop_height: true, + expected_calls: 1, + ); + + 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, + ); + + mount_sequencer_genesis!(test_conductor); + mount_celestia_header_network_head!( + test_conductor, + height: 1u32, + ); + mount_abci_info!( + test_conductor, + latest_sequencer_height: 3, + ); + + // Mount soft blocks for height 3 + mount_get_filtered_sequencer_block!( + test_conductor, + sequencer_height: 3, + ); + + // Mount firm blocks for height 3 + mount_celestia_blobs!( + test_conductor, + celestia_height: 1, + sequencer_heights: [3], + delay: Some(Duration::from_millis(200)), // ensure soft block is received first + ); + mount_sequencer_commit!( + test_conductor, + height: 3u32, + ); + mount_sequencer_validator_set!(test_conductor, height: 2u32); + + let execute_block_1 = mount_executed_block!( + test_conductor, + mock_name: "execute_block_1", + number: 2, + hash: [2; 64], + parent: [1; 64], + ); + + 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: 1, + ); + + 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, + ); + + timeout( + Duration::from_millis(1000), + join3( + execute_block_1.wait_until_satisfied(), + update_commitment_state_firm_1.wait_until_satisfied(), + update_commitment_state_soft_1.wait_until_satisfied(), + ), + ) + .await + .expect("conductor should have updated the firm commitment state within 1000ms"); + + // Allow time for a potential erroneous restart + sleep(Duration::from_millis(1000)).await; +} diff --git a/crates/astria-conductor/tests/blackbox/soft_only.rs b/crates/astria-conductor/tests/blackbox/soft_only.rs index 8fff4e8c1c..6e2e9e6f59 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, +/// 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 3, 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: 3, + 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..ac8dc15a7c 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,19 @@ 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, + /// Whether the conductor halt at the stop height, or otherwise attempt restart. + halt_at_stop_height: bool, } impl GenesisInfo { @@ -61,14 +83,39 @@ 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 + } + + #[must_use] + pub fn halt_at_stop_height(&self) -> bool { + self.halt_at_stop_height + } } impl From for raw::GenesisInfo { @@ -84,38 +131,72 @@ 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, + halt_at_stop_height, } = 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, + halt_at_stop_height: *halt_at_stop_height, }) } 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, + halt_at_stop_height, } = 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(), + halt_at_stop_height: *halt_at_stop_height, } } } diff --git a/crates/astria-core/src/generated/astria.execution.v1.rs b/crates/astria-core/src/generated/astria.execution.v1.rs index 35b5d20480..3c3538c0c6 100644 --- a/crates/astria-core/src/generated/astria.execution.v1.rs +++ b/crates/astria-core/src/generated/astria.execution.v1.rs @@ -10,10 +10,23 @@ 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, + /// True if the conductor should halt at the stop height instead of attempting restart. + #[prost(bool, tag = "8")] + pub halt_at_stop_height: bool, } 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..6e0defa094 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,54 @@ 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; + } + if self.halt_at_stop_height { + 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)?; + } + if self.halt_at_stop_height { + struct_ser.serialize_field("haltAtStopHeight", &self.halt_at_stop_height)?; + } struct_ser.end() } } @@ -739,17 +770,32 @@ 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", + "halt_at_stop_height", + "haltAtStopHeight", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { RollupId, - SequencerGenesisBlockHeight, + SequencerStartBlockHeight, + SequencerStopBlockHeight, CelestiaBlockVariance, + RollupStartBlockHeight, + SequencerChainId, + CelestiaChainId, + HaltAtStopHeight, } impl<'de> serde::Deserialize<'de> for GeneratedField { fn deserialize(deserializer: D) -> std::result::Result @@ -772,8 +818,13 @@ 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), + "haltAtStopHeight" | "halt_at_stop_height" => Ok(GeneratedField::HaltAtStopHeight), _ => Err(serde::de::Error::unknown_field(value, FIELDS)), } } @@ -794,8 +845,13 @@ 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; + let mut halt_at_stop_height__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::RollupId => { @@ -804,11 +860,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_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_genesis_block_height__ = + sequencer_stop_block_height__ = Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) ; } @@ -820,12 +884,43 @@ 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()?); + } + GeneratedField::HaltAtStopHeight => { + if halt_at_stop_height__.is_some() { + return Err(serde::de::Error::duplicate_field("haltAtStopHeight")); + } + halt_at_stop_height__ = 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(), + halt_at_stop_height: halt_at_stop_height__.unwrap_or_default(), }) } } diff --git a/dev/values/rollup/dev.yaml b/dev/values/rollup/dev.yaml index 0d9be85422..a8a0ef2481 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" + sequencer: + # The chain id of the sequencer chain + chainId: "sequencer-test-chain-0" + # The hrp for bech32m addresses, unlikely to be changed + addressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + startHeight: 2 + celestia: + # The chain id of the celestia chain + chainId: "celestia-local-0" + # The first Celestia height to utilize when looking for rollup data + startHeight: 2 + # The variance in Celestia height to allow before halting the chain + heightVariance: 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/evm-restart-test.yaml b/dev/values/rollup/evm-restart-test.yaml new file mode 100644 index 0000000000..177c2297ee --- /dev/null +++ b/dev/values/rollup/evm-restart-test.yaml @@ -0,0 +1,269 @@ +global: + useTTY: true + dev: true + evmChainId: 1337 + rollupName: astria + sequencerRpc: http://node0-sequencer-rpc-service.astria-dev-cluster.svc.cluster.local:26657 + sequencerGrpc: http://node0-sequencer-grpc-service.astria-dev-cluster.svc.cluster.local:8080 + sequencerChainId: sequencer-test-chain-0 + celestiaChainId: celestia-local-0 + +evm-rollup: + genesis: + # 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. + launch: + ## These values are used to configure the genesis block of the rollup chain + ## no defaults as they are unique to each chain + genesis: + # 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: "0xaC21B97d35Bf75A7dAb16f35b111a50e78A72F30" + # Configure EIP-1559 params, activated at block heights. + eip1559Params: {} + sequencer: + # The chain id of the sequencer chain + chainId: "sequencer-test-chain-0" + # The hrp for bech32m addresses, unlikely to be changed + addressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + startHeight: 2 + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork + stopHeight: 20 + celestia: + # The chain id of the celestia chain + chainId: "celestia-local-0" + # The first Celestia height to utilize when looking for rollup data + startHeight: 2 + # The variance in Celestia height to allow before halting the chain + heightVariance: 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 + restart: + height: 18 + halt: true + sequencer: + addressPrefix: "astria" + startHeight: 20 + stopHeight: 1000 + celestia: + startHeight: 20 + + ## These are general configuration values with some recommended defaults + + # Configure the gas Limit + gasLimit: "50000000" + # If set to true the genesis block will contain extra data + overrideGenesisExtraData: true + + ## Standard Eth Genesis config values + # Configuration of Eth forks, setting to 0 will enable from height, + # left as is these forks will not activate. + cancunTime: "" + pragueTime: "" + verkleTime: "" + # Can configure the genesis allocs for the chain + alloc: + # Deploying the deterministic deploy proxy contract in genesis + # Forge and other tools use this for their CREATE2 usage, but + # can only be included through the genesis block after EIP-155 + # https://github.com/Arachnid/deterministic-deployment-proxy + - address: "0x4e59b44847b379578588920cA78FbF26c0B4956C" + value: + balance: "0" + code: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" + - address: "0xA58639fB5458e65E4fA917FF951C390292C24A15" + value: + balance: "0" + code: "0x6080604052600436106100f35760003560e01c8063b6476c7e1161008a578063e74b981b11610059578063e74b981b1461027b578063ebd090541461029b578063f2fde38b146102bb578063fc88d31b146102db57600080fd5b8063b6476c7e1461021c578063bab916d01461023e578063d294f09314610251578063db97dc981461026657600080fd5b80638da5cb5b116100c65780638da5cb5b146101a1578063a7eaa739146101d3578063a996e020146101f3578063ad2282471461020657600080fd5b80636f46384a146100f8578063715018a6146101215780637eb6dec7146101385780638897397914610181575b600080fd5b34801561010457600080fd5b5061010e60035481565b6040519081526020015b60405180910390f35b34801561012d57600080fd5b506101366102f1565b005b34801561014457600080fd5b5061016c7f000000000000000000000000000000000000000000000000000000000000000981565b60405163ffffffff9091168152602001610118565b34801561018d57600080fd5b5061013661019c3660046107a6565b610305565b3480156101ad57600080fd5b506000546001600160a01b03165b6040516001600160a01b039091168152602001610118565b3480156101df57600080fd5b506101366101ee3660046107a6565b610312565b610136610201366004610808565b61031f565b34801561021257600080fd5b5061010e60065481565b34801561022857600080fd5b50610231610414565b6040516101189190610874565b61013661024c3660046108c3565b6104a2565b34801561025d57600080fd5b50610136610588565b34801561027257600080fd5b506102316106b4565b34801561028757600080fd5b50610136610296366004610905565b6106c1565b3480156102a757600080fd5b506005546101bb906001600160a01b031681565b3480156102c757600080fd5b506101366102d6366004610905565b6106eb565b3480156102e757600080fd5b5061010e60045481565b6102f9610729565b6103036000610756565b565b61030d610729565b600455565b61031a610729565b600355565b3460045480821161034b5760405162461bcd60e51b815260040161034290610935565b60405180910390fd5b60007f000000000000000000000000000000000000000000000000000000003b9aca006103788385610998565b61038291906109b1565b1161039f5760405162461bcd60e51b8152600401610342906109d3565b600454600660008282546103b39190610a61565b90915550506004546103c59034610998565b336001600160a01b03167f0c64e29a5254a71c7f4e52b3d2d236348c80e00a00ba2e1961962bd2827c03fb888888886040516104049493929190610a9d565b60405180910390a3505050505050565b6002805461042190610acf565b80601f016020809104026020016040519081016040528092919081815260200182805461044d90610acf565b801561049a5780601f1061046f5761010080835404028352916020019161049a565b820191906000526020600020905b81548152906001019060200180831161047d57829003601f168201915b505050505081565b346003548082116104c55760405162461bcd60e51b815260040161034290610935565b60007f000000000000000000000000000000000000000000000000000000003b9aca006104f28385610998565b6104fc91906109b1565b116105195760405162461bcd60e51b8152600401610342906109d3565b6003546006600082825461052d9190610a61565b909155505060035461053f9034610998565b336001600160a01b03167f0f4961cab7530804898499aa89f5ec81d1a73102e2e4a1f30f88e5ae3513ba2a868660405161057a929190610b09565b60405180910390a350505050565b6005546001600160a01b031633146105f45760405162461bcd60e51b815260206004820152602960248201527f41737472696142726964676561626c6545524332303a206f6e6c7920666565206044820152681c9958da5c1a595b9d60ba1b6064820152608401610342565b6005546006546040516000926001600160a01b031691908381818185875af1925050503d8060008114610643576040519150601f19603f3d011682016040523d82523d6000602084013e610648565b606091505b50509050806106ac5760405162461bcd60e51b815260206004820152602a60248201527f41737472696142726964676561626c6545524332303a20666565207472616e7360448201526919995c8819985a5b195960b21b6064820152608401610342565b506000600655565b6001805461042190610acf565b6106c9610729565b600580546001600160a01b0319166001600160a01b0392909216919091179055565b6106f3610729565b6001600160a01b03811661071d57604051631e4fbdf760e01b815260006004820152602401610342565b61072681610756565b50565b6000546001600160a01b031633146103035760405163118cdaa760e01b8152336004820152602401610342565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6000602082840312156107b857600080fd5b5035919050565b60008083601f8401126107d157600080fd5b50813567ffffffffffffffff8111156107e957600080fd5b60208301915083602082850101111561080157600080fd5b9250929050565b6000806000806040858703121561081e57600080fd5b843567ffffffffffffffff8082111561083657600080fd5b610842888389016107bf565b9096509450602087013591508082111561085b57600080fd5b50610868878288016107bf565b95989497509550505050565b60006020808352835180602085015260005b818110156108a257858101830151858201604001528201610886565b506000604082860101526040601f19601f8301168501019250505092915050565b600080602083850312156108d657600080fd5b823567ffffffffffffffff8111156108ed57600080fd5b6108f9858286016107bf565b90969095509350505050565b60006020828403121561091757600080fd5b81356001600160a01b038116811461092e57600080fd5b9392505050565b6020808252602d908201527f417374726961576974686472617765723a20696e73756666696369656e74207760408201526c69746864726177616c2066656560981b606082015260800190565b634e487b7160e01b600052601160045260246000fd5b818103818111156109ab576109ab610982565b92915050565b6000826109ce57634e487b7160e01b600052601260045260246000fd5b500490565b60208082526062908201527f417374726961576974686472617765723a20696e73756666696369656e74207660408201527f616c75652c206d7573742062652067726561746572207468616e203130202a2a60608201527f20283138202d20424153455f434841494e5f41535345545f505245434953494f6080820152614e2960f01b60a082015260c00190565b808201808211156109ab576109ab610982565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b604081526000610ab1604083018688610a74565b8281036020840152610ac4818587610a74565b979650505050505050565b600181811c90821680610ae357607f821691505b602082108103610b0357634e487b7160e01b600052602260045260246000fd5b50919050565b602081526000610b1d602083018486610a74565b94935050505056fea2646970667358221220842bd8104ffc1c611919341f64a8277f2fc808138b97720a6dc1382e5670099064736f6c63430008190033" + + + config: + # The level at which core astria components will log out + # Options are: error, warn, info, and debug + logLevel: "debug" + + conductor: + # Determines what will drive block execution, options are: + # - "SoftOnly" -> blocks are only pulled from the sequencer + # - "FirmOnly" -> blocks are only pulled from DA + # - "SoftAndFirm" -> blocks are pulled from both the sequencer and DA + executionCommitLevel: 'SoftAndFirm' + # The expected fastest block time possible from sequencer, determines polling + # rate. + sequencerBlockTimeMs: 2000 + # The maximum number of requests to make to the sequencer per second + sequencerRequestsPerSecond: 500 + + celestia: + rpc: "http://celestia-service.astria-dev-cluster.svc.cluster.local:26658" + token: "" + + resources: + conductor: + requests: + cpu: 0.01 + memory: 1Mi + limits: + cpu: 0.1 + memory: 20Mi + geth: + requests: + cpu: 0.25 + memory: 256Mi + limits: + cpu: 2 + memory: 1Gi + + storage: + enabled: false + + ingress: + enabled: true + services: + rpc: + enabled: true + ws: + enabled: true + +celestia-node: + enabled: false + +composer: + enabled: true + config: + privateKey: + devContent: "2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90" + +evm-bridge-withdrawer: + enabled: true + config: + minExpectedFeeAssetBalance: "0" + sequencerBridgeAddress: "astria13ahqz4pjqfmynk9ylrqv4fwe4957x2p0h5782u" + feeAssetDenom: "nria" + rollupAssetDenom: "nria" + evmContractAddress: "0xA58639fB5458e65E4fA917FF951C390292C24A15" + sequencerPrivateKey: + devContent: "dfa7108e38ab71f89f356c72afc38600d5758f11a8c337164713e4471411d2e0" + +evm-faucet: + enabled: true + ingress: + enabled: true + config: + privateKey: + devContent: "8b3a7999072c9c9314c084044fe705db11714c6c4ed7cddb64da18ea270dd203" + +postgresql: + enabled: true + nameOverride: blockscout-postegres + primary: + persistence: + enabled: false + resourcesPreset: "medium" + auth: + enablePostgresUser: true + postgresPassword: bigsecretpassword + username: blockscout + password: blockscout + database: blockscout + audit: + logHostname: true + logConnections: true + logDisconnections: true +blockscout-stack: + enabled: true + config: + network: + id: 1337 + name: Astria + shortname: Astria + currency: + name: RIA + symbol: RIA + decimals: 18 + testnet: true + prometheus: + enabled: false + blockscout: + extraEnv: + - name: ECTO_USE_SSL + value: "false" + - name: DATABASE_URL + value: "postgres://postgres:bigsecretpassword@astria-chain-chart-blockscout-postegres.astria-dev-cluster.svc.cluster.local:5432/blockscout" + - name: ETHEREUM_JSONRPC_VARIANT + value: "geth" + - name: ETHEREUM_JSONRPC_HTTP_URL + value: "http://astria-evm-service.astria-dev-cluster.svc.cluster.local:8545/" + - name: ETHEREUM_JSONRPC_INSECURE + value: "true" + - name: ETHEREUM_JSONRPC_WS_URL + value: "ws://astria-evm-service.astria-dev-cluster.svc.cluster.local:8546/" + - name: INDEXER_DISABLE_BEACON_BLOB_FETCHER + value: "true" + - name: NETWORK + value: "Astria" + - name: SUBNETWORK + value: "Local" + - name: CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS + value: "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,default" + - name: CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS + value: "byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,default" + - name: DISABLE_EXCHANGE_RATES + value: "true" + + ingress: + enabled: true + hostname: explorer.astria.localdev.me + paths: + - path: /api + pathType: Prefix + - path: /socket + pathType: Prefix + - path: /sitemap.xml + pathType: ImplementationSpecific + - path: /public-metrics + pathType: Prefix + - path: /auth/auth0 + pathType: Exact + - path: /auth/auth0/callback + pathType: Exact + - path: /auth/logout + pathType: Exact + + frontend: + extraEnv: + - name: NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE + value: "validation" + - name: NEXT_PUBLIC_AD_BANNER_PROVIDER + value: "none" + - name: NEXT_PUBLIC_API_PROTOCOL + value: "http" + - name: NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL + value: "ws" + - name: NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME + value: "aRia" + - name: NEXT_PUBLIC_AD_TEXT_PROVIDER + value: "none" + ingress: + enabled: true + hostname: explorer.astria.localdev.me diff --git a/dev/values/rollup/ibc-bridge-test.yaml b/dev/values/rollup/ibc-bridge-test.yaml index b5f198927c..9302310b47 100644 --- a/dev/values/rollup/ibc-bridge-test.yaml +++ b/dev/values/rollup/ibc-bridge-test.yaml @@ -1,12 +1,48 @@ # 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" + sequencer: + # The chain id of the sequencer chain + chainId: "sequencer-test-chain-0" + # The hrp for bech32m addresses, unlikely to be changed + addressPrefix: "astria" + # Block height to start syncing rollup from, lowest possible is 2 + startHeight: 2 + # Block height (on sequencer) to stop syncing rollup at, continuing to next configuration fork + stopHeight: 0 + celestia: + # The chain id of the celestia chain + chainId: "celestia-local-0" + # The first Celestia height to utilize when looking for rollup data + startHeight: 2 + # The variance in Celestia height to allow before halting the chain + heightVariance: 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..fe7cd76154 100644 --- a/proto/executionapis/astria/execution/v1/execution.proto +++ b/proto/executionapis/astria/execution/v1/execution.proto @@ -14,9 +14,17 @@ 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; + // True if the conductor should halt at the stop height instead of attempting restart. + bool halt_at_stop_height = 8; } // The set of information which deterministic driver of block production diff --git a/specs/conductor.md b/specs/conductor.md index a2075922d9..6a6a1b364d 100644 --- a/specs/conductor.md +++ b/specs/conductor.md @@ -126,3 +126,8 @@ only `CommitmentState.firm.number += 1` is advanced). Soft being ahead of firm is the expected operation. In certain rare situations the numbers can match exactly, and step `firm-only.10` and `firm-only.11` are executed as written. + +## Startup, Restarts, Execution, and Commitments + +See [`astria.execution.v1alpha2` API documentation](./execution-api.md) for more +information on Conductor startup, restart, execution, and commitment logic. diff --git a/specs/execution-api.md b/specs/execution-api.md index c765229007..1b269d8169 100644 --- a/specs/execution-api.md +++ b/specs/execution-api.md @@ -29,6 +29,31 @@ previous block data, Conductor must also track the block hash of any blocks between commitments, it will call `BatchGetBlocks` to get block information between commitments. +### Restart + +The conductor is able to gracefully restart under two scenarios: + +1. Conductor recieves a `PermissionDenied` status from the execution layer when +calling `ExecuteBlock`. This is meant to function seamlessly with [`astria-geth`](https://github.com/astriaorg/astria-geth)'s +behavior upon an unexpected restart. If `geth` receives a `ExecuteBlock` request +before receving *both* `GetGenesisInfo` and `GetCommitmentState` (which will be +the case if the execution layer has restarted), it responds with a `PremissionDenied` +status, prompting the conductor to restart. +2. `GenesisInfo` contains a `sequencer_stop_block_height`, indicating a planned +restart at a given sequencer block height. If the conductor reaches the stop height, +it will perform a restart in one of the following ways corresponding to its mode: + + - **Firm Only Mode**: Once the stop height is reached for the firm block stream, + the firm block at this height is executed (and commitment state updated) before + restarting the conductor, prompting the rollup for a new `GenesisInfo` with + new start/stop heights (and potentially chain IDs). + - **Soft and Firm Mode**: Once the stop height is reached for the soft block + stream, no more soft blocks are executed (including the one at the stop height). + The firm block is still executed for this height, and then Conductor restarts. + - **Soft Only Mode**: Once the stop height is reached for the soft block stream, + no more soft blocks are executed (including the one at the stop height). Conductor + is then restarted. + ### Execution & Commitments From the perspective of the sequencer: @@ -74,9 +99,18 @@ Note: For our EVM rollup, we map the `CommitmentState` to the `ForkchoiceRule`: ### GetGenesisInfo `GetGenesisInfo` returns information which is definitional to the rollup with -regards to how it serves data from the sequencer & celestia networks. This RPC -should ALWAYS succeed. The API is agnostic as to how the information is defined -in a rollups genesis, and used by the conductor as configuration on startup. +regards to how it serves data from the sequencer & celestia networks, along with +optional block heights for initiating a [conductor restart](#restart). This RPC +should **ALWAYS** succeed. The API is agnostic as to how the information is defined +in a rollup's genesis, and used by the conductor as configuration on startup *or* +upon a restart. + +If the `GenesisInfo` provided by this RPC contains a `sequencer_stop_block_height`, +the rollup should be prepared to provide an updated response when the conductor +restarts, including, at minimum, a new `rollup_start_block_height` and `sequencer_start_block_height`. +The updated response can also contain an updated `sequencer_stop_block_height` (if +another restart is desired), `celestia_chain_id`, and/or `sequencer_chain_id` (to +facilitate network migration). ### ExecuteBlock