diff --git a/Cargo.lock b/Cargo.lock index efdc444461..af8e934694 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,6 +512,7 @@ dependencies = [ "axum", "bytes", "futures", + "hex", "humantime", "insta", "itertools 0.12.1", diff --git a/crates/astria-auctioneer/Cargo.toml b/crates/astria-auctioneer/Cargo.toml index 73ef9a1f43..2d7fe56a72 100644 --- a/crates/astria-auctioneer/Cargo.toml +++ b/crates/astria-auctioneer/Cargo.toml @@ -25,6 +25,7 @@ async-trait = { workspace = true } axum = { workspace = true } bytes = { workspace = true } futures = { workspace = true } +hex = { workspace = true } humantime = { workspace = true } itertools = { workspace = true } pbjson-types = { workspace = true } diff --git a/crates/astria-auctioneer/src/auction/mod.rs b/crates/astria-auctioneer/src/auction/mod.rs index 1dc4b51a78..2174954ccd 100644 --- a/crates/astria-auctioneer/src/auction/mod.rs +++ b/crates/astria-auctioneer/src/auction/mod.rs @@ -2,7 +2,13 @@ mod builder; use std::time::Duration; use allocation_rule::FirstPrice; -use astria_core::protocol::transaction::v1::Transaction; +use astria_core::{ + primitive::v1::{ + asset, + RollupId, + }, + protocol::transaction::v1::Transaction, +}; use astria_eyre::eyre::{ self, eyre, @@ -21,6 +27,7 @@ use tokio_util::sync::CancellationToken; use crate::{ bundle::Bundle, + sequencer_key::SequencerKey, Metrics, }; @@ -114,17 +121,21 @@ struct Auction { latency_margin: Duration, /// The ID of the auction auction_id: Id, + /// The key used to sign transactions on the sequencer + sequencer_key: SequencerKey, + /// Fee asset for submitting transactions + fee_asset: asset::Denom, + /// Rollup ID to submit the auction result to + rollup_id: RollupId, } impl Auction { - // TODO: document auction as a state machine here pub(crate) async fn run(mut self) -> eyre::Result<()> { - // TODO: should the timer be inside the auction so that we only have one option? let mut latency_margin_timer = None; let allocation_rule = FirstPrice::new(); let mut auction_is_open = false; - let mut nonce_fetch: Option>> = None; + let mut nonce_fetch: Option>> = None; let auction_result = loop { select! { @@ -187,10 +198,12 @@ impl Auction { .wrap_err("failed to fetch nonce")?; // handle auction result - let transaction = auction_result + let transaction_body = auction_result .wrap_err("")? .ok_or_eyre("auction ended with no winning bid")? - .into_transaction_body(nonce); + .into_transaction_body(nonce, self.rollup_id, self.fee_asset.clone()); + + let transaction = transaction_body.sign(self.sequencer_key.signing_key()); let submission_result = select! { biased; diff --git a/crates/astria-auctioneer/src/bundle/mod.rs b/crates/astria-auctioneer/src/bundle/mod.rs index 457bb59183..a99aa04f11 100644 --- a/crates/astria-auctioneer/src/bundle/mod.rs +++ b/crates/astria-auctioneer/src/bundle/mod.rs @@ -1,14 +1,13 @@ use astria_core::{ - generated::{ - bundle::v1alpha1::{ - self as raw, - }, - primitive::v1::RollupId, + generated::bundle::v1alpha1::{ + self as raw, + }, + primitive::v1::{ + asset, + RollupId, }, - primitive::v1::asset, protocol::transaction::v1::{ action::RollupDataSubmission, - Transaction, TransactionBody, }, }; @@ -78,7 +77,7 @@ impl Bundle { .actions(vec![ RollupDataSubmission { rollup_id, - data, + data: data.into(), fee_asset, } .into(), diff --git a/crates/astria-auctioneer/src/lib.rs b/crates/astria-auctioneer/src/lib.rs index a0a6b68289..4f4ef1fcba 100644 --- a/crates/astria-auctioneer/src/lib.rs +++ b/crates/astria-auctioneer/src/lib.rs @@ -10,6 +10,7 @@ pub(crate) mod metrics; mod optimistic_execution_client; mod optimistic_executor; mod sequencer_grpc_client; +mod sequencer_key; use astria_eyre::{ eyre, diff --git a/crates/astria-auctioneer/src/optimistic_executor/mod.rs b/crates/astria-auctioneer/src/optimistic_executor/mod.rs index b7eb3f4f91..7392e0039b 100644 --- a/crates/astria-auctioneer/src/optimistic_executor/mod.rs +++ b/crates/astria-auctioneer/src/optimistic_executor/mod.rs @@ -171,10 +171,6 @@ impl Running { Ok(()) } - async fn shutdown(self) { - self.shutdown_token.cancel(); - } - #[instrument(skip(self), fields(auction.old_id = %base64(self.current_block.sequencer_block_hash())))] fn optimistic_block_handler( &mut self, @@ -241,4 +237,8 @@ impl Running { Ok(()) } + + async fn shutdown(self) { + self.shutdown_token.cancel(); + } } diff --git a/crates/astria-auctioneer/src/sequencer_key.rs b/crates/astria-auctioneer/src/sequencer_key.rs new file mode 100644 index 0000000000..d5a0c8be23 --- /dev/null +++ b/crates/astria-auctioneer/src/sequencer_key.rs @@ -0,0 +1,107 @@ +use std::{ + fs, + path::{ + Path, + PathBuf, + }, +}; + +use astria_core::{ + crypto::SigningKey, + primitive::v1::Address, +}; +use astria_eyre::eyre::{ + self, + bail, + eyre, + Context, +}; + +pub(crate) struct SequencerKey { + address: Address, + signing_key: SigningKey, +} + +pub(crate) struct SequencerKeyBuilder { + path: Option, + prefix: Option, +} + +impl SequencerKeyBuilder { + /// Sets the path from which the sequencey key is read. + /// + /// The file at `path` should contain a hex-encoded ed25519 secret key. + pub(crate) fn path>(self, path: P) -> Self { + Self { + path: Some(path.as_ref().to_path_buf()), + ..self + } + } + + /// Sets the prefix for constructing a bech32m sequencer address. + /// + /// The prefix must be a valid bech32 human-readable-prefix (Hrp). + pub(crate) fn prefix>(self, prefix: S) -> Self { + Self { + prefix: Some(prefix.as_ref().to_string()), + ..self + } + } + + pub(crate) fn try_build(self) -> eyre::Result { + let Some(path) = self.path else { + bail!("path to sequencer key file must be set"); + }; + let Some(prefix) = self.prefix else { + bail!( + "a prefix to construct bech32m complicant astria addresses from the signing key \ + must be set" + ); + }; + let hex = fs::read_to_string(&path).wrap_err_with(|| { + format!("failed to read sequencer key from path: {}", path.display()) + })?; + let bytes: [u8; 32] = hex::decode(hex.trim()) + .wrap_err_with(|| format!("failed to decode hex: {}", path.display()))? + .try_into() + .map_err(|_| { + eyre!( + "invalid private key length; must be 32 bytes: {}", + path.display() + ) + })?; + let signing_key = SigningKey::from(bytes); + let address = Address::builder() + .array(signing_key.address_bytes()) + .prefix(&prefix) + .try_build() + .wrap_err_with(|| { + format!( + "failed constructing valid sequencer address using the provided prefix \ + `{prefix}`" + ) + })?; + + Ok(SequencerKey { + address, + signing_key, + }) + } +} + +impl SequencerKey { + pub(crate) fn builder() -> SequencerKeyBuilder { + SequencerKeyBuilder { + path: None, + prefix: None, + } + } + + pub(crate) fn address(&self) -> &Address { + &self.address + } + + pub(crate) fn signing_key(&self) -> &SigningKey { + &self.signing_key + } +}