diff --git a/.cargo/audit.toml b/.cargo/audit.toml index bfe280fdd3..a24651942e 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -6,4 +6,10 @@ ignore = [ # `proc-macro-error` is Unmaintained. It is a transient dependency of borsh crates, so cannot # easily be replaced. "RUSTSEC-2024-0370", + # `instant` is Unmaintained. It is a transient dependency of `isahc`, `wiremock` and `ethers`, so + # cannot easily be replaced. + "RUSTSEC-2024-0384", + # `derivative` is Unmaintained. It is a transient dependency of many crates including several + # penumbra ones, so cannot easily be replaced. + "RUSTSEC-2024-0388", ] diff --git a/Cargo.lock b/Cargo.lock index b95f060418..c1605c33e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,15 +701,16 @@ name = "astria-core" version = "0.1.0" dependencies = [ "astria-core", + "astria-core-address", + "astria-core-consts", + "astria-core-crypto", "astria-merkle", "base64 0.21.7", "base64-serde", - "bech32 0.11.0", "brotli", "bytes", "celestia-tendermint", "celestia-types", - "ed25519-consensus", "hex", "ibc-types", "indexmap 2.4.0", @@ -728,6 +729,31 @@ dependencies = [ "thiserror", "tonic 0.10.2", "tracing", +] + +[[package]] +name = "astria-core-address" +version = "0.1.0" +dependencies = [ + "astria-core-consts", + "bech32 0.11.0", + "thiserror", +] + +[[package]] +name = "astria-core-consts" +version = "0.1.0" + +[[package]] +name = "astria-core-crypto" +version = "0.1.0" +dependencies = [ + "astria-core-consts", + "base64 0.21.7", + "ed25519-consensus", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror", "zeroize", ] @@ -795,6 +821,7 @@ dependencies = [ name = "astria-sequencer" version = "1.0.0" dependencies = [ + "assert-json-diff", "astria-build-info", "astria-config", "astria-core", diff --git a/Cargo.toml b/Cargo.toml index d1cc181333..bded5e96a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ members = [ "crates/astria-conductor", "crates/astria-config", "crates/astria-core", + "crates/astria-core-address", + "crates/astria-core-consts", + "crates/astria-core-crypto", "crates/astria-eyre", "crates/astria-grpc-mock", "crates/astria-grpc-mock-test", @@ -34,6 +37,9 @@ default-members = [ "crates/astria-conductor", "crates/astria-config", "crates/astria-core", + "crates/astria-core-address", + "crates/astria-core-consts", + "crates/astria-core-crypto", "crates/astria-grpc-mock", "crates/astria-grpc-mock-test", "crates/astria-grpc-mock-test-codegen", diff --git a/audits/Astria Shared Sequencer - Zellic Audit Report (10-15-24).pdf b/audits/Astria Shared Sequencer - Zellic Audit Report (10-15-24).pdf new file mode 100644 index 0000000000..319842d9ab Binary files /dev/null and b/audits/Astria Shared Sequencer - Zellic Audit Report (10-15-24).pdf differ diff --git a/audits/Astria Shared Sequencer - Zellic Audit Report Draft (10-15-24).pdf b/audits/Astria Shared Sequencer - Zellic Audit Report Draft (10-15-24).pdf deleted file mode 100644 index e20a25ee8a..0000000000 Binary files a/audits/Astria Shared Sequencer - Zellic Audit Report Draft (10-15-24).pdf and /dev/null differ diff --git a/charts/hermes/Chart.yaml b/charts/hermes/Chart.yaml index a16a7a270d..d7fe9a429e 100644 --- a/charts/hermes/Chart.yaml +++ b/charts/hermes/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: 0.5.1 +version: 0.5.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 diff --git a/charts/hermes/files/config.toml b/charts/hermes/files/config.toml index 190fe66410..9aa59863ec 100644 --- a/charts/hermes/files/config.toml +++ b/charts/hermes/files/config.toml @@ -2,7 +2,6 @@ log_level = '{{ .Values.global.logLevel }}' [rest] - # Whether or not to enable the REST service. Default: false enabled = {{ .Values.rest.enabled }} @@ -110,6 +109,15 @@ trust_threshold = '{{ $chain.trustThreshold }}' {{- if $chain.memoPrefix }} memo_prefix = '{{ $chain.memoPrefix }}' {{- end }} +{{- if $chain.feeGranter }} +fee_granter = '{{ $chain.feeGranter }}' +{{- end }} +{{- if $chain.compatMode }} +compat_mode = '{{ $chain.compatMode }}' +{{- end }} +{{- if $chain.clearInterval }} +clear_interval = '{{ $chain.clearInterval }}' +{{- end }} {{- if $chain.packetFilter }} {{- if $chain.packetFilter.policy }} @@ -132,14 +140,4 @@ recv = [ {{- end }} {{- end }} {{- end }} -{{- if $chain.feeGranter }} -fee_granter = '{{ $chain.feeGranter }}' -{{- end }} -{{- if $chain.compatMode }} -compat_mode = '{{ $chain.compatMode }}' -{{- end }} -{{- if $chain.clearInterval }} -clear_interval = '{{ $chain.clearInterval }}' -{{- end }} - {{ end }} diff --git a/charts/hermes/templates/configmaps.yaml b/charts/hermes/templates/configmaps.yaml index 4fdee17151..234fa42eaa 100644 --- a/charts/hermes/templates/configmaps.yaml +++ b/charts/hermes/templates/configmaps.yaml @@ -5,7 +5,7 @@ metadata: namespace: {{ include "hermes.namespace" . }} data: config.toml: | - {{- tpl (.Files.Get "files/config.toml") $ | nindent 4 }} + {{- tpl (.Files.Get "files/config.toml") $ | nindent 4 }} --- {{- if not .Values.secretProvider.enabled }} {{- range $chainId, $chain := .Values.chains }} diff --git a/charts/hermes/templates/servicemonitor.yaml b/charts/hermes/templates/servicemonitor.yaml index 6cf7dfc356..d99d368654 100644 --- a/charts/hermes/templates/servicemonitor.yaml +++ b/charts/hermes/templates/servicemonitor.yaml @@ -2,14 +2,14 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - name: hermes-relayer-metrics + name: {{ include "hermes.fullname" . }}-metrics labels: app: {{ include "hermes.fullname" . }} {{- with .Values.serviceMonitor.additionalLabels }} {{- toYaml . | nindent 4 }} {{- end }} spec: - jobLabel: hermes-relayer-metric + jobLabel: {{ include "hermes.fullname" . }}-metric namespaceSelector: matchNames: - {{ include "hermes.namespace" . }} diff --git a/charts/ibc-test.just b/charts/ibc-test.just index 0e330f8da1..6b8e472328 100644 --- a/charts/ibc-test.just +++ b/charts/ibc-test.just @@ -89,10 +89,7 @@ run-without-native tag=defaultTag: # Execute the transfer from Celestia to the Rollup just ibc-test _do-ibc-transfer {{defaultNamespace}} {{sequencer_sudo_address}} - # Add transfer/channel-0/utia as fee-asset - docker run --rm --network host $ASTRIA_CLI_IMAGE sequencer sudo fee-asset add --private-key {{sequencer_sudo_pkey}} --asset transfer/channel-0/utia --sequencer-url {{sequencer_rpc_url}} --sequencer.chain-id {{sequencer_chain_id}} # check that sequencer balance updated correctly - EXPECTED_BALANCE=$(echo "1 * {{transfer_amount}}" | bc) for i in {1..50} do @@ -110,6 +107,11 @@ run-without-native tag=defaultTag: exit 1 fi + # Add transfer/channel-0/utia as allowed fee-asset + docker run --rm --network host $ASTRIA_CLI_IMAGE sequencer sudo fee-asset add --private-key {{sequencer_sudo_pkey}} --asset transfer/channel-0/utia --sequencer-url {{sequencer_rpc_url}} --sequencer.chain-id {{sequencer_chain_id}} + + # TODO: query allowd fee asset verifying succefull addition + [no-cd] run tag=defaultTag: #!/usr/bin/env bash @@ -149,23 +151,6 @@ run tag=defaultTag: # Execute the transfer from Celstia to sequencer with compat address just ibc-test _do-ibc-transfer {{defaultNamespace}} {{compat_address}} - # check that celestia balance updated correctly - for i in {1..50} - do - current_celestia_balance=$(just ibc-test get-celestia-balance) - echo "check $i, balance: $current_celestia_balance, expected: $expected_celestia_balance" - if (( $expected_celestia_balance == $current_celestia_balance )); then - expected_celestia_balance_found="1" - break - else - sleep 1 - fi - done - if [[ -z $expected_celestia_balance_found ]]; then - echo "expected celestia balance was not found after withdraw; IBC transfer from Celestia to the Rollup failed" - exit 1 - fi - # check that sequencer balance updated correctly ASTRIA_CLI_IMAGE="{{cli_image}}{{ if tag != '' { replace(':#', '#', tag) } else { '' } }}" EXPECTED_BALANCE=$(echo "1 * {{transfer_amount}}" | bc) @@ -283,23 +268,6 @@ run-timeout tag=defaultTag: # Execute the transfer from Celstia to sequencer with compat address just ibc-test _do-ibc-transfer {{defaultNamespace}} {{compat_address}} - # check that celestia balance updated correctly - for i in {1..50} - do - current_celestia_balance=$(just ibc-test get-celestia-balance) - echo "check $i, balance: $current_celestia_balance, expected: $expected_celestia_balance" - if (( $expected_celestia_balance == $current_celestia_balance )); then - expected_celestia_balance_found="1" - break - else - sleep 1 - fi - done - if [[ -z $expected_celestia_balance_found ]]; then - echo "expected celestia balance was not found after withdraw; IBC transfer from Celestia to the Rollup failed" - exit 1 - fi - # check that sequencer balance updated correctly ASTRIA_CLI_IMAGE="{{cli_image}}{{ if tag != '' { replace(':#', '#', tag) } else { '' } }}" EXPECTED_BALANCE=$(echo "1 * {{transfer_amount}}" | bc) diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs index b1aa923f72..aeb0f1a721 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/mod.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use astria_core::generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; +use astria_core::generated::astria::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; use astria_eyre::eyre::{ self, WrapErr as _, diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs index 033b53f0a2..c80c170cab 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/startup.rs @@ -4,7 +4,7 @@ use std::{ }; use astria_core::{ - generated::sequencerblock::v1::sequencer_service_client::{ + generated::astria::sequencerblock::v1::sequencer_service_client::{ self, SequencerServiceClient, }, @@ -270,20 +270,21 @@ impl Startup { the sequencer logic." ); - let proto_tx = astria_core::generated::protocol::transaction::v1::Transaction::decode( - &*last_transaction.tx, - ) - .wrap_err_with(|| { - format!( + let proto_tx = + astria_core::generated::astria::protocol::transaction::v1::Transaction::decode( + &*last_transaction.tx, + ) + .wrap_err_with(|| { + format!( "failed to decode data in Sequencer CometBFT transaction as `{}`", - astria_core::generated::protocol::transaction::v1::Transaction::full_name(), + astria_core::generated::astria::protocol::transaction::v1::Transaction::full_name(), ) - })?; + })?; let tx = Transaction::try_from_raw(proto_tx).wrap_err_with(|| { format!( "failed to verify {}", - astria_core::generated::protocol::transaction::v1::Transaction::full_name() + astria_core::generated::astria::protocol::transaction::v1::Transaction::full_name() ) })?; diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs index 7b5593a0aa..62308d1572 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/builder.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use astria_core::generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; +use astria_core::generated::astria::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; use astria_eyre::eyre::{ self, Context as _, diff --git a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs index 0bd72886a5..78a38bf50b 100644 --- a/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs +++ b/crates/astria-bridge-withdrawer/src/bridge_withdrawer/submitter/mod.rs @@ -4,7 +4,7 @@ use std::{ }; use astria_core::{ - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_client::{ self, SequencerServiceClient, @@ -15,6 +15,7 @@ use astria_core::{ Action, TransactionBody, }, + Protobuf as _, }; use astria_eyre::eyre::{ self, diff --git a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs index c3f23d53fc..f0e4d0cef4 100644 --- a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs +++ b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_cometbft.rs @@ -332,7 +332,7 @@ fn prepare_broadcast_tx_sync_response(response: tx_sync::Response) -> Mock { /// Convert a wiremock request to an astria transaction pub fn tx_from_request(request: &wiremock::Request) -> Transaction { - use astria_core::generated::protocol::transaction::v1::Transaction as RawTransaction; + use astria_core::generated::astria::protocol::transaction::v1::Transaction as RawTransaction; use prost::Message as _; let wrapped_tx_sync_req: tendermint_rpc::request::Wrapper = diff --git a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_sequencer.rs b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_sequencer.rs index 66dc282e31..9a2971b790 100644 --- a/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_sequencer.rs +++ b/crates/astria-bridge-withdrawer/tests/blackbox/helpers/mock_sequencer.rs @@ -5,7 +5,7 @@ use std::{ use astria_core::{ self, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_server::{ SequencerService, SequencerServiceServer, diff --git a/crates/astria-cli/CHANGELOG.md b/crates/astria-cli/CHANGELOG.md index 09bc9674b2..738181bef5 100644 --- a/crates/astria-cli/CHANGELOG.md +++ b/crates/astria-cli/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `fee-assets` subcommand to `sequencer` CLI [#1816](https://github.com/astriaorg/astria/pull/1816). + ### Fixed - Fixed ICS20 withdrawal source when using channel with more than one diff --git a/crates/astria-cli/src/sequencer/account.rs b/crates/astria-cli/src/sequencer/account.rs index 269470725f..e6160730f0 100644 --- a/crates/astria-cli/src/sequencer/account.rs +++ b/crates/astria-cli/src/sequencer/account.rs @@ -51,7 +51,12 @@ impl Create { let signing_key = SigningKey::new(OsRng); let pretty_signing_key = hex::encode(signing_key.as_bytes()); let pretty_verifying_key = hex::encode(signing_key.verification_key().as_bytes()); - let pretty_address = SigningKey::try_address(&signing_key, &self.prefix)?; + + let pretty_address: Address = Address::builder() + .array(signing_key.address_bytes()) + .prefix(&self.prefix) + .try_build()?; + println!("Create Sequencer Account"); println!(); // TODO: don't print private keys to CLI, prefer writing to file: diff --git a/crates/astria-cli/src/sequencer/fee_assets.rs b/crates/astria-cli/src/sequencer/fee_assets.rs new file mode 100644 index 0000000000..b5fa567e5c --- /dev/null +++ b/crates/astria-cli/src/sequencer/fee_assets.rs @@ -0,0 +1,58 @@ +use astria_sequencer_client::{ + HttpClient, + SequencerClientExt as _, +}; +use clap::Subcommand; +use color_eyre::eyre::{ + self, + WrapErr as _, +}; + +#[derive(Debug, clap::Args)] +pub(super) struct Command { + #[command(subcommand)] + command: SubCommand, +} + +impl Command { + pub(super) async fn run(self) -> eyre::Result<()> { + let SubCommand::Get(get) = self.command; + get.run().await + } +} + +#[derive(Debug, Subcommand)] +enum SubCommand { + /// Get the balance of a Sequencer account + Get(Get), +} + +#[derive(clap::Args, Debug)] +struct Get { + /// The url of the Sequencer node + #[arg( + long, + env = "SEQUENCER_URL", + default_value = crate::DEFAULT_SEQUENCER_RPC + )] + sequencer_url: String, +} + +impl Get { + async fn run(self) -> eyre::Result<()> { + let sequencer_client = HttpClient::new(self.sequencer_url.as_str()) + .wrap_err("failed constructing http sequencer client")?; + + let res = sequencer_client + .get_allowed_fee_assets() + .await + .wrap_err("failed to get fee assets")?; + + println!("Allowed fee assets:"); + for asset in res.fee_assets { + println!(" {asset}"); + } + + Ok(()) + } +} diff --git a/crates/astria-cli/src/sequencer/mod.rs b/crates/astria-cli/src/sequencer/mod.rs index 6beda8d5ec..d8fa0a7233 100644 --- a/crates/astria-cli/src/sequencer/mod.rs +++ b/crates/astria-cli/src/sequencer/mod.rs @@ -8,6 +8,7 @@ mod block_height; mod bridge_account; mod bridge_lock; mod bridge_sudo_change; +mod fee_assets; mod ics20_withdrawal; mod init_bridge_account; mod sign; @@ -39,6 +40,7 @@ impl Command { SubCommand::Sign(sign) => sign.run(), SubCommand::BridgeSudoChange(bridge_sudo_change) => bridge_sudo_change.run().await, SubCommand::BridgeAccount(bridge_account) => bridge_account.run().await, + SubCommand::FeeAssets(fee_assets) => fee_assets.run().await, } } } @@ -80,4 +82,6 @@ enum SubCommand { BridgeSudoChange(bridge_sudo_change::Command), /// Commands for interacting with the bridge account BridgeAccount(bridge_account::Command), + /// Command for interacting with allowed fee assets + FeeAssets(fee_assets::Command), } diff --git a/crates/astria-cli/src/sequencer/threshold/sign.rs b/crates/astria-cli/src/sequencer/threshold/sign.rs index 93e65987fc..b5a3830f24 100644 --- a/crates/astria-cli/src/sequencer/threshold/sign.rs +++ b/crates/astria-cli/src/sequencer/threshold/sign.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use astria_core::generated::protocol::transaction::v1::{ +use astria_core::generated::astria::protocol::transaction::v1::{ Transaction, TransactionBody, }; diff --git a/crates/astria-cli/src/sequencer/threshold/verify.rs b/crates/astria-cli/src/sequencer/threshold/verify.rs index f616cb4475..ec4d027b46 100644 --- a/crates/astria-cli/src/sequencer/threshold/verify.rs +++ b/crates/astria-cli/src/sequencer/threshold/verify.rs @@ -1,4 +1,4 @@ -use astria_core::generated::protocol::transaction::v1::TransactionBody; +use astria_core::generated::astria::protocol::transaction::v1::TransactionBody; use color_eyre::eyre::{ self, WrapErr as _, diff --git a/crates/astria-composer/src/collectors/grpc.rs b/crates/astria-composer/src/collectors/grpc.rs index 18d95adb93..deeb580546 100644 --- a/crates/astria-composer/src/collectors/grpc.rs +++ b/crates/astria-composer/src/collectors/grpc.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use astria_core::{ - generated::composer::v1::{ + generated::astria::composer::v1::{ grpc_collector_service_server::GrpcCollectorService, SubmitRollupTransactionRequest, SubmitRollupTransactionResponse, diff --git a/crates/astria-composer/src/executor/builder.rs b/crates/astria-composer/src/executor/builder.rs index c388a63121..31a8ee04fa 100644 --- a/crates/astria-composer/src/executor/builder.rs +++ b/crates/astria-composer/src/executor/builder.rs @@ -6,7 +6,7 @@ use std::{ use astria_core::{ crypto::SigningKey, - generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, + generated::astria::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, primitive::v1::Address, protocol::transaction::v1::action::RollupDataSubmission, }; diff --git a/crates/astria-composer/src/executor/mod.rs b/crates/astria-composer/src/executor/mod.rs index a07b2e4fe6..62ed88e966 100644 --- a/crates/astria-composer/src/executor/mod.rs +++ b/crates/astria-composer/src/executor/mod.rs @@ -12,7 +12,7 @@ use std::{ use astria_core::{ crypto::SigningKey, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_client::{ self, SequencerServiceClient, diff --git a/crates/astria-composer/src/grpc.rs b/crates/astria-composer/src/grpc.rs index f9453b36ef..7c1d3b8ef1 100644 --- a/crates/astria-composer/src/grpc.rs +++ b/crates/astria-composer/src/grpc.rs @@ -9,7 +9,7 @@ use std::net::SocketAddr; use astria_core::{ - generated::composer::v1::grpc_collector_service_server::GrpcCollectorServiceServer, + generated::astria::composer::v1::grpc_collector_service_server::GrpcCollectorServiceServer, primitive::v1::asset, }; use astria_eyre::{ diff --git a/crates/astria-composer/tests/blackbox/grpc_collector.rs b/crates/astria-composer/tests/blackbox/grpc_collector.rs index f8286a25d8..18767ab0ab 100644 --- a/crates/astria-composer/tests/blackbox/grpc_collector.rs +++ b/crates/astria-composer/tests/blackbox/grpc_collector.rs @@ -1,7 +1,7 @@ use std::time::Duration; use astria_core::{ - generated::composer::v1::{ + generated::astria::composer::v1::{ grpc_collector_service_client::GrpcCollectorServiceClient, SubmitRollupTransactionRequest, }, diff --git a/crates/astria-composer/tests/blackbox/helper/mock_grpc_sequencer.rs b/crates/astria-composer/tests/blackbox/helper/mock_grpc_sequencer.rs index 23be34c31a..0897610967 100644 --- a/crates/astria-composer/tests/blackbox/helper/mock_grpc_sequencer.rs +++ b/crates/astria-composer/tests/blackbox/helper/mock_grpc_sequencer.rs @@ -5,7 +5,7 @@ use std::{ use astria_core::{ self, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_server::{ SequencerService, SequencerServiceServer, diff --git a/crates/astria-composer/tests/blackbox/helper/mod.rs b/crates/astria-composer/tests/blackbox/helper/mod.rs index 93f7f6a1a7..fca7da0f41 100644 --- a/crates/astria-composer/tests/blackbox/helper/mod.rs +++ b/crates/astria-composer/tests/blackbox/helper/mod.rs @@ -207,7 +207,7 @@ pub async fn loop_until_composer_is_ready(addr: SocketAddr) { } fn signed_tx_from_request(request: &Request) -> Transaction { - use astria_core::generated::protocol::transaction::v1::Transaction as RawTransaction; + use astria_core::generated::astria::protocol::transaction::v1::Transaction as RawTransaction; use prost::Message as _; let wrapped_tx_sync_req: request::Wrapper = diff --git a/crates/astria-conductor/src/celestia/block_verifier.rs b/crates/astria-conductor/src/celestia/block_verifier.rs index ff72aa8a7c..7b580b954e 100644 --- a/crates/astria-conductor/src/celestia/block_verifier.rs +++ b/crates/astria-conductor/src/celestia/block_verifier.rs @@ -221,7 +221,7 @@ mod tests { use std::collections::BTreeMap; use astria_core::{ - generated::sequencerblock::v1::SequencerBlockHeader as RawSequencerBlockHeader, + generated::astria::sequencerblock::v1::SequencerBlockHeader as RawSequencerBlockHeader, primitive::v1::RollupId, sequencerblock::v1::{ block::SequencerBlockHeader, diff --git a/crates/astria-conductor/src/celestia/convert.rs b/crates/astria-conductor/src/celestia/convert.rs index c2772d4f15..02861136d1 100644 --- a/crates/astria-conductor/src/celestia/convert.rs +++ b/crates/astria-conductor/src/celestia/convert.rs @@ -1,6 +1,6 @@ use astria_core::{ brotli::decompress_bytes, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ SubmittedMetadataList, SubmittedRollupDataList, }, diff --git a/crates/astria-conductor/src/executor/client.rs b/crates/astria-conductor/src/executor/client.rs index bbd0b550cf..1bab377d97 100644 --- a/crates/astria-conductor/src/executor/client.rs +++ b/crates/astria-conductor/src/executor/client.rs @@ -6,7 +6,7 @@ use astria_core::{ CommitmentState, GenesisInfo, }, - generated::{ + generated::astria::{ execution::{ v1 as raw, v1::execution_service_client::ExecutionServiceClient, diff --git a/crates/astria-conductor/src/executor/state.rs b/crates/astria-conductor/src/executor/state.rs index bfe794e6c1..2ae4e2c4b2 100644 --- a/crates/astria-conductor/src/executor/state.rs +++ b/crates/astria-conductor/src/executor/state.rs @@ -344,7 +344,7 @@ pub(super) fn map_sequencer_height_to_rollup_height( #[cfg(test)] mod tests { use astria_core::{ - generated::execution::v1 as raw, + generated::astria::execution::v1 as raw, Protobuf as _, }; use pbjson_types::Timestamp; diff --git a/crates/astria-conductor/src/executor/tests.rs b/crates/astria-conductor/src/executor/tests.rs index 164d8b0a20..e8ead9dc71 100644 --- a/crates/astria-conductor/src/executor/tests.rs +++ b/crates/astria-conductor/src/executor/tests.rs @@ -5,7 +5,7 @@ use astria_core::{ CommitmentState, GenesisInfo, }, - generated::execution::v1 as raw, + generated::astria::execution::v1 as raw, Protobuf as _, }; use bytes::Bytes; diff --git a/crates/astria-conductor/src/sequencer/client.rs b/crates/astria-conductor/src/sequencer/client.rs index 760a5ede27..e0e61d2229 100644 --- a/crates/astria-conductor/src/sequencer/client.rs +++ b/crates/astria-conductor/src/sequencer/client.rs @@ -3,7 +3,7 @@ use std::time::Duration; use astria_core::{ - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_client::SequencerServiceClient, GetFilteredSequencerBlockRequest, }, diff --git a/crates/astria-conductor/tests/blackbox/firm_only.rs b/crates/astria-conductor/tests/blackbox/firm_only.rs index 2fb2c43148..e634292a1c 100644 --- a/crates/astria-conductor/tests/blackbox/firm_only.rs +++ b/crates/astria-conductor/tests/blackbox/firm_only.rs @@ -5,7 +5,7 @@ use astria_conductor::{ Conductor, Config, }; -use astria_core::generated::execution::v1::{ +use astria_core::generated::astria::execution::v1::{ GetCommitmentStateRequest, GetGenesisInfoRequest, }; diff --git a/crates/astria-conductor/tests/blackbox/helpers/macros.rs b/crates/astria-conductor/tests/blackbox/helpers/macros.rs index 4119fe1bf6..981f4c31dc 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/macros.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/macros.rs @@ -1,7 +1,7 @@ #[macro_export] macro_rules! block { (number: $number:expr,hash: $hash:expr,parent: $parent:expr $(,)?) => { - ::astria_core::generated::execution::v1::Block { + ::astria_core::generated::astria::execution::v1::Block { number: $number, hash: ::bytes::Bytes::from(Vec::from($hash)), parent_block_hash: ::bytes::Bytes::from(Vec::from($parent)), @@ -59,7 +59,7 @@ macro_rules! commitment_state { soft: (number: $soft_number:expr,hash: $soft_hash:expr,parent: $soft_parent:expr $(,)?), base_celestia_height: $base_celestia_height:expr $(,)? ) => { - ::astria_core::generated::execution::v1::CommitmentState { + ::astria_core::generated::astria::execution::v1::CommitmentState { firm: Some($crate::block!( number: $firm_number, hash: $firm_hash, @@ -98,7 +98,7 @@ macro_rules! genesis_info { $sequencer_height:expr,celestia_block_variance: $variance:expr $(,)? ) => { - ::astria_core::generated::execution::v1::GenesisInfo { + ::astria_core::generated::astria::execution::v1::GenesisInfo { rollup_id: Some($crate::ROLLUP_ID.to_raw()), sequencer_genesis_block_height: $sequencer_height, celestia_block_variance: $variance, @@ -312,7 +312,7 @@ macro_rules! mount_get_filtered_sequencer_block { ($test_env:ident, sequencer_height: $height:expr, delay: $delay:expr $(,)?) => { $test_env .mount_get_filtered_sequencer_block( - ::astria_core::generated::sequencerblock::v1::GetFilteredSequencerBlockRequest { + ::astria_core::generated::astria::sequencerblock::v1::GetFilteredSequencerBlockRequest { height: $height, rollup_ids: vec![$crate::ROLLUP_ID.to_raw()], }, @@ -385,12 +385,12 @@ macro_rules! mount_get_block { hash: $hash, parent: $parent, ); - let identifier = ::astria_core::generated::execution::v1::BlockIdentifier { + let identifier = ::astria_core::generated::astria::execution::v1::BlockIdentifier { identifier: Some( - ::astria_core::generated::execution::v1::block_identifier::Identifier::BlockNumber(block.number) + ::astria_core::generated::astria::execution::v1::block_identifier::Identifier::BlockNumber(block.number) )}; $test_env.mount_get_block( - ::astria_core::generated::execution::v1::GetBlockRequest { + ::astria_core::generated::astria::execution::v1::GetBlockRequest { identifier: Some(identifier), }, block, diff --git a/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs b/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs index c0b8b7ff2f..457ef04e3d 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mock_grpc.rs @@ -3,7 +3,7 @@ use std::{ sync::Arc, }; -use astria_core::generated::{ +use astria_core::generated::astria::{ execution::v1::{ execution_service_server::{ ExecutionService, diff --git a/crates/astria-conductor/tests/blackbox/helpers/mod.rs b/crates/astria-conductor/tests/blackbox/helpers/mod.rs index da7dad5061..8890bc3f20 100644 --- a/crates/astria-conductor/tests/blackbox/helpers/mod.rs +++ b/crates/astria-conductor/tests/blackbox/helpers/mod.rs @@ -12,7 +12,7 @@ use astria_conductor::{ }; use astria_core::{ brotli::compress_bytes, - generated::{ + generated::astria::{ execution::v1::{ Block, CommitmentState, @@ -179,7 +179,7 @@ impl TestConductor { pub async fn mount_get_block( &self, expected_pbjson: S, - block: astria_core::generated::execution::v1::Block, + block: astria_core::generated::astria::execution::v1::Block, ) { use astria_grpc_mock::{ matcher::message_partial_pbjson, @@ -306,7 +306,7 @@ impl TestConductor { } pub async fn mount_get_genesis_info(&self, genesis_info: GenesisInfo) { - use astria_core::generated::execution::v1::GetGenesisInfoRequest; + use astria_core::generated::astria::execution::v1::GetGenesisInfoRequest; astria_grpc_mock::Mock::for_rpc_given( "get_genesis_info", astria_grpc_mock::matcher::message_type::(), @@ -318,7 +318,7 @@ impl TestConductor { } pub async fn mount_get_commitment_state(&self, commitment_state: CommitmentState) { - use astria_core::generated::execution::v1::GetCommitmentStateRequest; + use astria_core::generated::astria::execution::v1::GetCommitmentStateRequest; astria_grpc_mock::Mock::for_rpc_given( "get_commitment_state", @@ -381,7 +381,7 @@ impl TestConductor { commitment_state: CommitmentState, expected_calls: u64, ) -> astria_grpc_mock::MockGuard { - use astria_core::generated::execution::v1::UpdateCommitmentStateRequest; + use astria_core::generated::astria::execution::v1::UpdateCommitmentStateRequest; use astria_grpc_mock::{ matcher::message_partial_pbjson, response::constant_response, @@ -567,7 +567,7 @@ pub struct Blobs { #[must_use] pub fn make_blobs(heights: &[u32]) -> Blobs { - use astria_core::generated::sequencerblock::v1::{ + use astria_core::generated::astria::sequencerblock::v1::{ SubmittedMetadataList, SubmittedRollupDataList, }; diff --git a/crates/astria-conductor/tests/blackbox/soft_only.rs b/crates/astria-conductor/tests/blackbox/soft_only.rs index e82793c638..8fff4e8c1c 100644 --- a/crates/astria-conductor/tests/blackbox/soft_only.rs +++ b/crates/astria-conductor/tests/blackbox/soft_only.rs @@ -5,7 +5,7 @@ use astria_conductor::{ Conductor, Config, }; -use astria_core::generated::execution::v1::{ +use astria_core::generated::astria::execution::v1::{ GetCommitmentStateRequest, GetGenesisInfoRequest, }; diff --git a/crates/astria-core-address/CHANGELOG.md b/crates/astria-core-address/CHANGELOG.md new file mode 100644 index 0000000000..e8d6d38ac2 --- /dev/null +++ b/crates/astria-core-address/CHANGELOG.md @@ -0,0 +1,14 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Initial release. [#1802](https://github.com/astriaorg/astria/pull/1802) diff --git a/crates/astria-core-address/Cargo.toml b/crates/astria-core-address/Cargo.toml new file mode 100644 index 0000000000..85a2a03eaf --- /dev/null +++ b/crates/astria-core-address/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "astria-core-address" +version = "0.1.0" +edition = "2021" + +[dependencies] +astria-core-consts = { path = "../astria-core-consts" } + +thiserror = { workspace = true } + +bech32 = "0.11.0" + +[features] +unchecked-constructor = [] diff --git a/crates/astria-core-address/src/lib.rs b/crates/astria-core-address/src/lib.rs new file mode 100644 index 0000000000..3685222d87 --- /dev/null +++ b/crates/astria-core-address/src/lib.rs @@ -0,0 +1,440 @@ +use std::{ + marker::PhantomData, + str::FromStr, +}; + +pub use astria_core_consts::ADDRESS_LENGTH; + +#[derive(Debug, Hash)] +pub struct Address { + bytes: [u8; ADDRESS_LENGTH], + prefix: bech32::Hrp, + format: PhantomData, +} + +impl Clone for Address { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Address {} + +impl PartialEq for Address { + fn eq(&self, other: &Self) -> bool { + self.bytes.eq(&other.bytes) && self.prefix.eq(&other.prefix) + } +} + +impl Eq for Address {} + +impl Address { + #[must_use = "the builder must be used to construct an address to be useful"] + pub fn builder() -> Builder { + Builder::new() + } + + #[must_use] + pub fn bytes(self) -> [u8; ADDRESS_LENGTH] { + self.bytes + } + + #[must_use] + pub fn as_bytes(&self) -> &[u8; ADDRESS_LENGTH] { + &self.bytes + } + + #[must_use] + pub fn prefix(&self) -> &str { + self.prefix.as_str() + } + + /// Converts to a new address with the given `prefix`. + /// + /// # Errors + /// Returns an error if an address with `prefix` cannot be constructed. + /// The error conditions for this are the same as for [`AddressBuilder::try_build`]. + pub fn to_prefix(&self, prefix: &str) -> Result { + Self::builder() + .array(*self.as_bytes()) + .prefix(prefix) + .try_build() + } + + /// Converts to a new address with the type argument `OtherFormat`. + /// + /// `OtherFormat` is usually [`Bech32`] or [`Bech32m`]. + #[must_use] + pub fn to_format(&self) -> Address { + Address { + bytes: self.bytes, + prefix: self.prefix, + format: PhantomData, + } + } +} + +impl Address { + /// Should only be used where the inputs have been provided by a trusted entity, e.g. read + /// from our own state store. + /// + /// Note that this function is not considered part of the public API and is subject to breaking + /// change at any time. + #[cfg(feature = "unchecked-constructor")] + #[doc(hidden)] + #[must_use] + pub fn unchecked_from_parts(bytes: [u8; ADDRESS_LENGTH], prefix: &str) -> Self { + Self { + bytes, + prefix: bech32::Hrp::parse_unchecked(prefix), + format: PhantomData, + } + } +} + +impl FromStr for Address { + type Err = Error; + + fn from_str(s: &str) -> Result { + let checked = bech32::primitives::decode::CheckedHrpstring::new::(s) + .map_err(Self::Err::decode)?; + let hrp = checked.hrp(); + Self::builder() + .with_iter(checked.byte_iter()) + .prefix(hrp.as_str()) + .try_build() + } +} + +impl std::fmt::Display for Address { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use bech32::EncodeError; + match bech32::encode_lower_to_fmt::(f, self.prefix, self.as_bytes()) { + Ok(()) => Ok(()), + Err(EncodeError::Fmt(err)) => Err(err), + Err(err) => panic!( + "only formatting errors are valid when encoding astria addresses; all other error \ + variants (only TooLong as of bech32-0.11.0) are guaranteed to not happen because \ + `Address` is length checked:\n{err:?}", + ), + } + } +} + +pub struct NoBytes; +pub struct NoPrefix; +pub struct WithBytes<'a, I>(WithBytesInner<'a, I>); +enum WithBytesInner<'a, I> { + Array([u8; ADDRESS_LENGTH]), + Iter(I), + Slice(std::borrow::Cow<'a, [u8]>), +} +pub struct WithPrefix<'a>(std::borrow::Cow<'a, str>); + +pub struct NoBytesIter; + +impl Iterator for NoBytesIter { + type Item = u8; + + fn next(&mut self) -> Option { + None + } +} + +impl ExactSizeIterator for NoBytesIter { + fn len(&self) -> usize { + 0 + } +} + +pub struct Builder { + bytes: TBytes, + prefix: TPrefix, + format: PhantomData, +} + +impl Builder { + const fn new() -> Self { + Self { + bytes: NoBytes, + prefix: NoPrefix, + format: PhantomData, + } + } +} + +impl Builder { + #[must_use = "the builder must be built to construct an address to be useful"] + pub fn array( + self, + array: [u8; ADDRESS_LENGTH], + ) -> Builder, TPrefix> { + Builder { + bytes: WithBytes(WithBytesInner::Array(array)), + prefix: self.prefix, + format: self.format, + } + } + + #[must_use = "the builder must be built to construct an address to be useful"] + pub fn slice<'a, T: Into>>( + self, + bytes: T, + ) -> Builder, TPrefix> { + Builder { + bytes: WithBytes(WithBytesInner::Slice(bytes.into())), + prefix: self.prefix, + format: self.format, + } + } + + #[must_use = "the builder must be built to construct an address to be useful"] + fn with_iter(self, iter: T) -> Builder, TPrefix> + where + T: IntoIterator, + T::IntoIter: ExactSizeIterator, + { + Builder { + bytes: WithBytes(WithBytesInner::Iter(iter)), + prefix: self.prefix, + format: self.format, + } + } + + #[must_use = "the builder must be built to construct an address to be useful"] + pub fn prefix<'a, T: Into>>( + self, + prefix: T, + ) -> Builder> { + Builder { + bytes: self.bytes, + prefix: WithPrefix(prefix.into()), + format: self.format, + } + } +} + +impl<'a, 'b, TFormat, TBytesIter> Builder, WithPrefix<'b>> +where + TBytesIter: IntoIterator, + TBytesIter::IntoIter: ExactSizeIterator, +{ + /// Attempts to build an address from the configured prefix and bytes. + /// + /// # Errors + /// Returns an error if one of the following conditions are violated: + /// + if the prefix shorter than 1 or longer than 83 characters, or contains characters outside + /// 33-126 of ASCII characters. + /// + if the provided bytes are not exactly 20 bytes. + pub fn try_build(self) -> Result, Error> { + let Self { + bytes: WithBytes(bytes), + prefix: WithPrefix(prefix), + format, + } = self; + let bytes = match bytes { + WithBytesInner::Array(bytes) => bytes, + WithBytesInner::Iter(bytes) => try_collect_to_array(bytes)?, + WithBytesInner::Slice(bytes) => <[u8; ADDRESS_LENGTH]>::try_from(bytes.as_ref()) + .map_err(|_| Error::incorrect_length(bytes.len()))?, + }; + let prefix = bech32::Hrp::parse(&prefix).map_err(Error::invalid_prefix)?; + Ok(Address { + bytes, + prefix, + format, + }) + } +} + +fn try_collect_to_array(iter: I) -> Result<[u8; ADDRESS_LENGTH], Error> +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + let iter = iter.into_iter(); + + if iter.len() != ADDRESS_LENGTH { + return Err(Error::incorrect_length(iter.len())); + } + let mut arr = [0; ADDRESS_LENGTH]; + for (left, right) in arr.iter_mut().zip(iter) { + *left = right; + } + Ok(arr) +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(ErrorKind); + +impl Error { + fn decode(source: bech32::primitives::decode::CheckedHrpstringError) -> Self { + Self(ErrorKind::Decode { + source, + }) + } + + fn invalid_prefix(source: bech32::primitives::hrp::Error) -> Self { + Self(ErrorKind::InvalidPrefix { + source, + }) + } + + fn incorrect_length(received: usize) -> Self { + Self(ErrorKind::IncorrectLength { + received, + }) + } +} + +#[derive(Debug, thiserror::Error, PartialEq)] +enum ErrorKind { + #[error("failed decoding provided string")] + Decode { + source: bech32::primitives::decode::CheckedHrpstringError, + }, + #[error("expected an address of 20 bytes, got `{received}`")] + IncorrectLength { received: usize }, + #[error("the provided prefix was not a valid bech32 human readable prefix")] + InvalidPrefix { + source: bech32::primitives::hrp::Error, + }, +} + +#[derive(Clone, Copy, Debug)] +pub enum Bech32m {} +#[derive(Clone, Copy, Debug)] +pub enum Bech32 {} +#[derive(Clone, Copy, Debug)] +pub enum NoFormat {} + +#[expect( + private_bounds, + reason = "prevent downstream implementation of this trait" +)] +pub trait Format: Sealed { + type Checksum: bech32::Checksum; +} + +impl Format for Bech32m { + type Checksum = bech32::Bech32m; +} + +impl Format for Bech32 { + type Checksum = bech32::Bech32; +} + +impl Format for NoFormat { + type Checksum = bech32::NoChecksum; +} + +trait Sealed {} +impl Sealed for Bech32m {} +impl Sealed for Bech32 {} +impl Sealed for NoFormat {} + +#[cfg(test)] +mod tests { + use super::{ + Address, + Bech32, + Bech32m, + Error, + ErrorKind, + }; + + const ASTRIA_ADDRESS_PREFIX: &str = "astria"; + const ASTRIA_COMPAT_ADDRESS_PREFIX: &str = "astriacompat"; + + #[track_caller] + fn assert_wrong_address_bytes(bad_account: &[u8]) { + let error = Address::::builder() + .slice(bad_account) + .prefix(ASTRIA_ADDRESS_PREFIX) + .try_build() + .expect_err( + "converting from an incorrectly sized byte slice succeeded where it should have \ + failed", + ); + let Error(ErrorKind::IncorrectLength { + received, + }) = error + else { + panic!("expected ErrorKind::IncorrectLength, got {error:?}"); + }; + assert_eq!(bad_account.len(), received); + } + + #[test] + fn account_of_incorrect_length_gives_error() { + assert_wrong_address_bytes(&[42; 0]); + assert_wrong_address_bytes(&[42; 19]); + assert_wrong_address_bytes(&[42; 21]); + assert_wrong_address_bytes(&[42; 100]); + } + + #[test] + fn parse_bech32m_address() { + let expected = Address::builder() + .array([42; 20]) + .prefix(ASTRIA_ADDRESS_PREFIX) + .try_build() + .unwrap(); + let actual = expected.to_string().parse::
().unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn parse_bech32_address() { + let expected = Address::::builder() + .array([42; 20]) + .prefix(ASTRIA_COMPAT_ADDRESS_PREFIX) + .try_build() + .unwrap(); + let actual = expected.to_string().parse::>().unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn parsing_bech32_address_as_bech32m_fails() { + let expected = Address::::builder() + .array([42; 20]) + .prefix(ASTRIA_COMPAT_ADDRESS_PREFIX) + .try_build() + .unwrap(); + let err = expected + .to_string() + .parse::>() + .expect_err("this must not work"); + match err { + Error(ErrorKind::Decode { + .. + }) => {} + other => { + panic!("expected Error(ErrorKind::Decode {{ .. }}), but got {other:?}") + } + } + } + + #[test] + fn parsing_bech32m_address_as_bech32_fails() { + let expected = Address::::builder() + .array([42; 20]) + .prefix(ASTRIA_ADDRESS_PREFIX) + .try_build() + .unwrap(); + let err = expected + .to_string() + .parse::>() + .expect_err("this must not work"); + match err { + Error(ErrorKind::Decode { + .. + }) => {} + other => { + panic!("expected Error(ErrorKind::Decode {{ .. }}), but got {other:?}") + } + } + } +} diff --git a/crates/astria-core-consts/CHANGELOG.md b/crates/astria-core-consts/CHANGELOG.md new file mode 100644 index 0000000000..27692b72c0 --- /dev/null +++ b/crates/astria-core-consts/CHANGELOG.md @@ -0,0 +1,14 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Initial release. [#1800](https://github.com/astriaorg/astria/pull/1800) diff --git a/crates/astria-core-consts/Cargo.toml b/crates/astria-core-consts/Cargo.toml new file mode 100644 index 0000000000..125b069104 --- /dev/null +++ b/crates/astria-core-consts/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "astria-core-consts" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/astria-core-consts/src/lib.rs b/crates/astria-core-consts/src/lib.rs new file mode 100644 index 0000000000..1b05c26f66 --- /dev/null +++ b/crates/astria-core-consts/src/lib.rs @@ -0,0 +1,3 @@ +//! Public constants that are used throughout the Astria stack. + +pub const ADDRESS_LENGTH: usize = 20; diff --git a/crates/astria-core-crypto/CHANGELOG.md b/crates/astria-core-crypto/CHANGELOG.md new file mode 100644 index 0000000000..f782487e36 --- /dev/null +++ b/crates/astria-core-crypto/CHANGELOG.md @@ -0,0 +1,14 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Initial release [#1800](https://github.com/astriaorg/astria/pull/1800) diff --git a/crates/astria-core-crypto/Cargo.toml b/crates/astria-core-crypto/Cargo.toml new file mode 100644 index 0000000000..4e8b94bf7c --- /dev/null +++ b/crates/astria-core-crypto/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "astria-core-crypto" +version = "0.1.0" +edition = "2021" +rust-version = "1.81.0" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/astriaorg/astria" +homepage = "https://astria.org" + +[dependencies] +astria-core-consts = { path = "../astria-core-consts" } + +base64 = { workspace = true } +rand = { workspace = true } +sha2 = { workspace = true } +thiserror = { workspace = true } + +ed25519-consensus = { version = "2.1.0", default-features = false, features = [ + "std", +] } +zeroize = { version = "1.7.0", features = ["zeroize_derive"] } diff --git a/crates/astria-core/src/crypto.rs b/crates/astria-core-crypto/src/lib.rs similarity index 93% rename from crates/astria-core/src/crypto.rs rename to crates/astria-core-crypto/src/lib.rs index 2ce819d875..4213a7bdf4 100644 --- a/crates/astria-core/src/crypto.rs +++ b/crates/astria-core-crypto/src/lib.rs @@ -13,6 +13,7 @@ use std::{ sync::OnceLock, }; +pub use astria_core_consts::ADDRESS_LENGTH; use base64::{ display::Base64Display, prelude::BASE64_STANDARD, @@ -28,21 +29,11 @@ use rand::{ CryptoRng, RngCore, }; -use sha2::{ - Digest as _, - Sha256, -}; use zeroize::{ Zeroize, ZeroizeOnDrop, }; -use crate::primitive::v1::{ - Address, - AddressError, - ADDRESS_LEN, -}; - /// An Ed25519 signing key. // *Implementation note*: this is currently a refinement type around // ed25519_consensus::SigningKey overriding its Debug implementation @@ -85,22 +76,9 @@ impl SigningKey { /// Returns the address bytes of the verification key associated with this signing key. #[must_use] - pub fn address_bytes(&self) -> [u8; ADDRESS_LEN] { + pub fn address_bytes(&self) -> [u8; ADDRESS_LENGTH] { *self.verification_key().address_bytes() } - - /// Attempts to create an Astria bech32m `[Address]` with the given prefix. - /// - /// # Errors - /// Returns an [`AddressError`] if an address could not be constructed - /// with the given prefix. Usually if the prefix was too long or contained - /// characters not allowed by bech32m. - pub fn try_address(&self, prefix: &str) -> Result { - Address::builder() - .prefix(prefix) - .array(self.address_bytes()) - .try_build() - } } impl Debug for SigningKey { @@ -139,7 +117,7 @@ pub struct VerificationKey { // The address-bytes are lazily-initialized. Since it may or may not be initialized for any // given instance of a verification key, it is excluded from `PartialEq`, `Eq`, `PartialOrd`, // `Ord` and `Hash` impls. - address_bytes: OnceLock<[u8; ADDRESS_LEN]>, + address_bytes: OnceLock<[u8; ADDRESS_LENGTH]>, } impl VerificationKey { @@ -167,18 +145,23 @@ impl VerificationKey { /// /// The address is the first 20 bytes of the sha256 hash of the verification key. #[must_use] - pub fn address_bytes(&self) -> &[u8; ADDRESS_LEN] { + pub fn address_bytes(&self) -> &[u8; ADDRESS_LENGTH] { + use sha2::{ + Digest as _, + Sha256, + }; + self.address_bytes.get_or_init(|| { - fn first_20(array: [u8; 32]) -> [u8; ADDRESS_LEN] { + fn first_20(array: [u8; 32]) -> [u8; ADDRESS_LENGTH] { [ array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8], array[9], array[10], array[11], array[12], array[13], array[14], array[15], array[16], array[17], array[18], array[19], ] } - /// this ensures that `ADDRESS_LEN` is never accidentally changed to a value + /// this ensures that `ADDRESS_LENGTH` is never accidentally changed to a value /// that would violate this assumption. - const _: () = assert!(ADDRESS_LEN <= 32); + const _: () = assert!(ADDRESS_LENGTH <= 32); let bytes: [u8; 32] = Sha256::digest(self).into(); first_20(bytes) }) diff --git a/crates/astria-core/CHANGELOG.md b/crates/astria-core/CHANGELOG.md index dd487eeab3..0477f1767c 100644 --- a/crates/astria-core/CHANGELOG.md +++ b/crates/astria-core/CHANGELOG.md @@ -14,7 +14,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release. - Added method `TracePrefixed::leading_channel` to read the left-most channel of a trace prefixed ICS20 asset [#1768](https://github.com/astriaorg/astria/pull/1768) +- Added `impl Protobuf for Address` [#1802](https://github.com/astriaorg/astria/pull/1802) + +### Changed + +- Moved `astria_core::crypto` to `astria-core-crypto` and reexported + `astria_core_crypto as crypto` (this change is transparent) + [#1800](https://github.com/astriaorg/astria/pull/1800/) +- Moved definitions of address domain type to `astria-core-address` and + reexported items using the same aliases [#1802](https://github.com/astriaorg/astria/pull/1802) + +### Changed + +- Move all Astria APIs generated from the Protobuf spec from `astria_core::generated` + to `astria_core::generated::astria` + [#1825](https://github.com/astriaorg/astria/pull/1825) ### Removed - Removed method `TracePrefixed::last_channel` [#1768](https://github.com/astriaorg/astria/pull/1768) +- Removed method `SigningKey::try_address` [#1800](https://github.com/astriaorg/astria/pull/1800/) +- Removed inherent methods `Address::try_from_raw` and `Address::to_raw` + [#1802](https://github.com/astriaorg/astria/pull/1802) +- Removed `AddressBuilder::with_iter` from public interface [#1802](https://github.com/astriaorg/astria/pull/1802) diff --git a/crates/astria-core/Cargo.toml b/crates/astria-core/Cargo.toml index a7a45d5ba0..b816e3fdf5 100644 --- a/crates/astria-core/Cargo.toml +++ b/crates/astria-core/Cargo.toml @@ -16,18 +16,17 @@ keywords = ["astria", "grpc", "rpc", "blockchain", "execution", "protobuf"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bech32 = "0.11.0" brotli = { version = "5.0.0", optional = true } celestia-types = { version = "0.1.1", optional = true } pbjson = { version = "0.6.0", optional = true } +astria-core-address = { path = "../astria-core-address" } +astria-core-consts = { path = "../astria-core-consts" } +astria-core-crypto = { path = "../astria-core-crypto" } merkle = { package = "astria-merkle", path = "../astria-merkle" } bytes = { workspace = true } celestia-tendermint = { workspace = true } -ed25519-consensus = { version = "2.1.0", default-features = false, features = [ - "std", -] } hex = { workspace = true } ibc-types = { workspace = true } indexmap = { workspace = true } @@ -35,7 +34,7 @@ pbjson-types = { workspace = true } penumbra-ibc = { workspace = true } penumbra-proto = { workspace = true } prost = { workspace = true } -rand = { workspace = true } +rand = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"], optional = true } sha2 = { workspace = true } tendermint = { workspace = true } @@ -45,20 +44,19 @@ tonic = { workspace = true, optional = true } tracing = { workspace = true } base64-serde = { workspace = true, optional = true } base64 = { workspace = true } -zeroize = { version = "1.7.0", features = ["zeroize_derive"] } [features] celestia = ["dep:celestia-types"] client = ["dep:tonic"] serde = ["dep:serde", "dep:pbjson", "dep:base64-serde"] server = ["dep:tonic"] -test-utils = [] +test-utils = ["dep:rand"] base64-serde = ["dep:base64-serde"] brotli = ["dep:brotli"] # When enabled, this adds constructors for some types that skip the normal constructor validity # checks. It supports the case where the inputs are already deemed valid, e.g. having read them from # local storage. -unchecked-constructors = [] +unchecked-constructors = ["astria-core-address/unchecked-constructor"] [dev-dependencies] astria-core = { path = ".", features = ["serde"] } diff --git a/crates/astria-core/src/execution/v1/mod.rs b/crates/astria-core/src/execution/v1/mod.rs index 6daca96a42..3327cd0edc 100644 --- a/crates/astria-core/src/execution/v1/mod.rs +++ b/crates/astria-core/src/execution/v1/mod.rs @@ -2,7 +2,7 @@ use bytes::Bytes; use pbjson_types::Timestamp; use crate::{ - generated::execution::v1 as raw, + generated::astria::execution::v1 as raw, primitive::v1::{ IncorrectRollupIdLength, RollupId, @@ -43,7 +43,7 @@ enum GenesisInfoErrorKind { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr( feature = "serde", - serde(into = "crate::generated::execution::v1::GenesisInfo") + serde(into = "crate::generated::astria::execution::v1::GenesisInfo") )] pub struct GenesisInfo { /// The rollup id which is used to identify the rollup txs. @@ -148,7 +148,7 @@ enum BlockErrorKind { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr( feature = "serde", - serde(into = "crate::generated::execution::v1::Block") + serde(into = "crate::generated::astria::execution::v1::Block") )] pub struct Block { /// The block number @@ -380,7 +380,7 @@ impl CommitmentStateBuilder { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr( feature = "serde", - serde(into = "crate::generated::execution::v1::CommitmentState") + serde(into = "crate::generated::astria::execution::v1::CommitmentState") )] pub struct CommitmentState { /// Soft commitment is the rollup block matching latest sequencer block. diff --git a/crates/astria-core/src/generated/astria.protocol.transaction.v1.rs b/crates/astria-core/src/generated/astria.protocol.transaction.v1.rs index 6bd2ba2fdc..0b38628394 100644 --- a/crates/astria-core/src/generated/astria.protocol.transaction.v1.rs +++ b/crates/astria-core/src/generated/astria.protocol.transaction.v1.rs @@ -36,7 +36,7 @@ pub mod action { SudoAddressChange(super::SudoAddressChange), #[prost(message, tag = "51")] ValidatorUpdate( - crate::generated::astria_vendored::tendermint::abci::ValidatorUpdate, + super::super::super::super::super::astria_vendored::tendermint::abci::ValidatorUpdate, ), #[prost(message, tag = "52")] IbcRelayerChange(super::IbcRelayerChange), diff --git a/crates/astria-core/src/generated/astria.protocol.transaction.v1alpha1.rs b/crates/astria-core/src/generated/astria.protocol.transaction.v1alpha1.rs index 75c1bf44b4..a310970f2b 100644 --- a/crates/astria-core/src/generated/astria.protocol.transaction.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.protocol.transaction.v1alpha1.rs @@ -36,7 +36,7 @@ pub mod action { SudoAddressChange(super::SudoAddressChange), #[prost(message, tag = "51")] ValidatorUpdate( - crate::generated::astria_vendored::tendermint::abci::ValidatorUpdate, + super::super::super::super::super::astria_vendored::tendermint::abci::ValidatorUpdate, ), #[prost(message, tag = "52")] IbcRelayerChange(super::IbcRelayerChange), diff --git a/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.rs new file mode 100644 index 0000000000..b52cde36b9 --- /dev/null +++ b/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.rs @@ -0,0 +1,516 @@ +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetBlockCommitmentStreamRequest {} +impl ::prost::Name for GetBlockCommitmentStreamRequest { + const NAME: &'static str = "GetBlockCommitmentStreamRequest"; + const PACKAGE: &'static str = "astria.sequencerblock.optimistic.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimistic.v1alpha1.{}", Self::NAME + ) + } +} +/// Identifying metadata for blocks that have been successfully committed in the Sequencer. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SequencerBlockCommit { + /// Height of the sequencer block that was committed. + #[prost(uint64, tag = "1")] + pub height: u64, + /// Hash of the sequencer block that was committed. + #[prost(bytes = "bytes", tag = "2")] + pub block_hash: ::prost::bytes::Bytes, +} +impl ::prost::Name for SequencerBlockCommit { + const NAME: &'static str = "SequencerBlockCommit"; + const PACKAGE: &'static str = "astria.sequencerblock.optimistic.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimistic.v1alpha1.{}", Self::NAME + ) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetBlockCommitmentStreamResponse { + #[prost(message, optional, tag = "1")] + pub commitment: ::core::option::Option, +} +impl ::prost::Name for GetBlockCommitmentStreamResponse { + const NAME: &'static str = "GetBlockCommitmentStreamResponse"; + const PACKAGE: &'static str = "astria.sequencerblock.optimistic.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimistic.v1alpha1.{}", Self::NAME + ) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOptimisticBlockStreamRequest { + /// The rollup id for which the Sequencer block is being streamed. + #[prost(message, optional, tag = "1")] + pub rollup_id: ::core::option::Option, +} +impl ::prost::Name for GetOptimisticBlockStreamRequest { + const NAME: &'static str = "GetOptimisticBlockStreamRequest"; + const PACKAGE: &'static str = "astria.sequencerblock.optimistic.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimistic.v1alpha1.{}", Self::NAME + ) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetOptimisticBlockStreamResponse { + /// The optimistic Sequencer block that is being streamed, filtered for the provided rollup id. + #[prost(message, optional, tag = "1")] + pub block: ::core::option::Option, +} +impl ::prost::Name for GetOptimisticBlockStreamResponse { + const NAME: &'static str = "GetOptimisticBlockStreamResponse"; + const PACKAGE: &'static str = "astria.sequencerblock.optimistic.v1alpha1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "astria.sequencerblock.optimistic.v1alpha1.{}", Self::NAME + ) + } +} +/// Generated client implementations. +#[cfg(feature = "client")] +pub mod optimistic_block_service_client { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + use tonic::codegen::http::Uri; + /// The Sequencer will serve this to the aucitoneer + #[derive(Debug, Clone)] + pub struct OptimisticBlockServiceClient { + inner: tonic::client::Grpc, + } + impl OptimisticBlockServiceClient { + /// Attempt to create a new client by connecting to a given endpoint. + pub async fn connect(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; + Ok(Self::new(conn)) + } + } + impl OptimisticBlockServiceClient + where + T: tonic::client::GrpcService, + T::Error: Into, + T::ResponseBody: Body + Send + 'static, + ::Error: Into + Send, + { + pub fn new(inner: T) -> Self { + let inner = tonic::client::Grpc::new(inner); + Self { inner } + } + pub fn with_origin(inner: T, origin: Uri) -> Self { + let inner = tonic::client::Grpc::with_origin(inner, origin); + Self { inner } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> OptimisticBlockServiceClient> + where + F: tonic::service::Interceptor, + T::ResponseBody: Default, + T: tonic::codegen::Service< + http::Request, + Response = http::Response< + >::ResponseBody, + >, + >, + , + >>::Error: Into + Send + Sync, + { + OptimisticBlockServiceClient::new( + InterceptedService::new(inner, interceptor), + ) + } + /// Compress requests with the given encoding. + /// + /// This requires the server to support it otherwise it might respond with an + /// error. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.send_compressed(encoding); + self + } + /// Enable decompressing responses. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.inner = self.inner.accept_compressed(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_decoding_message_size(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.inner = self.inner.max_encoding_message_size(limit); + self + } + /// The Sequencer will stream the optimistic Sequencer block (filtered for the provided + /// rollup id) to the Auctioneer. + pub async fn get_optimistic_block_stream( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response< + tonic::codec::Streaming, + >, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService/GetOptimisticBlockStream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService", + "GetOptimisticBlockStream", + ), + ); + self.inner.server_streaming(req, path, codec).await + } + /// The Sequencer will stream the block commits to the Auctioneer. + pub async fn get_block_commitment_stream( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response< + tonic::codec::Streaming, + >, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService/GetBlockCommitmentStream", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService", + "GetBlockCommitmentStream", + ), + ); + self.inner.server_streaming(req, path, codec).await + } + } +} +/// Generated server implementations. +#[cfg(feature = "server")] +pub mod optimistic_block_service_server { + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with OptimisticBlockServiceServer. + #[async_trait] + pub trait OptimisticBlockService: Send + Sync + 'static { + /// Server streaming response type for the GetOptimisticBlockStream method. + type GetOptimisticBlockStreamStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result< + super::GetOptimisticBlockStreamResponse, + tonic::Status, + >, + > + + Send + + 'static; + /// The Sequencer will stream the optimistic Sequencer block (filtered for the provided + /// rollup id) to the Auctioneer. + async fn get_optimistic_block_stream( + self: std::sync::Arc, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + /// Server streaming response type for the GetBlockCommitmentStream method. + type GetBlockCommitmentStreamStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result< + super::GetBlockCommitmentStreamResponse, + tonic::Status, + >, + > + + Send + + 'static; + /// The Sequencer will stream the block commits to the Auctioneer. + async fn get_block_commitment_stream( + self: std::sync::Arc, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + } + /// The Sequencer will serve this to the aucitoneer + #[derive(Debug)] + pub struct OptimisticBlockServiceServer { + inner: _Inner, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + struct _Inner(Arc); + impl OptimisticBlockServiceServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + let inner = _Inner(inner); + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> + for OptimisticBlockServiceServer + where + T: OptimisticBlockService, + B: Body + Send + 'static, + B::Error: Into + Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + let inner = self.inner.clone(); + match req.uri().path() { + "/astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService/GetOptimisticBlockStream" => { + #[allow(non_camel_case_types)] + struct GetOptimisticBlockStreamSvc( + pub Arc, + ); + impl< + T: OptimisticBlockService, + > tonic::server::ServerStreamingService< + super::GetOptimisticBlockStreamRequest, + > for GetOptimisticBlockStreamSvc { + type Response = super::GetOptimisticBlockStreamResponse; + type ResponseStream = T::GetOptimisticBlockStreamStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GetOptimisticBlockStreamRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_optimistic_block_stream( + inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetOptimisticBlockStreamSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService/GetBlockCommitmentStream" => { + #[allow(non_camel_case_types)] + struct GetBlockCommitmentStreamSvc( + pub Arc, + ); + impl< + T: OptimisticBlockService, + > tonic::server::ServerStreamingService< + super::GetBlockCommitmentStreamRequest, + > for GetBlockCommitmentStreamSvc { + type Response = super::GetBlockCommitmentStreamResponse; + type ResponseStream = T::GetBlockCommitmentStreamStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + super::GetBlockCommitmentStreamRequest, + >, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_block_commitment_stream( + inner, + request, + ) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = GetBlockCommitmentStreamSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + Ok( + http::Response::builder() + .status(200) + .header("grpc-status", "12") + .header("content-type", "application/grpc") + .body(empty_body()) + .unwrap(), + ) + }) + } + } + } + } + impl Clone for OptimisticBlockServiceServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + impl Clone for _Inner { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } + } + impl std::fmt::Debug for _Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } + } + impl tonic::server::NamedService + for OptimisticBlockServiceServer { + const NAME: &'static str = "astria.sequencerblock.optimistic.v1alpha1.OptimisticBlockService"; + } +} diff --git a/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.serde.rs new file mode 100644 index 0000000000..816fc26a6c --- /dev/null +++ b/crates/astria-core/src/generated/astria.sequencerblock.optimistic.v1alpha1.serde.rs @@ -0,0 +1,460 @@ +impl serde::Serialize for GetBlockCommitmentStreamRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let len = 0; + let struct_ser = serializer.serialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamRequest", len)?; + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetBlockCommitmentStreamRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + Err(serde::de::Error::unknown_field(value, FIELDS)) + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetBlockCommitmentStreamRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + while map_.next_key::()?.is_some() { + let _ = map_.next_value::()?; + } + Ok(GetBlockCommitmentStreamRequest { + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetBlockCommitmentStreamResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.commitment.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamResponse", len)?; + if let Some(v) = self.commitment.as_ref() { + struct_ser.serialize_field("commitment", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetBlockCommitmentStreamResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "commitment", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Commitment, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "commitment" => Ok(GeneratedField::Commitment), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetBlockCommitmentStreamResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut commitment__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Commitment => { + if commitment__.is_some() { + return Err(serde::de::Error::duplicate_field("commitment")); + } + commitment__ = map_.next_value()?; + } + } + } + Ok(GetBlockCommitmentStreamResponse { + commitment: commitment__, + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetBlockCommitmentStreamResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetOptimisticBlockStreamRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.rollup_id.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamRequest", len)?; + if let Some(v) = self.rollup_id.as_ref() { + struct_ser.serialize_field("rollupId", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetOptimisticBlockStreamRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "rollup_id", + "rollupId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + RollupId, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetOptimisticBlockStreamRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut rollup_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::RollupId => { + if rollup_id__.is_some() { + return Err(serde::de::Error::duplicate_field("rollupId")); + } + rollup_id__ = map_.next_value()?; + } + } + } + Ok(GetOptimisticBlockStreamRequest { + rollup_id: rollup_id__, + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for GetOptimisticBlockStreamResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.block.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamResponse", len)?; + if let Some(v) = self.block.as_ref() { + struct_ser.serialize_field("block", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for GetOptimisticBlockStreamResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "block", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Block, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "block" => Ok(GeneratedField::Block), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GetOptimisticBlockStreamResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut block__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Block => { + if block__.is_some() { + return Err(serde::de::Error::duplicate_field("block")); + } + block__ = map_.next_value()?; + } + } + } + Ok(GetOptimisticBlockStreamResponse { + block: block__, + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.optimistic.v1alpha1.GetOptimisticBlockStreamResponse", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for SequencerBlockCommit { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.height != 0 { + len += 1; + } + if !self.block_hash.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.optimistic.v1alpha1.SequencerBlockCommit", len)?; + if self.height != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; + } + if !self.block_hash.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("blockHash", pbjson::private::base64::encode(&self.block_hash).as_str())?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for SequencerBlockCommit { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "height", + "block_hash", + "blockHash", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + Height, + BlockHash, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "height" => Ok(GeneratedField::Height), + "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), + _ => Err(serde::de::Error::unknown_field(value, FIELDS)), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = SequencerBlockCommit; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct astria.sequencerblock.optimistic.v1alpha1.SequencerBlockCommit") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut height__ = None; + let mut block_hash__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::Height => { + if height__.is_some() { + return Err(serde::de::Error::duplicate_field("height")); + } + height__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::BlockHash => { + if block_hash__.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + block_hash__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } + } + } + Ok(SequencerBlockCommit { + height: height__.unwrap_or_default(), + block_hash: block_hash__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("astria.sequencerblock.optimistic.v1alpha1.SequencerBlockCommit", FIELDS, GeneratedVisitor) + } +} diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs index 5537a617fb..3607446f91 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.rs @@ -330,512 +330,6 @@ impl ::prost::Name for SubmittedMetadata { } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetBlockCommitmentStreamRequest {} -impl ::prost::Name for GetBlockCommitmentStreamRequest { - const NAME: &'static str = "GetBlockCommitmentStreamRequest"; - const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) - } -} -/// Identifying metadata for blocks that have been successfully committed in the Sequencer. -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct SequencerBlockCommit { - /// Height of the sequencer block that was committed. - #[prost(uint64, tag = "1")] - pub height: u64, - /// Hash of the sequencer block that was committed. - #[prost(bytes = "bytes", tag = "2")] - pub block_hash: ::prost::bytes::Bytes, -} -impl ::prost::Name for SequencerBlockCommit { - const NAME: &'static str = "SequencerBlockCommit"; - const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetBlockCommitmentStreamResponse { - #[prost(message, optional, tag = "1")] - pub commitment: ::core::option::Option, -} -impl ::prost::Name for GetBlockCommitmentStreamResponse { - const NAME: &'static str = "GetBlockCommitmentStreamResponse"; - const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetOptimisticBlockStreamRequest { - /// The rollup id for which the Sequencer block is being streamed. - #[prost(message, optional, tag = "1")] - pub rollup_id: ::core::option::Option, -} -impl ::prost::Name for GetOptimisticBlockStreamRequest { - const NAME: &'static str = "GetOptimisticBlockStreamRequest"; - const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GetOptimisticBlockStreamResponse { - /// The optimistic Sequencer block that is being streamed, filtered for the provided rollup id. - #[prost(message, optional, tag = "1")] - pub block: ::core::option::Option, -} -impl ::prost::Name for GetOptimisticBlockStreamResponse { - const NAME: &'static str = "GetOptimisticBlockStreamResponse"; - const PACKAGE: &'static str = "astria.sequencerblock.v1alpha1"; - fn full_name() -> ::prost::alloc::string::String { - ::prost::alloc::format!("astria.sequencerblock.v1alpha1.{}", Self::NAME) - } -} -/// Generated client implementations. -#[cfg(feature = "client")] -pub mod optimistic_block_service_client { - #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::*; - use tonic::codegen::http::Uri; - /// The Sequencer will serve this to the aucitoneer - #[derive(Debug, Clone)] - pub struct OptimisticBlockServiceClient { - inner: tonic::client::Grpc, - } - impl OptimisticBlockServiceClient { - /// Attempt to create a new client by connecting to a given endpoint. - pub async fn connect(dst: D) -> Result - where - D: TryInto, - D::Error: Into, - { - let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; - Ok(Self::new(conn)) - } - } - impl OptimisticBlockServiceClient - where - T: tonic::client::GrpcService, - T::Error: Into, - T::ResponseBody: Body + Send + 'static, - ::Error: Into + Send, - { - pub fn new(inner: T) -> Self { - let inner = tonic::client::Grpc::new(inner); - Self { inner } - } - pub fn with_origin(inner: T, origin: Uri) -> Self { - let inner = tonic::client::Grpc::with_origin(inner, origin); - Self { inner } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> OptimisticBlockServiceClient> - where - F: tonic::service::Interceptor, - T::ResponseBody: Default, - T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, - >, - >, - , - >>::Error: Into + Send + Sync, - { - OptimisticBlockServiceClient::new( - InterceptedService::new(inner, interceptor), - ) - } - /// Compress requests with the given encoding. - /// - /// This requires the server to support it otherwise it might respond with an - /// error. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.send_compressed(encoding); - self - } - /// Enable decompressing responses. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.accept_compressed(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_decoding_message_size(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_encoding_message_size(limit); - self - } - /// The Sequencer will stream the optimistic Sequencer block (filtered for the provided - /// rollup id) to the Auctioneer. - pub async fn get_optimistic_block_stream( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response< - tonic::codec::Streaming, - >, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/astria.sequencerblock.v1alpha1.OptimisticBlockService/GetOptimisticBlockStream", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "astria.sequencerblock.v1alpha1.OptimisticBlockService", - "GetOptimisticBlockStream", - ), - ); - self.inner.server_streaming(req, path, codec).await - } - /// The Sequencer will stream the block commits to the Auctioneer. - pub async fn get_block_commitment_stream( - &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response< - tonic::codec::Streaming, - >, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::new( - tonic::Code::Unknown, - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/astria.sequencerblock.v1alpha1.OptimisticBlockService/GetBlockCommitmentStream", - ); - let mut req = request.into_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "astria.sequencerblock.v1alpha1.OptimisticBlockService", - "GetBlockCommitmentStream", - ), - ); - self.inner.server_streaming(req, path, codec).await - } - } -} -/// Generated server implementations. -#[cfg(feature = "server")] -pub mod optimistic_block_service_server { - #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] - use tonic::codegen::*; - /// Generated trait containing gRPC methods that should be implemented for use with OptimisticBlockServiceServer. - #[async_trait] - pub trait OptimisticBlockService: Send + Sync + 'static { - /// Server streaming response type for the GetOptimisticBlockStream method. - type GetOptimisticBlockStreamStream: tonic::codegen::tokio_stream::Stream< - Item = std::result::Result< - super::GetOptimisticBlockStreamResponse, - tonic::Status, - >, - > - + Send - + 'static; - /// The Sequencer will stream the optimistic Sequencer block (filtered for the provided - /// rollup id) to the Auctioneer. - async fn get_optimistic_block_stream( - self: std::sync::Arc, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - /// Server streaming response type for the GetBlockCommitmentStream method. - type GetBlockCommitmentStreamStream: tonic::codegen::tokio_stream::Stream< - Item = std::result::Result< - super::GetBlockCommitmentStreamResponse, - tonic::Status, - >, - > - + Send - + 'static; - /// The Sequencer will stream the block commits to the Auctioneer. - async fn get_block_commitment_stream( - self: std::sync::Arc, - request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; - } - /// The Sequencer will serve this to the aucitoneer - #[derive(Debug)] - pub struct OptimisticBlockServiceServer { - inner: _Inner, - accept_compression_encodings: EnabledCompressionEncodings, - send_compression_encodings: EnabledCompressionEncodings, - max_decoding_message_size: Option, - max_encoding_message_size: Option, - } - struct _Inner(Arc); - impl OptimisticBlockServiceServer { - pub fn new(inner: T) -> Self { - Self::from_arc(Arc::new(inner)) - } - pub fn from_arc(inner: Arc) -> Self { - let inner = _Inner(inner); - Self { - inner, - accept_compression_encodings: Default::default(), - send_compression_encodings: Default::default(), - max_decoding_message_size: None, - max_encoding_message_size: None, - } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService - where - F: tonic::service::Interceptor, - { - InterceptedService::new(Self::new(inner), interceptor) - } - /// Enable decompressing requests with the given encoding. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.accept_compression_encodings.enable(encoding); - self - } - /// Compress responses with the given encoding, if the client supports it. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.send_compression_encodings.enable(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.max_decoding_message_size = Some(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.max_encoding_message_size = Some(limit); - self - } - } - impl tonic::codegen::Service> - for OptimisticBlockServiceServer - where - T: OptimisticBlockService, - B: Body + Send + 'static, - B::Error: Into + Send + 'static, - { - type Response = http::Response; - type Error = std::convert::Infallible; - type Future = BoxFuture; - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, req: http::Request) -> Self::Future { - let inner = self.inner.clone(); - match req.uri().path() { - "/astria.sequencerblock.v1alpha1.OptimisticBlockService/GetOptimisticBlockStream" => { - #[allow(non_camel_case_types)] - struct GetOptimisticBlockStreamSvc( - pub Arc, - ); - impl< - T: OptimisticBlockService, - > tonic::server::ServerStreamingService< - super::GetOptimisticBlockStreamRequest, - > for GetOptimisticBlockStreamSvc { - type Response = super::GetOptimisticBlockStreamResponse; - type ResponseStream = T::GetOptimisticBlockStreamStream; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request< - super::GetOptimisticBlockStreamRequest, - >, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_optimistic_block_stream( - inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetOptimisticBlockStreamSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.server_streaming(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - "/astria.sequencerblock.v1alpha1.OptimisticBlockService/GetBlockCommitmentStream" => { - #[allow(non_camel_case_types)] - struct GetBlockCommitmentStreamSvc( - pub Arc, - ); - impl< - T: OptimisticBlockService, - > tonic::server::ServerStreamingService< - super::GetBlockCommitmentStreamRequest, - > for GetBlockCommitmentStreamSvc { - type Response = super::GetBlockCommitmentStreamResponse; - type ResponseStream = T::GetBlockCommitmentStreamStream; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request< - super::GetBlockCommitmentStreamRequest, - >, - ) -> Self::Future { - let inner = Arc::clone(&self.0); - let fut = async move { - ::get_block_commitment_stream( - inner, - request, - ) - .await - }; - Box::pin(fut) - } - } - let accept_compression_encodings = self.accept_compression_encodings; - let send_compression_encodings = self.send_compression_encodings; - let max_decoding_message_size = self.max_decoding_message_size; - let max_encoding_message_size = self.max_encoding_message_size; - let inner = self.inner.clone(); - let fut = async move { - let inner = inner.0; - let method = GetBlockCommitmentStreamSvc(inner); - let codec = tonic::codec::ProstCodec::default(); - let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config( - accept_compression_encodings, - send_compression_encodings, - ) - .apply_max_message_size_config( - max_decoding_message_size, - max_encoding_message_size, - ); - let res = grpc.server_streaming(method, req).await; - Ok(res) - }; - Box::pin(fut) - } - _ => { - Box::pin(async move { - Ok( - http::Response::builder() - .status(200) - .header("grpc-status", "12") - .header("content-type", "application/grpc") - .body(empty_body()) - .unwrap(), - ) - }) - } - } - } - } - impl Clone for OptimisticBlockServiceServer { - fn clone(&self) -> Self { - let inner = self.inner.clone(); - Self { - inner, - accept_compression_encodings: self.accept_compression_encodings, - send_compression_encodings: self.send_compression_encodings, - max_decoding_message_size: self.max_decoding_message_size, - max_encoding_message_size: self.max_encoding_message_size, - } - } - } - impl Clone for _Inner { - fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) - } - } - impl std::fmt::Debug for _Inner { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.0) - } - } - impl tonic::server::NamedService - for OptimisticBlockServiceServer { - const NAME: &'static str = "astria.sequencerblock.v1alpha1.OptimisticBlockService"; - } -} -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetSequencerBlockRequest { /// The height of the block to retrieve. #[prost(uint64, tag = "1")] diff --git a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs index 72c4d3ea26..8b1a35e407 100644 --- a/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs +++ b/crates/astria-core/src/generated/astria.sequencerblock.v1alpha1.serde.rs @@ -383,168 +383,6 @@ impl<'de> serde::Deserialize<'de> for FilteredSequencerBlock { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.FilteredSequencerBlock", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetBlockCommitmentStreamRequest { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let len = 0; - let struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamRequest", len)?; - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for GetBlockCommitmentStreamRequest { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - Err(serde::de::Error::unknown_field(value, FIELDS)) - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetBlockCommitmentStreamRequest; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamRequest") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - while map_.next_key::()?.is_some() { - let _ = map_.next_value::()?; - } - Ok(GetBlockCommitmentStreamRequest { - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamRequest", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for GetBlockCommitmentStreamResponse { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.commitment.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamResponse", len)?; - if let Some(v) = self.commitment.as_ref() { - struct_ser.serialize_field("commitment", v)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for GetBlockCommitmentStreamResponse { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "commitment", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Commitment, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "commitment" => Ok(GeneratedField::Commitment), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetBlockCommitmentStreamResponse; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamResponse") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut commitment__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Commitment => { - if commitment__.is_some() { - return Err(serde::de::Error::duplicate_field("commitment")); - } - commitment__ = map_.next_value()?; - } - } - } - Ok(GetBlockCommitmentStreamResponse { - commitment: commitment__, - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetBlockCommitmentStreamResponse", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for GetFilteredSequencerBlockRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -657,189 +495,6 @@ impl<'de> serde::Deserialize<'de> for GetFilteredSequencerBlockRequest { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetFilteredSequencerBlockRequest", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for GetOptimisticBlockStreamRequest { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.rollup_id.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamRequest", len)?; - if let Some(v) = self.rollup_id.as_ref() { - struct_ser.serialize_field("rollupId", v)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for GetOptimisticBlockStreamRequest { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "rollup_id", - "rollupId", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - RollupId, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "rollupId" | "rollup_id" => Ok(GeneratedField::RollupId), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetOptimisticBlockStreamRequest; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamRequest") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut rollup_id__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::RollupId => { - if rollup_id__.is_some() { - return Err(serde::de::Error::duplicate_field("rollupId")); - } - rollup_id__ = map_.next_value()?; - } - } - } - Ok(GetOptimisticBlockStreamRequest { - rollup_id: rollup_id__, - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamRequest", FIELDS, GeneratedVisitor) - } -} -impl serde::Serialize for GetOptimisticBlockStreamResponse { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.block.is_some() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamResponse", len)?; - if let Some(v) = self.block.as_ref() { - struct_ser.serialize_field("block", v)?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for GetOptimisticBlockStreamResponse { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "block", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Block, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "block" => Ok(GeneratedField::Block), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GetOptimisticBlockStreamResponse; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamResponse") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut block__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Block => { - if block__.is_some() { - return Err(serde::de::Error::duplicate_field("block")); - } - block__ = map_.next_value()?; - } - } - } - Ok(GetOptimisticBlockStreamResponse { - block: block__, - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.GetOptimisticBlockStreamResponse", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for GetPendingNonceRequest { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result @@ -1523,121 +1178,6 @@ impl<'de> serde::Deserialize<'de> for SequencerBlock { deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SequencerBlock", FIELDS, GeneratedVisitor) } } -impl serde::Serialize for SequencerBlockCommit { - #[allow(deprecated)] - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut len = 0; - if self.height != 0 { - len += 1; - } - if !self.block_hash.is_empty() { - len += 1; - } - let mut struct_ser = serializer.serialize_struct("astria.sequencerblock.v1alpha1.SequencerBlockCommit", len)?; - if self.height != 0 { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("height", ToString::to_string(&self.height).as_str())?; - } - if !self.block_hash.is_empty() { - #[allow(clippy::needless_borrow)] - struct_ser.serialize_field("blockHash", pbjson::private::base64::encode(&self.block_hash).as_str())?; - } - struct_ser.end() - } -} -impl<'de> serde::Deserialize<'de> for SequencerBlockCommit { - #[allow(deprecated)] - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - const FIELDS: &[&str] = &[ - "height", - "block_hash", - "blockHash", - ]; - - #[allow(clippy::enum_variant_names)] - enum GeneratedField { - Height, - BlockHash, - } - impl<'de> serde::Deserialize<'de> for GeneratedField { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - struct GeneratedVisitor; - - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = GeneratedField; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(formatter, "expected one of: {:?}", &FIELDS) - } - - #[allow(unused_variables)] - fn visit_str(self, value: &str) -> std::result::Result - where - E: serde::de::Error, - { - match value { - "height" => Ok(GeneratedField::Height), - "blockHash" | "block_hash" => Ok(GeneratedField::BlockHash), - _ => Err(serde::de::Error::unknown_field(value, FIELDS)), - } - } - } - deserializer.deserialize_identifier(GeneratedVisitor) - } - } - struct GeneratedVisitor; - impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { - type Value = SequencerBlockCommit; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("struct astria.sequencerblock.v1alpha1.SequencerBlockCommit") - } - - fn visit_map(self, mut map_: V) -> std::result::Result - where - V: serde::de::MapAccess<'de>, - { - let mut height__ = None; - let mut block_hash__ = None; - while let Some(k) = map_.next_key()? { - match k { - GeneratedField::Height => { - if height__.is_some() { - return Err(serde::de::Error::duplicate_field("height")); - } - height__ = - Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) - ; - } - GeneratedField::BlockHash => { - if block_hash__.is_some() { - return Err(serde::de::Error::duplicate_field("blockHash")); - } - block_hash__ = - Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) - ; - } - } - } - Ok(SequencerBlockCommit { - height: height__.unwrap_or_default(), - block_hash: block_hash__.unwrap_or_default(), - }) - } - } - deserializer.deserialize_struct("astria.sequencerblock.v1alpha1.SequencerBlockCommit", FIELDS, GeneratedVisitor) - } -} impl serde::Serialize for SequencerBlockHeader { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/astria-core/src/generated/mod.rs b/crates/astria-core/src/generated/mod.rs index a0a39c4d2a..41e186e2d7 100644 --- a/crates/astria-core/src/generated/mod.rs +++ b/crates/astria-core/src/generated/mod.rs @@ -38,139 +38,155 @@ pub mod astria_vendored { } #[path = ""] -pub mod bundle { - pub mod v1alpha1 { - include!("astria.bundle.v1alpha1.rs"); - - #[cfg(feature = "serde")] - mod _serde_impl { - use super::*; - include!("astria.bundle.v1alpha1.serde.rs"); - } - } -} - -#[path = ""] -pub mod execution { - pub mod v1 { - include!("astria.execution.v1.rs"); - - #[cfg(feature = "serde")] - mod _serde_impl { - use super::*; - include!("astria.execution.v1.serde.rs"); - } - } -} - -#[path = ""] -pub mod primitive { - pub mod v1 { - include!("astria.primitive.v1.rs"); +pub mod astria { + #[path = ""] + pub mod bundle { + pub mod v1alpha1 { + include!("astria.bundle.v1alpha1.rs"); - #[cfg(feature = "serde")] - mod _serde_impl { - use super::*; - include!("astria.primitive.v1.serde.rs"); + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.bundle.v1alpha1.serde.rs"); + } } } -} -#[path = ""] -pub mod protocol { #[path = ""] - pub mod accounts { - #[path = "astria.protocol.accounts.v1.rs"] - pub mod v1; - } - #[path = ""] - pub mod asset { - #[path = "astria.protocol.asset.v1.rs"] - pub mod v1; - } - #[path = ""] - pub mod bridge { - #[path = "astria.protocol.bridge.v1.rs"] - pub mod v1; - } - #[path = ""] - pub mod fees { - #[path = "astria.protocol.fees.v1.rs"] + pub mod execution { pub mod v1 { - include!("astria.protocol.fees.v1.rs"); + include!("astria.execution.v1.rs"); #[cfg(feature = "serde")] - mod _serde_impls { + mod _serde_impl { use super::*; - include!("astria.protocol.fees.v1.serde.rs"); + include!("astria.execution.v1.serde.rs"); } } } + #[path = ""] - pub mod genesis { + pub mod primitive { pub mod v1 { - include!("astria.protocol.genesis.v1.rs"); + include!("astria.primitive.v1.rs"); #[cfg(feature = "serde")] - mod _serde_impls { + mod _serde_impl { use super::*; - include!("astria.protocol.genesis.v1.serde.rs"); + include!("astria.primitive.v1.serde.rs"); } } } + #[path = ""] - pub mod memos { - pub mod v1 { - include!("astria.protocol.memos.v1.rs"); + pub mod protocol { + #[path = ""] + pub mod accounts { + #[path = "astria.protocol.accounts.v1.rs"] + pub mod v1; + } + #[path = ""] + pub mod asset { + #[path = "astria.protocol.asset.v1.rs"] + pub mod v1; + } + #[path = ""] + pub mod bridge { + #[path = "astria.protocol.bridge.v1.rs"] + pub mod v1; + } + #[path = ""] + pub mod fees { + #[path = "astria.protocol.fees.v1.rs"] + pub mod v1 { + include!("astria.protocol.fees.v1.rs"); - #[cfg(feature = "serde")] - mod _serde_impls { - use super::*; - include!("astria.protocol.memos.v1.serde.rs"); + #[cfg(feature = "serde")] + mod _serde_impls { + use super::*; + include!("astria.protocol.fees.v1.serde.rs"); + } + } + } + #[path = ""] + pub mod genesis { + pub mod v1 { + include!("astria.protocol.genesis.v1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impls { + use super::*; + include!("astria.protocol.genesis.v1.serde.rs"); + } + } + } + #[path = ""] + pub mod memos { + pub mod v1 { + include!("astria.protocol.memos.v1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impls { + use super::*; + include!("astria.protocol.memos.v1.serde.rs"); + } + } + } + + #[path = ""] + pub mod transaction { + pub mod v1 { + include!("astria.protocol.transaction.v1.rs"); + + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.protocol.transaction.v1.serde.rs"); + } } } } + #[path = ""] - pub mod transaction { - pub mod v1 { - include!("astria.protocol.transaction.v1.rs"); + pub mod sequencerblock { + pub mod v1alpha1 { + include!("astria.sequencerblock.v1alpha1.rs"); #[cfg(feature = "serde")] mod _serde_impl { use super::*; - include!("astria.protocol.transaction.v1.serde.rs"); + include!("astria.sequencerblock.v1alpha1.serde.rs"); } } - } -} -#[path = ""] -pub mod sequencerblock { - pub mod v1alpha1 { - include!("astria.sequencerblock.v1alpha1.rs"); + pub mod v1 { + include!("astria.sequencerblock.v1.rs"); - #[cfg(feature = "serde")] - mod _serde_impl { - use super::*; - include!("astria.sequencerblock.v1alpha1.serde.rs"); + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.sequencerblock.v1.serde.rs"); + } } - } - pub mod v1 { - include!("astria.sequencerblock.v1.rs"); + pub mod optimistic { + pub mod v1alpha1 { + include!("astria.sequencerblock.optimistic.v1alpha1.rs"); - #[cfg(feature = "serde")] - mod _serde_impl { - use super::*; - include!("astria.sequencerblock.v1.serde.rs"); + #[cfg(feature = "serde")] + mod _serde_impl { + use super::*; + include!("astria.sequencerblock.optimistic.v1alpha1.serde.rs"); + } + } } } -} -#[path = ""] -pub mod composer { - #[path = "astria.composer.v1.rs"] - pub mod v1; + #[path = ""] + pub mod composer { + #[path = "astria.composer.v1.rs"] + pub mod v1; + } } #[path = ""] diff --git a/crates/astria-core/src/lib.rs b/crates/astria-core/src/lib.rs index 5c97446d17..fbf02402dc 100644 --- a/crates/astria-core/src/lib.rs +++ b/crates/astria-core/src/lib.rs @@ -1,3 +1,4 @@ +pub use astria_core_crypto as crypto; use prost::Name; #[cfg(not(target_pointer_width = "64"))] @@ -13,7 +14,6 @@ compile_error!( )] pub mod generated; -pub mod crypto; pub mod execution; pub mod primitive; pub mod protocol; diff --git a/crates/astria-core/src/primitive/v1/mod.rs b/crates/astria-core/src/primitive/v1/mod.rs index 56cb6922b5..7477eac667 100644 --- a/crates/astria-core/src/primitive/v1/mod.rs +++ b/crates/astria-core/src/primitive/v1/mod.rs @@ -1,11 +1,15 @@ pub mod asset; pub mod u128; -use std::{ - marker::PhantomData, - str::FromStr, +pub use astria_core_address::{ + Address, + Bech32, + Bech32m, + Builder as AddressBuilder, + Error as AddressError, + Format, + ADDRESS_LENGTH as ADDRESS_LEN, }; - use base64::{ display::Base64Display, prelude::BASE64_URL_SAFE, @@ -17,16 +21,32 @@ use sha2::{ }; use crate::{ - generated::primitive::v1 as raw, + generated::astria::primitive::v1 as raw, Protobuf, }; -pub const ADDRESS_LEN: usize = 20; - pub const ROLLUP_ID_LEN: usize = 32; pub const TRANSACTION_ID_LEN: usize = 32; +impl Protobuf for Address { + type Error = AddressError; + type Raw = raw::Address; + + fn try_from_raw_ref(raw: &Self::Raw) -> Result { + let raw::Address { + bech32m, + } = raw; + bech32m.parse() + } + + fn to_raw(&self) -> Self::Raw { + raw::Address { + bech32m: self.to_string(), + } + } +} + impl Protobuf for merkle::Proof { type Error = merkle::audit::InvalidProof; type Raw = raw::Proof; @@ -254,431 +274,6 @@ pub struct IncorrectRollupIdLength { received: usize, } -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub struct AddressError(AddressErrorKind); - -impl AddressError { - fn decode(source: bech32::primitives::decode::CheckedHrpstringError) -> Self { - Self(AddressErrorKind::Decode { - source, - }) - } - - fn invalid_prefix(source: bech32::primitives::hrp::Error) -> Self { - Self(AddressErrorKind::InvalidPrefix { - source, - }) - } - - fn incorrect_address_length(received: usize) -> Self { - Self(AddressErrorKind::IncorrectAddressLength { - received, - }) - } -} - -#[derive(Debug, thiserror::Error, PartialEq)] -enum AddressErrorKind { - #[error("failed decoding provided string")] - Decode { - source: bech32::primitives::decode::CheckedHrpstringError, - }, - #[error("expected an address of 20 bytes, got `{received}`")] - IncorrectAddressLength { received: usize }, - #[error("the provided prefix was not a valid bech32 human readable prefix")] - InvalidPrefix { - source: bech32::primitives::hrp::Error, - }, -} - -pub struct NoBytes; -pub struct NoPrefix; -pub struct WithBytes<'a, I>(WithBytesInner<'a, I>); -enum WithBytesInner<'a, I> { - Array([u8; ADDRESS_LEN]), - Iter(I), - Slice(std::borrow::Cow<'a, [u8]>), -} -pub struct WithPrefix<'a>(std::borrow::Cow<'a, str>); - -pub struct NoBytesIter; - -impl Iterator for NoBytesIter { - type Item = u8; - - fn next(&mut self) -> Option { - None - } -} - -pub struct AddressBuilder { - bytes: TBytes, - prefix: TPrefix, - format: PhantomData, -} - -impl AddressBuilder { - const fn new() -> Self { - Self { - bytes: NoBytes, - prefix: NoPrefix, - format: PhantomData, - } - } -} - -impl AddressBuilder { - #[must_use = "the builder must be built to construct an address to be useful"] - pub fn array( - self, - array: [u8; ADDRESS_LEN], - ) -> AddressBuilder, TPrefix> { - AddressBuilder { - bytes: WithBytes(WithBytesInner::Array(array)), - prefix: self.prefix, - format: self.format, - } - } - - #[must_use = "the builder must be built to construct an address to be useful"] - pub fn slice<'a, T: Into>>( - self, - bytes: T, - ) -> AddressBuilder, TPrefix> { - AddressBuilder { - bytes: WithBytes(WithBytesInner::Slice(bytes.into())), - prefix: self.prefix, - format: self.format, - } - } - - #[must_use = "the builder must be built to construct an address to be useful"] - pub fn with_iter>( - self, - iter: T, - ) -> AddressBuilder, TPrefix> { - AddressBuilder { - bytes: WithBytes(WithBytesInner::Iter(iter)), - prefix: self.prefix, - format: self.format, - } - } - - /// Use the given verification key for address generation. - /// - /// The verification key is hashed with SHA256 and the first 20 bytes are used as the address - /// bytes. - #[expect(clippy::missing_panics_doc, reason = "the conversion is infallible")] - #[must_use = "the builder must be built to construct an address to be useful"] - pub fn verification_key( - self, - key: &crate::crypto::VerificationKey, - ) -> AddressBuilder, TPrefix> { - let hash = Sha256::digest(key.as_bytes()); - let array: [u8; ADDRESS_LEN] = hash[0..ADDRESS_LEN] - .try_into() - .expect("hash is 32 bytes long, so must always be able to convert to 20 bytes"); - self.array(array) - } - - #[must_use = "the builder must be built to construct an address to be useful"] - pub fn prefix<'a, T: Into>>( - self, - prefix: T, - ) -> AddressBuilder> { - AddressBuilder { - bytes: self.bytes, - prefix: WithPrefix(prefix.into()), - format: self.format, - } - } -} - -impl<'a, 'b, TFormat, TBytesIter> AddressBuilder, WithPrefix<'b>> -where - TBytesIter: IntoIterator, -{ - /// Attempts to build an address from the configured prefix and bytes. - /// - /// # Errors - /// Returns an error if one of the following conditions are violated: - /// + if the prefix shorter than 1 or longer than 83 characters, or contains characters outside - /// 33-126 of ASCII characters. - /// + if the provided bytes are not exactly 20 bytes. - pub fn try_build(self) -> Result, AddressError> { - let Self { - bytes: WithBytes(bytes), - prefix: WithPrefix(prefix), - format, - } = self; - let bytes = match bytes { - WithBytesInner::Array(bytes) => bytes, - WithBytesInner::Iter(bytes) => try_collect_to_array(bytes)?, - WithBytesInner::Slice(bytes) => <[u8; ADDRESS_LEN]>::try_from(bytes.as_ref()) - .map_err(|_| AddressError::incorrect_address_length(bytes.len()))?, - }; - let prefix = bech32::Hrp::parse(&prefix).map_err(AddressError::invalid_prefix)?; - Ok(Address { - bytes, - prefix, - format, - }) - } -} - -fn try_collect_to_array>( - iter: I, -) -> Result<[u8; ADDRESS_LEN], AddressError> { - let mut arr = [0; ADDRESS_LEN]; - let mut iter = iter.into_iter(); - let mut i = 0; - loop { - if i >= ADDRESS_LEN { - break; - } - let Some(byte) = iter.next() else { - break; - }; - arr[i] = byte; - i = i.saturating_add(1); - } - let items_in_iterator = i.saturating_add(iter.count()); - if items_in_iterator != ADDRESS_LEN { - return Err(AddressError::incorrect_address_length(items_in_iterator)); - } - Ok(arr) -} - -#[derive(Clone, Copy, Debug)] -pub enum Bech32m {} -#[derive(Clone, Copy, Debug)] -pub enum Bech32 {} -#[derive(Clone, Copy, Debug)] -pub enum NoFormat {} - -pub trait Format: private::Sealed { - type Checksum: bech32::Checksum; -} - -impl Format for Bech32m { - type Checksum = bech32::Bech32m; -} - -impl Format for Bech32 { - type Checksum = bech32::Bech32; -} - -impl Format for NoFormat { - type Checksum = bech32::NoChecksum; -} - -mod private { - pub trait Sealed {} - impl Sealed for super::Bech32m {} - impl Sealed for super::Bech32 {} - impl Sealed for super::NoFormat {} -} - -#[derive(Debug, Hash)] -pub struct Address { - bytes: [u8; ADDRESS_LEN], - prefix: bech32::Hrp, - format: PhantomData, -} - -// The serde impls need to be manually implemented for Address because they -// only work for Address which cannot be expressed using serde -// attributes. -#[cfg(feature = "serde")] -mod _serde_impls { - use serde::de::Error as _; - impl serde::Serialize for super::Address { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.to_raw().serialize(serializer) - } - } - impl<'de> serde::Deserialize<'de> for super::Address { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - super::raw::Address::deserialize(deserializer) - .and_then(|raw| raw.try_into().map_err(D::Error::custom)) - } - } -} - -impl Clone for Address { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Address {} - -impl PartialEq for Address { - fn eq(&self, other: &Self) -> bool { - self.bytes.eq(&other.bytes) && self.prefix.eq(&other.prefix) - } -} - -impl Eq for Address {} - -impl Address { - #[must_use = "the builder must be used to construct an address to be useful"] - pub fn builder() -> AddressBuilder { - AddressBuilder::::new() - } - - #[must_use] - pub fn bytes(self) -> [u8; ADDRESS_LEN] { - self.bytes - } - - #[must_use] - pub fn as_bytes(&self) -> &[u8; ADDRESS_LEN] { - &self.bytes - } - - #[must_use] - pub fn prefix(&self) -> &str { - self.prefix.as_str() - } - - /// Converts to a new address with the given `prefix`. - /// - /// # Errors - /// Returns an error if an address with `prefix` cannot be constructed. - /// The error conditions for this are the same as for [`AddressBuilder::try_build`]. - pub fn to_prefix(&self, prefix: &str) -> Result { - Self::builder() - .array(*self.as_bytes()) - .prefix(prefix) - .try_build() - } - - /// Converts to a new address with the type argument `OtherFormat`. - /// - /// `OtherFormat` is usually [`Bech32`] or [`Bech32m`]. - #[must_use] - pub fn to_format(&self) -> Address { - Address { - bytes: self.bytes, - prefix: self.prefix, - format: PhantomData, - } - } -} - -impl Address { - /// Convert [`Address`] to a [`raw::Address`]. - #[expect( - clippy::missing_panics_doc, - reason = "panics are checked to not happen" - )] - #[must_use] - pub fn to_raw(&self) -> raw::Address { - let bech32m = - bech32::encode_lower::<::Checksum>(self.prefix, self.as_bytes()) - .expect( - "should not fail because len(prefix) + len(bytes) <= 63 < BECH32M::CODELENGTH", - ); - raw::Address { - bech32m, - } - } - - #[must_use] - pub fn into_raw(self) -> raw::Address { - self.to_raw() - } - - /// Convert from protobuf to rust type an address. - /// - /// # Errors - /// - /// Returns an error if the account buffer was not 20 bytes long. - pub fn try_from_raw(raw: &raw::Address) -> Result { - let raw::Address { - bech32m, - } = raw; - bech32m.parse() - } - - /// This should only be used where the inputs have been provided by a trusted entity, e.g. read - /// from our own state store. - /// - /// Note that this function is not considered part of the public API and is subject to breaking - /// change at any time. - #[cfg(feature = "unchecked-constructors")] - #[doc(hidden)] - #[must_use] - pub fn unchecked_from_parts(bytes: [u8; ADDRESS_LEN], prefix: &str) -> Self { - Self { - bytes, - prefix: bech32::Hrp::parse_unchecked(prefix), - format: PhantomData, - } - } -} - -impl From> for raw::Address { - fn from(value: Address) -> Self { - value.into_raw() - } -} - -impl FromStr for Address { - type Err = AddressError; - - fn from_str(s: &str) -> Result { - let checked = bech32::primitives::decode::CheckedHrpstring::new::(s) - .map_err(Self::Err::decode)?; - let hrp = checked.hrp(); - Self::builder() - .with_iter(checked.byte_iter()) - .prefix(hrp.as_str()) - .try_build() - } -} - -impl TryFrom for Address { - type Error = AddressError; - - fn try_from(value: raw::Address) -> Result { - Self::try_from_raw(&value) - } -} - -impl std::fmt::Display for Address { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use bech32::EncodeError; - match bech32::encode_lower_to_fmt::(f, self.prefix, self.as_bytes()) { - Ok(()) => Ok(()), - Err(EncodeError::Fmt(err)) => Err(err), - Err(err) => panic!( - "only formatting errors are valid when encoding astria addresses; all other error \ - variants (only TooLong as of bech32-0.11.0) are guaranteed to not happen because \ - `Address` is length checked:\n{err:?}", - ), - } - } -} -/// Constructs a dummy address from a given `prefix`, otherwise fail. -pub(crate) fn try_construct_dummy_address_from_prefix( - prefix: &str, -) -> Result<(), AddressError> { - Address::::builder() - .array([0u8; ADDRESS_LEN]) - .prefix(prefix) - .try_build() - .map(|_| ()) -} - /// Derive a [`merkle::Tree`] from an iterable. /// /// It is the responsibility of the caller to ensure that the iterable is @@ -799,42 +394,12 @@ enum TransactionIdErrorKind { mod tests { use super::{ Address, - AddressError, - AddressErrorKind, - Bech32m, ADDRESS_LEN, }; - use crate::primitive::v1::Bech32; + use crate::Protobuf as _; const ASTRIA_ADDRESS_PREFIX: &str = "astria"; const ASTRIA_COMPAT_ADDRESS_PREFIX: &str = "astriacompat"; - #[track_caller] - fn assert_wrong_address_bytes(bad_account: &[u8]) { - let error = Address::::builder() - .slice(bad_account) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .expect_err( - "converting from an incorrectly sized byte slice succeeded where it should have \ - failed", - ); - let AddressError(AddressErrorKind::IncorrectAddressLength { - received, - }) = error - else { - panic!("expected AddressErrorKind::IncorrectAddressLength, got {error:?}"); - }; - assert_eq!(bad_account.len(), received); - } - - #[test] - fn account_of_incorrect_length_gives_error() { - assert_wrong_address_bytes(&[42; 0]); - assert_wrong_address_bytes(&[42; 19]); - assert_wrong_address_bytes(&[42; 21]); - assert_wrong_address_bytes(&[42; 100]); - } - #[cfg(feature = "serde")] #[test] fn snapshots() { @@ -856,74 +421,6 @@ mod tests { insta::assert_snapshot!(&compat_address); } - #[test] - fn parse_bech32m_address() { - let expected = Address::builder() - .array([42; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(); - let actual = expected.to_string().parse::
().unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn parse_bech32_address() { - let expected = Address::::builder() - .array([42; 20]) - .prefix(ASTRIA_COMPAT_ADDRESS_PREFIX) - .try_build() - .unwrap(); - let actual = expected.to_string().parse::>().unwrap(); - assert_eq!(expected, actual); - } - - #[test] - fn parsing_bech32_address_as_bech32m_fails() { - let expected = Address::::builder() - .array([42; 20]) - .prefix(ASTRIA_COMPAT_ADDRESS_PREFIX) - .try_build() - .unwrap(); - let err = expected - .to_string() - .parse::>() - .expect_err("this must not work"); - match err { - AddressError(AddressErrorKind::Decode { - .. - }) => {} - other => { - panic!( - "expected AddressError(AddressErrorKind::Decode {{ .. }}), but got {other:?}" - ) - } - } - } - - #[test] - fn parsing_bech32m_address_as_bech32_fails() { - let expected = Address::::builder() - .array([42; 20]) - .prefix(ASTRIA_ADDRESS_PREFIX) - .try_build() - .unwrap(); - let err = expected - .to_string() - .parse::>() - .expect_err("this must not work"); - match err { - AddressError(AddressErrorKind::Decode { - .. - }) => {} - other => { - panic!( - "expected AddressError(AddressErrorKind::Decode {{ .. }}), but got {other:?}" - ) - } - } - } - #[test] fn can_construct_protobuf_from_address_with_maximally_sized_prefix() { // 83 is the maximal length of a hrp @@ -946,7 +443,7 @@ mod tests { .try_build() .unwrap(); let unchecked = input.into_raw(); - let roundtripped = Address::try_from_raw(&unchecked).unwrap(); + let roundtripped = Address::try_from_raw(unchecked).unwrap(); assert_eq!(input, roundtripped); assert_eq!(input.as_bytes(), roundtripped.as_bytes()); assert_eq!("astria", input.prefix()); diff --git a/crates/astria-core/src/primitive/v1/u128.rs b/crates/astria-core/src/primitive/v1/u128.rs index ee77646a27..581f5b5ecc 100644 --- a/crates/astria-core/src/primitive/v1/u128.rs +++ b/crates/astria-core/src/primitive/v1/u128.rs @@ -1,6 +1,6 @@ //! Transformations of compiled protobuf types to other types. -use crate::generated::primitive::v1::Uint128; +use crate::generated::astria::primitive::v1::Uint128; impl From for Uint128 { fn from(primitive: u128) -> Self { let [ @@ -48,7 +48,8 @@ impl<'a> From<&'a u128> for Uint128 { #[cfg(test)] mod tests { - use crate::generated::primitive::v1::Uint128; + use super::Uint128; + #[track_caller] fn u128_roundtrip_check(expected: u128) { let pb: Uint128 = expected.into(); diff --git a/crates/astria-core/src/protocol/account/mod.rs b/crates/astria-core/src/protocol/account/mod.rs index 423eaae929..a3a6d96c3f 100644 --- a/crates/astria-core/src/protocol/account/mod.rs +++ b/crates/astria-core/src/protocol/account/mod.rs @@ -1,3 +1 @@ pub mod v1; - -use crate::generated::protocol::accounts::v1 as raw; diff --git a/crates/astria-core/src/protocol/account/v1/mod.rs b/crates/astria-core/src/protocol/account/v1/mod.rs index 697696ad66..1b16953fdc 100644 --- a/crates/astria-core/src/protocol/account/v1/mod.rs +++ b/crates/astria-core/src/protocol/account/v1/mod.rs @@ -1,7 +1,9 @@ -use super::raw; -use crate::primitive::v1::asset::{ - Denom, - ParseDenomError, +use crate::{ + generated::astria::protocol::accounts::v1 as raw, + primitive::v1::asset::{ + Denom, + ParseDenomError, + }, }; #[derive(Debug, thiserror::Error)] diff --git a/crates/astria-core/src/protocol/asset/mod.rs b/crates/astria-core/src/protocol/asset/mod.rs index bd31b9c85b..a3a6d96c3f 100644 --- a/crates/astria-core/src/protocol/asset/mod.rs +++ b/crates/astria-core/src/protocol/asset/mod.rs @@ -1,3 +1 @@ pub mod v1; - -use crate::generated::protocol::asset::v1 as raw; diff --git a/crates/astria-core/src/protocol/asset/v1/mod.rs b/crates/astria-core/src/protocol/asset/v1/mod.rs index c9330e32bf..9d8ec55c28 100644 --- a/crates/astria-core/src/protocol/asset/v1/mod.rs +++ b/crates/astria-core/src/protocol/asset/v1/mod.rs @@ -1,8 +1,10 @@ -use super::raw; -use crate::primitive::v1::asset::{ - self, - Denom, - ParseDenomError, +use crate::{ + generated::astria::protocol::asset::v1 as raw, + primitive::v1::asset::{ + self, + Denom, + ParseDenomError, + }, }; #[derive(Debug, thiserror::Error)] diff --git a/crates/astria-core/src/protocol/bridge/mod.rs b/crates/astria-core/src/protocol/bridge/mod.rs index f8b278c8f1..cab4a9fd57 100644 --- a/crates/astria-core/src/protocol/bridge/mod.rs +++ b/crates/astria-core/src/protocol/bridge/mod.rs @@ -1,3 +1,3 @@ pub mod v1; -use crate::generated::protocol::bridge::v1 as raw; +use crate::generated::astria::protocol::bridge::v1 as raw; diff --git a/crates/astria-core/src/protocol/bridge/v1/mod.rs b/crates/astria-core/src/protocol/bridge/v1/mod.rs index df30ed9068..49b3e07c90 100644 --- a/crates/astria-core/src/protocol/bridge/v1/mod.rs +++ b/crates/astria-core/src/protocol/bridge/v1/mod.rs @@ -1,13 +1,18 @@ use bytes::Bytes; use super::raw; -use crate::primitive::v1::{ - asset, - asset::denom::ParseDenomError, - Address, - AddressError, - IncorrectRollupIdLength, - RollupId, +use crate::{ + primitive::v1::{ + asset::{ + self, + denom::ParseDenomError, + }, + Address, + AddressError, + IncorrectRollupIdLength, + RollupId, + }, + Protobuf as _, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -159,9 +164,9 @@ impl BridgeAccountInfoResponse { rollup_id: RollupId::try_from_raw(rollup_id) .map_err(BridgeAccountInfoResponseError::invalid_rollup_id)?, asset, - sudo_address: Address::try_from_raw(&sudo_address) + sudo_address: Address::try_from_raw(sudo_address) .map_err(BridgeAccountInfoResponseError::invalid_sudo_address)?, - withdrawer_address: Address::try_from_raw(&withdrawer_address) + withdrawer_address: Address::try_from_raw(withdrawer_address) .map_err(BridgeAccountInfoResponseError::invalid_withdrawer_address)?, }), }) diff --git a/crates/astria-core/src/protocol/fees/v1.rs b/crates/astria-core/src/protocol/fees/v1.rs index 400a63651d..f77441a90c 100644 --- a/crates/astria-core/src/protocol/fees/v1.rs +++ b/crates/astria-core/src/protocol/fees/v1.rs @@ -1,7 +1,7 @@ use prost::Name as _; use crate::{ - generated::protocol::fees::v1 as raw, + generated::astria::protocol::fees::v1 as raw, primitive::v1::asset, Protobuf, }; diff --git a/crates/astria-core/src/protocol/genesis/v1.rs b/crates/astria-core/src/protocol/genesis/v1.rs index 69bb2269bd..168998a521 100644 --- a/crates/astria-core/src/protocol/genesis/v1.rs +++ b/crates/astria-core/src/protocol/genesis/v1.rs @@ -3,14 +3,13 @@ use std::convert::Infallible; pub use penumbra_ibc::params::IBCParameters; use crate::{ - generated::protocol::genesis::v1 as raw, + generated::astria::protocol::genesis::v1 as raw, primitive::v1::{ asset::{ self, denom::ParseTracePrefixedError, ParseDenomError, }, - try_construct_dummy_address_from_prefix, Address, AddressError, Bech32, @@ -183,16 +182,18 @@ impl Protobuf for GenesisAppState { .as_ref() .ok_or_else(|| Self::Error::field_not_set("authority_sudo_address")) .and_then(|addr| { - Address::try_from_raw(addr).map_err(Self::Error::authority_sudo_address) + Address::try_from_raw_ref(addr).map_err(Self::Error::authority_sudo_address) })?; let ibc_sudo_address = ibc_sudo_address .as_ref() .ok_or_else(|| Self::Error::field_not_set("ibc_sudo_address")) - .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::ibc_sudo_address))?; + .and_then(|addr| { + Address::try_from_raw_ref(addr).map_err(Self::Error::ibc_sudo_address) + })?; let ibc_relayer_addresses = ibc_relayer_addresses .iter() - .map(Address::try_from_raw) + .map(Address::try_from_raw_ref) .collect::>() .map_err(Self::Error::ibc_relayer_addresses)?; @@ -405,7 +406,7 @@ impl Protobuf for Account { let address = address .as_ref() .ok_or_else(|| AccountError::field_not_set("address")) - .and_then(|addr| Address::try_from_raw(addr).map_err(Self::Error::address))?; + .and_then(|addr| Address::try_from_raw_ref(addr).map_err(Self::Error::address))?; let balance = balance .ok_or_else(|| AccountError::field_not_set("balance")) .map(Into::into)?; @@ -481,12 +482,22 @@ impl Protobuf for AddressPrefixes { type Raw = raw::AddressPrefixes; fn try_from_raw_ref(raw: &Self::Raw) -> Result { + fn dummy_addr(prefix: &str) -> Result<(), AddressError> { + Address::::builder() + .array([0u8; crate::primitive::v1::ADDRESS_LEN]) + .prefix(prefix) + .try_build() + .map(|_| ()) + } + let Self::Raw { base, ibc_compat, } = raw; - try_construct_dummy_address_from_prefix::(base).map_err(Self::Error::base)?; - try_construct_dummy_address_from_prefix::(ibc_compat).map_err(Self::Error::base)?; + + dummy_addr::(base).map_err(Self::Error::base)?; + dummy_addr::(ibc_compat).map_err(Self::Error::base)?; + Ok(Self { base: base.to_string(), ibc_compat: ibc_compat.to_string(), diff --git a/crates/astria-core/src/protocol/memos/v1.rs b/crates/astria-core/src/protocol/memos/v1.rs index 796eea7f7b..9e9aa76cf2 100644 --- a/crates/astria-core/src/protocol/memos/v1.rs +++ b/crates/astria-core/src/protocol/memos/v1.rs @@ -1,4 +1,4 @@ -pub use crate::generated::protocol::memos::v1::{ +pub use crate::generated::astria::protocol::memos::v1::{ Ics20TransferDeposit, Ics20WithdrawalFromRollup, }; diff --git a/crates/astria-core/src/protocol/transaction/v1/action/mod.rs b/crates/astria-core/src/protocol/transaction/v1/action/mod.rs index c4f8320f9c..80ba7d2326 100644 --- a/crates/astria-core/src/protocol/transaction/v1/action/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1/action/mod.rs @@ -538,7 +538,7 @@ impl Protobuf for Transfer { let Some(to) = to else { return Err(TransferError::field_not_set("to")); }; - let to = Address::try_from_raw(to).map_err(TransferError::address)?; + let to = Address::try_from_raw_ref(to).map_err(TransferError::address)?; let amount = amount.map_or(0, Into::into); let asset = asset.parse().map_err(TransferError::asset)?; let fee_asset = fee_asset.parse().map_err(TransferError::fee_asset)?; @@ -780,7 +780,7 @@ impl Protobuf for SudoAddressChange { return Err(SudoAddressChangeError::field_not_set("new_address")); }; let new_address = - Address::try_from_raw(new_address).map_err(SudoAddressChangeError::address)?; + Address::try_from_raw_ref(new_address).map_err(SudoAddressChangeError::address)?; Ok(Self { new_address, }) @@ -847,7 +847,7 @@ impl Protobuf for IbcSudoChange { return Err(IbcSudoChangeError::field_not_set("new_address")); }; let new_address = - Address::try_from_raw(new_address).map_err(IbcSudoChangeError::address)?; + Address::try_from_raw_ref(new_address).map_err(IbcSudoChangeError::address)?; Ok(Self { new_address, }) @@ -1034,7 +1034,7 @@ impl Protobuf for Ics20Withdrawal { } = proto; let amount = amount.ok_or(Ics20WithdrawalError::field_not_set("amount"))?; let return_address = Address::try_from_raw( - &return_address.ok_or(Ics20WithdrawalError::field_not_set("return_address"))?, + return_address.ok_or(Ics20WithdrawalError::field_not_set("return_address"))?, ) .map_err(Ics20WithdrawalError::return_address)?; @@ -1042,7 +1042,6 @@ impl Protobuf for Ics20Withdrawal { .ok_or(Ics20WithdrawalError::field_not_set("timeout_height"))? .into(); let bridge_address = bridge_address - .as_ref() .map(Address::try_from_raw) .transpose() .map_err(Ics20WithdrawalError::invalid_bridge_address)?; @@ -1090,12 +1089,13 @@ impl Protobuf for Ics20Withdrawal { use_compat_address, } = proto; let amount = amount.ok_or(Ics20WithdrawalError::field_not_set("amount"))?; - let return_address = Address::try_from_raw( - return_address - .as_ref() - .ok_or(Ics20WithdrawalError::field_not_set("return_address"))?, - ) - .map_err(Ics20WithdrawalError::return_address)?; + let return_address = return_address + .as_ref() + .ok_or_else(|| Ics20WithdrawalError::field_not_set("return_address")) + .and_then(|return_address| { + Address::try_from_raw_ref(return_address) + .map_err(Ics20WithdrawalError::return_address) + })?; let timeout_height = timeout_height .clone() @@ -1103,7 +1103,7 @@ impl Protobuf for Ics20Withdrawal { .into(); let bridge_address = bridge_address .as_ref() - .map(Address::try_from_raw) + .map(Address::try_from_raw_ref) .transpose() .map_err(Ics20WithdrawalError::invalid_bridge_address)?; @@ -1245,14 +1245,14 @@ impl Protobuf for IbcRelayerChange { value: Some(raw::ibc_relayer_change::Value::Addition(address)), } => { let address = - Address::try_from_raw(address).map_err(IbcRelayerChangeError::address)?; + Address::try_from_raw_ref(address).map_err(IbcRelayerChangeError::address)?; Ok(IbcRelayerChange::Addition(address)) } raw::IbcRelayerChange { value: Some(raw::ibc_relayer_change::Value::Removal(address)), } => { let address = - Address::try_from_raw(address).map_err(IbcRelayerChangeError::address)?; + Address::try_from_raw_ref(address).map_err(IbcRelayerChangeError::address)?; Ok(IbcRelayerChange::Removal(address)) } _ => Err(IbcRelayerChangeError::missing_address()), @@ -1423,13 +1423,11 @@ impl Protobuf for InitBridgeAccount { .map_err(InitBridgeAccountError::invalid_fee_asset)?; let sudo_address = proto .sudo_address - .as_ref() .map(Address::try_from_raw) .transpose() .map_err(InitBridgeAccountError::invalid_sudo_address)?; let withdrawer_address = proto .withdrawer_address - .as_ref() .map(Address::try_from_raw) .transpose() .map_err(InitBridgeAccountError::invalid_withdrawer_address)?; @@ -1558,7 +1556,7 @@ impl Protobuf for BridgeLock { let Some(to) = proto.to else { return Err(BridgeLockError::field_not_set("to")); }; - let to = Address::try_from_raw(&to).map_err(BridgeLockError::address)?; + let to = Address::try_from_raw(to).map_err(BridgeLockError::address)?; let amount = proto.amount.ok_or(BridgeLockError::missing_amount())?; let asset = proto .asset @@ -1704,13 +1702,13 @@ impl Protobuf for BridgeUnlock { } = proto; let to = to .ok_or_else(|| BridgeUnlockError::field_not_set("to")) - .and_then(|to| Address::try_from_raw(&to).map_err(BridgeUnlockError::address))?; + .and_then(|to| Address::try_from_raw(to).map_err(BridgeUnlockError::address))?; let amount = amount.ok_or_else(|| BridgeUnlockError::field_not_set("amount"))?; let fee_asset = fee_asset.parse().map_err(BridgeUnlockError::fee_asset)?; let bridge_address = bridge_address .ok_or_else(|| BridgeUnlockError::field_not_set("bridge_address")) - .and_then(|to| Address::try_from_raw(&to).map_err(BridgeUnlockError::bridge_address))?; + .and_then(|to| Address::try_from_raw(to).map_err(BridgeUnlockError::bridge_address))?; Ok(Self { to, amount: amount.into(), @@ -1825,17 +1823,15 @@ impl Protobuf for BridgeSudoChange { let Some(bridge_address) = proto.bridge_address else { return Err(BridgeSudoChangeError::field_not_set("bridge_address")); }; - let bridge_address = Address::try_from_raw(&bridge_address) + let bridge_address = Address::try_from_raw(bridge_address) .map_err(BridgeSudoChangeError::invalid_bridge_address)?; let new_sudo_address = proto .new_sudo_address - .as_ref() .map(Address::try_from_raw) .transpose() .map_err(BridgeSudoChangeError::invalid_new_sudo_address)?; let new_withdrawer_address = proto .new_withdrawer_address - .as_ref() .map(Address::try_from_raw) .transpose() .map_err(BridgeSudoChangeError::invalid_new_withdrawer_address)?; diff --git a/crates/astria-core/src/protocol/transaction/v1/mod.rs b/crates/astria-core/src/protocol/transaction/v1/mod.rs index 6bfbd5b22c..59616514d8 100644 --- a/crates/astria-core/src/protocol/transaction/v1/mod.rs +++ b/crates/astria-core/src/protocol/transaction/v1/mod.rs @@ -11,7 +11,7 @@ use crate::{ SigningKey, VerificationKey, }, - generated::protocol::transaction::v1 as raw, + generated::astria::protocol::transaction::v1 as raw, primitive::v1::{ TransactionId, ADDRESS_LEN, diff --git a/crates/astria-core/src/sequencerblock/v1/block.rs b/crates/astria-core/src/sequencerblock/v1/block.rs index 48f8907fac..d4fc7e7b25 100644 --- a/crates/astria-core/src/sequencerblock/v1/block.rs +++ b/crates/astria-core/src/sequencerblock/v1/block.rs @@ -784,8 +784,9 @@ impl SequencerBlock { let mut rollup_datas = IndexMap::new(); for elem in data_list { - let raw_tx = crate::generated::protocol::transaction::v1::Transaction::decode(&*elem) - .map_err(SequencerBlockError::transaction_protobuf_decode)?; + let raw_tx = + crate::generated::astria::protocol::transaction::v1::Transaction::decode(&*elem) + .map_err(SequencerBlockError::transaction_protobuf_decode)?; let tx = Transaction::try_from_raw(raw_tx) .map_err(SequencerBlockError::raw_signed_transaction_conversion)?; for action in tx.into_unsigned().into_actions() { @@ -1363,7 +1364,7 @@ impl FilteredSequencerBlockError { #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr( feature = "serde", - serde(into = "crate::generated::sequencerblock::v1::Deposit") + serde(into = "crate::generated::astria::sequencerblock::v1::Deposit") )] pub struct Deposit { // the address on the sequencer to which the funds were sent to. @@ -1428,7 +1429,7 @@ impl Deposit { return Err(DepositError::field_not_set("bridge_address")); }; let bridge_address = - Address::try_from_raw(&bridge_address).map_err(DepositError::address)?; + Address::try_from_raw(bridge_address).map_err(DepositError::address)?; let amount = amount.ok_or(DepositError::field_not_set("amount"))?.into(); let Some(rollup_id) = rollup_id else { return Err(DepositError::field_not_set("rollup_id")); diff --git a/crates/astria-core/src/sequencerblock/v1/mod.rs b/crates/astria-core/src/sequencerblock/v1/mod.rs index 966696c9ff..9685f452f6 100644 --- a/crates/astria-core/src/sequencerblock/v1/mod.rs +++ b/crates/astria-core/src/sequencerblock/v1/mod.rs @@ -16,7 +16,7 @@ use sha2::{ }; use crate::{ - generated::sequencerblock::v1 as raw, + generated::astria::sequencerblock::v1 as raw, primitive::v1::{ derive_merkle_tree_from_rollup_txs, IncorrectRollupIdLength, diff --git a/crates/astria-sequencer-client/src/extension_trait.rs b/crates/astria-sequencer-client/src/extension_trait.rs index 59cab0f7e5..96eed49ef8 100644 --- a/crates/astria-sequencer-client/src/extension_trait.rs +++ b/crates/astria-sequencer-client/src/extension_trait.rs @@ -450,7 +450,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::accounts::v1::BalanceResponse::decode( + astria_core::generated::astria::protocol::accounts::v1::BalanceResponse::decode( &*response.value, ) .map_err(|e| { @@ -492,7 +492,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::asset::v1::AllowedFeeAssetsResponse::decode( + astria_core::generated::astria::protocol::asset::v1::AllowedFeeAssetsResponse::decode( &*response.value, ) .map_err(|e| { @@ -532,14 +532,12 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::accounts::v1::NonceResponse::decode(&*response.value) - .map_err(|e| { - Error::abci_query_deserialization( - "astria.sequencer.v1.NonceResponse", - response, - e, - ) - })?; + astria_core::generated::astria::protocol::accounts::v1::NonceResponse::decode( + &*response.value, + ) + .map_err(|e| { + Error::abci_query_deserialization("astria.sequencer.v1.NonceResponse", response, e) + })?; Ok(proto_response.to_native()) } @@ -567,7 +565,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::bridge::v1::BridgeAccountInfoResponse::decode( + astria_core::generated::astria::protocol::bridge::v1::BridgeAccountInfoResponse::decode( &*response.value, ) .map_err(|e| { @@ -599,7 +597,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::bridge::v1::BridgeAccountLastTxHashResponse::decode( + astria_core::generated::astria::protocol::bridge::v1::BridgeAccountLastTxHashResponse::decode( &*response.value, ) .map_err(|e| { @@ -631,7 +629,7 @@ pub trait SequencerClientExt: Client { .map_err(|e| Error::tendermint_rpc("abci_query", e))?; let proto_response = - astria_core::generated::protocol::fees::v1::TransactionFeeResponse::decode( + astria_core::generated::astria::protocol::fees::v1::TransactionFeeResponse::decode( &*response.value, ) .map_err(|e| { diff --git a/crates/astria-sequencer-client/src/tests/http.rs b/crates/astria-sequencer-client/src/tests/http.rs index 23f1ee481c..92f02124cd 100644 --- a/crates/astria-sequencer-client/src/tests/http.rs +++ b/crates/astria-sequencer-client/src/tests/http.rs @@ -2,7 +2,7 @@ use std::time::Duration; use astria_core::{ crypto::SigningKey, - generated::protocol::{ + generated::astria::protocol::{ asset::v1::AllowedFeeAssetsResponse, fees::v1::TransactionFee, }, @@ -12,6 +12,7 @@ use astria_core::{ Transaction, TransactionBody, }, + Protobuf as _, }; use hex_literal::hex; use prost::bytes::Bytes; @@ -170,7 +171,7 @@ fn create_signed_transaction() -> Transaction { #[tokio::test] async fn get_latest_nonce() { - use astria_core::generated::protocol::accounts::v1::NonceResponse; + use astria_core::generated::astria::protocol::accounts::v1::NonceResponse; let MockSequencer { server, client, @@ -197,7 +198,7 @@ async fn get_latest_nonce() { #[tokio::test] async fn get_latest_balance() { - use astria_core::generated::protocol::accounts::v1::{ + use astria_core::generated::astria::protocol::accounts::v1::{ AssetBalance, BalanceResponse, }; @@ -262,7 +263,7 @@ async fn get_allowed_fee_assets() { #[tokio::test] async fn get_bridge_account_info() { use astria_core::{ - generated::protocol::bridge::v1::BridgeAccountInfoResponse, + generated::astria::protocol::bridge::v1::BridgeAccountInfoResponse, primitive::v1::RollupId, }; @@ -294,7 +295,7 @@ async fn get_bridge_account_info() { #[tokio::test] async fn get_bridge_account_last_transaction_hash() { - use astria_core::generated::protocol::bridge::v1::BridgeAccountLastTxHashResponse; + use astria_core::generated::astria::protocol::bridge::v1::BridgeAccountLastTxHashResponse; let MockSequencer { server, @@ -324,7 +325,7 @@ async fn get_bridge_account_last_transaction_hash() { #[tokio::test] async fn get_transaction_fee() { - use astria_core::generated::protocol::fees::v1::TransactionFeeResponse; + use astria_core::generated::astria::protocol::fees::v1::TransactionFeeResponse; let MockSequencer { server, diff --git a/crates/astria-sequencer-relayer/src/relayer/builder.rs b/crates/astria-sequencer-relayer/src/relayer/builder.rs index 0609af19ce..fd4d3fbf17 100644 --- a/crates/astria-sequencer-relayer/src/relayer/builder.rs +++ b/crates/astria-sequencer-relayer/src/relayer/builder.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use astria_core::generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; +use astria_core::generated::astria::sequencerblock::v1::sequencer_service_client::SequencerServiceClient; use astria_eyre::eyre::{ self, WrapErr as _, diff --git a/crates/astria-sequencer-relayer/src/relayer/mod.rs b/crates/astria-sequencer-relayer/src/relayer/mod.rs index 54c613ee81..e4e58e0cb7 100644 --- a/crates/astria-sequencer-relayer/src/relayer/mod.rs +++ b/crates/astria-sequencer-relayer/src/relayer/mod.rs @@ -5,7 +5,7 @@ use std::{ }; use astria_core::{ - generated::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, + generated::astria::sequencerblock::v1::sequencer_service_client::SequencerServiceClient, sequencerblock::v1::SequencerBlock, }; use astria_eyre::eyre::{ diff --git a/crates/astria-sequencer-relayer/src/relayer/read.rs b/crates/astria-sequencer-relayer/src/relayer/read.rs index 58147fdfd2..6525f3e528 100644 --- a/crates/astria-sequencer-relayer/src/relayer/read.rs +++ b/crates/astria-sequencer-relayer/src/relayer/read.rs @@ -8,7 +8,7 @@ use std::{ }; use astria_core::{ - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_client::SequencerServiceClient, GetSequencerBlockRequest, }, diff --git a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs index 38733634d2..82d425ff89 100644 --- a/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs +++ b/crates/astria-sequencer-relayer/src/relayer/write/conversion.rs @@ -10,7 +10,7 @@ use std::{ use astria_core::{ brotli::compress_bytes, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ SubmittedMetadata, SubmittedMetadataList, SubmittedRollupData, diff --git a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs index 82534b292a..33ff34d989 100644 --- a/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs +++ b/crates/astria-sequencer-relayer/tests/blackbox/helpers/mock_sequencer_server.rs @@ -4,7 +4,7 @@ use std::{ }; use astria_core::{ - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_server::{ SequencerService, SequencerServiceServer, diff --git a/crates/astria-sequencer-utils/src/blob_parser.rs b/crates/astria-sequencer-utils/src/blob_parser.rs index c729751c96..6fb450ff90 100644 --- a/crates/astria-sequencer-utils/src/blob_parser.rs +++ b/crates/astria-sequencer-utils/src/blob_parser.rs @@ -13,7 +13,7 @@ use std::{ use astria_core::{ brotli::decompress_bytes, - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ rollup_data::Value as RawRollupDataValue, Deposit as RawDeposit, RollupData as RawRollupData, diff --git a/crates/astria-sequencer-utils/src/genesis_example.rs b/crates/astria-sequencer-utils/src/genesis_example.rs index a464fd26c2..6d7d277719 100644 --- a/crates/astria-sequencer-utils/src/genesis_example.rs +++ b/crates/astria-sequencer-utils/src/genesis_example.rs @@ -5,7 +5,7 @@ use std::{ }; use astria_core::{ - generated::protocol::genesis::v1::{ + generated::astria::protocol::genesis::v1::{ AddressPrefixes, GenesisFees, IbcParameters, @@ -91,8 +91,8 @@ fn address_prefixes() -> AddressPrefixes { } #[expect(clippy::too_many_lines, reason = "all lines reasonably necessary")] -fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1::GenesisAppState { - astria_core::generated::protocol::genesis::v1::GenesisAppState { +fn proto_genesis_state() -> astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { + astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { accounts: accounts().into_iter().map(Protobuf::into_raw).collect(), address_prefixes: Some(address_prefixes()), authority_sudo_address: Some(alice().to_raw()), diff --git a/crates/astria-sequencer/CHANGELOG.md b/crates/astria-sequencer/CHANGELOG.md index 335a3a4e80..3fffea9a55 100644 --- a/crates/astria-sequencer/CHANGELOG.md +++ b/crates/astria-sequencer/CHANGELOG.md @@ -9,9 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## Changed +### Changed - Index all event attributes [#1786](https://github.com/astriaorg/astria/pull/1786). +- Consolidate action handling to single module [#1759](https://github.com/astriaorg/astria/pull/1759). +- Ensure all deposit assets are trace prefixed [#1807](https://github.com/astriaorg/astria/pull/1807). ## [1.0.0] - 2024-10-25 diff --git a/crates/astria-sequencer/Cargo.toml b/crates/astria-sequencer/Cargo.toml index d56d38dd4c..999bd4c547 100644 --- a/crates/astria-sequencer/Cargo.toml +++ b/crates/astria-sequencer/Cargo.toml @@ -81,6 +81,7 @@ paste = "1.0.15" maplit = "1.0.2" rand_chacha = "0.3.1" tokio = { workspace = true, features = ["test-util"] } +assert-json-diff = "2.0.2" [build-dependencies] astria-build-info = { path = "../astria-build-info", features = ["build"] } diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs deleted file mode 100644 index dce040abf1..0000000000 --- a/crates/astria-sequencer/src/accounts/action.rs +++ /dev/null @@ -1,99 +0,0 @@ -use astria_core::protocol::transaction::v1::action::Transfer; -use astria_eyre::eyre::{ - ensure, - Result, - WrapErr as _, -}; -use cnidarium::{ - StateRead, - StateWrite, -}; - -use super::AddressBytes; -use crate::{ - accounts::{ - StateReadExt as _, - StateWriteExt as _, - }, - address::StateReadExt as _, - app::ActionHandler, - bridge::StateReadExt as _, - transaction::StateReadExt as _, -}; - -#[async_trait::async_trait] -impl ActionHandler for Transfer { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - async fn check_and_execute(&self, state: S) -> Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - - ensure!( - state - .get_bridge_account_rollup_id(&from) - .await - .wrap_err("failed to get bridge account rollup id")? - .is_none(), - "cannot transfer out of bridge account; BridgeUnlock must be used", - ); - - check_transfer(self, &from, &state).await?; - execute_transfer(self, &from, state).await?; - - Ok(()) - } -} - -pub(crate) async fn execute_transfer( - action: &Transfer, - from: &TAddress, - mut state: S, -) -> Result<()> -where - S: StateWrite, - TAddress: AddressBytes, -{ - let from = from.address_bytes(); - state - .decrease_balance(from, &action.asset, action.amount) - .await - .wrap_err("failed decreasing `from` account balance")?; - state - .increase_balance(&action.to, &action.asset, action.amount) - .await - .wrap_err("failed increasing `to` account balance")?; - - Ok(()) -} - -pub(crate) async fn check_transfer( - action: &Transfer, - from: &TAddress, - state: &S, -) -> Result<()> -where - S: StateRead, - TAddress: AddressBytes, -{ - state.ensure_base_prefix(&action.to).await.wrap_err( - "failed ensuring that the destination address matches the permitted base prefix", - )?; - - let transfer_asset = &action.asset; - - let from_transfer_balance = state - .get_account_balance(from, transfer_asset) - .await - .wrap_err("failed to get account balance in transfer check")?; - ensure!( - from_transfer_balance >= action.amount, - "insufficient funds for transfer" - ); - - Ok(()) -} diff --git a/crates/astria-sequencer/src/accounts/mod.rs b/crates/astria-sequencer/src/accounts/mod.rs index 436ff211ea..6931208e9f 100644 --- a/crates/astria-sequencer/src/accounts/mod.rs +++ b/crates/astria-sequencer/src/accounts/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod action; pub(crate) mod component; pub(crate) mod query; mod state_ext; diff --git a/crates/astria-sequencer/src/action_handler/impls/bridge_lock.rs b/crates/astria-sequencer/src/action_handler/impls/bridge_lock.rs new file mode 100644 index 0000000000..3be717fc61 --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/bridge_lock.rs @@ -0,0 +1,197 @@ +use astria_core::{ + protocol::transaction::v1::action::{ + BridgeLock, + Transfer, + }, + sequencerblock::v1::block::Deposit, +}; +use astria_eyre::eyre::{ + ensure, + OptionExt as _, + Result, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; + +use crate::{ + action_handler::{ + check_transfer, + execute_transfer, + ActionHandler, + }, + address::StateReadExt as _, + assets::StateReadExt as _, + bridge::{ + StateReadExt as _, + StateWriteExt, + }, + transaction::StateReadExt as _, + utils::create_deposit_event, +}; + +#[async_trait] +impl ActionHandler for BridgeLock { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + state + .ensure_base_prefix(&self.to) + .await + .wrap_err("failed check for base prefix of destination address")?; + // ensure the recipient is a bridge account. + let rollup_id = state + .get_bridge_account_rollup_id(&self.to) + .await + .wrap_err("failed to get bridge account rollup id")? + .ok_or_eyre("bridge lock must be sent to a bridge account")?; + + let allowed_asset = state + .get_bridge_account_ibc_asset(&self.to) + .await + .wrap_err("failed to get bridge account asset ID")?; + ensure!( + allowed_asset == self.asset.to_ibc_prefixed(), + "asset ID is not authorized for transfer to bridge account", + ); + + let source_transaction_id = state + .get_transaction_context() + .expect("current source should be set before executing action") + .transaction_id; + let source_action_index = state + .get_transaction_context() + .expect("current source should be set before executing action") + .position_in_transaction; + + // map asset to trace prefixed asset for deposit, if it is not already + let deposit_asset = match self.asset.as_trace_prefixed() { + Some(asset) => asset.clone(), + None => state + .map_ibc_to_trace_prefixed_asset(&allowed_asset) + .await + .wrap_err("failed to map IBC asset to trace prefixed asset")? + .ok_or_eyre("mapping from IBC prefixed bridge asset to trace prefixed not found")?, + }; + + let deposit = Deposit { + bridge_address: self.to, + rollup_id, + amount: self.amount, + asset: deposit_asset.into(), + destination_chain_address: self.destination_chain_address.clone(), + source_transaction_id, + source_action_index, + }; + let deposit_abci_event = create_deposit_event(&deposit); + + let transfer_action = Transfer { + to: self.to, + asset: self.asset.clone(), + amount: self.amount, + fee_asset: self.fee_asset.clone(), + }; + + check_transfer(&transfer_action, &from, &state).await?; + execute_transfer(&transfer_action, &from, &mut state).await?; + + state.cache_deposit_event(deposit); + state.record(deposit_abci_event); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use astria_core::{ + primitive::v1::{ + asset, + TransactionId, + }, + protocol::transaction::v1::action::BridgeLock, + }; + use cnidarium::StateDelta; + + use crate::{ + accounts::{ + AddressBytes, + StateWriteExt as _, + }, + action_handler::ActionHandler as _, + address::StateWriteExt as _, + assets::StateWriteExt as _, + benchmark_and_test_utils::{ + astria_address, + nria, + ASTRIA_PREFIX, + }, + bridge::{ + StateReadExt, + StateWriteExt as _, + }, + transaction::{ + StateWriteExt as _, + TransactionContext, + }, + }; + + #[tokio::test] + async fn bridge_lock_maps_ibc_to_trace_prefixed_for_deposit() { + let storage = cnidarium::TempStorage::new().await.unwrap(); + let snapshot = storage.latest_snapshot(); + let mut state = StateDelta::new(snapshot); + + let trace_asset = "trace_asset" + .parse::() + .unwrap(); + let ibc_asset = trace_asset.to_ibc_prefixed(); + let transfer_amount = 100; + let bridge_address = astria_address(&[3; 20]); + let from_address = astria_address(&[1; 20]); + + state.put_transaction_context(TransactionContext { + address_bytes: *from_address.address_bytes(), + transaction_id: TransactionId::new([0; 32]), + position_in_transaction: 0, + }); + state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); + state + .put_bridge_account_rollup_id(&bridge_address, [0; 32].into()) + .unwrap(); + state + .put_bridge_account_ibc_asset(&bridge_address, ibc_asset) + .unwrap(); + state.put_ibc_asset(trace_asset.clone()).unwrap(); + state + .put_account_balance(&from_address, &trace_asset, transfer_amount) + .unwrap(); + + let bridge_lock_action = BridgeLock { + to: bridge_address, + amount: transfer_amount, + asset: ibc_asset.into(), + fee_asset: nria().into(), + destination_chain_address: "ethan_was_here".to_string(), + }; + + bridge_lock_action + .check_and_execute(&mut state) + .await + .unwrap(); + + let deposits = state + .get_cached_block_deposits() + .values() + .next() + .unwrap() + .clone(); + assert_eq!(deposits.len(), 1); + assert!(deposits[0].asset.as_trace_prefixed().is_some()); + } +} diff --git a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs b/crates/astria-sequencer/src/action_handler/impls/bridge_sudo_change.rs similarity index 89% rename from crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs rename to crates/astria-sequencer/src/action_handler/impls/bridge_sudo_change.rs index 655694abfc..6bdb3ee4f4 100644 --- a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs +++ b/crates/astria-sequencer/src/action_handler/impls/bridge_sudo_change.rs @@ -5,18 +5,20 @@ use astria_eyre::eyre::{ Result, WrapErr as _, }; +use async_trait::async_trait; use cnidarium::StateWrite; use crate::{ + action_handler::ActionHandler, address::StateReadExt as _, - app::ActionHandler, - bridge::state_ext::{ + bridge::{ StateReadExt as _, - StateWriteExt as _, + StateWriteExt, }, transaction::StateReadExt as _, }; -#[async_trait::async_trait] + +#[async_trait] impl ActionHandler for BridgeSudoChange { async fn check_stateless(&self) -> Result<()> { Ok(()) @@ -79,22 +81,29 @@ impl ActionHandler for BridgeSudoChange { #[cfg(test)] mod tests { use astria_core::{ - primitive::v1::{ - asset, - TransactionId, + primitive::v1::TransactionId, + protocol::{ + fees::v1::BridgeSudoChangeFeeComponents, + transaction::v1::action::BridgeSudoChange, }, - protocol::fees::v1::BridgeSudoChangeFeeComponents, }; use cnidarium::StateDelta; - use super::*; use crate::{ accounts::StateWriteExt as _, + action_handler::{ + impls::test_utils::test_asset, + ActionHandler as _, + }, address::StateWriteExt as _, benchmark_and_test_utils::{ astria_address, ASTRIA_PREFIX, }, + bridge::{ + StateReadExt as _, + StateWriteExt as _, + }, fees::StateWriteExt as _, transaction::{ StateWriteExt as _, @@ -102,12 +111,8 @@ mod tests { }, }; - fn test_asset() -> asset::Denom { - "test".parse().unwrap() - } - #[tokio::test] - async fn fails_with_unauthorized_if_signer_is_not_sudo_address() { + async fn bridge_sudo_change_fails_with_unauthorized_if_signer_is_not_sudo_address() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -115,7 +120,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: [1; 20], transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); @@ -146,7 +151,7 @@ mod tests { } #[tokio::test] - async fn executes() { + async fn bridge_sudo_change_executes_as_expected() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -155,7 +160,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: sudo_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); state diff --git a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs b/crates/astria-sequencer/src/action_handler/impls/bridge_unlock.rs similarity index 94% rename from crates/astria-sequencer/src/bridge/bridge_unlock_action.rs rename to crates/astria-sequencer/src/action_handler/impls/bridge_unlock.rs index 52fcb2b407..10da98c5a4 100644 --- a/crates/astria-sequencer/src/bridge/bridge_unlock_action.rs +++ b/crates/astria-sequencer/src/action_handler/impls/bridge_unlock.rs @@ -8,23 +8,24 @@ use astria_eyre::eyre::{ Result, WrapErr as _, }; +use async_trait::async_trait; use cnidarium::StateWrite; use crate::{ - accounts::action::{ + action_handler::{ check_transfer, execute_transfer, + ActionHandler, }, address::StateReadExt as _, - app::ActionHandler, bridge::{ StateReadExt as _, - StateWriteExt as _, + StateWriteExt, }, transaction::StateReadExt as _, }; -#[async_trait::async_trait] +#[async_trait] impl ActionHandler for BridgeUnlock { // TODO(https://github.com/astriaorg/astria/issues/1430): move checks to the `BridgeUnlock` parsing. async fn check_stateless(&self) -> Result<()> { @@ -104,7 +105,6 @@ impl ActionHandler for BridgeUnlock { mod tests { use astria_core::{ primitive::v1::{ - asset, RollupId, TransactionId, }, @@ -117,8 +117,11 @@ mod tests { use crate::{ accounts::StateWriteExt as _, + action_handler::{ + impls::test_utils::test_asset, + ActionHandler as _, + }, address::StateWriteExt as _, - app::ActionHandler as _, benchmark_and_test_utils::{ assert_eyre_error, astria_address, @@ -132,12 +135,8 @@ mod tests { }, }; - fn test_asset() -> asset::Denom { - "test".parse().unwrap() - } - #[tokio::test] - async fn fails_if_bridge_account_has_no_withdrawer_address() { + async fn bridge_unlock_fails_if_bridge_account_has_no_withdrawer_address() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -145,7 +144,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: [1; 20], transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); @@ -176,7 +175,7 @@ mod tests { } #[tokio::test] - async fn fails_if_withdrawer_is_not_signer() { + async fn bridge_unlock_fails_if_withdrawer_is_not_signer() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -184,7 +183,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: [1; 20], transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); @@ -219,7 +218,7 @@ mod tests { } #[tokio::test] - async fn execute_with_duplicated_withdrawal_event_id() { + async fn bridge_unlock_executes_with_duplicated_withdrawal_event_id() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -228,7 +227,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); diff --git a/crates/astria-sequencer/src/action_handler/impls/fee_asset_change.rs b/crates/astria-sequencer/src/action_handler/impls/fee_asset_change.rs new file mode 100644 index 0000000000..c92161e751 --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/fee_asset_change.rs @@ -0,0 +1,65 @@ +use astria_core::protocol::transaction::v1::action::FeeAssetChange; +use astria_eyre::eyre::{ + self, + ensure, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; +use futures::StreamExt as _; +use tokio::pin; + +use crate::{ + action_handler::ActionHandler, + authority::StateReadExt as _, + fees::{ + StateReadExt as _, + StateWriteExt as _, + }, + transaction::StateReadExt as _, +}; + +#[async_trait] +impl ActionHandler for FeeAssetChange { + async fn check_stateless(&self) -> eyre::Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> eyre::Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + let authority_sudo_address = state + .get_sudo_address() + .await + .wrap_err("failed to get authority sudo address")?; + ensure!( + authority_sudo_address == from, + "unauthorized address for fee asset change" + ); + match self { + FeeAssetChange::Addition(asset) => { + state + .put_allowed_fee_asset(asset) + .context("failed to write allowed fee asset to state")?; + } + FeeAssetChange::Removal(asset) => { + state.delete_allowed_fee_asset(asset); + + pin!( + let assets = state.allowed_fee_assets(); + ); + ensure!( + assets + .filter_map(|item| std::future::ready(item.ok())) + .next() + .await + .is_some(), + "cannot remove last allowed fee asset", + ); + } + } + Ok(()) + } +} diff --git a/crates/astria-sequencer/src/fees/action.rs b/crates/astria-sequencer/src/action_handler/impls/fee_change.rs similarity index 73% rename from crates/astria-sequencer/src/fees/action.rs rename to crates/astria-sequencer/src/action_handler/impls/fee_change.rs index 927eea5ec9..d83cf2ed61 100644 --- a/crates/astria-sequencer/src/fees/action.rs +++ b/crates/astria-sequencer/src/action_handler/impls/fee_change.rs @@ -1,27 +1,20 @@ -use astria_core::protocol::transaction::v1::action::{ - FeeAssetChange, - FeeChange, -}; +use astria_core::protocol::transaction::v1::action::FeeChange; use astria_eyre::eyre::{ self, ensure, WrapErr as _, }; +use async_trait::async_trait; use cnidarium::StateWrite; -use futures::StreamExt; -use tokio::pin; use crate::{ - app::ActionHandler, + action_handler::ActionHandler, authority::StateReadExt as _, - fees::{ - StateReadExt as _, - StateWriteExt as _, - }, + fees::StateWriteExt as _, transaction::StateReadExt as _, }; -#[async_trait::async_trait] +#[async_trait] impl ActionHandler for FeeChange { async fn check_stateless(&self) -> eyre::Result<()> { Ok(()) @@ -88,51 +81,6 @@ impl ActionHandler for FeeChange { } } -#[async_trait::async_trait] -impl ActionHandler for FeeAssetChange { - async fn check_stateless(&self) -> eyre::Result<()> { - Ok(()) - } - - async fn check_and_execute(&self, mut state: S) -> eyre::Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - let authority_sudo_address = state - .get_sudo_address() - .await - .wrap_err("failed to get authority sudo address")?; - ensure!( - authority_sudo_address == from, - "unauthorized address for fee asset change" - ); - match self { - FeeAssetChange::Addition(asset) => { - state - .put_allowed_fee_asset(asset) - .context("failed to write allowed fee asset to state")?; - } - FeeAssetChange::Removal(asset) => { - state.delete_allowed_fee_asset(asset); - - pin!( - let assets = state.allowed_fee_assets(); - ); - ensure!( - assets - .filter_map(|item| std::future::ready(item.ok())) - .next() - .await - .is_some(), - "cannot remove last allowed fee asset", - ); - } - } - Ok(()) - } -} - #[cfg(test)] mod tests { use astria_core::{ @@ -141,7 +89,7 @@ mod tests { }; use crate::{ - app::ActionHandler as _, + action_handler::ActionHandler as _, authority::StateWriteExt as _, fees::StateReadExt as _, transaction::{ @@ -169,7 +117,7 @@ mod tests { state.put_transaction_context(TransactionContext { address_bytes: [1; 20], transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state.put_sudo_address([1; 20]).unwrap(); @@ -216,19 +164,19 @@ mod tests { } test_fee_change_action!( - transfer => Transfer, - rollup_data_submission => RollupDataSubmission, - ics20_withdrawal => Ics20Withdrawal, - init_bridge_account => InitBridgeAccount, - bridge_lock => BridgeLock, - bridge_unlock => BridgeUnlock, - bridge_sudo_change => BridgeSudoChange, - validator_update => ValidatorUpdate, - ibc_relayer_change => IbcRelayerChange, - ibc_relay => IbcRelay, - fee_asset_change => FeeAssetChange, - fee_change => FeeChange, - sudo_address_change => SudoAddressChange, - ibc_sudo_change => IbcSudoChange, + transfer => Transfer, + rollup_data_submission => RollupDataSubmission, + ics20_withdrawal => Ics20Withdrawal, + init_bridge_account => InitBridgeAccount, + bridge_lock => BridgeLock, + bridge_unlock => BridgeUnlock, + bridge_sudo_change => BridgeSudoChange, + validator_update => ValidatorUpdate, + ibc_relayer_change => IbcRelayerChange, + ibc_relay => IbcRelay, + fee_asset_change => FeeAssetChange, + fee_change => FeeChange, + sudo_address_change => SudoAddressChange, + ibc_sudo_change => IbcSudoChange, ); } diff --git a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs b/crates/astria-sequencer/src/action_handler/impls/ibc_relayer_change.rs similarity index 98% rename from crates/astria-sequencer/src/ibc/ibc_relayer_change.rs rename to crates/astria-sequencer/src/action_handler/impls/ibc_relayer_change.rs index 0c277b412e..e5de94afbe 100644 --- a/crates/astria-sequencer/src/ibc/ibc_relayer_change.rs +++ b/crates/astria-sequencer/src/action_handler/impls/ibc_relayer_change.rs @@ -8,8 +8,8 @@ use async_trait::async_trait; use cnidarium::StateWrite; use crate::{ + action_handler::ActionHandler, address::StateReadExt as _, - app::ActionHandler, ibc::{ StateReadExt as _, StateWriteExt as _, diff --git a/crates/astria-sequencer/src/action_handler/impls/ibc_sudo_change.rs b/crates/astria-sequencer/src/action_handler/impls/ibc_sudo_change.rs new file mode 100644 index 0000000000..f82af648fc --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/ibc_sudo_change.rs @@ -0,0 +1,44 @@ +use astria_core::protocol::transaction::v1::action::IbcSudoChange; +use astria_eyre::eyre::{ + ensure, + Result, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; + +use crate::{ + action_handler::ActionHandler, + address::StateReadExt as _, + authority::StateReadExt as _, + ibc::StateWriteExt as _, + transaction::StateReadExt as _, +}; + +#[async_trait] +impl ActionHandler for IbcSudoChange { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + state + .ensure_base_prefix(&self.new_address) + .await + .wrap_err("desired new ibc sudo address has an unsupported prefix")?; + // ensure signer is the valid `sudo` key in state + let sudo_address = state + .get_sudo_address() + .await + .wrap_err("failed to get sudo address from state")?; + ensure!(sudo_address == from, "signer is not the sudo key"); + state + .put_ibc_sudo_address(self.new_address) + .wrap_err("failed to put ibc sudo address in state")?; + Ok(()) + } +} diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/action_handler/impls/ics20_withdrawal.rs similarity index 94% rename from crates/astria-sequencer/src/ibc/ics20_withdrawal.rs rename to crates/astria-sequencer/src/action_handler/impls/ics20_withdrawal.rs index 12975e4abf..35d1523ece 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/action_handler/impls/ics20_withdrawal.rs @@ -6,7 +6,9 @@ use astria_core::{ }, protocol::{ memos::v1::Ics20WithdrawalFromRollup, - transaction::v1::action, + transaction::v1::action::{ + self, + }, }, }; use astria_eyre::{ @@ -19,10 +21,12 @@ use astria_eyre::{ WrapErr as _, }, }; +use async_trait::async_trait; use cnidarium::{ StateRead, StateWrite, }; +use ibc_proto::ibc::apps::transfer::v2::FungibleTokenPacketData; use ibc_types::core::channel::{ ChannelId, PortId, @@ -33,21 +37,18 @@ use penumbra_ibc::component::packet::{ SendPacketWrite as _, Unchecked, }; -use penumbra_proto::core::component::ibc::v1::FungibleTokenPacketData; use crate::{ accounts::{ - AddressBytes as _, + AddressBytes, StateWriteExt as _, }, + action_handler::ActionHandler, address::StateReadExt as _, - app::{ - ActionHandler, - StateReadExt as _, - }, + app::StateReadExt as _, bridge::{ StateReadExt as _, - StateWriteExt as _, + StateWriteExt, }, ibc::{ StateReadExt as _, @@ -56,92 +57,7 @@ use crate::{ transaction::StateReadExt as _, }; -async fn create_ibc_packet_from_withdrawal( - withdrawal: &action::Ics20Withdrawal, - state: S, -) -> Result> { - let sender = if withdrawal.use_compat_address { - let ibc_compat_prefix = state.get_ibc_compat_prefix().await.context( - "need to construct bech32 compatible address for IBC communication but failed reading \ - required prefix from state", - )?; - withdrawal - .return_address() - .to_prefix(&ibc_compat_prefix) - .context("failed to convert the address to the bech32 compatible prefix")? - .to_format::() - .to_string() - } else { - withdrawal.return_address.to_string() - }; - let packet = FungibleTokenPacketData { - amount: withdrawal.amount.to_string(), - denom: withdrawal.denom.to_string(), - sender, - receiver: withdrawal.destination_chain_address.clone(), - memo: withdrawal.memo.clone(), - }; - - let serialized_packet_data = - serde_json::to_vec(&packet).context("failed to serialize fungible token packet as JSON")?; - - Ok(IBCPacket::new( - PortId::transfer(), - withdrawal.source_channel().clone(), - *withdrawal.timeout_height(), - withdrawal.timeout_time(), - serialized_packet_data, - )) -} - -/// Establishes the withdrawal target. -/// -/// The function returns the following addresses under the following conditions: -/// 1. `action.bridge_address` if `action.bridge_address` is set and `from` is its stored withdrawer -/// address. -/// 2. `from` if `action.bridge_address` is unset and `from` is *not* a bridge account. -/// -/// Errors if: -/// 1. Errors reading from DB -/// 2. `action.bridge_address` is set, but `from` is not the withdrawer address. -/// 3. `action.bridge_address` is unset, but `from` is a bridge account. -async fn establish_withdrawal_target<'a, S: StateRead>( - action: &'a action::Ics20Withdrawal, - state: &S, - from: &'a [u8; 20], -) -> Result<&'a [u8; 20]> { - // If the bridge address is set, the withdrawer on that address must match - // the from address. - if let Some(bridge_address) = &action.bridge_address { - let Some(withdrawer) = state - .get_bridge_account_withdrawer_address(bridge_address) - .await - .wrap_err("failed to get bridge withdrawer")? - else { - bail!("bridge address must have a withdrawer address set"); - }; - - ensure!( - &withdrawer == from.address_bytes(), - "sender does not match bridge withdrawer address; unauthorized" - ); - - return Ok(bridge_address.as_bytes()); - } - - // If the bridge address is not set, the sender must not be a bridge account. - if state - .is_a_bridge_account(from) - .await - .context("failed to establish whether the sender is a bridge account")? - { - bail!("sender cannot be a bridge address if bridge address is not set"); - } - - Ok(from) -} - -#[async_trait::async_trait] +#[async_trait] impl ActionHandler for action::Ics20Withdrawal { // TODO(https://github.com/astriaorg/astria/issues/1430): move checks to the `Ics20Withdrawal` parsing. async fn check_stateless(&self) -> Result<()> { @@ -257,6 +173,91 @@ impl ActionHandler for action::Ics20Withdrawal { } } +async fn create_ibc_packet_from_withdrawal( + withdrawal: &action::Ics20Withdrawal, + state: S, +) -> Result> { + let sender = if withdrawal.use_compat_address { + let ibc_compat_prefix = state.get_ibc_compat_prefix().await.context( + "need to construct bech32 compatible address for IBC communication but failed reading \ + required prefix from state", + )?; + withdrawal + .return_address() + .to_prefix(&ibc_compat_prefix) + .context("failed to convert the address to the bech32 compatible prefix")? + .to_format::() + .to_string() + } else { + withdrawal.return_address.to_string() + }; + let packet = FungibleTokenPacketData { + amount: withdrawal.amount.to_string(), + denom: withdrawal.denom.to_string(), + sender, + receiver: withdrawal.destination_chain_address.clone(), + memo: withdrawal.memo.clone(), + }; + + let serialized_packet_data = + serde_json::to_vec(&packet).context("failed to serialize fungible token packet as JSON")?; + + Ok(IBCPacket::new( + PortId::transfer(), + withdrawal.source_channel().clone(), + *withdrawal.timeout_height(), + withdrawal.timeout_time(), + serialized_packet_data, + )) +} + +/// Establishes the withdrawal target. +/// +/// The function returns the following addresses under the following conditions: +/// 1. `action.bridge_address` if `action.bridge_address` is set and `from` is its stored withdrawer +/// address. +/// 2. `from` if `action.bridge_address` is unset and `from` is *not* a bridge account. +/// +/// Errors if: +/// 1. Errors reading from DB +/// 2. `action.bridge_address` is set, but `from` is not the withdrawer address. +/// 3. `action.bridge_address` is unset, but `from` is a bridge account. +async fn establish_withdrawal_target<'a, S: StateRead>( + action: &'a action::Ics20Withdrawal, + state: &S, + from: &'a [u8; 20], +) -> Result<&'a [u8; 20]> { + // If the bridge address is set, the withdrawer on that address must match + // the from address. + if let Some(bridge_address) = &action.bridge_address { + let Some(withdrawer) = state + .get_bridge_account_withdrawer_address(bridge_address) + .await + .wrap_err("failed to get bridge withdrawer")? + else { + bail!("bridge address must have a withdrawer address set"); + }; + + ensure!( + &withdrawer == from.address_bytes(), + "sender does not match bridge withdrawer address; unauthorized" + ); + + return Ok(bridge_address.as_bytes()); + } + + // If the bridge address is not set, the sender must not be a bridge account. + if state + .is_a_bridge_account(from) + .await + .context("failed to establish whether the sender is a bridge account")? + { + bail!("sender cannot be a bridge address if bridge address is not set"); + } + + Ok(from) +} + fn is_source(source_port: &PortId, source_channel: &ChannelId, asset: &Denom) -> bool { if let Denom::TracePrefixed(trace) = asset { !trace.has_leading_port(source_port) || !trace.has_leading_channel(source_channel) @@ -267,27 +268,34 @@ fn is_source(source_port: &PortId, source_channel: &ChannelId, asset: &Denom) -> #[cfg(test)] mod tests { - use astria_core::primitive::v1::RollupId; + use astria_core::{ + primitive::v1::RollupId, + protocol::transaction::v1::action, + }; use cnidarium::StateDelta; use ibc_types::core::client::Height; - use super::*; use crate::{ + action_handler::impls::{ + ics20_withdrawal::establish_withdrawal_target, + test_utils::test_asset, + }, address::StateWriteExt as _, benchmark_and_test_utils::{ assert_eyre_error, astria_address, ASTRIA_PREFIX, }, + bridge::StateWriteExt as _, }; #[tokio::test] - async fn sender_is_withdrawal_target_if_bridge_is_not_set_and_sender_is_not_bridge() { + async fn withdrawal_target_is_sender_if_bridge_is_not_set_and_sender_is_not_bridge() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let state = StateDelta::new(snapshot); - let denom = "test".parse::().unwrap(); + let denom = test_asset(); let from = [1u8; 20]; let action = action::Ics20Withdrawal { amount: 1, @@ -312,7 +320,7 @@ mod tests { } #[tokio::test] - async fn sender_is_withdrawal_target_if_bridge_is_unset_but_sender_is_bridge() { + async fn withdrawal_target_is_sender_if_bridge_is_unset_but_sender_is_bridge() { let storage = cnidarium::TempStorage::new().await.unwrap(); let snapshot = storage.latest_snapshot(); let mut state = StateDelta::new(snapshot); @@ -331,7 +339,7 @@ mod tests { .put_bridge_account_withdrawer_address(&bridge_address, bridge_address) .unwrap(); - let denom = "test".parse::().unwrap(); + let denom = test_asset(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), @@ -361,21 +369,17 @@ mod tests { [1; 20] } - fn denom() -> Denom { - "test".parse().unwrap() - } - fn action() -> action::Ics20Withdrawal { action::Ics20Withdrawal { amount: 1, - denom: denom(), + denom: test_asset(), bridge_address: None, destination_chain_address: "test".to_string(), return_address: astria_address(&[1; 20]), timeout_height: Height::new(1, 1).unwrap(), timeout_time: 1, source_channel: "channel-0".to_string().parse().unwrap(), - fee_asset: denom(), + fee_asset: test_asset(), memo: String::new(), use_compat_address: false, } @@ -439,7 +443,7 @@ mod tests { .put_bridge_account_withdrawer_address(&bridge_address, withdrawer_address) .unwrap(); - let denom = "test".parse::().unwrap(); + let denom = test_asset(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), @@ -471,7 +475,7 @@ mod tests { // sender is not the withdrawer address, so must fail let not_bridge_address = [1u8; 20]; - let denom = "test".parse::().unwrap(); + let denom = test_asset(); let action = action::Ics20Withdrawal { amount: 1, denom: denom.clone(), diff --git a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs b/crates/astria-sequencer/src/action_handler/impls/init_bridge_account.rs similarity index 95% rename from crates/astria-sequencer/src/bridge/init_bridge_account_action.rs rename to crates/astria-sequencer/src/action_handler/impls/init_bridge_account.rs index 72d62f1b76..85e05de042 100644 --- a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs +++ b/crates/astria-sequencer/src/action_handler/impls/init_bridge_account.rs @@ -7,19 +7,20 @@ use astria_eyre::eyre::{ Result, WrapErr as _, }; +use async_trait::async_trait; use cnidarium::StateWrite; use crate::{ + action_handler::ActionHandler, address::StateReadExt as _, - app::ActionHandler, - bridge::state_ext::{ + bridge::{ StateReadExt as _, - StateWriteExt as _, + StateWriteExt, }, transaction::StateReadExt as _, }; -#[async_trait::async_trait] +#[async_trait] impl ActionHandler for InitBridgeAccount { async fn check_stateless(&self) -> Result<()> { Ok(()) diff --git a/crates/astria-sequencer/src/action_handler/impls/mod.rs b/crates/astria-sequencer/src/action_handler/impls/mod.rs new file mode 100644 index 0000000000..c273cc26ff --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/mod.rs @@ -0,0 +1,16 @@ +pub(crate) mod bridge_lock; +pub(crate) mod bridge_sudo_change; +pub(crate) mod bridge_unlock; +pub(crate) mod fee_asset_change; +pub(crate) mod fee_change; +pub(crate) mod ibc_relayer_change; +pub(crate) mod ibc_sudo_change; +pub(crate) mod ics20_withdrawal; +pub(crate) mod init_bridge_account; +pub(crate) mod rollup_data_submission; +pub(crate) mod sudo_address_change; +#[cfg(test)] +pub(crate) mod test_utils; +pub(crate) mod transaction; +pub(crate) mod transfer; +pub(crate) mod validator_update; diff --git a/crates/astria-sequencer/src/rollup_data/action.rs b/crates/astria-sequencer/src/action_handler/impls/rollup_data_submission.rs similarity index 87% rename from crates/astria-sequencer/src/rollup_data/action.rs rename to crates/astria-sequencer/src/action_handler/impls/rollup_data_submission.rs index cde9f8e591..3881e221bf 100644 --- a/crates/astria-sequencer/src/rollup_data/action.rs +++ b/crates/astria-sequencer/src/action_handler/impls/rollup_data_submission.rs @@ -3,11 +3,12 @@ use astria_eyre::eyre::{ ensure, Result, }; +use async_trait::async_trait; use cnidarium::StateWrite; -use crate::app::ActionHandler; +use crate::action_handler::ActionHandler; -#[async_trait::async_trait] +#[async_trait] impl ActionHandler for RollupDataSubmission { async fn check_stateless(&self) -> Result<()> { // TODO: do we want to place a maximum on the size of the data? diff --git a/crates/astria-sequencer/src/action_handler/impls/sudo_address_change.rs b/crates/astria-sequencer/src/action_handler/impls/sudo_address_change.rs new file mode 100644 index 0000000000..7c7cb2e5e2 --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/sudo_address_change.rs @@ -0,0 +1,48 @@ +use astria_core::protocol::transaction::v1::action::SudoAddressChange; +use astria_eyre::eyre::{ + ensure, + Result, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; + +use crate::{ + action_handler::ActionHandler, + address::StateReadExt as _, + authority::{ + StateReadExt as _, + StateWriteExt as _, + }, + transaction::StateReadExt as _, +}; + +#[async_trait] +impl ActionHandler for SudoAddressChange { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + /// check that the signer of the transaction is the current sudo address, + /// as only that address can change the sudo address + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + state + .ensure_base_prefix(&self.new_address) + .await + .wrap_err("desired new sudo address has an unsupported prefix")?; + // ensure signer is the valid `sudo` key in state + let sudo_address = state + .get_sudo_address() + .await + .wrap_err("failed to get sudo address from state")?; + ensure!(sudo_address == from, "signer is not the sudo key"); + state + .put_sudo_address(self.new_address) + .wrap_err("failed to put sudo address in state")?; + Ok(()) + } +} diff --git a/crates/astria-sequencer/src/action_handler/impls/test_utils.rs b/crates/astria-sequencer/src/action_handler/impls/test_utils.rs new file mode 100644 index 0000000000..55ec91b098 --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/test_utils.rs @@ -0,0 +1,5 @@ +use astria_core::primitive::v1::asset; + +pub(super) fn test_asset() -> asset::Denom { + "test".parse().unwrap() +} diff --git a/crates/astria-sequencer/src/action_handler/impls/transaction.rs b/crates/astria-sequencer/src/action_handler/impls/transaction.rs new file mode 100644 index 0000000000..29df7cc10a --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/transaction.rs @@ -0,0 +1,281 @@ +use std::fmt; + +use astria_core::protocol::transaction::v1::{ + Action, + Transaction, +}; +use astria_eyre::{ + anyhow_to_eyre, + eyre::{ + ensure, + Context as _, + OptionExt as _, + Result, + }, +}; +use cnidarium::StateWrite; + +use crate::{ + accounts::{ + StateReadExt as _, + StateWriteExt as _, + }, + action_handler::ActionHandler, + app::StateReadExt as _, + bridge::{ + StateReadExt as _, + StateWriteExt, + }, + fees::FeeHandler, + ibc::{ + host_interface::AstriaHost, + StateReadExt as _, + }, + transaction::{ + check_balance_for_total_fees_and_transfers, + StateWriteExt as _, + }, +}; + +#[derive(Debug)] +pub(crate) struct InvalidChainId(pub(crate) String); + +impl fmt::Display for InvalidChainId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "provided chain id {} does not match expected chain id", + self.0, + ) + } +} + +impl std::error::Error for InvalidChainId {} + +#[derive(Debug)] +pub(crate) struct InvalidNonce(pub(crate) u32); + +impl fmt::Display for InvalidNonce { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "provided nonce {} does not match expected next nonce", + self.0, + ) + } +} + +impl std::error::Error for InvalidNonce {} + +#[async_trait::async_trait] +impl ActionHandler for Transaction { + async fn check_stateless(&self) -> Result<()> { + ensure!(!self.actions().is_empty(), "must have at least one action"); + + for action in self.actions() { + match action { + Action::Transfer(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for Transfer action")?, + Action::RollupDataSubmission(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for RollupDataSubmission action")?, + Action::ValidatorUpdate(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for ValidatorUpdate action")?, + Action::SudoAddressChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for SudoAddressChange action")?, + Action::IbcSudoChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for IbcSudoChange action")?, + Action::FeeChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for FeeChange action")?, + Action::Ibc(act) => { + let action = act + .clone() + .with_handler::(); + action + .check_stateless(()) + .await + .map_err(anyhow_to_eyre) + .wrap_err("stateless check failed for Ibc action")?; + } + Action::Ics20Withdrawal(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for Ics20Withdrawal action")?, + Action::IbcRelayerChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for IbcRelayerChange action")?, + Action::FeeAssetChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for FeeAssetChange action")?, + Action::InitBridgeAccount(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for InitBridgeAccount action")?, + Action::BridgeLock(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for BridgeLock action")?, + Action::BridgeUnlock(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for BridgeUnlock action")?, + Action::BridgeSudoChange(act) => act + .check_stateless() + .await + .wrap_err("stateless check failed for BridgeSudoChange action")?, + } + } + Ok(()) + } + + // FIXME (https://github.com/astriaorg/astria/issues/1584): because most lines come from delegating (and error wrapping) to the + // individual actions. This could be tidied up by implementing `ActionHandler for Action` + // and letting it delegate. + #[expect(clippy::too_many_lines, reason = "should be refactored")] + async fn check_and_execute(&self, mut state: S) -> Result<()> { + // Add the current signed transaction into the ephemeral state in case + // downstream actions require access to it. + // XXX: This must be deleted at the end of `check_stateful`. + let mut transaction_context = state.put_transaction_context(self); + + // Transactions must match the chain id of the node. + let chain_id = state.get_chain_id().await?; + ensure!( + self.chain_id() == chain_id.as_str(), + InvalidChainId(self.chain_id().to_string()) + ); + + // Nonce should be equal to the number of executed transactions before this tx. + // First tx has nonce 0. + let curr_nonce = state + .get_account_nonce(self.address_bytes()) + .await + .wrap_err("failed to get nonce for transaction signer")?; + ensure!(curr_nonce == self.nonce(), InvalidNonce(self.nonce())); + + // Should have enough balance to cover all actions. + check_balance_for_total_fees_and_transfers(self, &state) + .await + .wrap_err("failed to check balance for total fees and transfers")?; + + if state + .get_bridge_account_rollup_id(&self) + .await + .wrap_err("failed to check account rollup id")? + .is_some() + { + state + .put_last_transaction_id_for_bridge_account( + &self, + transaction_context.transaction_id, + ) + .wrap_err("failed to put last transaction id for bridge account")?; + } + + let from_nonce = state + .get_account_nonce(&self) + .await + .wrap_err("failed getting nonce of transaction signer")?; + let next_nonce = from_nonce + .checked_add(1) + .ok_or_eyre("overflow occurred incrementing stored nonce")?; + state + .put_account_nonce(&self, next_nonce) + .wrap_err("failed updating `from` nonce")?; + + // FIXME: this should create one span per `check_and_execute` + for (i, action) in (0..).zip(self.actions().iter()) { + transaction_context.position_in_transaction = i; + state.put_transaction_context(transaction_context); + + match action { + Action::Transfer(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing transfer action failed")?, + Action::RollupDataSubmission(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing sequence action failed")?, + Action::ValidatorUpdate(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing validor update")?, + Action::SudoAddressChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing sudo address change failed")?, + Action::IbcSudoChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing ibc sudo change failed")?, + Action::FeeChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("executing fee change failed")?, + Action::Ibc(act) => { + // FIXME: this check should be moved to check_and_execute, as it now has + // access to the the signer through state. However, what's the correct + // ibc AppHandler call to do it? Can we just update one of the trait methods + // of crate::ibc::ics20_transfer::Ics20Transfer? + ensure!( + state + .is_ibc_relayer(self) + .await + .wrap_err("failed to check if address is IBC relayer")?, + "only IBC sudo address can execute IBC actions" + ); + let action = act + .clone() + .with_handler::(); + action + .check_and_execute(&mut state) + .await + .map_err(anyhow_to_eyre) + .wrap_err("failed executing ibc action")?; + } + Action::Ics20Withdrawal(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing ics20 withdrawal")?, + Action::IbcRelayerChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing ibc relayer change")?, + Action::FeeAssetChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing fee asseet change")?, + Action::InitBridgeAccount(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing init bridge account")?, + Action::BridgeLock(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing bridge lock")?, + Action::BridgeUnlock(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing bridge unlock")?, + Action::BridgeSudoChange(act) => check_execute_and_pay_fees(act, &mut state) + .await + .wrap_err("failed executing bridge sudo change")?, + } + } + + // XXX: Delete the current transaction data from the ephemeral state. + state.delete_current_transaction_context(); + Ok(()) + } +} + +async fn check_execute_and_pay_fees( + action: &T, + mut state: S, +) -> Result<()> { + action.check_and_execute(&mut state).await?; + action.check_and_pay_fees(&mut state).await?; + Ok(()) +} diff --git a/crates/astria-sequencer/src/action_handler/impls/transfer.rs b/crates/astria-sequencer/src/action_handler/impls/transfer.rs new file mode 100644 index 0000000000..27d955bca3 --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/transfer.rs @@ -0,0 +1,46 @@ +use astria_core::protocol::transaction::v1::action::Transfer; +use astria_eyre::eyre::{ + ensure, + Result, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; + +use crate::{ + action_handler::{ + check_transfer, + execute_transfer, + ActionHandler, + }, + bridge::StateReadExt as _, + transaction::StateReadExt as _, +}; + +#[async_trait] +impl ActionHandler for Transfer { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, state: S) -> Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + + ensure!( + state + .get_bridge_account_rollup_id(&from) + .await + .wrap_err("failed to get bridge account rollup id")? + .is_none(), + "cannot transfer out of bridge account; BridgeUnlock must be used", + ); + + check_transfer(self, &from, &state).await?; + execute_transfer(self, &from, state).await?; + + Ok(()) + } +} diff --git a/crates/astria-sequencer/src/action_handler/impls/validator_update.rs b/crates/astria-sequencer/src/action_handler/impls/validator_update.rs new file mode 100644 index 0000000000..4bd0d72c3c --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/impls/validator_update.rs @@ -0,0 +1,67 @@ +use astria_core::protocol::transaction::v1::action::ValidatorUpdate; +use astria_eyre::eyre::{ + bail, + ensure, + Result, + WrapErr as _, +}; +use async_trait::async_trait; +use cnidarium::StateWrite; + +use crate::{ + action_handler::ActionHandler, + authority::{ + StateReadExt as _, + StateWriteExt as _, + }, + transaction::StateReadExt as _, +}; + +#[async_trait] +impl ActionHandler for ValidatorUpdate { + async fn check_stateless(&self) -> Result<()> { + Ok(()) + } + + async fn check_and_execute(&self, mut state: S) -> Result<()> { + let from = state + .get_transaction_context() + .expect("transaction source must be present in state when executing an action") + .address_bytes(); + // ensure signer is the valid `sudo` key in state + let sudo_address = state + .get_sudo_address() + .await + .wrap_err("failed to get sudo address from state")?; + ensure!(sudo_address == from, "signer is not the sudo key"); + + // ensure that we're not removing the last validator or a validator + // that doesn't exist, these both cause issues in cometBFT + if self.power == 0 { + let validator_set = state + .get_validator_set() + .await + .wrap_err("failed to get validator set from state")?; + // check that validator exists + if validator_set + .get(self.verification_key.address_bytes()) + .is_none() + { + bail!("cannot remove a non-existing validator"); + } + // check that this is not the only validator, cannot remove the last one + ensure!(validator_set.len() != 1, "cannot remove the last validator"); + } + + // add validator update in non-consensus state to be used in end_block + let mut validator_updates = state + .get_validator_updates() + .await + .wrap_err("failed getting validator updates from state")?; + validator_updates.push_update(self.clone()); + state + .put_validator_updates(validator_updates) + .wrap_err("failed to put validator updates in state")?; + Ok(()) + } +} diff --git a/crates/astria-sequencer/src/action_handler/mod.rs b/crates/astria-sequencer/src/action_handler/mod.rs new file mode 100644 index 0000000000..09bde22b0e --- /dev/null +++ b/crates/astria-sequencer/src/action_handler/mod.rs @@ -0,0 +1,94 @@ +//! Contains the `ActionHandler` trait, which houses all stateless/stateful checks and execution, as +//! well as all of its implementations. + +use astria_core::protocol::transaction::v1::action::Transfer; +use astria_eyre::eyre::{ + ensure, + Result, + WrapErr as _, +}; +use cnidarium::{ + StateRead, + StateWrite, +}; + +use crate::{ + accounts::{ + AddressBytes, + StateReadExt as _, + StateWriteExt as _, + }, + address::StateReadExt as _, +}; + +pub(crate) mod impls; + +/// This trait is a verbatim copy of `cnidarium_component::ActionHandler`. +/// +/// It's duplicated here because all actions are foreign types, forbidding +/// the implementation of [`cnidarium_component::ActionHandler`][1] for +/// these types due to Rust orphan rules. +/// +/// [1]: https://github.com/penumbra-zone/penumbra/blob/14959350abcb8cfbf33f9aedc7463fccfd8e3f9f/crates/cnidarium-component/src/action_handler.rs#L30 +#[async_trait::async_trait] +pub(crate) trait ActionHandler { + // Commenting out for the time being as this is currently not being used. Leaving this in + // for reference as this is copied from cnidarium_component. + // ``` + // type CheckStatelessContext: Clone + Send + Sync + 'static; + // async fn check_stateless(&self, context: Self::CheckStatelessContext) -> anyhow::Result<()>; + // async fn check_historical(&self, _state: Arc) -> anyhow::Result<()> { + // Ok(()) + // } + // ``` + + async fn check_stateless(&self) -> astria_eyre::eyre::Result<()>; + + async fn check_and_execute(&self, mut state: S) + -> astria_eyre::eyre::Result<()>; +} + +async fn execute_transfer( + action: &Transfer, + from: &TAddress, + mut state: S, +) -> Result<()> +where + S: StateWrite, + TAddress: AddressBytes, +{ + let from = from.address_bytes(); + state + .decrease_balance(from, &action.asset, action.amount) + .await + .wrap_err("failed decreasing `from` account balance")?; + state + .increase_balance(&action.to, &action.asset, action.amount) + .await + .wrap_err("failed increasing `to` account balance")?; + + Ok(()) +} + +async fn check_transfer(action: &Transfer, from: &TAddress, state: &S) -> Result<()> +where + S: StateRead, + TAddress: AddressBytes, +{ + state.ensure_base_prefix(&action.to).await.wrap_err( + "failed ensuring that the destination address matches the permitted base prefix", + )?; + + let transfer_asset = &action.asset; + + let from_transfer_balance = state + .get_account_balance(from, transfer_asset) + .await + .wrap_err("failed to get account balance in transfer check")?; + ensure!( + from_transfer_balance >= action.amount, + "insufficient funds for transfer" + ); + + Ok(()) +} diff --git a/crates/astria-sequencer/src/app/action_handler.rs b/crates/astria-sequencer/src/app/action_handler.rs deleted file mode 100644 index 1ef67ea4bb..0000000000 --- a/crates/astria-sequencer/src/app/action_handler.rs +++ /dev/null @@ -1,26 +0,0 @@ -use cnidarium::StateWrite; - -/// This trait is a verbatim copy of `cnidarium_component::ActionHandler`. -/// -/// It's duplicated here because all actions are foreign types, forbidding -/// the implementation of [`cnidarium_component::ActionHandler`][1] for -/// these types due to Rust orphan rules. -/// -/// [1]: https://github.com/penumbra-zone/penumbra/blob/14959350abcb8cfbf33f9aedc7463fccfd8e3f9f/crates/cnidarium-component/src/action_handler.rs#L30 -#[async_trait::async_trait] -pub(crate) trait ActionHandler { - // Commenting out for the time being as this is currently not being used. Leaving this in - // for reference as this is copied from cnidarium_component. - // ``` - // type CheckStatelessContext: Clone + Send + Sync + 'static; - // async fn check_stateless(&self, context: Self::CheckStatelessContext) -> anyhow::Result<()>; - // async fn check_historical(&self, _state: Arc) -> anyhow::Result<()> { - // Ok(()) - // } - // ``` - - async fn check_stateless(&self) -> astria_eyre::eyre::Result<()>; - - async fn check_and_execute(&self, mut state: S) - -> astria_eyre::eyre::Result<()>; -} diff --git a/crates/astria-sequencer/src/app/benchmark_and_test_utils.rs b/crates/astria-sequencer/src/app/benchmark_and_test_utils.rs index 6c46e5c6e9..e2852fd225 100644 --- a/crates/astria-sequencer/src/app/benchmark_and_test_utils.rs +++ b/crates/astria-sequencer/src/app/benchmark_and_test_utils.rs @@ -60,7 +60,7 @@ pub(crate) const TED_ADDRESS: &str = "4c4f91d8a918357ab5f6f19c1e179968fc39bb44"; pub(crate) fn address_prefixes() -> AddressPrefixes { AddressPrefixes::try_from_raw( - astria_core::generated::protocol::genesis::v1::AddressPrefixes { + astria_core::generated::astria::protocol::genesis::v1::AddressPrefixes { base: crate::benchmark_and_test_utils::ASTRIA_PREFIX.into(), ibc_compat: crate::benchmark_and_test_utils::ASTRIA_COMPAT_PREFIX.into(), }, @@ -129,9 +129,9 @@ pub(crate) fn default_fees() -> astria_core::protocol::genesis::v1::GenesisFees } } -pub(crate) fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1::GenesisAppState -{ - use astria_core::generated::protocol::genesis::v1::{ +pub(crate) fn proto_genesis_state() +-> astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { + use astria_core::generated::astria::protocol::genesis::v1::{ GenesisAppState, IbcParameters, }; diff --git a/crates/astria-sequencer/src/app/benchmarks.rs b/crates/astria-sequencer/src/app/benchmarks.rs index e40f5e2b1b..2267aa97b1 100644 --- a/crates/astria-sequencer/src/app/benchmarks.rs +++ b/crates/astria-sequencer/src/app/benchmarks.rs @@ -61,7 +61,7 @@ impl Fixture { .collect::>(); let first_address = accounts.first().cloned().unwrap().address; let genesis_state = GenesisAppState::try_from_raw( - astria_core::generated::protocol::genesis::v1::GenesisAppState { + astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { accounts, authority_sudo_address: first_address.clone(), ibc_sudo_address: first_address.clone(), diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index dc6bfba8f2..0e2b823f5c 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -1,4 +1,3 @@ -mod action_handler; #[cfg(any(test, feature = "benchmark"))] pub(crate) mod benchmark_and_test_utils; #[cfg(feature = "benchmark")] @@ -22,7 +21,7 @@ use std::{ }; use astria_core::{ - generated::protocol::transaction::v1 as raw, + generated::astria::protocol::transaction::v1 as raw, primitive::v1::TRANSACTION_ID_LEN, protocol::{ abci::AbciErrorCode, @@ -82,15 +81,16 @@ use tracing::{ instrument, }; -pub(crate) use self::{ - action_handler::ActionHandler, - state_ext::{ - StateReadExt, - StateWriteExt, - }, +pub(crate) use self::state_ext::{ + StateReadExt, + StateWriteExt, }; use crate::{ accounts::component::AccountsComponent, + action_handler::{ + impls::transaction::InvalidNonce, + ActionHandler as _, + }, address::StateWriteExt as _, assets::StateWriteExt as _, authority::{ @@ -124,7 +124,6 @@ use crate::{ GeneratedCommitments, }, }, - transaction::InvalidNonce, }; // ephemeral store key for the cache of results of executing of transactions in `prepare_proposal`. diff --git a/crates/astria-sequencer/src/app/tests_app/mod.rs b/crates/astria-sequencer/src/app/tests_app/mod.rs index 5e37a29117..cddf0a1abd 100644 --- a/crates/astria-sequencer/src/app/tests_app/mod.rs +++ b/crates/astria-sequencer/src/app/tests_app/mod.rs @@ -275,7 +275,7 @@ async fn app_transfer_block_fees_to_sudo() { #[tokio::test] async fn app_create_sequencer_block_with_sequenced_data_and_deposits() { use astria_core::{ - generated::sequencerblock::v1::RollupData as RawRollupData, + generated::astria::sequencerblock::v1::RollupData as RawRollupData, sequencerblock::v1::block::RollupData, }; diff --git a/crates/astria-sequencer/src/app/tests_block_fees.rs b/crates/astria-sequencer/src/app/tests_block_fees.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/astria-sequencer/src/app/tests_breaking_changes.rs b/crates/astria-sequencer/src/app/tests_breaking_changes.rs index e300b076f9..51dea33dce 100644 --- a/crates/astria-sequencer/src/app/tests_breaking_changes.rs +++ b/crates/astria-sequencer/src/app/tests_breaking_changes.rs @@ -15,7 +15,10 @@ use std::{ }; use astria_core::{ - primitive::v1::RollupId, + primitive::v1::{ + Address, + RollupId, + }, protocol::{ genesis::v1::Account, transaction::v1::{ @@ -187,10 +190,24 @@ async fn app_execute_transaction_with_every_action_snapshot() { }); acc.into_iter().map(Protobuf::into_raw).collect() }; - let genesis_state = astria_core::generated::protocol::genesis::v1::GenesisAppState { + let genesis_state = astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { accounts, - authority_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), - ibc_sudo_address: Some(alice.try_address(ASTRIA_PREFIX).unwrap().to_raw()), + authority_sudo_address: Some( + Address::builder() + .prefix(ASTRIA_PREFIX) + .array(alice.address_bytes()) + .try_build() + .unwrap() + .to_raw(), + ), + ibc_sudo_address: Some( + Address::builder() + .prefix(ASTRIA_PREFIX) + .array(alice.address_bytes()) + .try_build() + .unwrap() + .to_raw(), + ), ..proto_genesis_state() } .try_into() diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 76b1a1ae8e..011d789270 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -4,6 +4,7 @@ use astria_core::{ crypto::SigningKey, primitive::v1::{ asset, + Address, RollupId, }, protocol::{ @@ -39,6 +40,10 @@ use cnidarium::{ use super::test_utils::get_alice_signing_key; use crate::{ accounts::StateReadExt as _, + action_handler::{ + impls::transaction::InvalidChainId, + ActionHandler as _, + }, app::{ benchmark_and_test_utils::{ BOB_ADDRESS, @@ -48,7 +53,7 @@ use crate::{ get_bridge_signing_key, initialize_app, }, - ActionHandler as _, + InvalidNonce, }, authority::StateReadExt as _, benchmark_and_test_utils::{ @@ -68,24 +73,24 @@ use crate::{ }, ibc::StateReadExt as _, test_utils::calculate_rollup_data_submission_fee_from_state, - transaction::{ - InvalidChainId, - InvalidNonce, - }, utils::create_deposit_event, }; -fn proto_genesis_state() -> astria_core::generated::protocol::genesis::v1::GenesisAppState { - astria_core::generated::protocol::genesis::v1::GenesisAppState { +fn proto_genesis_state() -> astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { + astria_core::generated::astria::protocol::genesis::v1::GenesisAppState { authority_sudo_address: Some( - get_alice_signing_key() - .try_address(ASTRIA_PREFIX) + Address::builder() + .prefix(ASTRIA_PREFIX) + .array(get_alice_signing_key().address_bytes()) + .try_build() .unwrap() .to_raw(), ), ibc_sudo_address: Some( - get_alice_signing_key() - .try_address(ASTRIA_PREFIX) + Address::builder() + .prefix(ASTRIA_PREFIX) + .array(get_alice_signing_key().address_bytes()) + .try_build() .unwrap() .to_raw(), ), diff --git a/crates/astria-sequencer/src/authority/action.rs b/crates/astria-sequencer/src/authority/action.rs index 5082134fac..e69de29bb2 100644 --- a/crates/astria-sequencer/src/authority/action.rs +++ b/crates/astria-sequencer/src/authority/action.rs @@ -1,130 +0,0 @@ -use astria_core::protocol::transaction::v1::action::{ - IbcSudoChange, - SudoAddressChange, - ValidatorUpdate, -}; -use astria_eyre::eyre::{ - bail, - ensure, - Result, - WrapErr as _, -}; -use cnidarium::StateWrite; - -use crate::{ - address::StateReadExt as _, - app::ActionHandler, - authority::{ - StateReadExt as _, - StateWriteExt as _, - }, - ibc::StateWriteExt as _, - transaction::StateReadExt as _, -}; - -#[async_trait::async_trait] -impl ActionHandler for ValidatorUpdate { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - async fn check_and_execute(&self, mut state: S) -> Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - // ensure signer is the valid `sudo` key in state - let sudo_address = state - .get_sudo_address() - .await - .wrap_err("failed to get sudo address from state")?; - ensure!(sudo_address == from, "signer is not the sudo key"); - - // ensure that we're not removing the last validator or a validator - // that doesn't exist, these both cause issues in cometBFT - if self.power == 0 { - let validator_set = state - .get_validator_set() - .await - .wrap_err("failed to get validator set from state")?; - // check that validator exists - if validator_set - .get(self.verification_key.address_bytes()) - .is_none() - { - bail!("cannot remove a non-existing validator"); - } - // check that this is not the only validator, cannot remove the last one - ensure!(validator_set.len() != 1, "cannot remove the last validator"); - } - - // add validator update in non-consensus state to be used in handle_post_tx_execution - let mut validator_updates = state - .get_validator_updates() - .await - .wrap_err("failed getting validator updates from state")?; - validator_updates.push_update(self.clone()); - state - .put_validator_updates(validator_updates) - .wrap_err("failed to put validator updates in state")?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl ActionHandler for SudoAddressChange { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - /// check that the signer of the transaction is the current sudo address, - /// as only that address can change the sudo address - async fn check_and_execute(&self, mut state: S) -> Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - state - .ensure_base_prefix(&self.new_address) - .await - .wrap_err("desired new sudo address has an unsupported prefix")?; - // ensure signer is the valid `sudo` key in state - let sudo_address = state - .get_sudo_address() - .await - .wrap_err("failed to get sudo address from state")?; - ensure!(sudo_address == from, "signer is not the sudo key"); - state - .put_sudo_address(self.new_address) - .wrap_err("failed to put sudo address in state")?; - Ok(()) - } -} - -#[async_trait::async_trait] -impl ActionHandler for IbcSudoChange { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - async fn check_and_execute(&self, mut state: S) -> Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - state - .ensure_base_prefix(&self.new_address) - .await - .wrap_err("desired new ibc sudo address has an unsupported prefix")?; - // ensure signer is the valid `sudo` key in state - let sudo_address = state - .get_sudo_address() - .await - .wrap_err("failed to get sudo address from state")?; - ensure!(sudo_address == from, "signer is not the sudo key"); - state - .put_ibc_sudo_address(self.new_address) - .wrap_err("failed to put ibc sudo address in state")?; - Ok(()) - } -} diff --git a/crates/astria-sequencer/src/authority/mod.rs b/crates/astria-sequencer/src/authority/mod.rs index 22d8a00784..0431f4295d 100644 --- a/crates/astria-sequencer/src/authority/mod.rs +++ b/crates/astria-sequencer/src/authority/mod.rs @@ -1,4 +1,3 @@ -mod action; pub(crate) mod component; mod state_ext; pub(crate) mod storage; diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs deleted file mode 100644 index 3a3a4218ea..0000000000 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ /dev/null @@ -1,96 +0,0 @@ -use astria_core::{ - protocol::transaction::v1::action::{ - BridgeLock, - Transfer, - }, - sequencerblock::v1::block::Deposit, -}; -use astria_eyre::eyre::{ - ensure, - OptionExt as _, - Result, - WrapErr as _, -}; -use cnidarium::StateWrite; - -use crate::{ - accounts::action::{ - check_transfer, - execute_transfer, - }, - address::StateReadExt as _, - app::ActionHandler, - bridge::{ - StateReadExt as _, - StateWriteExt as _, - }, - transaction::StateReadExt as _, - utils::create_deposit_event, -}; - -#[async_trait::async_trait] -impl ActionHandler for BridgeLock { - async fn check_stateless(&self) -> Result<()> { - Ok(()) - } - - async fn check_and_execute(&self, mut state: S) -> Result<()> { - let from = state - .get_transaction_context() - .expect("transaction source must be present in state when executing an action") - .address_bytes(); - state - .ensure_base_prefix(&self.to) - .await - .wrap_err("failed check for base prefix of destination address")?; - // ensure the recipient is a bridge account. - let rollup_id = state - .get_bridge_account_rollup_id(&self.to) - .await - .wrap_err("failed to get bridge account rollup id")? - .ok_or_eyre("bridge lock must be sent to a bridge account")?; - - let allowed_asset = state - .get_bridge_account_ibc_asset(&self.to) - .await - .wrap_err("failed to get bridge account asset ID")?; - ensure!( - allowed_asset == self.asset.to_ibc_prefixed(), - "asset ID is not authorized for transfer to bridge account", - ); - - let source_transaction_id = state - .get_transaction_context() - .expect("current source should be set before executing action") - .transaction_id; - let source_action_index = state - .get_transaction_context() - .expect("current source should be set before executing action") - .source_action_index; - - let deposit = Deposit { - bridge_address: self.to, - rollup_id, - amount: self.amount, - asset: self.asset.clone(), - destination_chain_address: self.destination_chain_address.clone(), - source_transaction_id, - source_action_index, - }; - let deposit_abci_event = create_deposit_event(&deposit); - - let transfer_action = Transfer { - to: self.to, - asset: self.asset.clone(), - amount: self.amount, - fee_asset: self.fee_asset.clone(), - }; - - check_transfer(&transfer_action, &from, &state).await?; - execute_transfer(&transfer_action, &from, &mut state).await?; - - state.cache_deposit_event(deposit); - state.record(deposit_abci_event); - Ok(()) - } -} diff --git a/crates/astria-sequencer/src/bridge/mod.rs b/crates/astria-sequencer/src/bridge/mod.rs index 5aec65349a..8531007ab1 100644 --- a/crates/astria-sequencer/src/bridge/mod.rs +++ b/crates/astria-sequencer/src/bridge/mod.rs @@ -1,7 +1,3 @@ -mod bridge_lock_action; -mod bridge_sudo_change_action; -mod bridge_unlock_action; -pub(crate) mod init_bridge_account_action; pub(crate) mod query; mod state_ext; pub(crate) mod storage; diff --git a/crates/astria-sequencer/src/bridge/query.rs b/crates/astria-sequencer/src/bridge/query.rs index 2e3ff25ba2..ed5d1c5177 100644 --- a/crates/astria-sequencer/src/bridge/query.rs +++ b/crates/astria-sequencer/src/bridge/query.rs @@ -293,7 +293,7 @@ fn preprocess_request(params: &[(String, String)]) -> Result( .get_transaction_context() .expect("transaction source must be present in state when executing an action"); let from = transaction_context.address_bytes(); - let source_action_index = transaction_context.source_action_index; + let position_in_transaction = transaction_context.position_in_transaction; ensure!( state @@ -348,9 +347,7 @@ async fn check_and_pay_fees( .wrap_err("failed to check allowed fee assets in state")?, "invalid fee asset", ); - state - .add_fee_to_block_fees::<_, T>(fee_asset, total_fees, source_action_index) - .wrap_err("failed to add to block fees")?; + state.add_fee_to_block_fees::<_, T>(fee_asset, total_fees, position_in_transaction); state .decrease_balance(&from, fee_asset, total_fees) .await diff --git a/crates/astria-sequencer/src/fees/query.rs b/crates/astria-sequencer/src/fees/query.rs index 6342843668..b2a74e18e6 100644 --- a/crates/astria-sequencer/src/fees/query.rs +++ b/crates/astria-sequencer/src/fees/query.rs @@ -4,7 +4,7 @@ use std::{ }; use astria_core::{ - generated::protocol::transaction::v1::TransactionBody as RawBody, + generated::astria::protocol::transaction::v1::TransactionBody as RawBody, primitive::v1::asset::{ self, Denom, @@ -42,6 +42,7 @@ use tendermint::abci::{ Code, }; use tokio::{ + join, sync::OnceCell, try_join, }; @@ -134,6 +135,44 @@ pub(crate) async fn allowed_fee_assets_request( } } +pub(crate) async fn components( + storage: Storage, + request: request::Query, + _params: Vec<(String, String)>, +) -> response::Query { + let snapshot = storage.latest_snapshot(); + + let height = async { + snapshot + .get_block_height() + .await + .wrap_err("failed getting block height") + }; + let fee_components = get_all_fee_components(&snapshot).map(Ok); + let (height, fee_components) = match try_join!(height, fee_components) { + Ok(vals) => vals, + Err(err) => { + return response::Query { + code: Code::Err(AbciErrorCode::INTERNAL_ERROR.value()), + info: AbciErrorCode::INTERNAL_ERROR.info(), + log: format!("{err:#}"), + ..response::Query::default() + }; + } + }; + + let height = tendermint::block::Height::try_from(height).expect("height must fit into an i64"); + response::Query { + code: tendermint::abci::Code::Ok, + key: request.path.into_bytes().into(), + value: serde_json::to_vec(&fee_components) + .expect("object does not contain keys that don't map to json keys") + .into(), + height, + ..response::Query::default() + } +} + pub(crate) async fn transaction_fee_request( storage: Storage, request: request::Query, @@ -458,3 +497,138 @@ fn preprocess_fees_request(request: &request::Query) -> Result From>> for FetchResult +where + FeeComponent: From, +{ + fn from(value: eyre::Result>) -> Self { + match value { + Ok(Some(val)) => Self::Component(val.into()), + Ok(None) => Self::Missing("not set"), + Err(err) => Self::Err(err.to_string()), + } + } +} + +async fn get_all_fee_components(state: &S) -> AllFeeComponents { + let ( + transfer, + rollup_data_submission, + ics20_withdrawal, + init_bridge_account, + bridge_lock, + bridge_unlock, + bridge_sudo_change, + ibc_relay, + validator_update, + fee_asset_change, + fee_change, + ibc_relayer_change, + sudo_address_change, + ibc_sudo_change, + ) = join!( + state.get_transfer_fees().map(fetch_to_response), + state + .get_rollup_data_submission_fees() + .map(fetch_to_response), + state.get_ics20_withdrawal_fees().map(fetch_to_response), + state.get_init_bridge_account_fees().map(fetch_to_response), + state.get_bridge_lock_fees().map(fetch_to_response), + state.get_bridge_unlock_fees().map(fetch_to_response), + state.get_bridge_sudo_change_fees().map(fetch_to_response), + state.get_ibc_relay_fees().map(fetch_to_response), + state.get_validator_update_fees().map(fetch_to_response), + state.get_fee_asset_change_fees().map(fetch_to_response), + state.get_fee_change_fees().map(fetch_to_response), + state.get_ibc_relayer_change_fees().map(fetch_to_response), + state.get_sudo_address_change_fees().map(fetch_to_response), + state.get_ibc_sudo_change_fees().map(fetch_to_response), + ); + AllFeeComponents { + transfer, + rollup_data_submission, + ics20_withdrawal, + init_bridge_account, + bridge_lock, + bridge_unlock, + bridge_sudo_change, + ibc_relay, + validator_update, + fee_asset_change, + fee_change, + ibc_relayer_change, + sudo_address_change, + ibc_sudo_change, + } +} + +fn fetch_to_response(value: eyre::Result>) -> FetchResult +where + FeeComponent: From, +{ + value.into() +} + +#[derive(serde::Serialize)] +struct FeeComponent { + base: u128, + multiplier: u128, +} + +macro_rules! impl_from_domain_fee_component { + ( $($dt:ty ),* $(,)?) => { + $( + impl From<$dt> for FeeComponent { + fn from(val: $dt) -> Self { + Self { + base: val.base, + multiplier: val.multiplier, + } + } + } + )* + } +} +impl_from_domain_fee_component!( + astria_core::protocol::fees::v1::BridgeLockFeeComponents, + astria_core::protocol::fees::v1::BridgeSudoChangeFeeComponents, + astria_core::protocol::fees::v1::BridgeUnlockFeeComponents, + astria_core::protocol::fees::v1::FeeAssetChangeFeeComponents, + astria_core::protocol::fees::v1::FeeChangeFeeComponents, + astria_core::protocol::fees::v1::IbcRelayFeeComponents, + astria_core::protocol::fees::v1::IbcRelayerChangeFeeComponents, + astria_core::protocol::fees::v1::IbcSudoChangeFeeComponents, + astria_core::protocol::fees::v1::Ics20WithdrawalFeeComponents, + astria_core::protocol::fees::v1::InitBridgeAccountFeeComponents, + astria_core::protocol::fees::v1::RollupDataSubmissionFeeComponents, + astria_core::protocol::fees::v1::SudoAddressChangeFeeComponents, + astria_core::protocol::fees::v1::TransferFeeComponents, + astria_core::protocol::fees::v1::ValidatorUpdateFeeComponents, +); diff --git a/crates/astria-sequencer/src/fees/state_ext.rs b/crates/astria-sequencer/src/fees/state_ext.rs index 5035fc6ef5..d3a74ba65f 100644 --- a/crates/astria-sequencer/src/fees/state_ext.rs +++ b/crates/astria-sequencer/src/fees/state_ext.rs @@ -382,9 +382,8 @@ pub(crate) trait StateWriteExt: StateWrite { &mut self, asset: &'a TAsset, amount: u128, - source_action_index: u64, - ) -> Result<()> - where + position_in_transaction: u64, + ) where TAsset: Sync + std::fmt::Display, asset::IbcPrefixed: From<&'a TAsset>, { @@ -394,7 +393,7 @@ pub(crate) trait StateWriteExt: StateWrite { action_name: T::full_name(), asset: asset::IbcPrefixed::from(asset).into(), amount, - source_action_index, + position_in_transaction, }; // Fee ABCI event recorded for reporting @@ -409,7 +408,6 @@ pub(crate) trait StateWriteExt: StateWrite { }; self.object_put(keys::BLOCK, new_fees); - Ok(()) } #[instrument(skip_all)] @@ -574,7 +572,10 @@ fn construct_tx_fee_event(fee: &Fee) -> Event { ("actionName", fee.action_name.to_string()), ("asset", fee.asset.to_string()), ("feeAmount", fee.amount.to_string()), - ("positionInTransaction", fee.source_action_index.to_string()), + ( + "positionInTransaction", + fee.position_in_transaction.to_string(), + ), ], ) } @@ -619,9 +620,7 @@ mod tests { // can write let asset = asset_0(); let amount = 100u128; - state - .add_fee_to_block_fees::<_, Transfer>(&asset, amount, 0) - .unwrap(); + state.add_fee_to_block_fees::<_, Transfer>(&asset, amount, 0); // holds expected let fee_balances_updated = state.get_block_fees(); @@ -631,7 +630,7 @@ mod tests { action_name: "astria.protocol.transaction.v1.Transfer".to_string(), asset: asset.to_ibc_prefixed().into(), amount, - source_action_index: 0 + position_in_transaction: 0 }, "fee balances are not what they were expected to be" ); @@ -649,12 +648,8 @@ mod tests { let amount_first = 100u128; let amount_second = 200u128; - state - .add_fee_to_block_fees::<_, Transfer>(&asset_first, amount_first, 0) - .unwrap(); - state - .add_fee_to_block_fees::<_, Transfer>(&asset_second, amount_second, 1) - .unwrap(); + state.add_fee_to_block_fees::<_, Transfer>(&asset_first, amount_first, 0); + state.add_fee_to_block_fees::<_, Transfer>(&asset_second, amount_second, 1); // holds expected let fee_balances = HashSet::<_>::from_iter(state.get_block_fees()); assert_eq!( @@ -664,13 +659,13 @@ mod tests { action_name: "astria.protocol.transaction.v1.Transfer".to_string(), asset: asset_first.to_ibc_prefixed().into(), amount: amount_first, - source_action_index: 0 + position_in_transaction: 0 }, Fee { action_name: "astria.protocol.transaction.v1.Transfer".to_string(), asset: asset_second.to_ibc_prefixed().into(), amount: amount_second, - source_action_index: 1 + position_in_transaction: 1 }, ]), "returned fee balance vector not what was expected" diff --git a/crates/astria-sequencer/src/fees/tests.rs b/crates/astria-sequencer/src/fees/tests.rs index f486683a9e..ca43c492c9 100644 --- a/crates/astria-sequencer/src/fees/tests.rs +++ b/crates/astria-sequencer/src/fees/tests.rs @@ -30,12 +30,14 @@ use astria_core::{ }, }, sequencerblock::v1::block::Deposit, + Protobuf as _, }; use cnidarium::StateDelta; use super::base_deposit_fee; use crate::{ accounts::StateWriteExt as _, + action_handler::ActionHandler as _, address::StateWriteExt as _, app::{ benchmark_and_test_utils::{ @@ -46,7 +48,6 @@ use crate::{ get_alice_signing_key, get_bridge_signing_key, }, - ActionHandler as _, }, benchmark_and_test_utils::{ assert_eyre_error, @@ -333,7 +334,7 @@ async fn bridge_lock_fee_calculation_works_as_expected() { state.put_transaction_context(TransactionContext { address_bytes: from_address.bytes(), transaction_id, - source_action_index: 0, + position_in_transaction: 0, }); state.put_base_prefix(ASTRIA_PREFIX.to_string()).unwrap(); @@ -444,7 +445,7 @@ fn get_base_deposit_fee() { .slice(&[0u8; ADDRESS_LEN][..]) .try_build() .unwrap(); - let raw_deposit = astria_core::generated::sequencerblock::v1::Deposit { + let raw_deposit = astria_core::generated::astria::sequencerblock::v1::Deposit { bridge_address: Some(bridge_address.to_raw()), rollup_id: Some(RollupId::from_unhashed_bytes([0; ROLLUP_ID_LEN]).to_raw()), amount: Some(1000.into()), diff --git a/crates/astria-sequencer/src/grpc/sequencer.rs b/crates/astria-sequencer/src/grpc/sequencer.rs index 7d74f077f6..dbe25337c2 100644 --- a/crates/astria-sequencer/src/grpc/sequencer.rs +++ b/crates/astria-sequencer/src/grpc/sequencer.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use astria_core::{ - generated::sequencerblock::v1::{ + generated::astria::sequencerblock::v1::{ sequencer_service_server::SequencerService, FilteredSequencerBlock as RawFilteredSequencerBlock, GetFilteredSequencerBlockRequest, @@ -188,7 +188,7 @@ impl SequencerService for SequencerServer { )); }; - let address = Address::try_from_raw(&address).map_err(|e| { + let address = Address::try_from_raw(address).map_err(|e| { info!( error = %e, "failed to parse address from request", diff --git a/crates/astria-sequencer/src/ibc/ics20_transfer.rs b/crates/astria-sequencer/src/ibc/ics20_transfer.rs index 4fa98ee3c3..c94b87ed0f 100644 --- a/crates/astria-sequencer/src/ibc/ics20_transfer.rs +++ b/crates/astria-sequencer/src/ibc/ics20_transfer.rs @@ -724,7 +724,7 @@ async fn emit_deposit( .get_transaction_context() .ok_or_eyre("transaction source should be present in state when executing an action")?; let source_transaction_id = transaction_context.transaction_id; - let source_action_index = transaction_context.source_action_index; + let source_action_index = transaction_context.position_in_transaction; let deposit = Deposit { bridge_address: *bridge_address, @@ -934,7 +934,7 @@ mod tests { state_tx.put_transaction_context(TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); let rollup_deposit_address = "rollupaddress"; @@ -1016,7 +1016,7 @@ mod tests { state_tx.put_transaction_context(TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); let rollup_deposit_address = "rollupaddress"; @@ -1266,7 +1266,7 @@ mod tests { state_tx.put_transaction_context(TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state_tx @@ -1350,7 +1350,7 @@ mod tests { state_tx.put_transaction_context(TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }); state_tx @@ -1455,7 +1455,7 @@ mod tests { let transaction_context = TransactionContext { address_bytes: bridge_address.bytes(), transaction_id: TransactionId::new([0; 32]), - source_action_index: 0, + position_in_transaction: 0, }; state_tx.put_transaction_context(transaction_context); diff --git a/crates/astria-sequencer/src/ibc/mod.rs b/crates/astria-sequencer/src/ibc/mod.rs index 5a94d8b536..b0f37b19e1 100644 --- a/crates/astria-sequencer/src/ibc/mod.rs +++ b/crates/astria-sequencer/src/ibc/mod.rs @@ -1,8 +1,6 @@ pub(crate) mod component; pub(crate) mod host_interface; -pub(crate) mod ibc_relayer_change; pub(crate) mod ics20_transfer; -pub(crate) mod ics20_withdrawal; mod state_ext; pub(crate) mod storage; diff --git a/crates/astria-sequencer/src/lib.rs b/crates/astria-sequencer/src/lib.rs index 8701ee6a72..76596e40eb 100644 --- a/crates/astria-sequencer/src/lib.rs +++ b/crates/astria-sequencer/src/lib.rs @@ -1,4 +1,5 @@ pub(crate) mod accounts; +pub(crate) mod action_handler; pub(crate) mod address; pub(crate) mod app; pub(crate) mod assets; @@ -17,7 +18,6 @@ pub(crate) mod ibc; mod mempool; pub(crate) mod metrics; pub(crate) mod proposal; -pub(crate) mod rollup_data; mod sequencer; pub(crate) mod service; pub(crate) mod storage; diff --git a/crates/astria-sequencer/src/rollup_data/mod.rs b/crates/astria-sequencer/src/rollup_data/mod.rs deleted file mode 100644 index f04dbbc6c9..0000000000 --- a/crates/astria-sequencer/src/rollup_data/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod action; diff --git a/crates/astria-sequencer/src/sequencer.rs b/crates/astria-sequencer/src/sequencer.rs index 8d0dca32c6..c5b3411d1c 100644 --- a/crates/astria-sequencer/src/sequencer.rs +++ b/crates/astria-sequencer/src/sequencer.rs @@ -1,4 +1,4 @@ -use astria_core::generated::sequencerblock::v1::sequencer_service_server::SequencerServiceServer; +use astria_core::generated::astria::sequencerblock::v1::sequencer_service_server::SequencerServiceServer; use astria_eyre::{ anyhow_to_eyre, eyre::{ diff --git a/crates/astria-sequencer/src/service/consensus.rs b/crates/astria-sequencer/src/service/consensus.rs index 057fa091eb..a0b6a4a5ae 100644 --- a/crates/astria-sequencer/src/service/consensus.rs +++ b/crates/astria-sequencer/src/service/consensus.rs @@ -450,13 +450,15 @@ mod tests { async fn new_consensus_service(funded_key: Option) -> (Consensus, Mempool) { let accounts = if let Some(funded_key) = funded_key { - vec![astria_core::generated::protocol::genesis::v1::Account { - address: Some( - crate::benchmark_and_test_utils::astria_address(funded_key.address_bytes()) - .to_raw(), - ), - balance: Some(10u128.pow(19).into()), - }] + vec![ + astria_core::generated::astria::protocol::genesis::v1::Account { + address: Some( + crate::benchmark_and_test_utils::astria_address(funded_key.address_bytes()) + .to_raw(), + ), + balance: Some(10u128.pow(19).into()), + }, + ] } else { vec![] }; diff --git a/crates/astria-sequencer/src/service/info/abci_query_router.rs b/crates/astria-sequencer/src/service/info/abci_query_router.rs index 988e0b7791..00a127772d 100644 --- a/crates/astria-sequencer/src/service/info/abci_query_router.rs +++ b/crates/astria-sequencer/src/service/info/abci_query_router.rs @@ -40,7 +40,6 @@ use std::{ use cnidarium::Storage; use matchit::{ - InsertError, Match, MatchError, }; @@ -49,6 +48,13 @@ use tendermint::abci::{ response, }; +#[derive(Debug, thiserror::Error)] +#[error("`{route}` is an invalid route")] +pub(crate) struct InsertError { + route: String, + source: matchit::InsertError, +} + /// `Router` is a wrapper around [`matchit::Router`] to route abci queries /// to handlers. #[derive(Clone)] @@ -75,8 +81,13 @@ impl Router { route: impl Into, handler: impl AbciQueryHandler, ) -> Result<(), InsertError> { + let route = route.into(); self.query_router - .insert(route, BoxedAbciQueryHandler::from_handler(handler)) + .insert(route.clone(), BoxedAbciQueryHandler::from_handler(handler)) + .map_err(|source| InsertError { + route, + source, + }) } } diff --git a/crates/astria-sequencer/src/service/info/mod.rs b/crates/astria-sequencer/src/service/info/mod.rs index 23b863aebc..6254f58632 100644 --- a/crates/astria-sequencer/src/service/info/mod.rs +++ b/crates/astria-sequencer/src/service/info/mod.rs @@ -45,48 +45,40 @@ pub(crate) struct Info { query_router: abci_query_router::Router, } +const ACCOUNT_BALANCE: &str = "accounts/balance/:account"; +const ACCOUNT_NONCE: &str = "accounts/nonce/:account"; +const ASSET_DENOM: &str = "asset/denom/:id"; +const FEE_ALLOWED_ASSETS: &str = "asset/allowed_fee_assets"; + +const BRIDGE_ACCOUNT_LAST_TX_ID: &str = "bridge/account_last_tx_hash/:address"; +const BRIDGE_ACCOUNT_INFO: &str = "bridge/account_info/:address"; + +const TRANSACTION_FEE: &str = "transaction/fee"; + +const FEES_COMPONENTS: &str = "fees/components"; + impl Info { pub(crate) fn new(storage: Storage) -> Result { let mut query_router = abci_query_router::Router::new(); - query_router - .insert( - "accounts/balance/:account", - crate::accounts::query::balance_request, - ) - .wrap_err("invalid path: `accounts/balance/:account`")?; - query_router - .insert( - "accounts/nonce/:account", - crate::accounts::query::nonce_request, - ) - .wrap_err("invalid path: `accounts/nonce/:account`")?; - query_router - .insert("asset/denom/:id", crate::assets::query::denom_request) - .wrap_err("invalid path: `asset/denom/:id`")?; - query_router - .insert( - "asset/allowed_fee_assets", - crate::fees::query::allowed_fee_assets_request, - ) - .wrap_err("invalid path: `asset/allowed_fee_asset_ids`")?; - query_router - .insert( - "bridge/account_last_tx_hash/:address", - crate::bridge::query::bridge_account_last_tx_hash_request, - ) - .wrap_err("invalid path: `bridge/account_last_tx_hash/:address`")?; - query_router - .insert( - "transaction/fee", - crate::fees::query::transaction_fee_request, - ) - .wrap_err("invalid path: `transaction/fee`")?; - query_router - .insert( - "bridge/account_info/:address", - crate::bridge::query::bridge_account_info_request, - ) - .wrap_err("invalid path: `bridge/account_info/:address`")?; + + // NOTE: Skipping error context because `InsertError` contains all required information. + query_router.insert(ACCOUNT_BALANCE, crate::accounts::query::balance_request)?; + query_router.insert(ACCOUNT_NONCE, crate::accounts::query::nonce_request)?; + query_router.insert(ASSET_DENOM, crate::assets::query::denom_request)?; + query_router.insert( + FEE_ALLOWED_ASSETS, + crate::fees::query::allowed_fee_assets_request, + )?; + query_router.insert( + BRIDGE_ACCOUNT_LAST_TX_ID, + crate::bridge::query::bridge_account_last_tx_hash_request, + )?; + query_router.insert( + BRIDGE_ACCOUNT_INFO, + crate::bridge::query::bridge_account_info_request, + )?; + query_router.insert(TRANSACTION_FEE, crate::fees::query::transaction_fee_request)?; + query_router.insert(FEES_COMPONENTS, crate::fees::query::components)?; Ok(Self { storage, query_router, @@ -183,9 +175,28 @@ mod tests { protocol::{ account::v1::BalanceResponse, asset::v1::DenomResponse, + fees::v1::{ + BridgeLockFeeComponents, + BridgeSudoChangeFeeComponents, + BridgeUnlockFeeComponents, + FeeAssetChangeFeeComponents, + FeeChangeFeeComponents, + IbcRelayFeeComponents, + IbcRelayerChangeFeeComponents, + IbcSudoChangeFeeComponents, + Ics20WithdrawalFeeComponents, + InitBridgeAccountFeeComponents, + RollupDataSubmissionFeeComponents, + SudoAddressChangeFeeComponents, + TransferFeeComponents, + ValidatorUpdateFeeComponents, + }, }, }; - use cnidarium::StateDelta; + use cnidarium::{ + StateDelta, + StateWrite, + }; use prost::Message as _; use tendermint::v0_38::abci::{ request, @@ -212,7 +223,7 @@ mod tests { #[tokio::test] async fn handle_balance_query() { use astria_core::{ - generated::protocol::accounts::v1 as raw, + generated::astria::protocol::accounts::v1 as raw, protocol::account::v1::AssetBalance, }; @@ -279,7 +290,7 @@ mod tests { #[tokio::test] async fn handle_denom_query() { - use astria_core::generated::protocol::asset::v1 as raw; + use astria_core::generated::astria::protocol::asset::v1 as raw; let storage = cnidarium::TempStorage::new().await.unwrap(); let mut state = StateDelta::new(storage.latest_snapshot()); @@ -323,7 +334,7 @@ mod tests { #[tokio::test] async fn handle_allowed_fee_assets_query() { - use astria_core::generated::protocol::asset::v1 as raw; + use astria_core::generated::astria::protocol::asset::v1 as raw; let storage = cnidarium::TempStorage::new().await.unwrap(); let mut state = StateDelta::new(storage.latest_snapshot()); @@ -367,7 +378,7 @@ mod tests { InfoResponse::Query(query) => query, other => panic!("expected InfoResponse::Query, got {other:?}"), }; - assert!(query_response.code.is_ok()); + assert!(query_response.code.is_ok(), "{query_response:?}"); let allowed_fee_assets_resp = raw::AllowedFeeAssetsResponse::decode(query_response.value) .unwrap() @@ -384,4 +395,190 @@ mod tests { ); } } + + #[tokio::test] + async fn handle_fee_components() { + let storage = cnidarium::TempStorage::new().await.unwrap(); + let mut state = StateDelta::new(storage.latest_snapshot()); + + let height = 99; + + state.put_block_height(height).unwrap(); + write_all_the_fees(&mut state); + storage.commit(state).await.unwrap(); + + let info_request = InfoRequest::Query(request::Query { + path: "fees/components".to_string(), + data: vec![].into(), + height: u32::try_from(height).unwrap().into(), + prove: false, + }); + + let response = { + let storage = (*storage).clone(); + let info_service = Info::new(storage).unwrap(); + info_service + .handle_info_request(info_request) + .await + .unwrap() + }; + let query_response = match response { + InfoResponse::Query(query) => query, + other => panic!("expected InfoResponse::Query, got {other:?}"), + }; + assert!(query_response.code.is_ok(), "{query_response:?}"); + + let actual_fees = + serde_json::from_slice::(&query_response.value).unwrap(); + + assert_json_diff::assert_json_eq!(expected_fees(), actual_fees); + } + + fn expected_fees() -> serde_json::Value { + serde_json::json!({ + "bridge_lock": { + "base": 1, + "multiplier": 1 + }, + "bridge_sudo_change": { + "base": 3, + "multiplier": 3 + }, + "bridge_unlock": { + "base": 2, + "multiplier": 2 + }, + "fee_asset_change": { + "base": 4, + "multiplier": 4 + }, + "fee_change": { + "base": 5, + "multiplier": 5 + }, + "ibc_relay": { + "base": 7, + "multiplier": 7 + }, + "ibc_relayer_change": { + "base": 8, + "multiplier": 8 + }, + "ibc_sudo_change": { + "base": 9, + "multiplier": 9 + }, + "ics20_withdrawal": { + "base": 10, + "multiplier": 10 + }, + "init_bridge_account": { + "base": 6, + "multiplier": 6 + }, + "rollup_data_submission": { + "base": 11, + "multiplier": 11 + }, + "sudo_address_change": { + "base": 12, + "multiplier": 12 + }, + "transfer": { + "base": 13, + "multiplier": 13 + }, + "validator_update": { + "base": 14, + "multiplier": 14 + } + }) + } + + fn write_all_the_fees(mut state: S) { + state + .put_bridge_lock_fees(BridgeLockFeeComponents { + base: 1, + multiplier: 1, + }) + .unwrap(); + state + .put_bridge_unlock_fees(BridgeUnlockFeeComponents { + base: 2, + multiplier: 2, + }) + .unwrap(); + state + .put_bridge_sudo_change_fees(BridgeSudoChangeFeeComponents { + base: 3, + multiplier: 3, + }) + .unwrap(); + state + .put_fee_asset_change_fees(FeeAssetChangeFeeComponents { + base: 4, + multiplier: 4, + }) + .unwrap(); + state + .put_fee_change_fees(FeeChangeFeeComponents { + base: 5, + multiplier: 5, + }) + .unwrap(); + state + .put_init_bridge_account_fees(InitBridgeAccountFeeComponents { + base: 6, + multiplier: 6, + }) + .unwrap(); + state + .put_ibc_relay_fees(IbcRelayFeeComponents { + base: 7, + multiplier: 7, + }) + .unwrap(); + state + .put_ibc_relayer_change_fees(IbcRelayerChangeFeeComponents { + base: 8, + multiplier: 8, + }) + .unwrap(); + state + .put_ibc_sudo_change_fees(IbcSudoChangeFeeComponents { + base: 9, + multiplier: 9, + }) + .unwrap(); + state + .put_ics20_withdrawal_fees(Ics20WithdrawalFeeComponents { + base: 10, + multiplier: 10, + }) + .unwrap(); + state + .put_rollup_data_submission_fees(RollupDataSubmissionFeeComponents { + base: 11, + multiplier: 11, + }) + .unwrap(); + state + .put_sudo_address_change_fees(SudoAddressChangeFeeComponents { + base: 12, + multiplier: 12, + }) + .unwrap(); + state + .put_transfer_fees(TransferFeeComponents { + base: 13, + multiplier: 13, + }) + .unwrap(); + state + .put_validator_update_fees(ValidatorUpdateFeeComponents { + base: 14, + multiplier: 14, + }) + .unwrap(); + } } diff --git a/crates/astria-sequencer/src/service/mempool/mod.rs b/crates/astria-sequencer/src/service/mempool/mod.rs index 88b59a5b77..47bd1dfe91 100644 --- a/crates/astria-sequencer/src/service/mempool/mod.rs +++ b/crates/astria-sequencer/src/service/mempool/mod.rs @@ -10,7 +10,7 @@ use std::{ }; use astria_core::{ - generated::protocol::transaction::v1 as raw, + generated::astria::protocol::transaction::v1 as raw, primitive::v1::asset::IbcPrefixed, protocol::{ abci::AbciErrorCode, @@ -50,8 +50,8 @@ use tracing::{ use crate::{ accounts::StateReadExt as _, + action_handler::ActionHandler as _, address::StateReadExt as _, - app::ActionHandler as _, mempool::{ get_account_balances, InsertionError, diff --git a/crates/astria-sequencer/src/transaction/mod.rs b/crates/astria-sequencer/src/transaction/mod.rs index d430cca111..fbfe3e3fb6 100644 --- a/crates/astria-sequencer/src/transaction/mod.rs +++ b/crates/astria-sequencer/src/transaction/mod.rs @@ -1,27 +1,11 @@ mod checks; mod state_ext; -use std::fmt; - -use astria_core::protocol::transaction::v1::{ - action::Action, - Transaction, -}; -use astria_eyre::{ - anyhow_to_eyre, - eyre::{ - ensure, - OptionExt as _, - Result, - WrapErr as _, - }, -}; pub(crate) use checks::{ check_balance_for_total_fees_and_transfers, check_chain_id_mempool, get_total_transaction_cost, }; -use cnidarium::StateWrite; // Conditional to quiet warnings. This object is used throughout the codebase, // but is never explicitly named - hence Rust warns about it being unused. #[cfg(test)] @@ -30,266 +14,3 @@ pub(crate) use state_ext::{ StateReadExt, StateWriteExt, }; - -use crate::{ - accounts::{ - StateReadExt as _, - StateWriteExt as _, - }, - app::{ - ActionHandler, - StateReadExt as _, - }, - bridge::{ - StateReadExt as _, - StateWriteExt as _, - }, - fees::FeeHandler, - ibc::{ - host_interface::AstriaHost, - StateReadExt as _, - }, -}; - -#[derive(Debug)] -pub(crate) struct InvalidChainId(pub(crate) String); - -impl fmt::Display for InvalidChainId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "provided chain id {} does not match expected chain id", - self.0, - ) - } -} - -impl std::error::Error for InvalidChainId {} - -#[derive(Debug)] -pub(crate) struct InvalidNonce(pub(crate) u32); - -impl fmt::Display for InvalidNonce { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "provided nonce {} does not match expected next nonce", - self.0, - ) - } -} - -impl std::error::Error for InvalidNonce {} - -#[async_trait::async_trait] -impl ActionHandler for Transaction { - async fn check_stateless(&self) -> Result<()> { - ensure!(!self.actions().is_empty(), "must have at least one action"); - - for action in self.actions() { - match action { - Action::Transfer(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for TransferAction")?, - Action::RollupDataSubmission(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for SequenceAction")?, - Action::ValidatorUpdate(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for ValidatorUpdateAction")?, - Action::SudoAddressChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for SudoAddressChangeAction")?, - Action::IbcSudoChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for IbcSudoChangeAction")?, - Action::FeeChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for FeeChangeAction")?, - Action::Ibc(act) => { - let action = act - .clone() - .with_handler::(); - action - .check_stateless(()) - .await - .map_err(anyhow_to_eyre) - .wrap_err("stateless check failed for IbcAction")?; - } - Action::Ics20Withdrawal(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for Ics20WithdrawalAction")?, - Action::IbcRelayerChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for IbcRelayerChangeAction")?, - Action::FeeAssetChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for FeeAssetChangeAction")?, - Action::InitBridgeAccount(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for InitBridgeAccountAction")?, - Action::BridgeLock(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for BridgeLockAction")?, - Action::BridgeUnlock(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for BridgeUnlockAction")?, - Action::BridgeSudoChange(act) => act - .check_stateless() - .await - .wrap_err("stateless check failed for BridgeSudoChangeAction")?, - } - } - Ok(()) - } - - // FIXME (https://github.com/astriaorg/astria/issues/1584): because most lines come from delegating (and error wrapping) to the - // individual actions. This could be tidied up by implementing `ActionHandler for Action` - // and letting it delegate. - #[expect(clippy::too_many_lines, reason = "should be refactored")] - async fn check_and_execute(&self, mut state: S) -> Result<()> { - // Add the current signed transaction into the ephemeral state in case - // downstream actions require access to it. - // XXX: This must be deleted at the end of `check_stateful`. - let mut transaction_context = state.put_transaction_context(self); - - // Transactions must match the chain id of the node. - let chain_id = state.get_chain_id().await?; - ensure!( - self.chain_id() == chain_id.as_str(), - InvalidChainId(self.chain_id().to_string()) - ); - - // Nonce should be equal to the number of executed transactions before this tx. - // First tx has nonce 0. - let curr_nonce = state - .get_account_nonce(self.address_bytes()) - .await - .wrap_err("failed to get nonce for transaction signer")?; - ensure!(curr_nonce == self.nonce(), InvalidNonce(self.nonce())); - - // Should have enough balance to cover all actions. - check_balance_for_total_fees_and_transfers(self, &state) - .await - .wrap_err("failed to check balance for total fees and transfers")?; - - if state - .get_bridge_account_rollup_id(&self) - .await - .wrap_err("failed to check account rollup id")? - .is_some() - { - state - .put_last_transaction_id_for_bridge_account( - &self, - transaction_context.transaction_id, - ) - .wrap_err("failed to put last transaction id for bridge account")?; - } - - let from_nonce = state - .get_account_nonce(&self) - .await - .wrap_err("failed getting nonce of transaction signer")?; - let next_nonce = from_nonce - .checked_add(1) - .ok_or_eyre("overflow occurred incrementing stored nonce")?; - state - .put_account_nonce(&self, next_nonce) - .wrap_err("failed updating `from` nonce")?; - - // FIXME: this should create one span per `check_and_execute` - for (i, action) in (0..).zip(self.actions().iter()) { - transaction_context.source_action_index = i; - state.put_transaction_context(transaction_context); - - match action { - Action::Transfer(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing transfer action failed")?, - Action::RollupDataSubmission(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing sequence action failed")?, - Action::ValidatorUpdate(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing validor update")?, - Action::SudoAddressChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing sudo address change failed")?, - Action::IbcSudoChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing ibc sudo change failed")?, - Action::FeeChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("executing fee change failed")?, - Action::Ibc(act) => { - // FIXME: this check should be moved to check_and_execute, as it now has - // access to the the signer through state. However, what's the correct - // ibc AppHandler call to do it? Can we just update one of the trait methods - // of crate::ibc::ics20_transfer::Ics20Transfer? - ensure!( - state - .is_ibc_relayer(self) - .await - .wrap_err("failed to check if address is IBC relayer")?, - "only IBC sudo address can execute IBC actions" - ); - let action = act - .clone() - .with_handler::(); - action - .check_and_execute(&mut state) - .await - .map_err(anyhow_to_eyre) - .wrap_err("failed executing ibc action")?; - } - Action::Ics20Withdrawal(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing ics20 withdrawal")?, - Action::IbcRelayerChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing ibc relayer change")?, - Action::FeeAssetChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing fee asseet change")?, - Action::InitBridgeAccount(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing init bridge account")?, - Action::BridgeLock(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing bridge lock")?, - Action::BridgeUnlock(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing bridge unlock")?, - Action::BridgeSudoChange(act) => check_execute_and_pay_fees(act, &mut state) - .await - .wrap_err("failed executing bridge sudo change")?, - } - } - - // XXX: Delete the current transaction data from the ephemeral state. - state.delete_current_transaction_context(); - Ok(()) - } -} - -async fn check_execute_and_pay_fees( - action: &T, - mut state: S, -) -> Result<()> { - action.check_and_execute(&mut state).await?; - action.check_and_pay_fees(&mut state).await?; - Ok(()) -} diff --git a/crates/astria-sequencer/src/transaction/state_ext.rs b/crates/astria-sequencer/src/transaction/state_ext.rs index 2dbfed621c..4a3427e10d 100644 --- a/crates/astria-sequencer/src/transaction/state_ext.rs +++ b/crates/astria-sequencer/src/transaction/state_ext.rs @@ -18,7 +18,7 @@ fn transaction_context() -> &'static str { pub(crate) struct TransactionContext { pub(crate) address_bytes: [u8; ADDRESS_LEN], pub(crate) transaction_id: TransactionId, - pub(crate) source_action_index: u64, + pub(crate) position_in_transaction: u64, } impl TransactionContext { @@ -32,7 +32,7 @@ impl From<&Transaction> for TransactionContext { Self { address_bytes: *value.address_bytes(), transaction_id: value.id(), - source_action_index: 0, + position_in_transaction: 0, } } } diff --git a/justfile b/justfile index 6c09ebd06e..e6ac8d0884 100644 --- a/justfile +++ b/justfile @@ -41,7 +41,7 @@ _crate_short_name crate quiet="": set -eu case {{crate}} in astria-bridge-withdrawer) short_name=bridge-withdrawer ;; - astria-cli) short_name=cli ;; + astria-cli) short_name=astria-cli ;; astria-composer) short_name=composer ;; astria-conductor) short_name=conductor ;; astria-sequencer) short_name=sequencer ;; diff --git a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/optimistic_block.proto b/proto/sequencerblockapis/astria/sequencerblock/optimistic/v1alpha1/service.proto similarity index 88% rename from proto/sequencerblockapis/astria/sequencerblock/v1alpha1/optimistic_block.proto rename to proto/sequencerblockapis/astria/sequencerblock/optimistic/v1alpha1/service.proto index 575643e73b..347a3670cc 100644 --- a/proto/sequencerblockapis/astria/sequencerblock/v1alpha1/optimistic_block.proto +++ b/proto/sequencerblockapis/astria/sequencerblock/optimistic/v1alpha1/service.proto @@ -1,9 +1,9 @@ syntax = "proto3"; -package astria.sequencerblock.v1alpha1; +package astria.sequencerblock.optimistic.v1alpha1; import "astria/primitive/v1/types.proto"; -import "astria/sequencerblock/v1alpha1/block.proto"; +import "astria/sequencerblock/v1/block.proto"; message GetBlockCommitmentStreamRequest {} @@ -26,7 +26,7 @@ message GetOptimisticBlockStreamRequest { message GetOptimisticBlockStreamResponse { // The optimistic Sequencer block that is being streamed, filtered for the provided rollup id. - astria.sequencerblock.v1alpha1.FilteredSequencerBlock block = 1; + astria.sequencerblock.v1.FilteredSequencerBlock block = 1; } // The Sequencer will serve this to the aucitoneer diff --git a/specs/data-flow-and-verification.md b/specs/data-flow-and-verification.md index 613b00f70a..6bde2222d9 100644 --- a/specs/data-flow-and-verification.md +++ b/specs/data-flow-and-verification.md @@ -11,32 +11,33 @@ the exit point is execution by a rollup node. ## Entry point -The entry point for rollup data is via a `sequence::Action`, which can become -part of a sequencer transaction. The data types are as follows: +The entry point for rollup data is via a `RollupDataSubmission` action, which can +become part of a sequencer transaction. The data types are as follows: ```rust -// sequence::Action -pub struct Action { - pub(crate) chain_id: Vec, - pub(crate) data: Vec, +pub struct RollupDataSubmission { + rollup_id: RollupId, + data: Bytes, + // the asset to pay fees with + fee_asset: asset::Denom, } ``` ```rust -// an unsigned transaction -pub struct Unsigned { - pub(crate) nonce: Nonce, - pub(crate) actions: Vec, +// an unsigned transaction body +pub struct TransactionBody { + actions: Actions, // vector of actions + params: TransactionParams, // chain id, nonce } ``` -The `data` field inside the `sequence::Action` is arbitrary bytes, which should +The `data` field inside the `RollupDataSubmission` is arbitrary bytes, which should be an encoded rollup transaction. The sequencer is agnostic to the transaction -format of the rollups using it. The `chain_id` field is an identifier for the +format of the rollups using it. The `rollup_id` field is an identifier for the rollup the data is destined for. To submit rollup data to the system, the user creates a transaction with a -`sequence::Action` within it and signs and submits it to the sequencer. The +`RollupDataSubmission` within it and signs and submits it to the sequencer. The sequencer will then include it in a block, thus finalizing its ordering. ## Sequencer to data availability @@ -47,56 +48,81 @@ the block data is published via a data availability layer. The block data published is as follows: ```rust -pub struct SequencerBlockData { - block_hash: Hash, - header: Header, - /// chain ID -> rollup transactions - rollup_data: BTreeMap>>, - /// The root of the action tree for this block. - action_tree_root: [u8; 32], - /// The inclusion proof that the action tree root is included - /// in `Header::data_hash`. - action_tree_root_inclusion_proof: merkle::Proof, - /// The commitment to the chain IDs of the rollup data. - /// The merkle root of the tree where the leaves are the chain IDs. - chain_ids_commitment: [u8; 32], - /// The inclusion proof that the chain IDs commitment is included - /// in `Header::data_hash`. - chain_ids_commitment_inclusion_proof: merkle::Proof, +pub struct SequencerBlockHeader { + chain_id: tendermint::chain::Id, + height: tendermint::block::Height, + time: Time, + // the merkle root of all the rollup data in the block + rollup_transactions_root: [u8; 32], + // the merkle root of all transactions in the block + data_hash: [u8; 32], + proposer_address: account::Id, +} + +pub struct SequencerBlock { + /// the cometbft block hash for this block + block_hash: [u8; 32], + /// the block header, which contains cometbft header info and additional sequencer-specific + /// commitments. + header: SequencerBlockHeader, + /// The collection of rollup transactions that were included in this block. + rollup_transactions: IndexMap, + /// The inclusion proof that the rollup transactions merkle root is included + /// in `header.data_hash`. + rollup_transactions_proof: merkle::Proof, + /// The inclusion proof that the rollup IDs commitment is included + /// in `header.data_hash`. + rollup_ids_proof: merkle::Proof, } ``` -When this data is actually published, it's split into multiple structures. +When the `SequencerBlock` is actually published, it's split into multiple structures. Specifically, the data for each rollup is written independently, while a "base" -data type which contains the rollup chain IDs included in the block is also -written. This allows each rollup to only require the `SequencerNamespaceData` -for the block and the `RollupNamespaceData` for its own rollup transactions. For -each block, if there are N rollup chain IDs included, 1 + N structures are -written to DA. +data type which contains all the other `SequencerBlock` info, plus the list of +rollup IDs in the block, is written. This allows each rollup to only require +the `SequencerNamespaceData` for the block and the `RollupNamespaceData` for +its own rollup transactions. For each block, if there are N rollup chain IDs +included, 1 + N structures are written to DA. ```rust -/// SequencerNamespaceData represents the data written to the "base" -/// sequencer namespace. It contains all the other chain IDs (and thus, +/// SubmittedMetadata represents the data written to the "base" +/// sequencer namespace. It contains all the other rollup IDs (and thus, /// namespaces) that were also written to in the same block. -#[derive(Serialize, Deserialize, Debug)] -pub struct SequencerNamespaceData { - pub block_hash: Hash, - pub header: Header, - pub rollup_chain_ids: Vec, - pub action_tree_root: [u8; 32], - pub action_tree_root_inclusion_proof: InclusionProof, - pub chain_ids_commitment: [u8; 32], +pub struct SubmittedMetadata { + /// The block hash obtained from hashing `.header`. + block_hash: [u8; 32], + /// The sequencer block header. + header: SequencerBlockHeader, + /// The rollup IDs for which `SubmittedRollupData`s were submitted to celestia. + /// Corresponds to the `astria.sequencer.v1.RollupTransactions.id` field + /// and is extracted from `astria.SequencerBlock.rollup_transactions`. + rollup_ids: Vec, + /// The proof that the rollup transactions are included in sequencer block. + /// Corresponds to `astria.SequencerBlock.rollup_transactions_proof`. + rollup_transactions_proof: merkle::Proof, + /// The proof that this sequencer blob includes all rollup IDs of + /// the original sequencer block it was derived from. + /// This proof together with `Sha256(MTH(rollup_ids))` (Sha256 + /// applied to the Merkle Tree Hash of the rollup ID sequence) must be + /// equal to `header.data_hash` which itself must match + /// `astria.SequencerBlock.header.data_hash`. This field corresponds to + /// `astria.SequencerBlock.rollup_ids_proof`. + rollup_ids_proof: merkle::Proof, } ``` ```rust -/// RollupNamespaceData represents the data written to a rollup namespace. -#[derive(Serialize, Deserialize, Debug)] -pub struct RollupNamespaceData { - pub(crate) block_hash: Hash, - pub(crate) chain_id: ChainId, - pub rollup_txs: Vec>, - pub(crate) inclusion_proof: InclusionProof, +/// SubmittedRollupData represents the data written to a rollup namespace. +pub struct SubmittedRollupData { + /// The hash of the sequencer block. Must be 32 bytes. + sequencer_block_hash: [u8; 32], + /// The 32 bytes identifying the rollup this blob belongs to. Matches + /// `astria.sequencerblock.v1.RollupTransactions.rollup_id` + rollup_id: RollupId, + /// A list of opaque bytes that are serialized rollup transactions. + transactions: Vec, + /// The proof that these rollup transactions are included in sequencer block. + proof: merkle::Proof, } ``` @@ -108,9 +134,6 @@ properties as ordering, completeness, and correctness respectively. It is able to do this *without* requiring the full transaction data of the block, as is explained below. -Note that the `Header` field in `SequencerNamespaceData` is a [Tendermint -header](https://github.com/informalsystems/tendermint-rs/blob/4d81b67c28510db7d2d99ed62ebfa9fdf0e02141/tendermint/src/block/header.rs#L25). - ## Data availability to rollup node For a rollup node to verify the ordering, completeness, and correctness of the @@ -120,13 +143,13 @@ block data it receives, it must verify the following: 2. the block hash was in fact committed by the sequencer (ie. >2/3 stake voted to commit this block hash to the chain) 3. the block header correctly hashes to the block hash -4. the `data_hash` inside the header contains the `action_tree_root` of the +4. the `data_hash` inside the header contains the `rollup_transactions_root` of the block (see [sequencer inclusion proofs](sequencer-inclusion-proofs.md) for - details), which is a commitment to the `sequence:Action`s in the block -5. the `rollup_txs` inside `RollupNamespaceData` is contained within the - `action_tree_root` -6. the `chain_ids_commitment` is a valid commitment to `rollup_chain_ids` -7. the `data_hash` inside the header contains the `chain_ids_commitment` + details), which is a commitment to the `RollupDataSubmission`s in the block +5. the `transactions` inside `SubmittedRollupData` is contained within the + `rollup_transactions_root` +6. the `rollup_ids_commitment` is a valid commitment to `rollup_ids` +7. the `data_hash` inside the header contains the `rollup_ids_commitment` for the block. Let's go through these one-by-one. @@ -159,45 +182,45 @@ The block hash is a commitment to the block header (specifically, the merkle root of the tree where the leaves are each header field). We then verify that the block header merkleizes to the block hash correctly. -### 4. `action_tree_root` +### 4. `rollup_transactions_root` -The block's data (transactions) contain the `action_tree_root` of the block (see -[sequencer inclusion proofs](sequencer-inclusion-proofs.md) for details), which -is a commitment to the `sequence:Action`s in the block. Specifically, the -`action_tree_root` is the root of a merkle tree where each leaf is a commitment -to the rollup data for one spceific rollup. The block header contains the field -`data_hash` which is the merkle root of all the transactions in a block. Since -`action_tree_root` is a transaction, we can prove its inclusion inside -`data_hash` (the `action_tree_root_inclusion_proof` field inside -`SequencerNamespaceData`). Then, in the next step, we can verify that the rollup -data we received was included inside `action_tree_root`. +The block's data (transactions) contain the `rollup_transactions_root` of the +block (see [sequencer inclusion proofs](sequencer-inclusion-proofs.md) for details), +which is a commitment to the `RollupDataSubmission`s in the block. Specifically, +the `rollup_transactions_root` is the root of a merkle tree where each leaf is a + commitment to the rollup data for one spceific rollup. The block header contains +the field `data_hash` which is the merkle root of all the transactions in a block. +Since `rollup_transactions_root` is a transaction, we can prove its inclusion inside +`data_hash` (the `rollup_transactions_proof` field inside +`SubmittedMetadata`). Then, in the next step, we can verify that the rollup +data we received was included inside `rollup_transactions_root`. ### 5. `rollup_txs` We calculate a commitment of the rollup data we receive (`rollup_txs` inside -`RollupNamespaceData`). We then verify that this data is included inside -`action_tree_root` (via the `inclusion_proof` field inside -`RollupNamespaceData`). At this point, we are now certain that the rollup data +`SubmittedRollupMetadata`). We then verify that this data is included inside +`rollup_transactions_root` (via the `proof` field inside +`SubmittedRollupMetadata`). At this point, we are now certain that the rollup data we received, which is a subset of the entire block's data, was in fact committed by the majority of the sequencer chain's validators. -### 6. `chain_ids_commitment` +### 6. `rollup_ids_root` -The `SequencerNamespaceData` contains a list of the `rollup_chain_ids` that were -included in the block. However, to ensure that chain IDs are not omitted when +The `SubmittedMetadata` contains a list of the `rollup_ids` that were +included in the block. However, to ensure that rollup IDs are not omitted when publishing the data (which would be undetectable to rollup nodes without forcing -them to pull the entire block's data), we also add a commitment to the chain IDs +them to pull the entire block's data), we also add a commitment to the rollup IDs in the block inside the block's transaction data. We ensure that the -`rollup_chain_ids` inside `SequencerNamespaceData` match the -`chain_ids_commitment`. This proves that no chain IDs were omitted from the -published block, as if any were omitted, then the `chain_ids_commitment` would -not match the commitment generated from `rollup_chain_ids`. +`rollup_ids` inside `SubmittedMetadata` match the +`rollup_ids_root`. This proves that no rollup IDs were omitted from the +published block, as if any were omitted, then the `rollup_ids_root` would +not match the commitment generated from `rollup_ids`. -### 7. `chain_ids_commitment_inclusion_proof` +### 7. `rollup_ids_root_inclusion_proof` -Similarly to verification of `action_tree_root` inside `data_hash`, we also verify -an inclusion proof of `chain_ids_commitment` inside `data_hash` when receiving a -published block. +Similarly to verification of `rollup_transactions_root` inside `data_hash`, we also +verify an inclusion proof of `rollup_ids_root` inside `data_hash` when receiving +a published block. ## Exit point diff --git a/specs/sequencer-inclusion-proofs.md b/specs/sequencer-inclusion-proofs.md index 273d2d028d..51b1f99417 100644 --- a/specs/sequencer-inclusion-proofs.md +++ b/specs/sequencer-inclusion-proofs.md @@ -29,14 +29,18 @@ A rollup conductor needs to be able to verify its subset of relevant data without needing to pull all the transaction data for a block. To do this, the block proposer includes a special "commitment tx" at the start of the block. This a special transaction type that can only be included by the proposer. +The "commitment tx" is the merkle root of a tree where each leaf is a commitment +to the batch of transactions for one specific rollup. ![image](assets/sequencer_inclusion_proof_0.png) When building a block, the proposer deconstructs all txs into their contained -`sequence::Action`s and groups them all. Remember that 1 `sequence::Action` -corresponds to 1 rollup transaction. Then, a commitment to the set of all -actions for a chain becomes a leaf in a merkle tree, the root of which becomes -the "commitment tx" +`RollupDataSubmission` actions and groups them all. Remember that 1 +`RollupDataSubmission` action corresponds to 1 rollup transaction. Then, a +commitment to the set of all actions for a chain becomes a leaf in a merkle tree, +the root of which becomes the "commitment tx". The other validators reconstruct +this merkle tree when validating the proposal, and only accept the proposal if +the tree is a valid representation of the rollup data in the block. ![image](assets/sequencer_inclusion_proof_2.png) @@ -55,8 +59,8 @@ For example: layer and calculate the commitment which should match the leaf in red (in the above diagram) - we use the inclusion proof (in green) to verify the txs for chain C were - included in the action tree -- we verify a proof of inclusion of "commitment tx" (action tree root) inside + included in the rollup data merkle tree +- we verify a proof of inclusion of "commitment tx" (rollup data tree root) inside the block header's `data_hash` - we verify that `data_hash` was correctly included in the block's `block_hash` - verify `block_hash`'s cometbft >2/3 commitment @@ -68,8 +72,8 @@ staking power of the sequencer chain. Additionally, the commitment to the actions for a chain actually also includes a merkle root. The commitment contains of the merkle root of a tree where where -the leaves are the transactions for that rollup; ie. all the `sequence::Action`s -for that chain. "Commitment to actions for chain X" is implemented as `(chain_id +the leaves are the transactions for that rollup; ie. all the `RollupDataSubmission` +actions for that chain. "Commitment to actions for chain X" is implemented as `(chain_id || root of tx tree for rollup)`, allowing for easy verification that a specific rollup transaction was included in a sequencer block. This isn't required for any specific conductor logic, but nice for applications building on top of the diff --git a/tools/protobuf-compiler/CHANGELOG.md b/tools/protobuf-compiler/CHANGELOG.md new file mode 100644 index 0000000000..a017ade42d --- /dev/null +++ b/tools/protobuf-compiler/CHANGELOG.md @@ -0,0 +1,15 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +### Added + +- Clears target directory before generating new code. + [#1825](https://github.com/astriaorg/astria/pull/1825) diff --git a/tools/protobuf-compiler/src/main.rs b/tools/protobuf-compiler/src/main.rs index 1034672e90..c166ce0b54 100644 --- a/tools/protobuf-compiler/src/main.rs +++ b/tools/protobuf-compiler/src/main.rs @@ -1,3 +1,8 @@ +//! Generates Rust code of protobuf specs located in proto/ and writes +//! the result to crates/astria-core/src/generated. +//! +//! This tool will delete everything in crates/astria-core/src/generated (except +//! mod.rs). use std::{ collections::{ HashMap, @@ -59,23 +64,16 @@ fn main() { let files = find_protos(src_dir); + purge_out_dir(&out_dir); + tonic_build::configure() .build_client(true) .build_server(true) .emit_rerun_if_changed(false) - .bytes([ - ".astria", - ".celestia", - ".cosmos", - ".tendermint", - ]) + .bytes([".astria", ".celestia", ".cosmos", ".tendermint"]) .client_mod_attribute(".", "#[cfg(feature=\"client\")]") .server_mod_attribute(".", "#[cfg(feature=\"server\")]") .extern_path(".astria_vendored.penumbra", "::penumbra-proto") - .extern_path( - ".astria_vendored.tendermint.abci.ValidatorUpdate", - "crate::generated::astria_vendored::tendermint::abci::ValidatorUpdate", - ) .type_attribute(".astria.primitive.v1.Uint128", "#[derive(Copy)]") .type_attribute( ".astria.protocol.genesis.v1.IbcParameters", @@ -206,10 +204,10 @@ fn get_buf_from_env() -> PathBuf { "linux" => "You can download it from https://github.com/bufbuild/buf/releases; if you are on Arch Linux, install it from the AUR with `rua install buf` or another helper", _other => "Check if there is a precompiled version for your OS at https://github.com/bufbuild/buf/releases" }; - let error_msg = "Could not find `buf` installation and this build crate cannot proceed without - this knowledge. If `buf` is installed and this crate had trouble finding - it, you can set the `BUF` environment variable with the specific path to your - installed `buf` binary."; + let error_msg = "Could not find `buf` installation and this build crate cannot proceed \ + without this knowledge. If `buf` is installed and this crate had trouble \ + finding it, you can set the `BUF` environment variable with the specific \ + path to your installed `buf` binary."; let msg = format!("{error_msg} {os_specific_hint}"); env::var_os("BUF") @@ -217,3 +215,27 @@ fn get_buf_from_env() -> PathBuf { .or_else(|| which::which("buf").ok()) .expect(&msg) } + +fn purge_out_dir(path: impl AsRef) { + let read_dir_msg = format!( + "should be able to read generated file out dir files `{}`", + path.as_ref().display() + ); + let entry_msg = format!( + "every entry in generated file out dir `{}` should have a name", + path.as_ref().display() + ); + let remove_msg = format!( + "all entries in the generated file out dir should `{}` should be files, and the out dir \ + is expected to have read, write, execute permissions set", + path.as_ref().display() + ); + for entry in read_dir(path).expect(&read_dir_msg).flatten() { + // skip mod.rs as it's assumed to be the only non-generated file in the out dir. + if entry.path().file_name().expect(&entry_msg) == "mod.rs" { + continue; + } + + std::fs::remove_file(entry.path()).expect(&remove_msg); + } +}